diff --git a/.github/utils/extract_changelogs.py b/.github/utils/extract_changelogs.py index 25d346c1..d5a659cc 100644 --- a/.github/utils/extract_changelogs.py +++ b/.github/utils/extract_changelogs.py @@ -23,11 +23,11 @@ if __name__ == "__main__": (title, changelog) = get_changelog(project_dir, format="plain") # plain text changelog - Firebase App Distribution - with open(dir + "whatsnew-titled.txt", "w", encoding="utf-8") as f: + with open(dir + "whatsnew_titled.txt", "w", encoding="utf-8") as f: f.write(title) f.write("\n") f.write(changelog) - print("::set-output name=changelogPlainTitledFile::" + dir + "whatsnew-titled.txt") + print("::set-output name=changelogPlainTitledFile::" + dir + "whatsnew_titled.txt") print("::set-output name=changelogTitle::" + title) diff --git a/.github/workflows/build-release-aab-play.yml b/.github/workflows/build-release-aab-play.yml index 95fec5cb..eb6923bb 100644 --- a/.github/workflows/build-release-aab-play.yml +++ b/.github/workflows/build-release-aab-play.yml @@ -113,10 +113,11 @@ jobs: with: serviceAccountJsonPlainText: ${{ secrets.PLAY_SERVICE_ACCOUNT_JSON }} packageName: pl.szczodrzynski.edziennik - releaseFile: ${{ needs.sign.outputs.signedReleaseFile }} + releaseFiles: ${{ needs.sign.outputs.signedReleaseFile }} releaseName: ${{ steps.changelog.outputs.appVersionName }} track: ${{ secrets.PLAY_RELEASE_TRACK }} whatsNewDirectory: ${{ steps.changelog.outputs.changelogDir }} + status: completed - name: Upload workflow artifact uses: actions/upload-artifact@v2 diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index ab75be24..c8ff3936 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -1,9 +1,17 @@ + + + + + \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 5d0e7564..5926d556 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -148,28 +148,29 @@ dependencies { // Language cores implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation "androidx.multidex:multidex:2.0.1" - coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:1.1.5" + coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:2.0.4" // Android Jetpack - implementation "androidx.appcompat:appcompat:1.5.1" + implementation "androidx.appcompat:appcompat:1.7.0" implementation "androidx.cardview:cardview:1.0.0" implementation "androidx.constraintlayout:constraintlayout:2.1.4" - implementation "androidx.core:core-ktx:1.9.0" - implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1" - implementation "androidx.navigation:navigation-fragment-ktx:2.5.2" - implementation "androidx.recyclerview:recyclerview:1.2.1" - implementation "androidx.room:room-runtime:2.4.3" - implementation "androidx.work:work-runtime-ktx:2.7.1" - kapt "androidx.room:room-compiler:2.4.3" + implementation "androidx.core:core-ktx:1.13.1" + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.8.2" + implementation "androidx.navigation:navigation-fragment-ktx:2.7.7" + implementation "androidx.recyclerview:recyclerview:1.3.2" + implementation "androidx.room:room-runtime:2.6.1" + implementation "androidx.room:room-ktx:2.6.1" + implementation "androidx.work:work-runtime-ktx:2.9.0" + kapt "androidx.room:room-compiler:2.6.1" // Google design libs - implementation "com.google.android.material:material:1.11.0-beta01" + implementation "com.google.android.material:material:1.12.0" implementation "com.google.android.flexbox:flexbox:3.0.0" // Play Services/Firebase - implementation "com.google.android.gms:play-services-wearable:17.1.0" + implementation "com.google.android.gms:play-services-wearable:18.2.0" implementation("com.google.firebase:firebase-core") { version { strictly "19.0.2" } } - implementation "com.google.firebase:firebase-crashlytics:18.2.13" + implementation "com.google.firebase:firebase-crashlytics:19.0.1" implementation("com.google.firebase:firebase-messaging") { version { strictly "20.1.3" } } // OkHttp, Retrofit, Gson, Jsoup @@ -177,7 +178,7 @@ dependencies { implementation "com.squareup.retrofit2:retrofit:2.9.0" implementation "com.squareup.retrofit2:converter-gson:2.9.0" implementation "com.squareup.retrofit2:converter-scalars:2.9.0" - implementation 'com.google.code.gson:gson:2.8.8' + implementation 'com.google.code.gson:gson:2.10.1' implementation 'org.jsoup:jsoup:1.14.3' implementation "pl.droidsonroids:jspoon:1.3.2" implementation "pl.droidsonroids.retrofit2:converter-jspoon:1.3.2" @@ -255,5 +256,5 @@ dependencies { debugImplementation 'net.yslibrary.licenseadapter:licenseadapter:3.0.0' - testImplementation 'junit:junit:4.13' + testImplementation 'junit:junit:4.13.2' } diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 1d72bf89..8618f020 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -22,6 +22,7 @@ -keep class android.support.v7.widget.** { *; } -keep class pl.szczodrzynski.edziennik.utils.models.** { *; } +-keep class pl.szczodrzynski.edziennik.data.db.enums.* { *; } -keep class pl.szczodrzynski.edziennik.data.db.entity.Event { *; } -keep class pl.szczodrzynski.edziennik.data.db.full.EventFull { *; } -keep class pl.szczodrzynski.edziennik.data.db.entity.FeedbackMessage { *; } @@ -31,6 +32,9 @@ -keepnames class pl.szczodrzynski.edziennik.ui.widgets.timetable.WidgetTimetableProvider -keepnames class pl.szczodrzynski.edziennik.ui.widgets.notifications.WidgetNotificationsProvider -keepnames class pl.szczodrzynski.edziennik.ui.widgets.luckynumber.WidgetLuckyNumberProvider +-keep class pl.szczodrzynski.edziennik.config.AppData { *; } +-keep class pl.szczodrzynski.edziennik.config.AppData$** { *; } +-keep class pl.szczodrzynski.edziennik.utils.managers.TextStylingManager$HtmlMode { *; } -keepnames class androidx.appcompat.view.menu.MenuBuilder { setHeaderTitleInt(java.lang.CharSequence); } -keepnames class androidx.appcompat.view.menu.MenuPopupHelper { showPopup(int, int, boolean, boolean); } diff --git a/app/schemas/pl.szczodrzynski.edziennik.data.db.AppDb/100.json b/app/schemas/pl.szczodrzynski.edziennik.data.db.AppDb/100.json new file mode 100644 index 00000000..1bbdcf44 --- /dev/null +++ b/app/schemas/pl.szczodrzynski.edziennik.data.db.AppDb/100.json @@ -0,0 +1,2320 @@ +{ + "formatVersion": 1, + "database": { + "version": 100, + "identityHash": "4c141460d807d32d00faad4fb3c12522", + "entities": [ + { + "tableName": "grades", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `gradeId` INTEGER NOT NULL, `gradeName` TEXT NOT NULL, `gradeType` INTEGER NOT NULL, `gradeValue` REAL NOT NULL, `gradeWeight` REAL NOT NULL, `gradeColor` INTEGER NOT NULL, `gradeCategory` TEXT, `gradeDescription` TEXT, `gradeComment` TEXT, `gradeSemester` INTEGER NOT NULL, `teacherId` INTEGER NOT NULL, `subjectId` INTEGER NOT NULL, `addedDate` INTEGER NOT NULL, `gradeValueMax` REAL, `gradeClassAverage` REAL, `gradeParentId` INTEGER, `gradeIsImprovement` INTEGER NOT NULL, `keep` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `gradeId`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "gradeId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "gradeName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "gradeType", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "gradeValue", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "weight", + "columnName": "gradeWeight", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "gradeColor", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "gradeCategory", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "description", + "columnName": "gradeDescription", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "comment", + "columnName": "gradeComment", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "semester", + "columnName": "gradeSemester", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacherId", + "columnName": "teacherId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subjectId", + "columnName": "subjectId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "addedDate", + "columnName": "addedDate", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "valueMax", + "columnName": "gradeValueMax", + "affinity": "REAL", + "notNull": false + }, + { + "fieldPath": "classAverage", + "columnName": "gradeClassAverage", + "affinity": "REAL", + "notNull": false + }, + { + "fieldPath": "parentId", + "columnName": "gradeParentId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "isImprovement", + "columnName": "gradeIsImprovement", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "keep", + "columnName": "keep", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "profileId", + "gradeId" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_grades_profileId", + "unique": false, + "columnNames": [ + "profileId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_grades_profileId` ON `${TABLE_NAME}` (`profileId`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "teachers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `teacherId` INTEGER NOT NULL, `teacherLoginId` TEXT, `teacherName` TEXT, `teacherSurname` TEXT, `teacherType` INTEGER NOT NULL, `teacherTypeDescription` TEXT, `teacherSubjects` TEXT NOT NULL, PRIMARY KEY(`profileId`, `teacherId`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "teacherId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "loginId", + "columnName": "teacherLoginId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "name", + "columnName": "teacherName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "surname", + "columnName": "teacherSurname", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "type", + "columnName": "teacherType", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "typeDescription", + "columnName": "teacherTypeDescription", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "subjects", + "columnName": "teacherSubjects", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "profileId", + "teacherId" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "teacherAbsence", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `teacherAbsenceId` INTEGER NOT NULL, `teacherAbsenceType` INTEGER NOT NULL, `teacherAbsenceName` TEXT, `teacherAbsenceDateFrom` TEXT NOT NULL, `teacherAbsenceDateTo` TEXT NOT NULL, `teacherAbsenceTimeFrom` TEXT, `teacherAbsenceTimeTo` TEXT, `teacherId` INTEGER NOT NULL, `addedDate` INTEGER NOT NULL, `keep` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `teacherAbsenceId`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "teacherAbsenceId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "teacherAbsenceType", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "teacherAbsenceName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "dateFrom", + "columnName": "teacherAbsenceDateFrom", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "dateTo", + "columnName": "teacherAbsenceDateTo", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "timeFrom", + "columnName": "teacherAbsenceTimeFrom", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "timeTo", + "columnName": "teacherAbsenceTimeTo", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "teacherId", + "columnName": "teacherId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "addedDate", + "columnName": "addedDate", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "keep", + "columnName": "keep", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "profileId", + "teacherAbsenceId" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_teacherAbsence_profileId", + "unique": false, + "columnNames": [ + "profileId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_teacherAbsence_profileId` ON `${TABLE_NAME}` (`profileId`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "teacherAbsenceTypes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `teacherAbsenceTypeId` INTEGER NOT NULL, `teacherAbsenceTypeName` TEXT NOT NULL, PRIMARY KEY(`profileId`, `teacherAbsenceTypeId`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "teacherAbsenceTypeId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "teacherAbsenceTypeName", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "profileId", + "teacherAbsenceTypeId" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "subjects", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `subjectId` INTEGER NOT NULL, `subjectLongName` TEXT, `subjectShortName` TEXT, `subjectColor` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `subjectId`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "subjectId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "longName", + "columnName": "subjectLongName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "shortName", + "columnName": "subjectShortName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "color", + "columnName": "subjectColor", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "profileId", + "subjectId" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "notices", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `noticeId` INTEGER NOT NULL, `noticeType` INTEGER NOT NULL, `noticeSemester` INTEGER NOT NULL, `noticeText` TEXT NOT NULL, `noticeCategory` TEXT, `noticePoints` REAL, `teacherId` INTEGER NOT NULL, `addedDate` INTEGER NOT NULL, `keep` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `noticeId`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "noticeId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "noticeType", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semester", + "columnName": "noticeSemester", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "text", + "columnName": "noticeText", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "noticeCategory", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "points", + "columnName": "noticePoints", + "affinity": "REAL", + "notNull": false + }, + { + "fieldPath": "teacherId", + "columnName": "teacherId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "addedDate", + "columnName": "addedDate", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "keep", + "columnName": "keep", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "profileId", + "noticeId" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_notices_profileId", + "unique": false, + "columnNames": [ + "profileId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_notices_profileId` ON `${TABLE_NAME}` (`profileId`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "teams", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `teamId` INTEGER NOT NULL, `teamType` INTEGER NOT NULL, `teamName` TEXT, `teamCode` TEXT, `teamTeacherId` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `teamId`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "teamId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "teamType", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "teamName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "code", + "columnName": "teamCode", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "teacherId", + "columnName": "teamTeacherId", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "profileId", + "teamId" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "attendances", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `attendanceId` INTEGER NOT NULL, `attendanceBaseType` INTEGER NOT NULL, `attendanceTypeName` TEXT NOT NULL, `attendanceTypeShort` TEXT NOT NULL, `attendanceTypeSymbol` TEXT NOT NULL, `attendanceTypeColor` INTEGER, `attendanceDate` TEXT NOT NULL, `attendanceTime` TEXT, `attendanceSemester` INTEGER NOT NULL, `teacherId` INTEGER NOT NULL, `subjectId` INTEGER NOT NULL, `addedDate` INTEGER NOT NULL, `attendanceLessonTopic` TEXT, `attendanceLessonNumber` INTEGER, `attendanceIsCounted` INTEGER NOT NULL, `keep` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `attendanceId`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "attendanceId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "baseType", + "columnName": "attendanceBaseType", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "typeName", + "columnName": "attendanceTypeName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "typeShort", + "columnName": "attendanceTypeShort", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "typeSymbol", + "columnName": "attendanceTypeSymbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "typeColor", + "columnName": "attendanceTypeColor", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "date", + "columnName": "attendanceDate", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "startTime", + "columnName": "attendanceTime", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "semester", + "columnName": "attendanceSemester", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacherId", + "columnName": "teacherId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subjectId", + "columnName": "subjectId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "addedDate", + "columnName": "addedDate", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lessonTopic", + "columnName": "attendanceLessonTopic", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "lessonNumber", + "columnName": "attendanceLessonNumber", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "isCounted", + "columnName": "attendanceIsCounted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "keep", + "columnName": "keep", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "profileId", + "attendanceId" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_attendances_profileId", + "unique": false, + "columnNames": [ + "profileId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_attendances_profileId` ON `${TABLE_NAME}` (`profileId`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "events", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `eventId` INTEGER NOT NULL, `eventDate` TEXT NOT NULL, `eventTime` TEXT, `eventTopic` TEXT NOT NULL, `eventColor` INTEGER, `eventType` INTEGER NOT NULL, `teacherId` INTEGER NOT NULL, `subjectId` INTEGER NOT NULL, `teamId` INTEGER NOT NULL, `addedDate` INTEGER NOT NULL, `eventAddedManually` INTEGER NOT NULL, `eventSharedBy` TEXT, `eventSharedByName` TEXT, `eventBlacklisted` INTEGER NOT NULL, `eventIsDone` INTEGER NOT NULL, `eventIsDownloaded` INTEGER NOT NULL, `homeworkBody` TEXT, `attachmentIds` TEXT, `attachmentNames` TEXT, `keep` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `eventId`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "eventId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "eventDate", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "time", + "columnName": "eventTime", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "topic", + "columnName": "eventTopic", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "eventColor", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "type", + "columnName": "eventType", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacherId", + "columnName": "teacherId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subjectId", + "columnName": "subjectId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teamId", + "columnName": "teamId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "addedDate", + "columnName": "addedDate", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "addedManually", + "columnName": "eventAddedManually", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sharedBy", + "columnName": "eventSharedBy", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "sharedByName", + "columnName": "eventSharedByName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "blacklisted", + "columnName": "eventBlacklisted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isDone", + "columnName": "eventIsDone", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isDownloaded", + "columnName": "eventIsDownloaded", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "homeworkBody", + "columnName": "homeworkBody", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "attachmentIds", + "columnName": "attachmentIds", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "attachmentNames", + "columnName": "attachmentNames", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "keep", + "columnName": "keep", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "profileId", + "eventId" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_events_profileId_eventDate_eventTime", + "unique": false, + "columnNames": [ + "profileId", + "eventDate", + "eventTime" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_events_profileId_eventDate_eventTime` ON `${TABLE_NAME}` (`profileId`, `eventDate`, `eventTime`)" + }, + { + "name": "index_events_profileId_eventType", + "unique": false, + "columnNames": [ + "profileId", + "eventType" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_events_profileId_eventType` ON `${TABLE_NAME}` (`profileId`, `eventType`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "eventTypes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `eventType` INTEGER NOT NULL, `eventTypeName` TEXT NOT NULL, `eventTypeColor` INTEGER NOT NULL, `eventTypeOrder` INTEGER NOT NULL, `eventTypeSource` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `eventType`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "eventType", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "eventTypeName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "eventTypeColor", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "order", + "columnName": "eventTypeOrder", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "source", + "columnName": "eventTypeSource", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "profileId", + "eventType" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "loginStores", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`loginStoreId` INTEGER NOT NULL, `loginStoreType` INTEGER NOT NULL, `loginStoreMode` INTEGER NOT NULL, `loginStoreData` TEXT NOT NULL, PRIMARY KEY(`loginStoreId`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "loginStoreId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "loginStoreType", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "mode", + "columnName": "loginStoreMode", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "data", + "columnName": "loginStoreData", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "loginStoreId" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "profiles", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `loginStoreId` INTEGER NOT NULL, `loginStoreType` INTEGER NOT NULL, `name` TEXT NOT NULL, `subname` TEXT, `studentNameLong` TEXT NOT NULL, `studentNameShort` TEXT NOT NULL, `accountName` TEXT, `studentData` TEXT NOT NULL, `image` TEXT, `empty` INTEGER NOT NULL, `archived` INTEGER NOT NULL, `syncEnabled` INTEGER NOT NULL, `enableSharedEvents` INTEGER NOT NULL, `registration` INTEGER NOT NULL, `userCode` TEXT NOT NULL, `archiveId` INTEGER, `studentNumber` INTEGER NOT NULL, `studentClassName` TEXT, `studentSchoolYearStart` INTEGER NOT NULL, `dateSemester1Start` TEXT NOT NULL, `dateSemester2Start` TEXT NOT NULL, `dateYearEnd` TEXT NOT NULL, `disabledNotifications` TEXT, `lastReceiversSync` INTEGER NOT NULL, PRIMARY KEY(`profileId`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "loginStoreId", + "columnName": "loginStoreId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "loginStoreType", + "columnName": "loginStoreType", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subname", + "columnName": "subname", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "studentNameLong", + "columnName": "studentNameLong", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentNameShort", + "columnName": "studentNameShort", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "accountName", + "columnName": "accountName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "studentData", + "columnName": "studentData", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "image", + "columnName": "image", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "empty", + "columnName": "empty", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "archived", + "columnName": "archived", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "syncEnabled", + "columnName": "syncEnabled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unused1", + "columnName": "enableSharedEvents", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "registration", + "columnName": "registration", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userCode", + "columnName": "userCode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "archiveId", + "columnName": "archiveId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "studentNumber", + "columnName": "studentNumber", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentClassName", + "columnName": "studentClassName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "studentSchoolYearStart", + "columnName": "studentSchoolYearStart", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dateSemester1Start", + "columnName": "dateSemester1Start", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "dateSemester2Start", + "columnName": "dateSemester2Start", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "dateYearEnd", + "columnName": "dateYearEnd", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "disabledNotifications", + "columnName": "disabledNotifications", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "lastReceiversSync", + "columnName": "lastReceiversSync", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "profileId" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "luckyNumbers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `luckyNumberDate` INTEGER NOT NULL, `luckyNumber` INTEGER NOT NULL, `keep` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `luckyNumberDate`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "luckyNumberDate", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "luckyNumber", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "keep", + "columnName": "keep", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "profileId", + "luckyNumberDate" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "announcements", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `announcementId` INTEGER NOT NULL, `announcementSubject` TEXT NOT NULL, `announcementText` TEXT, `announcementStartDate` TEXT, `announcementEndDate` TEXT, `teacherId` INTEGER NOT NULL, `addedDate` INTEGER NOT NULL, `announcementIdString` TEXT, `keep` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `announcementId`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "announcementId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "announcementSubject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "text", + "columnName": "announcementText", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "startDate", + "columnName": "announcementStartDate", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "endDate", + "columnName": "announcementEndDate", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "teacherId", + "columnName": "teacherId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "addedDate", + "columnName": "addedDate", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "idString", + "columnName": "announcementIdString", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "keep", + "columnName": "keep", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "profileId", + "announcementId" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_announcements_profileId", + "unique": false, + "columnNames": [ + "profileId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_announcements_profileId` ON `${TABLE_NAME}` (`profileId`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "gradeCategories", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `categoryId` INTEGER NOT NULL, `weight` REAL NOT NULL, `color` INTEGER NOT NULL, `text` TEXT, `columns` TEXT, `valueFrom` REAL NOT NULL, `valueTo` REAL NOT NULL, `type` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `categoryId`, `type`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "categoryId", + "columnName": "categoryId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "weight", + "columnName": "weight", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "text", + "columnName": "text", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "columns", + "columnName": "columns", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "valueFrom", + "columnName": "valueFrom", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "valueTo", + "columnName": "valueTo", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "profileId", + "categoryId", + "type" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "feedbackMessages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`messageId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `received` INTEGER NOT NULL, `text` TEXT NOT NULL, `senderName` TEXT NOT NULL, `deviceId` TEXT, `deviceName` TEXT, `devId` INTEGER, `devImage` TEXT, `sentTime` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "messageId", + "columnName": "messageId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "received", + "columnName": "received", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "text", + "columnName": "text", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderName", + "columnName": "senderName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "deviceId", + "columnName": "deviceId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "deviceName", + "columnName": "deviceName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "devId", + "columnName": "devId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "devImage", + "columnName": "devImage", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "sentTime", + "columnName": "sentTime", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "messageId" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `messageId` INTEGER NOT NULL, `messageType` INTEGER NOT NULL, `messageSubject` TEXT NOT NULL, `messageBody` TEXT, `senderId` INTEGER, `addedDate` INTEGER NOT NULL, `messageIsPinned` INTEGER NOT NULL, `hasAttachments` INTEGER NOT NULL, `attachmentIds` TEXT, `attachmentNames` TEXT, `attachmentSizes` TEXT, `keep` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `messageId`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "messageId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "messageType", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "messageSubject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "body", + "columnName": "messageBody", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "senderId", + "columnName": "senderId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "addedDate", + "columnName": "addedDate", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isStarred", + "columnName": "messageIsPinned", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hasAttachments", + "columnName": "hasAttachments", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "attachmentIds", + "columnName": "attachmentIds", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "attachmentNames", + "columnName": "attachmentNames", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "attachmentSizes", + "columnName": "attachmentSizes", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "keep", + "columnName": "keep", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "profileId", + "messageId" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_messages_profileId_messageType", + "unique": false, + "columnNames": [ + "profileId", + "messageType" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_messages_profileId_messageType` ON `${TABLE_NAME}` (`profileId`, `messageType`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "messageRecipients", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `messageRecipientId` INTEGER NOT NULL, `messageRecipientReplyId` INTEGER NOT NULL, `messageRecipientReadDate` INTEGER NOT NULL, `messageId` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `messageRecipientId`, `messageId`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "messageRecipientId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "replyId", + "columnName": "messageRecipientReplyId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "readDate", + "columnName": "messageRecipientReadDate", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "messageId", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "profileId", + "messageRecipientId", + "messageId" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "debugLogs", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `text` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "text", + "columnName": "text", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "endpointTimers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `endpointId` INTEGER NOT NULL, `endpointLastSync` INTEGER, `endpointNextSync` INTEGER NOT NULL, `endpointViewId` INTEGER, PRIMARY KEY(`profileId`, `endpointId`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "endpointId", + "columnName": "endpointId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastSync", + "columnName": "endpointLastSync", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "nextSync", + "columnName": "endpointNextSync", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "featureType", + "columnName": "endpointViewId", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "profileId", + "endpointId" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "lessonRanges", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `lessonRangeNumber` INTEGER NOT NULL, `lessonRangeStart` TEXT NOT NULL, `lessonRangeEnd` TEXT NOT NULL, PRIMARY KEY(`profileId`, `lessonRangeNumber`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lessonNumber", + "columnName": "lessonRangeNumber", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "startTime", + "columnName": "lessonRangeStart", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "endTime", + "columnName": "lessonRangeEnd", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "profileId", + "lessonRangeNumber" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "notifications", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `title` TEXT NOT NULL, `text` TEXT NOT NULL, `textLong` TEXT, `type` INTEGER NOT NULL, `profileId` INTEGER, `profileName` TEXT, `posted` INTEGER NOT NULL, `viewId` INTEGER, `extras` TEXT, `addedDate` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "text", + "columnName": "text", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "textLong", + "columnName": "textLong", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "profileName", + "columnName": "profileName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "posted", + "columnName": "posted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "navTarget", + "columnName": "viewId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "extras", + "columnName": "extras", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "addedDate", + "columnName": "addedDate", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "classrooms", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `id` INTEGER NOT NULL, `name` TEXT NOT NULL, PRIMARY KEY(`profileId`, `id`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "profileId", + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "noticeTypes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `id` INTEGER NOT NULL, `name` TEXT NOT NULL, PRIMARY KEY(`profileId`, `id`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "profileId", + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "attendanceTypes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `id` INTEGER NOT NULL, `baseType` INTEGER NOT NULL, `typeName` TEXT NOT NULL, `typeShort` TEXT NOT NULL, `typeSymbol` TEXT NOT NULL, `typeColor` INTEGER, PRIMARY KEY(`profileId`, `id`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "baseType", + "columnName": "baseType", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "typeName", + "columnName": "typeName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "typeShort", + "columnName": "typeShort", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "typeSymbol", + "columnName": "typeSymbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "typeColor", + "columnName": "typeColor", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "profileId", + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "timetable", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `id` INTEGER NOT NULL, `type` INTEGER NOT NULL, `date` TEXT, `lessonNumber` INTEGER, `startTime` TEXT, `endTime` TEXT, `subjectId` INTEGER, `teacherId` INTEGER, `teamId` INTEGER, `classroom` TEXT, `oldDate` TEXT, `oldLessonNumber` INTEGER, `oldStartTime` TEXT, `oldEndTime` TEXT, `oldSubjectId` INTEGER, `oldTeacherId` INTEGER, `oldTeamId` INTEGER, `oldClassroom` TEXT, `isExtra` INTEGER NOT NULL, `ownerId` INTEGER NOT NULL, `color` INTEGER, `keep` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `id`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "lessonNumber", + "columnName": "lessonNumber", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "startTime", + "columnName": "startTime", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "endTime", + "columnName": "endTime", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "subjectId", + "columnName": "subjectId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "teacherId", + "columnName": "teacherId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "teamId", + "columnName": "teamId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "classroom", + "columnName": "classroom", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "oldDate", + "columnName": "oldDate", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "oldLessonNumber", + "columnName": "oldLessonNumber", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "oldStartTime", + "columnName": "oldStartTime", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "oldEndTime", + "columnName": "oldEndTime", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "oldSubjectId", + "columnName": "oldSubjectId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "oldTeacherId", + "columnName": "oldTeacherId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "oldTeamId", + "columnName": "oldTeamId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "oldClassroom", + "columnName": "oldClassroom", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "isExtra", + "columnName": "isExtra", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "ownerId", + "columnName": "ownerId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "keep", + "columnName": "keep", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "profileId", + "id" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_timetable_profileId_type_date", + "unique": false, + "columnNames": [ + "profileId", + "type", + "date" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_timetable_profileId_type_date` ON `${TABLE_NAME}` (`profileId`, `type`, `date`)" + }, + { + "name": "index_timetable_profileId_type_oldDate", + "unique": false, + "columnNames": [ + "profileId", + "type", + "oldDate" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_timetable_profileId_type_oldDate` ON `${TABLE_NAME}` (`profileId`, `type`, `oldDate`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "config", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `key` TEXT NOT NULL, `value` TEXT, PRIMARY KEY(`profileId`, `key`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "key", + "columnName": "key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "profileId", + "key" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "librusLessons", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `lessonId` INTEGER NOT NULL, `teacherId` INTEGER NOT NULL, `subjectId` INTEGER NOT NULL, `teamId` INTEGER, PRIMARY KEY(`profileId`, `lessonId`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lessonId", + "columnName": "lessonId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacherId", + "columnName": "teacherId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subjectId", + "columnName": "subjectId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teamId", + "columnName": "teamId", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "profileId", + "lessonId" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_librusLessons_profileId", + "unique": false, + "columnNames": [ + "profileId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_librusLessons_profileId` ON `${TABLE_NAME}` (`profileId`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "timetableManual", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `type` INTEGER NOT NULL, `repeatBy` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `date` INTEGER, `weekDay` INTEGER, `lessonNumber` INTEGER, `startTime` TEXT, `endTime` TEXT, `subjectId` INTEGER, `teacherId` INTEGER, `teamId` INTEGER, `classroom` TEXT)", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "repeatBy", + "columnName": "repeatBy", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "weekDay", + "columnName": "weekDay", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "lessonNumber", + "columnName": "lessonNumber", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "startTime", + "columnName": "startTime", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "endTime", + "columnName": "endTime", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "subjectId", + "columnName": "subjectId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "teacherId", + "columnName": "teacherId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "teamId", + "columnName": "teamId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "classroom", + "columnName": "classroom", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_timetableManual_profileId_date", + "unique": false, + "columnNames": [ + "profileId", + "date" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_timetableManual_profileId_date` ON `${TABLE_NAME}` (`profileId`, `date`)" + }, + { + "name": "index_timetableManual_profileId_weekDay", + "unique": false, + "columnNames": [ + "profileId", + "weekDay" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_timetableManual_profileId_weekDay` ON `${TABLE_NAME}` (`profileId`, `weekDay`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "notes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `noteId` INTEGER NOT NULL, `noteOwnerType` TEXT, `noteOwnerId` INTEGER, `noteReplacesOriginal` INTEGER NOT NULL, `noteTopic` TEXT, `noteBody` TEXT NOT NULL, `noteColor` INTEGER, `noteSharedBy` TEXT, `noteSharedByName` TEXT, `addedDate` INTEGER NOT NULL, PRIMARY KEY(`noteId`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "noteId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "ownerType", + "columnName": "noteOwnerType", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "ownerId", + "columnName": "noteOwnerId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "replacesOriginal", + "columnName": "noteReplacesOriginal", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "topic", + "columnName": "noteTopic", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "body", + "columnName": "noteBody", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "noteColor", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "sharedBy", + "columnName": "noteSharedBy", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "sharedByName", + "columnName": "noteSharedByName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "addedDate", + "columnName": "addedDate", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "noteId" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_notes_profileId_noteOwnerType_noteOwnerId", + "unique": false, + "columnNames": [ + "profileId", + "noteOwnerType", + "noteOwnerId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_notes_profileId_noteOwnerType_noteOwnerId` ON `${TABLE_NAME}` (`profileId`, `noteOwnerType`, `noteOwnerId`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "metadata", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `metadataId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `thingType` INTEGER NOT NULL, `thingId` INTEGER NOT NULL, `seen` INTEGER NOT NULL, `notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "metadataId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "thingType", + "columnName": "thingType", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "thingId", + "columnName": "thingId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "seen", + "columnName": "seen", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "notified", + "columnName": "notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "metadataId" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_metadata_profileId_thingType_thingId", + "unique": true, + "columnNames": [ + "profileId", + "thingType", + "thingId" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_metadata_profileId_thingType_thingId` ON `${TABLE_NAME}` (`profileId`, `thingType`, `thingId`)" + } + ], + "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, '4c141460d807d32d00faad4fb3c12522')" + ] + } +} \ No newline at end of file diff --git a/app/schemas/pl.szczodrzynski.edziennik.data.db.AppDb/98.json b/app/schemas/pl.szczodrzynski.edziennik.data.db.AppDb/98.json new file mode 100644 index 00000000..1a291aef --- /dev/null +++ b/app/schemas/pl.szczodrzynski.edziennik.data.db.AppDb/98.json @@ -0,0 +1,2314 @@ +{ + "formatVersion": 1, + "database": { + "version": 98, + "identityHash": "2612ebba9802eedc7ebc69724606a23c", + "entities": [ + { + "tableName": "grades", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `gradeId` INTEGER NOT NULL, `gradeName` TEXT NOT NULL, `gradeType` INTEGER NOT NULL, `gradeValue` REAL NOT NULL, `gradeWeight` REAL NOT NULL, `gradeColor` INTEGER NOT NULL, `gradeCategory` TEXT, `gradeDescription` TEXT, `gradeComment` TEXT, `gradeSemester` INTEGER NOT NULL, `teacherId` INTEGER NOT NULL, `subjectId` INTEGER NOT NULL, `addedDate` INTEGER NOT NULL, `gradeValueMax` REAL, `gradeClassAverage` REAL, `gradeParentId` INTEGER, `gradeIsImprovement` INTEGER NOT NULL, `keep` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `gradeId`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "gradeId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "gradeName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "gradeType", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "gradeValue", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "weight", + "columnName": "gradeWeight", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "gradeColor", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "gradeCategory", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "description", + "columnName": "gradeDescription", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "comment", + "columnName": "gradeComment", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "semester", + "columnName": "gradeSemester", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacherId", + "columnName": "teacherId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subjectId", + "columnName": "subjectId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "addedDate", + "columnName": "addedDate", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "valueMax", + "columnName": "gradeValueMax", + "affinity": "REAL", + "notNull": false + }, + { + "fieldPath": "classAverage", + "columnName": "gradeClassAverage", + "affinity": "REAL", + "notNull": false + }, + { + "fieldPath": "parentId", + "columnName": "gradeParentId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "isImprovement", + "columnName": "gradeIsImprovement", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "keep", + "columnName": "keep", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "profileId", + "gradeId" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_grades_profileId", + "unique": false, + "columnNames": [ + "profileId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_grades_profileId` ON `${TABLE_NAME}` (`profileId`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "teachers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `teacherId` INTEGER NOT NULL, `teacherLoginId` TEXT, `teacherName` TEXT, `teacherSurname` TEXT, `teacherType` INTEGER NOT NULL, `teacherTypeDescription` TEXT, `teacherSubjects` TEXT NOT NULL, PRIMARY KEY(`profileId`, `teacherId`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "teacherId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "loginId", + "columnName": "teacherLoginId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "name", + "columnName": "teacherName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "surname", + "columnName": "teacherSurname", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "type", + "columnName": "teacherType", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "typeDescription", + "columnName": "teacherTypeDescription", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "subjects", + "columnName": "teacherSubjects", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "profileId", + "teacherId" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "teacherAbsence", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `teacherAbsenceId` INTEGER NOT NULL, `teacherAbsenceType` INTEGER NOT NULL, `teacherAbsenceName` TEXT, `teacherAbsenceDateFrom` TEXT NOT NULL, `teacherAbsenceDateTo` TEXT NOT NULL, `teacherAbsenceTimeFrom` TEXT, `teacherAbsenceTimeTo` TEXT, `teacherId` INTEGER NOT NULL, `addedDate` INTEGER NOT NULL, `keep` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `teacherAbsenceId`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "teacherAbsenceId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "teacherAbsenceType", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "teacherAbsenceName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "dateFrom", + "columnName": "teacherAbsenceDateFrom", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "dateTo", + "columnName": "teacherAbsenceDateTo", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "timeFrom", + "columnName": "teacherAbsenceTimeFrom", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "timeTo", + "columnName": "teacherAbsenceTimeTo", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "teacherId", + "columnName": "teacherId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "addedDate", + "columnName": "addedDate", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "keep", + "columnName": "keep", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "profileId", + "teacherAbsenceId" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_teacherAbsence_profileId", + "unique": false, + "columnNames": [ + "profileId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_teacherAbsence_profileId` ON `${TABLE_NAME}` (`profileId`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "teacherAbsenceTypes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `teacherAbsenceTypeId` INTEGER NOT NULL, `teacherAbsenceTypeName` TEXT NOT NULL, PRIMARY KEY(`profileId`, `teacherAbsenceTypeId`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "teacherAbsenceTypeId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "teacherAbsenceTypeName", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "profileId", + "teacherAbsenceTypeId" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "subjects", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `subjectId` INTEGER NOT NULL, `subjectLongName` TEXT, `subjectShortName` TEXT, `subjectColor` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `subjectId`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "subjectId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "longName", + "columnName": "subjectLongName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "shortName", + "columnName": "subjectShortName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "color", + "columnName": "subjectColor", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "profileId", + "subjectId" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "notices", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `noticeId` INTEGER NOT NULL, `noticeType` INTEGER NOT NULL, `noticeSemester` INTEGER NOT NULL, `noticeText` TEXT NOT NULL, `noticeCategory` TEXT, `noticePoints` REAL, `teacherId` INTEGER NOT NULL, `addedDate` INTEGER NOT NULL, `keep` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `noticeId`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "noticeId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "noticeType", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semester", + "columnName": "noticeSemester", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "text", + "columnName": "noticeText", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "noticeCategory", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "points", + "columnName": "noticePoints", + "affinity": "REAL", + "notNull": false + }, + { + "fieldPath": "teacherId", + "columnName": "teacherId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "addedDate", + "columnName": "addedDate", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "keep", + "columnName": "keep", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "profileId", + "noticeId" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_notices_profileId", + "unique": false, + "columnNames": [ + "profileId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_notices_profileId` ON `${TABLE_NAME}` (`profileId`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "teams", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `teamId` INTEGER NOT NULL, `teamType` INTEGER NOT NULL, `teamName` TEXT, `teamCode` TEXT, `teamTeacherId` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `teamId`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "teamId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "teamType", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "teamName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "code", + "columnName": "teamCode", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "teacherId", + "columnName": "teamTeacherId", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "profileId", + "teamId" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "attendances", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `attendanceId` INTEGER NOT NULL, `attendanceBaseType` INTEGER NOT NULL, `attendanceTypeName` TEXT NOT NULL, `attendanceTypeShort` TEXT NOT NULL, `attendanceTypeSymbol` TEXT NOT NULL, `attendanceTypeColor` INTEGER, `attendanceDate` TEXT NOT NULL, `attendanceTime` TEXT, `attendanceSemester` INTEGER NOT NULL, `teacherId` INTEGER NOT NULL, `subjectId` INTEGER NOT NULL, `addedDate` INTEGER NOT NULL, `attendanceLessonTopic` TEXT, `attendanceLessonNumber` INTEGER, `attendanceIsCounted` INTEGER NOT NULL, `keep` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `attendanceId`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "attendanceId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "baseType", + "columnName": "attendanceBaseType", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "typeName", + "columnName": "attendanceTypeName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "typeShort", + "columnName": "attendanceTypeShort", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "typeSymbol", + "columnName": "attendanceTypeSymbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "typeColor", + "columnName": "attendanceTypeColor", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "date", + "columnName": "attendanceDate", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "startTime", + "columnName": "attendanceTime", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "semester", + "columnName": "attendanceSemester", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacherId", + "columnName": "teacherId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subjectId", + "columnName": "subjectId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "addedDate", + "columnName": "addedDate", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lessonTopic", + "columnName": "attendanceLessonTopic", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "lessonNumber", + "columnName": "attendanceLessonNumber", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "isCounted", + "columnName": "attendanceIsCounted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "keep", + "columnName": "keep", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "profileId", + "attendanceId" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_attendances_profileId", + "unique": false, + "columnNames": [ + "profileId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_attendances_profileId` ON `${TABLE_NAME}` (`profileId`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "events", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `eventId` INTEGER NOT NULL, `eventDate` TEXT NOT NULL, `eventTime` TEXT, `eventTopic` TEXT NOT NULL, `eventColor` INTEGER, `eventType` INTEGER NOT NULL, `teacherId` INTEGER NOT NULL, `subjectId` INTEGER NOT NULL, `teamId` INTEGER NOT NULL, `addedDate` INTEGER NOT NULL, `eventAddedManually` INTEGER NOT NULL, `eventSharedBy` TEXT, `eventSharedByName` TEXT, `eventBlacklisted` INTEGER NOT NULL, `eventIsDone` INTEGER NOT NULL, `eventIsDownloaded` INTEGER NOT NULL, `homeworkBody` TEXT, `attachmentIds` TEXT, `attachmentNames` TEXT, `keep` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `eventId`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "eventId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "eventDate", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "time", + "columnName": "eventTime", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "topic", + "columnName": "eventTopic", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "eventColor", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "type", + "columnName": "eventType", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacherId", + "columnName": "teacherId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subjectId", + "columnName": "subjectId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teamId", + "columnName": "teamId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "addedDate", + "columnName": "addedDate", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "addedManually", + "columnName": "eventAddedManually", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sharedBy", + "columnName": "eventSharedBy", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "sharedByName", + "columnName": "eventSharedByName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "blacklisted", + "columnName": "eventBlacklisted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isDone", + "columnName": "eventIsDone", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isDownloaded", + "columnName": "eventIsDownloaded", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "homeworkBody", + "columnName": "homeworkBody", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "attachmentIds", + "columnName": "attachmentIds", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "attachmentNames", + "columnName": "attachmentNames", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "keep", + "columnName": "keep", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "profileId", + "eventId" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_events_profileId_eventDate_eventTime", + "unique": false, + "columnNames": [ + "profileId", + "eventDate", + "eventTime" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_events_profileId_eventDate_eventTime` ON `${TABLE_NAME}` (`profileId`, `eventDate`, `eventTime`)" + }, + { + "name": "index_events_profileId_eventType", + "unique": false, + "columnNames": [ + "profileId", + "eventType" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_events_profileId_eventType` ON `${TABLE_NAME}` (`profileId`, `eventType`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "eventTypes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `eventType` INTEGER NOT NULL, `eventTypeName` TEXT NOT NULL, `eventTypeColor` INTEGER NOT NULL, `eventTypeOrder` INTEGER NOT NULL, `eventTypeSource` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `eventType`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "eventType", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "eventTypeName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "eventTypeColor", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "order", + "columnName": "eventTypeOrder", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "source", + "columnName": "eventTypeSource", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "profileId", + "eventType" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "loginStores", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`loginStoreId` INTEGER NOT NULL, `loginStoreType` INTEGER NOT NULL, `loginStoreMode` INTEGER NOT NULL, `loginStoreData` TEXT NOT NULL, PRIMARY KEY(`loginStoreId`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "loginStoreId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "loginStoreType", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "mode", + "columnName": "loginStoreMode", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "data", + "columnName": "loginStoreData", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "loginStoreId" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "profiles", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `loginStoreId` INTEGER NOT NULL, `loginStoreType` INTEGER NOT NULL, `name` TEXT NOT NULL, `subname` TEXT, `studentNameLong` TEXT NOT NULL, `studentNameShort` TEXT NOT NULL, `accountName` TEXT, `studentData` TEXT NOT NULL, `image` TEXT, `empty` INTEGER NOT NULL, `archived` INTEGER NOT NULL, `archiveId` INTEGER, `syncEnabled` INTEGER NOT NULL, `enableSharedEvents` INTEGER NOT NULL, `registration` INTEGER NOT NULL, `userCode` TEXT NOT NULL, `studentNumber` INTEGER NOT NULL, `studentClassName` TEXT, `studentSchoolYearStart` INTEGER NOT NULL, `dateSemester1Start` TEXT NOT NULL, `dateSemester2Start` TEXT NOT NULL, `dateYearEnd` TEXT NOT NULL, `disabledNotifications` TEXT, `lastReceiversSync` INTEGER NOT NULL, PRIMARY KEY(`profileId`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "loginStoreId", + "columnName": "loginStoreId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "loginStoreType", + "columnName": "loginStoreType", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subname", + "columnName": "subname", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "studentNameLong", + "columnName": "studentNameLong", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentNameShort", + "columnName": "studentNameShort", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "accountName", + "columnName": "accountName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "studentData", + "columnName": "studentData", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "image", + "columnName": "image", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "empty", + "columnName": "empty", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "archived", + "columnName": "archived", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "archiveId", + "columnName": "archiveId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "syncEnabled", + "columnName": "syncEnabled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "enableSharedEvents", + "columnName": "enableSharedEvents", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "registration", + "columnName": "registration", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userCode", + "columnName": "userCode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentNumber", + "columnName": "studentNumber", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentClassName", + "columnName": "studentClassName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "studentSchoolYearStart", + "columnName": "studentSchoolYearStart", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dateSemester1Start", + "columnName": "dateSemester1Start", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "dateSemester2Start", + "columnName": "dateSemester2Start", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "dateYearEnd", + "columnName": "dateYearEnd", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "disabledNotifications", + "columnName": "disabledNotifications", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "lastReceiversSync", + "columnName": "lastReceiversSync", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "profileId" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "luckyNumbers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `luckyNumberDate` INTEGER NOT NULL, `luckyNumber` INTEGER NOT NULL, `keep` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `luckyNumberDate`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "luckyNumberDate", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "luckyNumber", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "keep", + "columnName": "keep", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "profileId", + "luckyNumberDate" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "announcements", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `announcementId` INTEGER NOT NULL, `announcementSubject` TEXT NOT NULL, `announcementText` TEXT, `announcementStartDate` TEXT, `announcementEndDate` TEXT, `teacherId` INTEGER NOT NULL, `addedDate` INTEGER NOT NULL, `announcementIdString` TEXT, `keep` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `announcementId`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "announcementId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "announcementSubject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "text", + "columnName": "announcementText", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "startDate", + "columnName": "announcementStartDate", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "endDate", + "columnName": "announcementEndDate", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "teacherId", + "columnName": "teacherId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "addedDate", + "columnName": "addedDate", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "idString", + "columnName": "announcementIdString", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "keep", + "columnName": "keep", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "profileId", + "announcementId" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_announcements_profileId", + "unique": false, + "columnNames": [ + "profileId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_announcements_profileId` ON `${TABLE_NAME}` (`profileId`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "gradeCategories", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `categoryId` INTEGER NOT NULL, `weight` REAL NOT NULL, `color` INTEGER NOT NULL, `text` TEXT, `columns` TEXT, `valueFrom` REAL NOT NULL, `valueTo` REAL NOT NULL, `type` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `categoryId`, `type`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "categoryId", + "columnName": "categoryId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "weight", + "columnName": "weight", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "text", + "columnName": "text", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "columns", + "columnName": "columns", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "valueFrom", + "columnName": "valueFrom", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "valueTo", + "columnName": "valueTo", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "profileId", + "categoryId", + "type" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "feedbackMessages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`messageId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `received` INTEGER NOT NULL, `text` TEXT NOT NULL, `senderName` TEXT NOT NULL, `deviceId` TEXT, `deviceName` TEXT, `devId` INTEGER, `devImage` TEXT, `sentTime` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "messageId", + "columnName": "messageId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "received", + "columnName": "received", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "text", + "columnName": "text", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderName", + "columnName": "senderName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "deviceId", + "columnName": "deviceId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "deviceName", + "columnName": "deviceName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "devId", + "columnName": "devId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "devImage", + "columnName": "devImage", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "sentTime", + "columnName": "sentTime", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "messageId" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `messageId` INTEGER NOT NULL, `messageType` INTEGER NOT NULL, `messageSubject` TEXT NOT NULL, `messageBody` TEXT, `senderId` INTEGER, `addedDate` INTEGER NOT NULL, `messageIsPinned` INTEGER NOT NULL, `hasAttachments` INTEGER NOT NULL, `attachmentIds` TEXT, `attachmentNames` TEXT, `attachmentSizes` TEXT, `keep` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `messageId`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "messageId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "messageType", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "messageSubject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "body", + "columnName": "messageBody", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "senderId", + "columnName": "senderId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "addedDate", + "columnName": "addedDate", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isStarred", + "columnName": "messageIsPinned", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hasAttachments", + "columnName": "hasAttachments", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "attachmentIds", + "columnName": "attachmentIds", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "attachmentNames", + "columnName": "attachmentNames", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "attachmentSizes", + "columnName": "attachmentSizes", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "keep", + "columnName": "keep", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "profileId", + "messageId" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_messages_profileId_messageType", + "unique": false, + "columnNames": [ + "profileId", + "messageType" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_messages_profileId_messageType` ON `${TABLE_NAME}` (`profileId`, `messageType`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "messageRecipients", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `messageRecipientId` INTEGER NOT NULL, `messageRecipientReplyId` INTEGER NOT NULL, `messageRecipientReadDate` INTEGER NOT NULL, `messageId` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `messageRecipientId`, `messageId`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "messageRecipientId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "replyId", + "columnName": "messageRecipientReplyId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "readDate", + "columnName": "messageRecipientReadDate", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "messageId", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "profileId", + "messageRecipientId", + "messageId" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "debugLogs", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `text` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "text", + "columnName": "text", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "endpointTimers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `endpointId` INTEGER NOT NULL, `endpointLastSync` INTEGER, `endpointNextSync` INTEGER NOT NULL, `endpointViewId` INTEGER, PRIMARY KEY(`profileId`, `endpointId`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "endpointId", + "columnName": "endpointId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastSync", + "columnName": "endpointLastSync", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "nextSync", + "columnName": "endpointNextSync", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "viewId", + "columnName": "endpointViewId", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "profileId", + "endpointId" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "lessonRanges", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `lessonRangeNumber` INTEGER NOT NULL, `lessonRangeStart` TEXT NOT NULL, `lessonRangeEnd` TEXT NOT NULL, PRIMARY KEY(`profileId`, `lessonRangeNumber`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lessonNumber", + "columnName": "lessonRangeNumber", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "startTime", + "columnName": "lessonRangeStart", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "endTime", + "columnName": "lessonRangeEnd", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "profileId", + "lessonRangeNumber" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "notifications", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `title` TEXT NOT NULL, `text` TEXT NOT NULL, `textLong` TEXT, `type` INTEGER NOT NULL, `profileId` INTEGER, `profileName` TEXT, `posted` INTEGER NOT NULL, `viewId` INTEGER, `extras` TEXT, `addedDate` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "text", + "columnName": "text", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "textLong", + "columnName": "textLong", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "profileName", + "columnName": "profileName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "posted", + "columnName": "posted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "viewId", + "columnName": "viewId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "extras", + "columnName": "extras", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "addedDate", + "columnName": "addedDate", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "classrooms", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `id` INTEGER NOT NULL, `name` TEXT NOT NULL, PRIMARY KEY(`profileId`, `id`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "profileId", + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "noticeTypes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `id` INTEGER NOT NULL, `name` TEXT NOT NULL, PRIMARY KEY(`profileId`, `id`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "profileId", + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "attendanceTypes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `id` INTEGER NOT NULL, `baseType` INTEGER NOT NULL, `typeName` TEXT NOT NULL, `typeShort` TEXT NOT NULL, `typeSymbol` TEXT NOT NULL, `typeColor` INTEGER, PRIMARY KEY(`profileId`, `id`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "baseType", + "columnName": "baseType", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "typeName", + "columnName": "typeName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "typeShort", + "columnName": "typeShort", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "typeSymbol", + "columnName": "typeSymbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "typeColor", + "columnName": "typeColor", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "profileId", + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "timetable", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `id` INTEGER NOT NULL, `type` INTEGER NOT NULL, `date` TEXT, `lessonNumber` INTEGER, `startTime` TEXT, `endTime` TEXT, `subjectId` INTEGER, `teacherId` INTEGER, `teamId` INTEGER, `classroom` TEXT, `oldDate` TEXT, `oldLessonNumber` INTEGER, `oldStartTime` TEXT, `oldEndTime` TEXT, `oldSubjectId` INTEGER, `oldTeacherId` INTEGER, `oldTeamId` INTEGER, `oldClassroom` TEXT, `isExtra` INTEGER NOT NULL, `color` INTEGER, `keep` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `id`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "lessonNumber", + "columnName": "lessonNumber", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "startTime", + "columnName": "startTime", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "endTime", + "columnName": "endTime", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "subjectId", + "columnName": "subjectId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "teacherId", + "columnName": "teacherId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "teamId", + "columnName": "teamId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "classroom", + "columnName": "classroom", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "oldDate", + "columnName": "oldDate", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "oldLessonNumber", + "columnName": "oldLessonNumber", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "oldStartTime", + "columnName": "oldStartTime", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "oldEndTime", + "columnName": "oldEndTime", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "oldSubjectId", + "columnName": "oldSubjectId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "oldTeacherId", + "columnName": "oldTeacherId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "oldTeamId", + "columnName": "oldTeamId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "oldClassroom", + "columnName": "oldClassroom", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "isExtra", + "columnName": "isExtra", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "keep", + "columnName": "keep", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "profileId", + "id" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_timetable_profileId_type_date", + "unique": false, + "columnNames": [ + "profileId", + "type", + "date" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_timetable_profileId_type_date` ON `${TABLE_NAME}` (`profileId`, `type`, `date`)" + }, + { + "name": "index_timetable_profileId_type_oldDate", + "unique": false, + "columnNames": [ + "profileId", + "type", + "oldDate" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_timetable_profileId_type_oldDate` ON `${TABLE_NAME}` (`profileId`, `type`, `oldDate`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "config", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `key` TEXT NOT NULL, `value` TEXT, PRIMARY KEY(`profileId`, `key`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "key", + "columnName": "key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "profileId", + "key" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "librusLessons", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `lessonId` INTEGER NOT NULL, `teacherId` INTEGER NOT NULL, `subjectId` INTEGER NOT NULL, `teamId` INTEGER, PRIMARY KEY(`profileId`, `lessonId`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lessonId", + "columnName": "lessonId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacherId", + "columnName": "teacherId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subjectId", + "columnName": "subjectId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teamId", + "columnName": "teamId", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "profileId", + "lessonId" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_librusLessons_profileId", + "unique": false, + "columnNames": [ + "profileId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_librusLessons_profileId` ON `${TABLE_NAME}` (`profileId`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "timetableManual", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `type` INTEGER NOT NULL, `repeatBy` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `date` INTEGER, `weekDay` INTEGER, `lessonNumber` INTEGER, `startTime` TEXT, `endTime` TEXT, `subjectId` INTEGER, `teacherId` INTEGER, `teamId` INTEGER, `classroom` TEXT)", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "repeatBy", + "columnName": "repeatBy", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "weekDay", + "columnName": "weekDay", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "lessonNumber", + "columnName": "lessonNumber", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "startTime", + "columnName": "startTime", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "endTime", + "columnName": "endTime", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "subjectId", + "columnName": "subjectId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "teacherId", + "columnName": "teacherId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "teamId", + "columnName": "teamId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "classroom", + "columnName": "classroom", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_timetableManual_profileId_date", + "unique": false, + "columnNames": [ + "profileId", + "date" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_timetableManual_profileId_date` ON `${TABLE_NAME}` (`profileId`, `date`)" + }, + { + "name": "index_timetableManual_profileId_weekDay", + "unique": false, + "columnNames": [ + "profileId", + "weekDay" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_timetableManual_profileId_weekDay` ON `${TABLE_NAME}` (`profileId`, `weekDay`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "notes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `noteId` INTEGER NOT NULL, `noteOwnerType` TEXT, `noteOwnerId` INTEGER, `noteReplacesOriginal` INTEGER NOT NULL, `noteTopic` TEXT, `noteBody` TEXT NOT NULL, `noteColor` INTEGER, `noteSharedBy` TEXT, `noteSharedByName` TEXT, `addedDate` INTEGER NOT NULL, PRIMARY KEY(`noteId`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "noteId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "ownerType", + "columnName": "noteOwnerType", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "ownerId", + "columnName": "noteOwnerId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "replacesOriginal", + "columnName": "noteReplacesOriginal", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "topic", + "columnName": "noteTopic", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "body", + "columnName": "noteBody", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "noteColor", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "sharedBy", + "columnName": "noteSharedBy", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "sharedByName", + "columnName": "noteSharedByName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "addedDate", + "columnName": "addedDate", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "noteId" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_notes_profileId_noteOwnerType_noteOwnerId", + "unique": false, + "columnNames": [ + "profileId", + "noteOwnerType", + "noteOwnerId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_notes_profileId_noteOwnerType_noteOwnerId` ON `${TABLE_NAME}` (`profileId`, `noteOwnerType`, `noteOwnerId`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "metadata", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `metadataId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `thingType` INTEGER NOT NULL, `thingId` INTEGER NOT NULL, `seen` INTEGER NOT NULL, `notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "metadataId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "thingType", + "columnName": "thingType", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "thingId", + "columnName": "thingId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "seen", + "columnName": "seen", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "notified", + "columnName": "notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "metadataId" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_metadata_profileId_thingType_thingId", + "unique": true, + "columnNames": [ + "profileId", + "thingType", + "thingId" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_metadata_profileId_thingType_thingId` ON `${TABLE_NAME}` (`profileId`, `thingType`, `thingId`)" + } + ], + "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, '2612ebba9802eedc7ebc69724606a23c')" + ] + } +} \ No newline at end of file diff --git a/app/schemas/pl.szczodrzynski.edziennik.data.db.AppDb/99.json b/app/schemas/pl.szczodrzynski.edziennik.data.db.AppDb/99.json new file mode 100644 index 00000000..7db65929 --- /dev/null +++ b/app/schemas/pl.szczodrzynski.edziennik.data.db.AppDb/99.json @@ -0,0 +1,2314 @@ +{ + "formatVersion": 1, + "database": { + "version": 99, + "identityHash": "2612ebba9802eedc7ebc69724606a23c", + "entities": [ + { + "tableName": "grades", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `gradeId` INTEGER NOT NULL, `gradeName` TEXT NOT NULL, `gradeType` INTEGER NOT NULL, `gradeValue` REAL NOT NULL, `gradeWeight` REAL NOT NULL, `gradeColor` INTEGER NOT NULL, `gradeCategory` TEXT, `gradeDescription` TEXT, `gradeComment` TEXT, `gradeSemester` INTEGER NOT NULL, `teacherId` INTEGER NOT NULL, `subjectId` INTEGER NOT NULL, `addedDate` INTEGER NOT NULL, `gradeValueMax` REAL, `gradeClassAverage` REAL, `gradeParentId` INTEGER, `gradeIsImprovement` INTEGER NOT NULL, `keep` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `gradeId`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "gradeId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "gradeName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "gradeType", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "gradeValue", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "weight", + "columnName": "gradeWeight", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "gradeColor", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "gradeCategory", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "description", + "columnName": "gradeDescription", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "comment", + "columnName": "gradeComment", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "semester", + "columnName": "gradeSemester", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacherId", + "columnName": "teacherId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subjectId", + "columnName": "subjectId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "addedDate", + "columnName": "addedDate", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "valueMax", + "columnName": "gradeValueMax", + "affinity": "REAL", + "notNull": false + }, + { + "fieldPath": "classAverage", + "columnName": "gradeClassAverage", + "affinity": "REAL", + "notNull": false + }, + { + "fieldPath": "parentId", + "columnName": "gradeParentId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "isImprovement", + "columnName": "gradeIsImprovement", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "keep", + "columnName": "keep", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "profileId", + "gradeId" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_grades_profileId", + "unique": false, + "columnNames": [ + "profileId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_grades_profileId` ON `${TABLE_NAME}` (`profileId`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "teachers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `teacherId` INTEGER NOT NULL, `teacherLoginId` TEXT, `teacherName` TEXT, `teacherSurname` TEXT, `teacherType` INTEGER NOT NULL, `teacherTypeDescription` TEXT, `teacherSubjects` TEXT NOT NULL, PRIMARY KEY(`profileId`, `teacherId`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "teacherId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "loginId", + "columnName": "teacherLoginId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "name", + "columnName": "teacherName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "surname", + "columnName": "teacherSurname", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "type", + "columnName": "teacherType", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "typeDescription", + "columnName": "teacherTypeDescription", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "subjects", + "columnName": "teacherSubjects", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "profileId", + "teacherId" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "teacherAbsence", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `teacherAbsenceId` INTEGER NOT NULL, `teacherAbsenceType` INTEGER NOT NULL, `teacherAbsenceName` TEXT, `teacherAbsenceDateFrom` TEXT NOT NULL, `teacherAbsenceDateTo` TEXT NOT NULL, `teacherAbsenceTimeFrom` TEXT, `teacherAbsenceTimeTo` TEXT, `teacherId` INTEGER NOT NULL, `addedDate` INTEGER NOT NULL, `keep` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `teacherAbsenceId`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "teacherAbsenceId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "teacherAbsenceType", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "teacherAbsenceName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "dateFrom", + "columnName": "teacherAbsenceDateFrom", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "dateTo", + "columnName": "teacherAbsenceDateTo", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "timeFrom", + "columnName": "teacherAbsenceTimeFrom", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "timeTo", + "columnName": "teacherAbsenceTimeTo", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "teacherId", + "columnName": "teacherId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "addedDate", + "columnName": "addedDate", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "keep", + "columnName": "keep", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "profileId", + "teacherAbsenceId" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_teacherAbsence_profileId", + "unique": false, + "columnNames": [ + "profileId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_teacherAbsence_profileId` ON `${TABLE_NAME}` (`profileId`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "teacherAbsenceTypes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `teacherAbsenceTypeId` INTEGER NOT NULL, `teacherAbsenceTypeName` TEXT NOT NULL, PRIMARY KEY(`profileId`, `teacherAbsenceTypeId`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "teacherAbsenceTypeId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "teacherAbsenceTypeName", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "profileId", + "teacherAbsenceTypeId" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "subjects", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `subjectId` INTEGER NOT NULL, `subjectLongName` TEXT, `subjectShortName` TEXT, `subjectColor` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `subjectId`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "subjectId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "longName", + "columnName": "subjectLongName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "shortName", + "columnName": "subjectShortName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "color", + "columnName": "subjectColor", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "profileId", + "subjectId" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "notices", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `noticeId` INTEGER NOT NULL, `noticeType` INTEGER NOT NULL, `noticeSemester` INTEGER NOT NULL, `noticeText` TEXT NOT NULL, `noticeCategory` TEXT, `noticePoints` REAL, `teacherId` INTEGER NOT NULL, `addedDate` INTEGER NOT NULL, `keep` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `noticeId`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "noticeId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "noticeType", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semester", + "columnName": "noticeSemester", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "text", + "columnName": "noticeText", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "noticeCategory", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "points", + "columnName": "noticePoints", + "affinity": "REAL", + "notNull": false + }, + { + "fieldPath": "teacherId", + "columnName": "teacherId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "addedDate", + "columnName": "addedDate", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "keep", + "columnName": "keep", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "profileId", + "noticeId" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_notices_profileId", + "unique": false, + "columnNames": [ + "profileId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_notices_profileId` ON `${TABLE_NAME}` (`profileId`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "teams", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `teamId` INTEGER NOT NULL, `teamType` INTEGER NOT NULL, `teamName` TEXT, `teamCode` TEXT, `teamTeacherId` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `teamId`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "teamId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "teamType", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "teamName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "code", + "columnName": "teamCode", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "teacherId", + "columnName": "teamTeacherId", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "profileId", + "teamId" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "attendances", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `attendanceId` INTEGER NOT NULL, `attendanceBaseType` INTEGER NOT NULL, `attendanceTypeName` TEXT NOT NULL, `attendanceTypeShort` TEXT NOT NULL, `attendanceTypeSymbol` TEXT NOT NULL, `attendanceTypeColor` INTEGER, `attendanceDate` TEXT NOT NULL, `attendanceTime` TEXT, `attendanceSemester` INTEGER NOT NULL, `teacherId` INTEGER NOT NULL, `subjectId` INTEGER NOT NULL, `addedDate` INTEGER NOT NULL, `attendanceLessonTopic` TEXT, `attendanceLessonNumber` INTEGER, `attendanceIsCounted` INTEGER NOT NULL, `keep` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `attendanceId`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "attendanceId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "baseType", + "columnName": "attendanceBaseType", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "typeName", + "columnName": "attendanceTypeName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "typeShort", + "columnName": "attendanceTypeShort", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "typeSymbol", + "columnName": "attendanceTypeSymbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "typeColor", + "columnName": "attendanceTypeColor", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "date", + "columnName": "attendanceDate", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "startTime", + "columnName": "attendanceTime", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "semester", + "columnName": "attendanceSemester", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacherId", + "columnName": "teacherId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subjectId", + "columnName": "subjectId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "addedDate", + "columnName": "addedDate", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lessonTopic", + "columnName": "attendanceLessonTopic", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "lessonNumber", + "columnName": "attendanceLessonNumber", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "isCounted", + "columnName": "attendanceIsCounted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "keep", + "columnName": "keep", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "profileId", + "attendanceId" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_attendances_profileId", + "unique": false, + "columnNames": [ + "profileId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_attendances_profileId` ON `${TABLE_NAME}` (`profileId`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "events", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `eventId` INTEGER NOT NULL, `eventDate` TEXT NOT NULL, `eventTime` TEXT, `eventTopic` TEXT NOT NULL, `eventColor` INTEGER, `eventType` INTEGER NOT NULL, `teacherId` INTEGER NOT NULL, `subjectId` INTEGER NOT NULL, `teamId` INTEGER NOT NULL, `addedDate` INTEGER NOT NULL, `eventAddedManually` INTEGER NOT NULL, `eventSharedBy` TEXT, `eventSharedByName` TEXT, `eventBlacklisted` INTEGER NOT NULL, `eventIsDone` INTEGER NOT NULL, `eventIsDownloaded` INTEGER NOT NULL, `homeworkBody` TEXT, `attachmentIds` TEXT, `attachmentNames` TEXT, `keep` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `eventId`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "eventId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "eventDate", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "time", + "columnName": "eventTime", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "topic", + "columnName": "eventTopic", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "eventColor", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "type", + "columnName": "eventType", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacherId", + "columnName": "teacherId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subjectId", + "columnName": "subjectId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teamId", + "columnName": "teamId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "addedDate", + "columnName": "addedDate", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "addedManually", + "columnName": "eventAddedManually", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sharedBy", + "columnName": "eventSharedBy", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "sharedByName", + "columnName": "eventSharedByName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "blacklisted", + "columnName": "eventBlacklisted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isDone", + "columnName": "eventIsDone", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isDownloaded", + "columnName": "eventIsDownloaded", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "homeworkBody", + "columnName": "homeworkBody", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "attachmentIds", + "columnName": "attachmentIds", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "attachmentNames", + "columnName": "attachmentNames", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "keep", + "columnName": "keep", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "profileId", + "eventId" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_events_profileId_eventDate_eventTime", + "unique": false, + "columnNames": [ + "profileId", + "eventDate", + "eventTime" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_events_profileId_eventDate_eventTime` ON `${TABLE_NAME}` (`profileId`, `eventDate`, `eventTime`)" + }, + { + "name": "index_events_profileId_eventType", + "unique": false, + "columnNames": [ + "profileId", + "eventType" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_events_profileId_eventType` ON `${TABLE_NAME}` (`profileId`, `eventType`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "eventTypes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `eventType` INTEGER NOT NULL, `eventTypeName` TEXT NOT NULL, `eventTypeColor` INTEGER NOT NULL, `eventTypeOrder` INTEGER NOT NULL, `eventTypeSource` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `eventType`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "eventType", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "eventTypeName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "eventTypeColor", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "order", + "columnName": "eventTypeOrder", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "source", + "columnName": "eventTypeSource", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "profileId", + "eventType" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "loginStores", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`loginStoreId` INTEGER NOT NULL, `loginStoreType` INTEGER NOT NULL, `loginStoreMode` INTEGER NOT NULL, `loginStoreData` TEXT NOT NULL, PRIMARY KEY(`loginStoreId`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "loginStoreId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "loginStoreType", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "mode", + "columnName": "loginStoreMode", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "data", + "columnName": "loginStoreData", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "loginStoreId" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "profiles", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `loginStoreId` INTEGER NOT NULL, `loginStoreType` INTEGER NOT NULL, `name` TEXT NOT NULL, `subname` TEXT, `studentNameLong` TEXT NOT NULL, `studentNameShort` TEXT NOT NULL, `accountName` TEXT, `studentData` TEXT NOT NULL, `image` TEXT, `empty` INTEGER NOT NULL, `archived` INTEGER NOT NULL, `archiveId` INTEGER, `syncEnabled` INTEGER NOT NULL, `enableSharedEvents` INTEGER NOT NULL, `registration` INTEGER NOT NULL, `userCode` TEXT NOT NULL, `studentNumber` INTEGER NOT NULL, `studentClassName` TEXT, `studentSchoolYearStart` INTEGER NOT NULL, `dateSemester1Start` TEXT NOT NULL, `dateSemester2Start` TEXT NOT NULL, `dateYearEnd` TEXT NOT NULL, `disabledNotifications` TEXT, `lastReceiversSync` INTEGER NOT NULL, PRIMARY KEY(`profileId`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "loginStoreId", + "columnName": "loginStoreId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "loginStoreType", + "columnName": "loginStoreType", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subname", + "columnName": "subname", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "studentNameLong", + "columnName": "studentNameLong", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentNameShort", + "columnName": "studentNameShort", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "accountName", + "columnName": "accountName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "studentData", + "columnName": "studentData", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "image", + "columnName": "image", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "empty", + "columnName": "empty", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "archived", + "columnName": "archived", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "archiveId", + "columnName": "archiveId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "syncEnabled", + "columnName": "syncEnabled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "enableSharedEvents", + "columnName": "enableSharedEvents", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "registration", + "columnName": "registration", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userCode", + "columnName": "userCode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentNumber", + "columnName": "studentNumber", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentClassName", + "columnName": "studentClassName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "studentSchoolYearStart", + "columnName": "studentSchoolYearStart", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dateSemester1Start", + "columnName": "dateSemester1Start", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "dateSemester2Start", + "columnName": "dateSemester2Start", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "dateYearEnd", + "columnName": "dateYearEnd", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "disabledNotifications", + "columnName": "disabledNotifications", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "lastReceiversSync", + "columnName": "lastReceiversSync", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "profileId" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "luckyNumbers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `luckyNumberDate` INTEGER NOT NULL, `luckyNumber` INTEGER NOT NULL, `keep` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `luckyNumberDate`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "luckyNumberDate", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "luckyNumber", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "keep", + "columnName": "keep", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "profileId", + "luckyNumberDate" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "announcements", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `announcementId` INTEGER NOT NULL, `announcementSubject` TEXT NOT NULL, `announcementText` TEXT, `announcementStartDate` TEXT, `announcementEndDate` TEXT, `teacherId` INTEGER NOT NULL, `addedDate` INTEGER NOT NULL, `announcementIdString` TEXT, `keep` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `announcementId`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "announcementId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "announcementSubject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "text", + "columnName": "announcementText", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "startDate", + "columnName": "announcementStartDate", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "endDate", + "columnName": "announcementEndDate", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "teacherId", + "columnName": "teacherId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "addedDate", + "columnName": "addedDate", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "idString", + "columnName": "announcementIdString", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "keep", + "columnName": "keep", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "profileId", + "announcementId" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_announcements_profileId", + "unique": false, + "columnNames": [ + "profileId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_announcements_profileId` ON `${TABLE_NAME}` (`profileId`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "gradeCategories", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `categoryId` INTEGER NOT NULL, `weight` REAL NOT NULL, `color` INTEGER NOT NULL, `text` TEXT, `columns` TEXT, `valueFrom` REAL NOT NULL, `valueTo` REAL NOT NULL, `type` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `categoryId`, `type`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "categoryId", + "columnName": "categoryId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "weight", + "columnName": "weight", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "text", + "columnName": "text", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "columns", + "columnName": "columns", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "valueFrom", + "columnName": "valueFrom", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "valueTo", + "columnName": "valueTo", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "profileId", + "categoryId", + "type" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "feedbackMessages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`messageId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `received` INTEGER NOT NULL, `text` TEXT NOT NULL, `senderName` TEXT NOT NULL, `deviceId` TEXT, `deviceName` TEXT, `devId` INTEGER, `devImage` TEXT, `sentTime` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "messageId", + "columnName": "messageId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "received", + "columnName": "received", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "text", + "columnName": "text", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderName", + "columnName": "senderName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "deviceId", + "columnName": "deviceId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "deviceName", + "columnName": "deviceName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "devId", + "columnName": "devId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "devImage", + "columnName": "devImage", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "sentTime", + "columnName": "sentTime", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "messageId" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `messageId` INTEGER NOT NULL, `messageType` INTEGER NOT NULL, `messageSubject` TEXT NOT NULL, `messageBody` TEXT, `senderId` INTEGER, `addedDate` INTEGER NOT NULL, `messageIsPinned` INTEGER NOT NULL, `hasAttachments` INTEGER NOT NULL, `attachmentIds` TEXT, `attachmentNames` TEXT, `attachmentSizes` TEXT, `keep` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `messageId`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "messageId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "messageType", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "messageSubject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "body", + "columnName": "messageBody", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "senderId", + "columnName": "senderId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "addedDate", + "columnName": "addedDate", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isStarred", + "columnName": "messageIsPinned", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hasAttachments", + "columnName": "hasAttachments", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "attachmentIds", + "columnName": "attachmentIds", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "attachmentNames", + "columnName": "attachmentNames", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "attachmentSizes", + "columnName": "attachmentSizes", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "keep", + "columnName": "keep", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "profileId", + "messageId" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_messages_profileId_messageType", + "unique": false, + "columnNames": [ + "profileId", + "messageType" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_messages_profileId_messageType` ON `${TABLE_NAME}` (`profileId`, `messageType`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "messageRecipients", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `messageRecipientId` INTEGER NOT NULL, `messageRecipientReplyId` INTEGER NOT NULL, `messageRecipientReadDate` INTEGER NOT NULL, `messageId` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `messageRecipientId`, `messageId`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "messageRecipientId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "replyId", + "columnName": "messageRecipientReplyId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "readDate", + "columnName": "messageRecipientReadDate", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "messageId", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "profileId", + "messageRecipientId", + "messageId" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "debugLogs", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `text` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "text", + "columnName": "text", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "endpointTimers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `endpointId` INTEGER NOT NULL, `endpointLastSync` INTEGER, `endpointNextSync` INTEGER NOT NULL, `endpointViewId` INTEGER, PRIMARY KEY(`profileId`, `endpointId`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "endpointId", + "columnName": "endpointId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastSync", + "columnName": "endpointLastSync", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "nextSync", + "columnName": "endpointNextSync", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "viewId", + "columnName": "endpointViewId", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "profileId", + "endpointId" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "lessonRanges", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `lessonRangeNumber` INTEGER NOT NULL, `lessonRangeStart` TEXT NOT NULL, `lessonRangeEnd` TEXT NOT NULL, PRIMARY KEY(`profileId`, `lessonRangeNumber`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lessonNumber", + "columnName": "lessonRangeNumber", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "startTime", + "columnName": "lessonRangeStart", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "endTime", + "columnName": "lessonRangeEnd", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "profileId", + "lessonRangeNumber" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "notifications", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `title` TEXT NOT NULL, `text` TEXT NOT NULL, `textLong` TEXT, `type` INTEGER NOT NULL, `profileId` INTEGER, `profileName` TEXT, `posted` INTEGER NOT NULL, `viewId` INTEGER, `extras` TEXT, `addedDate` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "text", + "columnName": "text", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "textLong", + "columnName": "textLong", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "profileName", + "columnName": "profileName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "posted", + "columnName": "posted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "viewId", + "columnName": "viewId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "extras", + "columnName": "extras", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "addedDate", + "columnName": "addedDate", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "classrooms", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `id` INTEGER NOT NULL, `name` TEXT NOT NULL, PRIMARY KEY(`profileId`, `id`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "profileId", + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "noticeTypes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `id` INTEGER NOT NULL, `name` TEXT NOT NULL, PRIMARY KEY(`profileId`, `id`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "profileId", + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "attendanceTypes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `id` INTEGER NOT NULL, `baseType` INTEGER NOT NULL, `typeName` TEXT NOT NULL, `typeShort` TEXT NOT NULL, `typeSymbol` TEXT NOT NULL, `typeColor` INTEGER, PRIMARY KEY(`profileId`, `id`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "baseType", + "columnName": "baseType", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "typeName", + "columnName": "typeName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "typeShort", + "columnName": "typeShort", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "typeSymbol", + "columnName": "typeSymbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "typeColor", + "columnName": "typeColor", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "profileId", + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "timetable", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `id` INTEGER NOT NULL, `type` INTEGER NOT NULL, `date` TEXT, `lessonNumber` INTEGER, `startTime` TEXT, `endTime` TEXT, `subjectId` INTEGER, `teacherId` INTEGER, `teamId` INTEGER, `classroom` TEXT, `oldDate` TEXT, `oldLessonNumber` INTEGER, `oldStartTime` TEXT, `oldEndTime` TEXT, `oldSubjectId` INTEGER, `oldTeacherId` INTEGER, `oldTeamId` INTEGER, `oldClassroom` TEXT, `isExtra` INTEGER NOT NULL, `color` INTEGER, `keep` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `id`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "lessonNumber", + "columnName": "lessonNumber", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "startTime", + "columnName": "startTime", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "endTime", + "columnName": "endTime", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "subjectId", + "columnName": "subjectId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "teacherId", + "columnName": "teacherId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "teamId", + "columnName": "teamId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "classroom", + "columnName": "classroom", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "oldDate", + "columnName": "oldDate", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "oldLessonNumber", + "columnName": "oldLessonNumber", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "oldStartTime", + "columnName": "oldStartTime", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "oldEndTime", + "columnName": "oldEndTime", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "oldSubjectId", + "columnName": "oldSubjectId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "oldTeacherId", + "columnName": "oldTeacherId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "oldTeamId", + "columnName": "oldTeamId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "oldClassroom", + "columnName": "oldClassroom", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "isExtra", + "columnName": "isExtra", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "keep", + "columnName": "keep", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "profileId", + "id" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_timetable_profileId_type_date", + "unique": false, + "columnNames": [ + "profileId", + "type", + "date" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_timetable_profileId_type_date` ON `${TABLE_NAME}` (`profileId`, `type`, `date`)" + }, + { + "name": "index_timetable_profileId_type_oldDate", + "unique": false, + "columnNames": [ + "profileId", + "type", + "oldDate" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_timetable_profileId_type_oldDate` ON `${TABLE_NAME}` (`profileId`, `type`, `oldDate`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "config", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `key` TEXT NOT NULL, `value` TEXT, PRIMARY KEY(`profileId`, `key`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "key", + "columnName": "key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "profileId", + "key" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "librusLessons", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `lessonId` INTEGER NOT NULL, `teacherId` INTEGER NOT NULL, `subjectId` INTEGER NOT NULL, `teamId` INTEGER, PRIMARY KEY(`profileId`, `lessonId`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lessonId", + "columnName": "lessonId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacherId", + "columnName": "teacherId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subjectId", + "columnName": "subjectId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teamId", + "columnName": "teamId", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "profileId", + "lessonId" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_librusLessons_profileId", + "unique": false, + "columnNames": [ + "profileId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_librusLessons_profileId` ON `${TABLE_NAME}` (`profileId`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "timetableManual", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `type` INTEGER NOT NULL, `repeatBy` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `date` INTEGER, `weekDay` INTEGER, `lessonNumber` INTEGER, `startTime` TEXT, `endTime` TEXT, `subjectId` INTEGER, `teacherId` INTEGER, `teamId` INTEGER, `classroom` TEXT)", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "repeatBy", + "columnName": "repeatBy", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "weekDay", + "columnName": "weekDay", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "lessonNumber", + "columnName": "lessonNumber", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "startTime", + "columnName": "startTime", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "endTime", + "columnName": "endTime", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "subjectId", + "columnName": "subjectId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "teacherId", + "columnName": "teacherId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "teamId", + "columnName": "teamId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "classroom", + "columnName": "classroom", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_timetableManual_profileId_date", + "unique": false, + "columnNames": [ + "profileId", + "date" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_timetableManual_profileId_date` ON `${TABLE_NAME}` (`profileId`, `date`)" + }, + { + "name": "index_timetableManual_profileId_weekDay", + "unique": false, + "columnNames": [ + "profileId", + "weekDay" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_timetableManual_profileId_weekDay` ON `${TABLE_NAME}` (`profileId`, `weekDay`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "notes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `noteId` INTEGER NOT NULL, `noteOwnerType` TEXT, `noteOwnerId` INTEGER, `noteReplacesOriginal` INTEGER NOT NULL, `noteTopic` TEXT, `noteBody` TEXT NOT NULL, `noteColor` INTEGER, `noteSharedBy` TEXT, `noteSharedByName` TEXT, `addedDate` INTEGER NOT NULL, PRIMARY KEY(`noteId`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "noteId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "ownerType", + "columnName": "noteOwnerType", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "ownerId", + "columnName": "noteOwnerId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "replacesOriginal", + "columnName": "noteReplacesOriginal", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "topic", + "columnName": "noteTopic", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "body", + "columnName": "noteBody", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "noteColor", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "sharedBy", + "columnName": "noteSharedBy", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "sharedByName", + "columnName": "noteSharedByName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "addedDate", + "columnName": "addedDate", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "noteId" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_notes_profileId_noteOwnerType_noteOwnerId", + "unique": false, + "columnNames": [ + "profileId", + "noteOwnerType", + "noteOwnerId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_notes_profileId_noteOwnerType_noteOwnerId` ON `${TABLE_NAME}` (`profileId`, `noteOwnerType`, `noteOwnerId`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "metadata", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `metadataId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `thingType` INTEGER NOT NULL, `thingId` INTEGER NOT NULL, `seen` INTEGER NOT NULL, `notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "metadataId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "thingType", + "columnName": "thingType", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "thingId", + "columnName": "thingId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "seen", + "columnName": "seen", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "notified", + "columnName": "notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "metadataId" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_metadata_profileId_thingType_thingId", + "unique": true, + "columnNames": [ + "profileId", + "thingType", + "thingId" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_metadata_profileId_thingType_thingId` ON `${TABLE_NAME}` (`profileId`, `thingType`, `thingId`)" + } + ], + "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, '2612ebba9802eedc7ebc69724606a23c')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 172ed891..c7f3d68c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -13,7 +13,6 @@ - @@ -159,6 +158,14 @@ android:configChanges="orientation|keyboardHidden" android:exported="false" android:theme="@style/Base.Theme.AppCompat" /> + + diff --git a/app/src/main/assets/pl-changelog.html b/app/src/main/assets/pl-changelog.html index 2548b456..af4aaca4 100644 --- a/app/src/main/assets/pl-changelog.html +++ b/app/src/main/assets/pl-changelog.html @@ -1,12 +1,10 @@ -

Wersja 4.12.1, 2022-09-23

+

Wersja 4.13.6, 2023-03-24

    -
  • Vulcan UONET+: naprawiono działanie systemu wiadomości. @Antoni-Czaplicki
  • -
  • Vulcan UONET+: naprawiono błędy wersji 4.11.9 i starszych.
  • -
  • Poprawiono wyświetlanie lekcji odwołanych na stronie głównej.
  • -
  • Dodano dostęp do Laboratorium na ekranie logowania.
  • -
  • Usunięto obsługę dziennika EduDziennik. [*]
  • +
  • Naprawiono pobieranie załączników na Androidzie 13 i nowszym.
  • +
  • Dodano opcję odświeżenia planu lekcji na wybrany tydzień.
  • +
  • Usunięto błędy logowania. @BxOxSxS


Dzięki za korzystanie ze Szkolnego!
-© [Kuba Szczodrzyński](@kuba2k2), [Kacper Ziubryniewicz](@kapi2289) 2022 +© [Kuba Szczodrzyński](@kuba2k2) 2023 diff --git a/app/src/main/cpp/szkolny-signing.cpp b/app/src/main/cpp/szkolny-signing.cpp index 639ca56e..9ae8f745 100644 --- a/app/src/main/cpp/szkolny-signing.cpp +++ b/app/src/main/cpp/szkolny-signing.cpp @@ -9,7 +9,7 @@ /*secret password - removed for source code publication*/ static toys AES_IV[16] = { - 0xdf, 0xe4, 0x2d, 0xa3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; + 0x6d, 0xa5, 0x32, 0xe6, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; unsigned char *agony(unsigned int laugh, unsigned char *box, unsigned char *heat); diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/App.kt b/app/src/main/java/pl/szczodrzynski/edziennik/App.kt index 56886775..74e23d88 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/App.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/App.kt @@ -12,6 +12,7 @@ import android.graphics.drawable.Icon import android.os.Build import android.provider.Settings import android.util.Log +import android.widget.Toast import androidx.appcompat.app.AppCompatDelegate import androidx.multidex.MultiDexApplication import androidx.work.Configuration @@ -27,36 +28,65 @@ import com.google.gson.Gson import com.hypertrack.hyperlog.HyperLog import com.mikepenz.iconics.Iconics import im.wangchao.mhttp.MHttp -import kotlinx.coroutines.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import me.leolin.shortcutbadger.ShortcutBadger import okhttp3.OkHttpClient import org.greenrobot.eventbus.EventBus +import pl.szczodrzynski.edziennik.config.AppData import pl.szczodrzynski.edziennik.config.Config import pl.szczodrzynski.edziennik.data.api.events.ProfileListEmptyEvent import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi import pl.szczodrzynski.edziennik.data.api.szkolny.interceptor.Signing import pl.szczodrzynski.edziennik.data.db.AppDb import pl.szczodrzynski.edziennik.data.db.entity.Profile +import pl.szczodrzynski.edziennik.data.db.enums.LoginType import pl.szczodrzynski.edziennik.ext.DAY import pl.szczodrzynski.edziennik.ext.MS +import pl.szczodrzynski.edziennik.ext.putExtras import pl.szczodrzynski.edziennik.ext.setLanguage import pl.szczodrzynski.edziennik.network.SSLProviderInstaller import pl.szczodrzynski.edziennik.network.cookie.DumbCookieJar import pl.szczodrzynski.edziennik.sync.SyncWorker import pl.szczodrzynski.edziennik.sync.UpdateWorker import pl.szczodrzynski.edziennik.ui.base.CrashActivity -import pl.szczodrzynski.edziennik.utils.* +import pl.szczodrzynski.edziennik.ui.base.enums.NavTarget +import pl.szczodrzynski.edziennik.utils.DebugLogFormat +import pl.szczodrzynski.edziennik.utils.PermissionChecker +import pl.szczodrzynski.edziennik.utils.Themes +import pl.szczodrzynski.edziennik.utils.Utils import pl.szczodrzynski.edziennik.utils.Utils.d -import pl.szczodrzynski.edziennik.utils.managers.* +import pl.szczodrzynski.edziennik.utils.managers.AttendanceManager +import pl.szczodrzynski.edziennik.utils.managers.AvailabilityManager +import pl.szczodrzynski.edziennik.utils.managers.BuildManager +import pl.szczodrzynski.edziennik.utils.managers.EventManager +import pl.szczodrzynski.edziennik.utils.managers.GradesManager +import pl.szczodrzynski.edziennik.utils.managers.MessageManager +import pl.szczodrzynski.edziennik.utils.managers.NoteManager +import pl.szczodrzynski.edziennik.utils.managers.NotificationChannelsManager +import pl.szczodrzynski.edziennik.utils.managers.PermissionManager +import pl.szczodrzynski.edziennik.utils.managers.TextStylingManager +import pl.szczodrzynski.edziennik.utils.managers.TimetableManager +import pl.szczodrzynski.edziennik.utils.managers.UpdateManager +import pl.szczodrzynski.edziennik.utils.managers.UserActionManager import java.util.concurrent.TimeUnit import kotlin.coroutines.CoroutineContext +import kotlin.system.exitProcess class App : MultiDexApplication(), Configuration.Provider, CoroutineScope { companion object { @Volatile lateinit var db: AppDb + private set lateinit var config: Config + // private set // for LabFragment lateinit var profile: Profile + private set + lateinit var data: AppData + private set val profileId get() = profile.id @@ -66,18 +96,19 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope { } val api by lazy { SzkolnyApi(this) } - val notificationChannelsManager by lazy { NotificationChannelsManager(this) } - val userActionManager by lazy { UserActionManager(this) } - val gradesManager by lazy { GradesManager(this) } - val timetableManager by lazy { TimetableManager(this) } - val eventManager by lazy { EventManager(this) } - val permissionManager by lazy { PermissionManager(this) } val attendanceManager by lazy { AttendanceManager(this) } - val buildManager by lazy { BuildManager(this) } val availabilityManager by lazy { AvailabilityManager(this) } - val textStylingManager by lazy { TextStylingManager(this) } + val buildManager by lazy { BuildManager(this) } + val eventManager by lazy { EventManager(this) } + val gradesManager by lazy { GradesManager(this) } val messageManager by lazy { MessageManager(this) } val noteManager by lazy { NoteManager(this) } + val notificationChannelsManager by lazy { NotificationChannelsManager(this) } + val permissionManager by lazy { PermissionManager(this) } + val textStylingManager by lazy { TextStylingManager(this) } + val timetableManager by lazy { TimetableManager(this) } + val updateManager by lazy { UpdateManager(this) } + val userActionManager by lazy { UserActionManager(this) } val db get() = App.db @@ -87,6 +118,8 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope { get() = App.profile val profileId get() = App.profileId + val data + get() = App.data private val job = Job() override val coroutineContext: CoroutineContext @@ -121,9 +154,6 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope { SSLProviderInstaller.enableSupportedTls(builder, enableCleartext = true) if (devMode) { - HyperLog.initialize(this) - HyperLog.setLogLevel(Log.VERBOSE) - HyperLog.setLogFormat(DebugLogFormat(this)) if (enableChucker) { val chuckerCollector = ChuckerCollector(this, true, RetentionManager.Period.ONE_HOUR) val chuckerInterceptor = ChuckerInterceptor(this, chuckerCollector) @@ -178,15 +208,23 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope { Iconics.respectFontBoundsDefault = true // initialize companion object values + AppData.read(this) App.db = AppDb(this) App.config = Config(App.db) - App.profile = Profile(0, 0, 0, "") debugMode = BuildConfig.DEBUG devMode = config.devMode ?: debugMode enableChucker = config.enableChucker ?: devMode + if (devMode) { + HyperLog.initialize(this) + HyperLog.setLogLevel(Log.VERBOSE) + HyperLog.setLogFormat(DebugLogFormat(this)) + } + if (!profileLoadById(config.lastProfileId)) { - db.profileDao().firstId?.let { profileLoadById(it) } + val success = db.profileDao().firstId?.let { profileLoadById(it) } + if (success != true) + profileLoad(Profile(0, 0, LoginType.TEMPLATE, "")) } buildHttp() @@ -197,6 +235,7 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope { } Signing.getCert(this) + Utils.initializeStorageDir(this) launch { withContext(Dispatchers.Default) { @@ -224,35 +263,35 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope { .setShortLabel(getString(R.string.shortcut_timetable)).setLongLabel(getString(R.string.shortcut_timetable)) .setIcon(Icon.createWithResource(this@App, R.mipmap.ic_shortcut_timetable)) .setIntent(Intent(Intent.ACTION_MAIN, null, this@App, MainActivity::class.java) - .putExtra("fragmentId", MainActivity.DRAWER_ITEM_TIMETABLE)) + .putExtras("fragmentId" to NavTarget.TIMETABLE)) .build() val shortcutAgenda = ShortcutInfo.Builder(this@App, "item_agenda") .setShortLabel(getString(R.string.shortcut_agenda)).setLongLabel(getString(R.string.shortcut_agenda)) .setIcon(Icon.createWithResource(this@App, R.mipmap.ic_shortcut_agenda)) .setIntent(Intent(Intent.ACTION_MAIN, null, this@App, MainActivity::class.java) - .putExtra("fragmentId", MainActivity.DRAWER_ITEM_AGENDA)) + .putExtras("fragmentId" to NavTarget.AGENDA)) .build() val shortcutGrades = ShortcutInfo.Builder(this@App, "item_grades") .setShortLabel(getString(R.string.shortcut_grades)).setLongLabel(getString(R.string.shortcut_grades)) .setIcon(Icon.createWithResource(this@App, R.mipmap.ic_shortcut_grades)) .setIntent(Intent(Intent.ACTION_MAIN, null, this@App, MainActivity::class.java) - .putExtra("fragmentId", MainActivity.DRAWER_ITEM_GRADES)) + .putExtras("fragmentId" to NavTarget.GRADES)) .build() val shortcutHomework = ShortcutInfo.Builder(this@App, "item_homeworks") .setShortLabel(getString(R.string.shortcut_homework)).setLongLabel(getString(R.string.shortcut_homework)) .setIcon(Icon.createWithResource(this@App, R.mipmap.ic_shortcut_homework)) .setIntent(Intent(Intent.ACTION_MAIN, null, this@App, MainActivity::class.java) - .putExtra("fragmentId", MainActivity.DRAWER_ITEM_HOMEWORK)) + .putExtras("fragmentId" to NavTarget.HOMEWORK)) .build() val shortcutMessages = ShortcutInfo.Builder(this@App, "item_messages") .setShortLabel(getString(R.string.shortcut_messages)).setLongLabel(getString(R.string.shortcut_messages)) .setIcon(Icon.createWithResource(this@App, R.mipmap.ic_shortcut_messages)) .setIntent(Intent(Intent.ACTION_MAIN, null, this@App, MainActivity::class.java) - .putExtra("fragmentId", MainActivity.DRAWER_ITEM_MESSAGES)) + .putExtras("fragmentId" to NavTarget.MESSAGES)) .build() shortcutManager.dynamicShortcuts = listOf( @@ -378,10 +417,28 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope { } } + fun profileLoad(profile: Profile) { + App.profile = profile + App.config.lastProfileId = profile.id + try { + App.data = AppData.get(profile.loginStoreType) + d("App", "Loaded AppData: ${App.data}") + // apply newly-added config overrides, if not changed by the user yet + for ((key, value) in App.data.configOverrides) { + val config = App.profile.config + if (!config.has(key)) + config.set(key, value) + } + } catch (e: Exception) { + Log.e("App", "Cannot load AppData", e) + Toast.makeText(this, R.string.app_cannot_load_data, Toast.LENGTH_LONG).show() + exitProcess(0) + } + } + private fun profileLoadById(profileId: Int): Boolean { db.profileDao().getByIdNow(profileId)?.also { - App.profile = it - App.config.lastProfileId = it.id + profileLoad(it) return true } return false @@ -412,6 +469,8 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope { } fun profileSave() = profileSave(profile) fun profileSave(profile: Profile) { + if (profile.id == profileId) + App.profile = profile launch(Dispatchers.Default) { App.db.profileDao().add(profile) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/MainActivity.kt b/app/src/main/java/pl/szczodrzynski/edziennik/MainActivity.kt index 2f633ffe..ad59bf43 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/MainActivity.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/MainActivity.kt @@ -1,6 +1,5 @@ package pl.szczodrzynski.edziennik -import android.annotation.SuppressLint import android.app.ActivityManager import android.content.BroadcastReceiver import android.content.Context @@ -8,7 +7,6 @@ import android.content.Intent import android.content.IntentFilter import android.content.pm.PackageManager import android.graphics.BitmapFactory -import android.graphics.Color import android.graphics.drawable.BitmapDrawable import android.os.Build import android.os.Bundle @@ -17,8 +15,6 @@ import android.view.Gravity import android.view.View import android.widget.Toast import androidx.appcompat.app.AppCompatActivity -import androidx.core.graphics.ColorUtils -import androidx.core.view.ViewCompat import androidx.core.view.WindowCompat import androidx.core.view.isVisible import androidx.navigation.NavOptions @@ -33,7 +29,6 @@ import com.mikepenz.iconics.utils.sizeDp import com.mikepenz.materialdrawer.model.* import com.mikepenz.materialdrawer.model.interfaces.* import com.mikepenz.materialdrawer.model.utils.hiddenInMiniDrawer -import eu.szkolny.font.SzkolnyFont import kotlinx.coroutines.* import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.Subscribe @@ -44,243 +39,49 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask import pl.szczodrzynski.edziennik.data.api.events.* import pl.szczodrzynski.edziennik.data.api.models.ApiError import pl.szczodrzynski.edziennik.data.api.szkolny.response.Update -import pl.szczodrzynski.edziennik.data.db.entity.LoginStore -import pl.szczodrzynski.edziennik.data.db.entity.Metadata.* +import pl.szczodrzynski.edziennik.data.db.entity.Message import pl.szczodrzynski.edziennik.data.db.entity.Profile +import pl.szczodrzynski.edziennik.data.db.enums.FeatureType import pl.szczodrzynski.edziennik.databinding.ActivitySzkolnyBinding import pl.szczodrzynski.edziennik.ext.* import pl.szczodrzynski.edziennik.sync.AppManagerDetectedEvent import pl.szczodrzynski.edziennik.sync.SyncWorker +import pl.szczodrzynski.edziennik.sync.UpdateStateEvent import pl.szczodrzynski.edziennik.sync.UpdateWorker -import pl.szczodrzynski.edziennik.ui.agenda.AgendaFragment -import pl.szczodrzynski.edziennik.ui.announcements.AnnouncementsFragment -import pl.szczodrzynski.edziennik.ui.attendance.AttendanceFragment import pl.szczodrzynski.edziennik.ui.base.MainSnackbar -import pl.szczodrzynski.edziennik.ui.behaviour.BehaviourFragment -import pl.szczodrzynski.edziennik.ui.debug.DebugFragment -import pl.szczodrzynski.edziennik.ui.debug.LabFragment +import pl.szczodrzynski.edziennik.ui.base.enums.NavTarget +import pl.szczodrzynski.edziennik.ui.base.enums.NavTargetLocation import pl.szczodrzynski.edziennik.ui.dialogs.ChangelogDialog import pl.szczodrzynski.edziennik.ui.dialogs.settings.ProfileConfigDialog import pl.szczodrzynski.edziennik.ui.dialogs.sync.RegisterUnavailableDialog import pl.szczodrzynski.edziennik.ui.dialogs.sync.ServerMessageDialog import pl.szczodrzynski.edziennik.ui.dialogs.sync.SyncViewListDialog import pl.szczodrzynski.edziennik.ui.dialogs.sync.UpdateAvailableDialog +import pl.szczodrzynski.edziennik.ui.dialogs.sync.UpdateProgressDialog import pl.szczodrzynski.edziennik.ui.error.ErrorDetailsDialog import pl.szczodrzynski.edziennik.ui.error.ErrorSnackbar import pl.szczodrzynski.edziennik.ui.event.EventManualDialog -import pl.szczodrzynski.edziennik.ui.feedback.FeedbackFragment -import pl.szczodrzynski.edziennik.ui.grades.GradesListFragment -import pl.szczodrzynski.edziennik.ui.grades.editor.GradesEditorFragment -import pl.szczodrzynski.edziennik.ui.home.HomeFragment -import pl.szczodrzynski.edziennik.ui.homework.HomeworkFragment import pl.szczodrzynski.edziennik.ui.login.LoginActivity -import pl.szczodrzynski.edziennik.ui.messages.compose.MessagesComposeFragment import pl.szczodrzynski.edziennik.ui.messages.list.MessagesFragment -import pl.szczodrzynski.edziennik.ui.messages.single.MessageFragment -import pl.szczodrzynski.edziennik.ui.notes.NotesFragment -import pl.szczodrzynski.edziennik.ui.notifications.NotificationsListFragment -import pl.szczodrzynski.edziennik.ui.settings.ProfileManagerFragment -import pl.szczodrzynski.edziennik.ui.settings.SettingsFragment -import pl.szczodrzynski.edziennik.ui.teachers.TeachersListFragment import pl.szczodrzynski.edziennik.ui.timetable.TimetableFragment -import pl.szczodrzynski.edziennik.ui.webpush.WebPushFragment import pl.szczodrzynski.edziennik.utils.* import pl.szczodrzynski.edziennik.utils.Utils.d -import pl.szczodrzynski.edziennik.utils.Utils.dpToPx import pl.szczodrzynski.edziennik.utils.managers.AvailabilityManager.Error.Type +import pl.szczodrzynski.edziennik.utils.managers.UserActionManager import pl.szczodrzynski.edziennik.utils.models.Date -import pl.szczodrzynski.edziennik.utils.models.NavTarget import pl.szczodrzynski.navlib.* -import pl.szczodrzynski.navlib.SystemBarsUtil.Companion.COLOR_HALF_TRANSPARENT import pl.szczodrzynski.navlib.bottomsheet.NavBottomSheet import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetSeparatorItem import pl.szczodrzynski.navlib.drawer.NavDrawer import pl.szczodrzynski.navlib.drawer.items.DrawerPrimaryItem import java.io.IOException -import java.util.* import kotlin.coroutines.CoroutineContext import kotlin.math.roundToInt class MainActivity : AppCompatActivity(), CoroutineScope { - @Suppress("MemberVisibilityCanBePrivate") companion object { - - const val TAG = "MainActivity" - - const val DRAWER_PROFILE_ADD_NEW = 200 - const val DRAWER_PROFILE_SYNC_ALL = 201 - const val DRAWER_PROFILE_MANAGE = 203 - const val DRAWER_PROFILE_MARK_ALL_AS_READ = 204 - const val DRAWER_ITEM_HOME = 1 - const val DRAWER_ITEM_TIMETABLE = 11 - const val DRAWER_ITEM_AGENDA = 12 - const val DRAWER_ITEM_GRADES = 13 - const val DRAWER_ITEM_MESSAGES = 17 - const val DRAWER_ITEM_HOMEWORK = 14 - const val DRAWER_ITEM_BEHAVIOUR = 15 - const val DRAWER_ITEM_ATTENDANCE = 16 - const val DRAWER_ITEM_ANNOUNCEMENTS = 18 - const val DRAWER_ITEM_NOTIFICATIONS = 20 - const val DRAWER_ITEM_MORE = 21 - const val DRAWER_ITEM_TEACHERS = 22 - const val DRAWER_ITEM_NOTES = 23 - const val DRAWER_ITEM_SETTINGS = 101 - const val DRAWER_ITEM_DEBUG = 102 - - const val TARGET_GRADES_EDITOR = 501 - const val TARGET_FEEDBACK = 120 - const val TARGET_MESSAGES_DETAILS = 503 - const val TARGET_MESSAGES_COMPOSE = 504 - const val TARGET_WEB_PUSH = 140 - const val TARGET_LAB = 1000 - - const val HOME_ID = DRAWER_ITEM_HOME - - val navTargetList: List by lazy { - val list: MutableList = mutableListOf() - val moreList: MutableList = mutableListOf() - - moreList += NavTarget( - id = DRAWER_ITEM_NOTES, - name = R.string.menu_notes, - fragmentClass = NotesFragment::class) - .withIcon(CommunityMaterial.Icon3.cmd_text_box_multiple_outline) - .isStatic(true) - - moreList += NavTarget(DRAWER_ITEM_TEACHERS, - R.string.menu_teachers, - TeachersListFragment::class) - .withIcon(CommunityMaterial.Icon3.cmd_shield_account_outline) - .isStatic(true) - - // home item - list += NavTarget(DRAWER_ITEM_HOME, R.string.menu_home_page, HomeFragment::class) - .withTitle(R.string.app_name) - .withIcon(CommunityMaterial.Icon2.cmd_home_outline) - .isInDrawer(true) - .isStatic(true) - .withPopToHome(false) - - list += NavTarget(DRAWER_ITEM_TIMETABLE, - R.string.menu_timetable, - TimetableFragment::class) - .withIcon(CommunityMaterial.Icon3.cmd_timetable) - .withBadgeTypeId(TYPE_LESSON_CHANGE) - .isInDrawer(true) - - list += NavTarget(DRAWER_ITEM_AGENDA, R.string.menu_agenda, AgendaFragment::class) - .withIcon(CommunityMaterial.Icon.cmd_calendar_outline) - .withBadgeTypeId(TYPE_EVENT) - .isInDrawer(true) - - list += NavTarget(DRAWER_ITEM_GRADES, R.string.menu_grades, GradesListFragment::class) - .withIcon(CommunityMaterial.Icon3.cmd_numeric_5_box_outline) - .withBadgeTypeId(TYPE_GRADE) - .isInDrawer(true) - - list += NavTarget(DRAWER_ITEM_MESSAGES, R.string.menu_messages, MessagesFragment::class) - .withIcon(CommunityMaterial.Icon.cmd_email_outline) - .withBadgeTypeId(TYPE_MESSAGE) - .isInDrawer(true) - - list += NavTarget(DRAWER_ITEM_HOMEWORK, R.string.menu_homework, HomeworkFragment::class) - .withIcon(SzkolnyFont.Icon.szf_notebook_outline) - .withBadgeTypeId(TYPE_HOMEWORK) - .isInDrawer(true) - - list += NavTarget(DRAWER_ITEM_BEHAVIOUR, - R.string.menu_notices, - BehaviourFragment::class) - .withIcon(CommunityMaterial.Icon.cmd_emoticon_outline) - .withBadgeTypeId(TYPE_NOTICE) - .isInDrawer(true) - - list += NavTarget(DRAWER_ITEM_ATTENDANCE, - R.string.menu_attendance, - AttendanceFragment::class) - .withIcon(CommunityMaterial.Icon.cmd_calendar_remove_outline) - .withBadgeTypeId(TYPE_ATTENDANCE) - .isInDrawer(true) - - list += NavTarget(DRAWER_ITEM_ANNOUNCEMENTS, - R.string.menu_announcements, - AnnouncementsFragment::class) - .withIcon(CommunityMaterial.Icon.cmd_bullhorn_outline) - .withBadgeTypeId(TYPE_ANNOUNCEMENT) - .isInDrawer(true) - - list += NavTarget(DRAWER_ITEM_MORE, R.string.menu_more, null) - .withIcon(CommunityMaterial.Icon.cmd_dots_horizontal) - .isInDrawer(true) - .isStatic(true) - .withSubItems(*moreList.toTypedArray()) - - - // static drawer items - list += NavTarget(DRAWER_ITEM_NOTIFICATIONS, - R.string.menu_notifications, - NotificationsListFragment::class) - .withIcon(CommunityMaterial.Icon.cmd_bell_ring_outline) - .isInDrawer(true) - .isStatic(true) - .isBelowSeparator(true) - - list += NavTarget(DRAWER_ITEM_SETTINGS, R.string.menu_settings, SettingsFragment::class) - .withIcon(CommunityMaterial.Icon.cmd_cog_outline) - .isInDrawer(true) - .isStatic(true) - .isBelowSeparator(true) - - - // profile settings items - list += NavTarget(DRAWER_PROFILE_ADD_NEW, R.string.menu_add_new_profile, null) - .withIcon(CommunityMaterial.Icon3.cmd_plus) - .withDescription(R.string.drawer_add_new_profile_desc) - .isInProfileList(true) - - list += NavTarget(DRAWER_PROFILE_MANAGE, - R.string.menu_manage_profiles, - ProfileManagerFragment::class) - .withTitle(R.string.title_profile_manager) - .withIcon(CommunityMaterial.Icon.cmd_account_group) - .withDescription(R.string.drawer_manage_profiles_desc) - .isInProfileList(false) - - list += NavTarget(DRAWER_PROFILE_MARK_ALL_AS_READ, - R.string.menu_mark_everything_as_read, - null) - .withIcon(CommunityMaterial.Icon.cmd_eye_check_outline) - .isInProfileList(true) - - list += NavTarget(DRAWER_PROFILE_SYNC_ALL, R.string.menu_sync_all, null) - .withIcon(CommunityMaterial.Icon.cmd_download_outline) - .isInProfileList(true) - - - // other target items, not directly navigated - list += NavTarget(TARGET_GRADES_EDITOR, - R.string.menu_grades_editor, - GradesEditorFragment::class) - list += NavTarget(TARGET_FEEDBACK, R.string.menu_feedback, FeedbackFragment::class) - list += NavTarget(TARGET_MESSAGES_DETAILS, - R.string.menu_message, - MessageFragment::class).withPopTo(DRAWER_ITEM_MESSAGES) - list += NavTarget(TARGET_MESSAGES_COMPOSE, - R.string.menu_message_compose, - MessagesComposeFragment::class) - list += NavTarget(TARGET_WEB_PUSH, R.string.menu_web_push, WebPushFragment::class) - if (App.devMode) { - list += NavTarget(DRAWER_ITEM_DEBUG, R.string.menu_debug, DebugFragment::class) - list += NavTarget(TARGET_LAB, R.string.menu_lab, LabFragment::class) - .withIcon(CommunityMaterial.Icon2.cmd_flask_outline) - .isInDrawer(true) - .isBelowSeparator(true) - .isStatic(true) - } - - list - } + private const val TAG = "MainActivity" } private var job = Job() @@ -298,18 +99,16 @@ class MainActivity : AppCompatActivity(), CoroutineScope { val swipeRefreshLayout: SwipeRefreshLayoutNoTouch by lazy { b.swipeRefreshLayout } var onBeforeNavigate: (() -> Boolean)? = null - var pausedNavigationData: PausedNavigationData? = null - private set + private var pausedNavigationData: PausedNavigationData? = null val app: App by lazy { applicationContext as App } private val fragmentManager by lazy { supportFragmentManager } - private lateinit var navTarget: NavTarget + lateinit var navTarget: NavTarget + private set private var navArguments: Bundle? = null - val navTargetId - get() = navTarget.id private val navBackStack = mutableListOf>() private var navLoading = true @@ -414,12 +213,15 @@ class MainActivity : AppCompatActivity(), CoroutineScope { drawerProfileListEmptyListener = { onProfileListEmptyEvent(ProfileListEmptyEvent()) } - drawerItemSelectedListener = { id, _, _ -> - loadTarget(id) + drawerItemSelectedListener = { id, _, item -> + if (item is ExpandableDrawerItem) + false + else + navigate(navTarget = id.asNavTargetOrNull()) } drawerProfileSelectedListener = { id, _, _, _ -> // why is this negated -_- - !loadProfile(id) + !navigate(profileId = id) } drawerProfileLongClickListener = { _, profile, _, view -> if (view != null && profile is ProfileDrawerItem) { @@ -443,7 +245,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope { } } - navTarget = navTargetList[0] + navTarget = NavTarget.HOME if (savedInstanceState != null) { intent?.putExtras(savedInstanceState) @@ -475,9 +277,6 @@ class MainActivity : AppCompatActivity(), CoroutineScope { handleIntent(intent?.extras) app.db.metadataDao().unreadCounts.observe(this) { unreadCounters -> - unreadCounters.map { - it.type = it.thingType - } drawer.setUnreadCounterList(unreadCounters) } @@ -500,11 +299,11 @@ class MainActivity : AppCompatActivity(), CoroutineScope { app.db.profileDao().getNotArchivedOf(app.profile.archiveId!!) } if (profile != null) - loadProfile(profile) + navigate(profile = profile) else - loadProfile(0) + navigate(profileId = 0) } else { - loadProfile(0) + navigate(profileId = 0) } } } @@ -514,7 +313,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope { // IT'S WINTER MY DUDES val today = Date.getToday() - if ((today.month % 11 == 1) && app.config.ui.snowfall) { + if ((today.month / 3 % 4 == 0) && app.config.ui.snowfall) { b.rootFrame.addView(layoutInflater.inflate(R.layout.snowfall, b.rootFrame, false)) } else if (app.config.ui.eggfall && BigNightUtil().isDataWielkanocyNearDzisiaj()) { val eggfall = layoutInflater.inflate( @@ -596,39 +395,32 @@ class MainActivity : AppCompatActivity(), CoroutineScope { .withIcon(CommunityMaterial.Icon.cmd_download_outline) .withOnClickListener { bottomSheet.close() - SyncViewListDialog(this, navTargetId).show() + SyncViewListDialog(this, navTarget).show() }, BottomSheetSeparatorItem(false), - BottomSheetPrimaryItem(false) - .withTitle(R.string.menu_settings) - .withIcon(CommunityMaterial.Icon.cmd_cog_outline) - .withOnClickListener { loadTarget(DRAWER_ITEM_SETTINGS) }, - BottomSheetPrimaryItem(false) - .withTitle(R.string.menu_feedback) - .withIcon(CommunityMaterial.Icon2.cmd_help_circle_outline) - .withOnClickListener { loadTarget(TARGET_FEEDBACK) } ) - if (App.devMode) { - bottomSheet += BottomSheetPrimaryItem(false) - .withTitle(R.string.menu_debug) - .withIcon(CommunityMaterial.Icon.cmd_android_debug_bridge) - .withOnClickListener { loadTarget(DRAWER_ITEM_DEBUG) } + for (target in NavTarget.values()) { + if (target.location != NavTargetLocation.BOTTOM_SHEET) + continue + if (target.devModeOnly && !App.devMode) + continue + bottomSheet += target.toBottomSheetItem(this) } } - private var profileSettingClickListener = { id: Int, _: View? -> - when (id) { - DRAWER_PROFILE_ADD_NEW -> { + private var profileSettingClickListener = { itemId: Int, _: View? -> + when (val item = itemId.asNavTarget()) { + NavTarget.PROFILE_ADD -> { requestHandler.requestLogin() } - DRAWER_PROFILE_SYNC_ALL -> { + NavTarget.PROFILE_SYNC_ALL -> { EdziennikTask.sync().enqueue(this) } - DRAWER_PROFILE_MARK_ALL_AS_READ -> { + NavTarget.PROFILE_MARK_AS_READ -> { launch { withContext(Dispatchers.Default) { app.db.profileDao().allNow.forEach { profile -> - if (profile.loginStoreType != LoginStore.LOGIN_TYPE_LIBRUS) + if (!profile.getAppData().uiConfig.enableMarkAsReadAnnouncements) app.db.metadataDao() .setAllSeenExceptMessagesAndAnnouncements(profile.id, true) else @@ -641,7 +433,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope { } } else -> { - loadTarget(id) + navigate(navTarget = item) } } false @@ -698,7 +490,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope { when (error?.type) { Type.NOT_AVAILABLE -> { swipeRefreshLayout.isRefreshing = false - loadTarget(DRAWER_ITEM_HOME) + navigate(navTarget = NavTarget.HOME) RegisterUnavailableDialog(this, error.status!!).show() return } @@ -709,23 +501,25 @@ class MainActivity : AppCompatActivity(), CoroutineScope { Type.NO_API_ACCESS -> { Toast.makeText(this, R.string.error_no_api_access, Toast.LENGTH_SHORT).show() } - - null -> TODO() + else -> {} } swipeRefreshLayout.isRefreshing = true - Toast.makeText(this, fragmentToSyncName(navTargetId), Toast.LENGTH_SHORT).show() - val fragmentParam = when (navTargetId) { - DRAWER_ITEM_MESSAGES -> MessagesFragment.pageSelection - else -> 0 + Toast.makeText(this, fragmentToSyncName(navTarget), Toast.LENGTH_SHORT).show() + val featureType = when (navTarget) { + NavTarget.MESSAGES -> when (MessagesFragment.pageSelection) { + Message.TYPE_SENT -> FeatureType.MESSAGES_SENT + else -> FeatureType.MESSAGES_INBOX + } + else -> navTarget.featureType } - val arguments = when (navTargetId) { - DRAWER_ITEM_TIMETABLE -> JsonObject("weekStart" to TimetableFragment.pageSelection?.weekStart?.stringY_m_d) + val arguments = when (navTarget) { + NavTarget.TIMETABLE -> JsonObject("weekStart" to TimetableFragment.pageSelection?.weekStart?.stringY_m_d) else -> null } EdziennikTask.syncProfile( App.profileId, - listOf(navTargetId to fragmentParam), + featureType?.let { setOf(it) }, arguments = arguments ).enqueue(this) } @@ -736,6 +530,14 @@ class MainActivity : AppCompatActivity(), CoroutineScope { UpdateAvailableDialog(this, event).show() } + @Subscribe(threadMode = ThreadMode.MAIN, sticky = true) + fun onUpdateStateEvent(event: UpdateStateEvent) { + if (!event.running) + return + EventBus.getDefault().removeStickyEvent(event) + UpdateProgressDialog(this, event.update ?: return, event.downloadId).show() + } + @Subscribe(threadMode = ThreadMode.MAIN, sticky = true) fun onRegisterAvailabilityEvent(event: RegisterAvailabilityEvent) { EventBus.getDefault().removeStickyEvent(event) @@ -748,6 +550,11 @@ class MainActivity : AppCompatActivity(), CoroutineScope { @Subscribe(threadMode = ThreadMode.MAIN) fun onApiTaskStartedEvent(event: ApiTaskStartedEvent) { swipeRefreshLayout.isRefreshing = true + if (event.profileId == App.profileId) { + navView.toolbar.apply { + subtitle = getString(R.string.toolbar_subtitle_syncing) + } + } } @Subscribe(threadMode = ThreadMode.MAIN) @@ -758,9 +565,31 @@ class MainActivity : AppCompatActivity(), CoroutineScope { finish() } + @Subscribe(threadMode = ThreadMode.MAIN) + fun onApiTaskProgressEvent(event: ApiTaskProgressEvent) { + if (event.profileId == App.profileId) { + navView.toolbar.apply { + subtitle = if (event.progress < 0f) + event.progressText ?: "" + else + getString( + R.string.toolbar_subtitle_syncing_format, + event.progress.roundToInt(), + event.progressText ?: "", + ) + + } + } + } + @Subscribe(threadMode = ThreadMode.MAIN, sticky = true) fun onApiTaskFinishedEvent(event: ApiTaskFinishedEvent) { EventBus.getDefault().removeStickyEvent(event) + if (event.profileId == App.profileId) { + navView.toolbar.apply { + subtitle = "Gotowe" + } + } } @Subscribe(threadMode = ThreadMode.MAIN, sticky = true) @@ -777,6 +606,9 @@ class MainActivity : AppCompatActivity(), CoroutineScope { return ErrorDetailsDialog(this, listOf(event.error)).show() } + navView.toolbar.apply { + subtitle = "Gotowe" + } mainSnackbar.dismiss() errorSnackbar.addError(event.error).show() } @@ -817,22 +649,22 @@ class MainActivity : AppCompatActivity(), CoroutineScope { @Subscribe(threadMode = ThreadMode.MAIN) fun onUserActionRequiredEvent(event: UserActionRequiredEvent) { - app.userActionManager.execute(this, event.profileId, event.type) + app.userActionManager.execute(this, event, UserActionManager.UserActionCallback()) } - private fun fragmentToSyncName(currentFragment: Int): Int { - return when (currentFragment) { - DRAWER_ITEM_TIMETABLE -> R.string.sync_feature_timetable - DRAWER_ITEM_AGENDA -> R.string.sync_feature_agenda - DRAWER_ITEM_GRADES -> R.string.sync_feature_grades - DRAWER_ITEM_HOMEWORK -> R.string.sync_feature_homework - DRAWER_ITEM_BEHAVIOUR -> R.string.sync_feature_notices - DRAWER_ITEM_ATTENDANCE -> R.string.sync_feature_attendance - DRAWER_ITEM_MESSAGES -> when (MessagesFragment.pageSelection) { - 1 -> R.string.sync_feature_messages_outbox + private fun fragmentToSyncName(navTarget: NavTarget): Int { + return when (navTarget) { + NavTarget.TIMETABLE -> R.string.sync_feature_timetable + NavTarget.AGENDA -> R.string.sync_feature_agenda + NavTarget.GRADES -> R.string.sync_feature_grades + NavTarget.HOMEWORK -> R.string.sync_feature_homework + NavTarget.BEHAVIOUR -> R.string.sync_feature_notices + NavTarget.ATTENDANCE -> R.string.sync_feature_attendance + NavTarget.MESSAGES -> when (MessagesFragment.pageSelection) { + Message.TYPE_SENT -> R.string.sync_feature_messages_outbox else -> R.string.sync_feature_messages_inbox } - DRAWER_ITEM_ANNOUNCEMENTS -> R.string.sync_feature_announcements + NavTarget.ANNOUNCEMENTS -> R.string.sync_feature_announcements else -> R.string.sync_feature_syncing_all } } @@ -850,18 +682,21 @@ class MainActivity : AppCompatActivity(), CoroutineScope { } fun handleIntent(extras: Bundle?) { - d(TAG, "handleIntent() {") extras?.keySet()?.forEach { key -> d(TAG, " \"$key\": " + extras.get(key)) } d(TAG, "}") - var intentProfileId = -1 - var intentTargetId = -1 + val intentProfileId = extras.getIntOrNull("profileId").takePositive() + var intentNavTarget = extras.getIntOrNull("fragmentId").asNavTargetOrNull() if (extras?.containsKey("action") == true) { val handled = when (extras.getString("action")) { + "updateRequest" -> { + UpdateAvailableDialog(this, app.config.update).show() + true + } "serverMessage" -> { ServerMessageDialog( this, @@ -871,15 +706,19 @@ class MainActivity : AppCompatActivity(), CoroutineScope { true } "feedbackMessage" -> { - intentTargetId = TARGET_FEEDBACK + intentNavTarget = NavTarget.FEEDBACK false } "userActionRequired" -> { - app.userActionManager.execute( - this, - extras.getInt("profileId"), - extras.getInt("type") + val event = UserActionRequiredEvent( + profileId = extras.getInt("profileId"), + type = extras.getEnum("type") ?: return, + params = extras.getBundle("params") ?: return, + errorText = 0, ) + app.userActionManager.execute(this, + event, + UserActionManager.UserActionCallback()) true } "createManualEvent" -> { @@ -901,72 +740,59 @@ class MainActivity : AppCompatActivity(), CoroutineScope { } if (extras?.containsKey("reloadProfileId") == true) { - val reloadProfileId = extras.getInt("reloadProfileId", -1) - extras.remove("reloadProfileId") - if (reloadProfileId == -1 || app.profile.id == reloadProfileId) { + val reloadProfileId = extras.getIntOrNull("reloadProfileId").takePositive() + if (reloadProfileId == null || app.profile.id == reloadProfileId) { reloadTarget() return } } - if (extras?.getInt("profileId", -1) != -1) { - intentProfileId = extras.getInt("profileId", -1) - extras?.remove("profileId") - } - - if (extras?.getInt("fragmentId", -1) != -1) { - intentTargetId = extras.getInt("fragmentId", -1) - extras?.remove("fragmentId") - } + extras?.remove("profileId") + extras?.remove("fragmentId") + extras?.remove("reloadProfileId") /*if (intentTargetId == -1 && navController.currentDestination?.id == R.id.loadingFragment) { intentTargetId = navTarget.id }*/ - if (navLoading) { + if (navLoading) b.fragment.removeAllViews() - if (intentTargetId == -1) - intentTargetId = HOME_ID - } when { - app.profile.id == 0 -> { - if (intentProfileId == -1) - intentProfileId = app.config.lastProfileId - loadProfile(intentProfileId, intentTargetId, extras) - } - intentProfileId != -1 -> { - if (app.profile.id != intentProfileId) - loadProfile(intentProfileId, intentTargetId, extras) - else - loadTarget(intentTargetId, extras) - } - intentTargetId != -1 -> { - drawer.currentProfile = app.profile.id - if (navTargetId != intentTargetId || navLoading) - loadTarget(intentTargetId, extras) - } - else -> { - drawer.currentProfile = app.profile.id - } + app.profile.id == 0 -> navigate( + profileId = intentProfileId ?: app.config.lastProfileId, + navTarget = intentNavTarget, + args = extras, + ) + intentProfileId != null -> navigate( + profileId = intentProfileId, + navTarget = intentNavTarget, + args = extras, + ) + intentNavTarget != null -> navigate( + navTarget = intentNavTarget, + args = extras, + ) + navLoading -> navigate() + else -> drawer.currentProfile = app.profile.id } navLoading = false } override fun recreate() { - recreate(navTargetId) + recreate(navTarget) } - fun recreate(targetId: Int) { - recreate(targetId, null) + fun recreate(navTarget: NavTarget) { + recreate(navTarget, null) } - fun recreate(targetId: Int? = null, arguments: Bundle? = null) { + fun recreate(navTarget: NavTarget? = null, arguments: Bundle? = null) { val intent = Intent(this, MainActivity::class.java) if (arguments != null) intent.putExtras(arguments) - if (targetId != null) { - intent.putExtra("fragmentId", targetId) + if (navTarget != null) { + intent.putExtra("fragmentId", navTarget.id) } finish() overridePendingTransition(R.anim.fade_in, R.anim.fade_out) @@ -1012,7 +838,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope { override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) - outState.putInt("fragmentId", navTargetId) + outState.putExtras("fragmentId" to navTarget) } override fun onNewIntent(intent: Intent?) { @@ -1043,187 +869,126 @@ class MainActivity : AppCompatActivity(), CoroutineScope { private fun canNavigate(): Boolean = onBeforeNavigate?.invoke() != false fun resumePausedNavigation(): Boolean { - if (pausedNavigationData == null) - return false - pausedNavigationData?.let { data -> - when (data) { - is PausedNavigationData.LoadProfile -> loadProfile( - id = data.id, - drawerSelection = data.drawerSelection, - arguments = data.arguments, - skipBeforeNavigate = true, - ) - is PausedNavigationData.LoadTarget -> loadTarget( - id = data.id, - arguments = data.arguments, - skipBeforeNavigate = true, - ) - else -> return false - } - } + val data = pausedNavigationData ?: return false + navigate( + profileId = data.profileId, + navTarget = data.navTarget, + args = data.args, + skipBeforeNavigate = true, + ) pausedNavigationData = null return true } - fun loadProfile(id: Int) = loadProfile(id, navTargetId) - - // fun loadProfile(id: Int, arguments: Bundle?) = loadProfile(id, navTargetId, arguments) - fun loadProfile(profile: Profile): Boolean { - if (!canNavigate()) { - pausedNavigationData = PausedNavigationData.LoadProfile( - id = profile.id, - drawerSelection = navTargetId, - arguments = null, - ) - return false - } - - loadProfile(profile, navTargetId, null) - return true - } - - private fun loadProfile( - id: Int, - drawerSelection: Int, - arguments: Bundle? = null, - skipBeforeNavigate: Boolean = false, - ): Boolean { - if (!skipBeforeNavigate && !canNavigate()) { - drawer.close() - // restore the previous profile after changing it with the drawer - // well, it still does not change the toolbar profile image, - // but that's now NavView's problem, not mine. - drawer.currentProfile = app.profile.id - pausedNavigationData = PausedNavigationData.LoadProfile( - id = id, - drawerSelection = drawerSelection, - arguments = arguments, - ) - return false - } - - if (App.profileId == id) { - drawer.currentProfile = app.profile.id - // skipBeforeNavigate because it's checked above already - loadTarget(drawerSelection, arguments, skipBeforeNavigate = true) - return true - } - app.profileLoad(id) { - loadProfile(it, drawerSelection, arguments) - } - return true - } - - private fun loadProfile(profile: Profile, drawerSelection: Int, arguments: Bundle?) { - App.profile = profile - MessagesFragment.pageSelection = -1 - - setDrawerItems() - - val previousArchivedId = if (app.profile.archived) app.profile.id else null - if (previousArchivedId != null) { - // prevents accidentally removing the first item if the archived profile is not shown - drawer.removeProfileById(previousArchivedId) - } - if (profile.archived) { - drawer.prependProfile(Profile( - id = profile.id, - loginStoreId = profile.loginStoreId, - loginStoreType = profile.loginStoreType, - name = profile.name, - subname = "Archiwum - ${profile.subname}" - ).also { - it.archived = true - }) - } - - // the drawer profile is updated automatically when the drawer item is clicked - // update it manually when switching profiles from other source - //if (drawer.currentProfile != app.profile.id) - drawer.currentProfile = app.profileId - loadTarget(drawerSelection, arguments, skipBeforeNavigate = true) - } - - fun loadTarget( - id: Int, - arguments: Bundle? = null, - skipBeforeNavigate: Boolean = false, - ): Boolean { - var loadId = id - if (loadId == -1) { - loadId = DRAWER_ITEM_HOME - } - val targets = navTargetList - .flatMap { it.subItems?.toList() ?: emptyList() } - .plus(navTargetList) - val target = targets.firstOrNull { it.id == loadId } - return when { - target == null -> { - Toast.makeText( - this, - getString(R.string.error_invalid_fragment, id), - Toast.LENGTH_LONG, - ).show() - loadTarget(navTargetList.first(), arguments, skipBeforeNavigate) - } - target.fragmentClass != null -> { - loadTarget(target, arguments, skipBeforeNavigate) - } - else -> { - false - } - } - } - - private fun loadTarget( - target: NavTarget, + fun navigate( + profileId: Int? = null, + profile: Profile? = null, + navTarget: NavTarget? = null, args: Bundle? = null, skipBeforeNavigate: Boolean = false, ): Boolean { - d("NavDebug", "loadTarget(target = $target, args = $args)") - - if (!skipBeforeNavigate && !canNavigate()) { + d(TAG, "navigate(profileId = ${profile?.id ?: profileId}, target = ${navTarget?.name}, args = $args)") + if (!(skipBeforeNavigate || navTarget == this.navTarget) && !canNavigate()) { bottomSheet.close() drawer.close() - pausedNavigationData = PausedNavigationData.LoadTarget( - id = target.id, - arguments = args, - ) + // restore the previous profile if changing it with the drawer + // well, it still does not change the toolbar profile image, + // but that's now NavView's problem, not mine. + drawer.currentProfile = App.profile.id + pausedNavigationData = PausedNavigationData(profileId, navTarget, args) return false } - pausedNavigationData = null + + val loadNavTarget = navTarget ?: this.navTarget + if (profile != null && profile.id != App.profileId) { + navigateImpl(profile, loadNavTarget, args, profileChanged = true) + return true + } + if (profileId != null && profileId != App.profileId) { + app.profileLoad(profileId) { + navigateImpl(it, loadNavTarget, args, profileChanged = true) + } + return true + } + navigateImpl(App.profile, loadNavTarget, args, profileChanged = false) + return true + } + + private fun navigateImpl( + profile: Profile, + navTarget: NavTarget, + args: Bundle?, + profileChanged: Boolean, + ) { + d(TAG, "navigateImpl(profileId = ${profile.id}, target = ${navTarget.name}, args = $args)") + + if (navTarget.featureType != null && !profile.hasUIFeature(navTarget.featureType)) { + navigateImpl(profile, NavTarget.HOME, args, profileChanged) + return + } + + if (profileChanged) { + if (App.profileId != profile.id) + app.profileLoad(profile) + MessagesFragment.pageSelection = -1 + // set new drawer items for this profile + setDrawerItems() + + val previousArchivedId = if (app.profile.archived) app.profile.id else null + if (previousArchivedId != null) { + // prevents accidentally removing the first item if the archived profile is not shown + drawer.removeProfileById(previousArchivedId) + } + if (profile.archived) { + // add the same profile but with a different name + // (other fields are not needed by the drawer) + drawer.prependProfile(Profile( + id = profile.id, + loginStoreId = profile.loginStoreId, + loginStoreType = profile.loginStoreType, + name = profile.name, + subname = "Archiwum - ${profile.subname}" + ).also { + it.archived = true + }) + } + + // the drawer profile is updated automatically when the drawer item is clicked + // update it manually when switching profiles from other source + //if (drawer.currentProfile != app.profile.id) + drawer.currentProfile = App.profileId + } val arguments = args - ?: navBackStack.firstOrNull { it.first.id == target.id }?.second + ?: navBackStack.firstOrNull { it.first == navTarget }?.second ?: Bundle() bottomSheet.close() bottomSheet.removeAllContextual() bottomSheet.toggleGroupEnabled = false drawer.close() - if (drawer.getSelection() != target.id) - drawer.setSelection(target.id, fireOnClick = false) - navView.toolbar.setTitle(target.title ?: target.name) + if (drawer.getSelection() != navTarget.id) + drawer.setSelection(navTarget.id, fireOnClick = false) + navView.toolbar.setTitle(navTarget.titleRes ?: navTarget.nameRes) navView.bottomBar.fabEnable = false navView.bottomBar.fabExtended = false navView.bottomBar.setFabOnClickListener(null) - d("NavDebug", - "Navigating from ${navTarget.fragmentClass?.java?.simpleName} to ${target.fragmentClass?.java?.simpleName}") + d("NavDebug", "Navigating from ${this.navTarget.name} to ${navTarget.name}") - val fragment = target.fragmentClass?.java?.newInstance() ?: return false + val fragment = navTarget.fragmentClass?.newInstance() ?: return fragment.arguments = arguments val transaction = fragmentManager.beginTransaction() - if (navTarget == target) { + if (navTarget == this.navTarget) { // just reload the current target transaction.setCustomAnimations( R.anim.fade_in, R.anim.fade_out ) } else { - navBackStack.keys().lastIndexOf(target).let { + navBackStack.keys().lastIndexOf(navTarget).let { if (it == -1) - return@let target + return@let navTarget // pop the back stack up until that target transaction.setCustomAnimations( R.anim.task_close_enter, @@ -1242,8 +1007,8 @@ class MainActivity : AppCompatActivity(), CoroutineScope { for (i in 0 until popCount) { navBackStack.removeAt(navBackStack.lastIndex) } - navTarget = target - navArguments = arguments + this.navTarget = navTarget + this.navArguments = arguments return@let null }?.let { @@ -1253,13 +1018,13 @@ class MainActivity : AppCompatActivity(), CoroutineScope { R.anim.task_open_enter, R.anim.task_open_exit ) - navBackStack.add(navTarget to navArguments) - navTarget = target - navArguments = arguments + navBackStack.add(this.navTarget to this.navArguments) + this.navTarget = navTarget + this.navArguments = arguments } } - if (navTarget.popToHome) { + if (navTarget.popTo == NavTarget.HOME) { // if the current has popToHome, let only home be in the back stack // probably `if (navTarget.popToHome)` in popBackStack() is not needed now val popCount = navBackStack.size - 1 @@ -1268,50 +1033,49 @@ class MainActivity : AppCompatActivity(), CoroutineScope { } } - d("NavDebug", - "Current fragment ${navTarget.fragmentClass?.java?.simpleName}, pop to home ${navTarget.popToHome}, back stack:") - navBackStack.forEachIndexed { index, target2 -> - d("NavDebug", " - $index: ${target2.first.fragmentClass?.java?.simpleName}") + d("NavDebug", "Current fragment ${navTarget.name}, back stack:") + navBackStack.forEachIndexed { index, item -> + d("NavDebug", " - $index: ${item.first.name}") } transaction.replace(R.id.fragment, fragment) transaction.commitAllowingStateLoss() // TASK DESCRIPTION - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - val bm = BitmapFactory.decodeResource(resources, R.mipmap.ic_launcher) + val bm = BitmapFactory.decodeResource(resources, R.mipmap.ic_launcher) - @Suppress("deprecation") - val taskDesc = ActivityManager.TaskDescription( - if (target.id == HOME_ID) - getString(R.string.app_name) - else - getString(R.string.app_task_format, getString(target.name)), - bm, - ) - setTaskDescription(taskDesc) - } - return true + @Suppress("deprecation") + val taskDesc = ActivityManager.TaskDescription( + if (navTarget == NavTarget.HOME) + getString(R.string.app_name) + else + getString(R.string.app_task_format, getString(navTarget.nameRes)), + bm, + getColorFromAttr(this, R.attr.colorSurface) + ) + setTaskDescription(taskDesc) + return } - fun reloadTarget() = loadTarget(navTarget) + fun reloadTarget() = navigate() private fun popBackStack(skipBeforeNavigate: Boolean = false): Boolean { if (navBackStack.size == 0) { return false } // TODO back stack argument support - when { - navTarget.popToHome -> { - loadTarget(HOME_ID, skipBeforeNavigate = skipBeforeNavigate) - } - navTarget.popTo != null -> { - loadTarget(navTarget.popTo ?: HOME_ID, skipBeforeNavigate = skipBeforeNavigate) - } - else -> { - navBackStack.last().let { - loadTarget(it.first, it.second, skipBeforeNavigate = skipBeforeNavigate) - } + if (navTarget.popTo != null) { + navigate( + navTarget = navTarget.popTo, + skipBeforeNavigate = skipBeforeNavigate, + ) + } else { + navBackStack.last().let { + navigate( + navTarget = it.first, + args = it.second, + skipBeforeNavigate = skipBeforeNavigate, + ) } } return true @@ -1363,33 +1127,32 @@ class MainActivity : AppCompatActivity(), CoroutineScope { | | | | '__/ _` \ \ /\ / / _ \ '__| | | __/ _ \ '_ ` _ \/ __| | |__| | | | (_| |\ V V / __/ | | | || __/ | | | | \__ \ |_____/|_| \__,_| \_/\_/ \___|_| |_|\__\___|_| |_| |_|__*/ - @Suppress("UNUSED_PARAMETER") private fun createDrawerItem(target: NavTarget, level: Int = 1): IDrawerItem<*> { val item = when { - target.subItems != null -> ExpandableDrawerItem() + // target.subItems != null -> ExpandableDrawerItem() level > 1 -> SecondaryDrawerItem() else -> DrawerPrimaryItem() } item.also { it.identifier = target.id.toLong() - it.nameRes = target.name - it.hiddenInMiniDrawer = !app.config.ui.miniMenuButtons.contains(target.id) - it.description = target.description?.toStringHolder() + it.nameRes = target.nameRes + it.descriptionRes = target.descriptionRes ?: -1 it.icon = target.icon?.toImageHolder() + it.hiddenInMiniDrawer = !app.config.ui.miniMenuButtons.contains(target) if (it is DrawerPrimaryItem) - it.appTitle = target.title?.resolveString(this) - if (it is ColorfulBadgeable && target.badgeTypeId != null) + it.appTitle = target.titleRes?.resolveString(this) + if (/* it is ColorfulBadgeable && */ target.badgeType != null) it.badgeStyle = drawer.badgeStyle it.isSelectedBackgroundAnimated = false it.level = level } - if (target.badgeTypeId != null) - drawer.addUnreadCounterType(target.badgeTypeId!!, target.id) + if (target.badgeType != null) + drawer.addUnreadCounterType(target.badgeType.id, target.id) - item.subItems = target.subItems?.map { + /* item.subItems = target.subItems?.map { createDrawerItem(it, level + 1) - }?.toMutableList() ?: mutableListOf() + }?.toMutableList() ?: mutableListOf() */ return item } @@ -1397,67 +1160,68 @@ class MainActivity : AppCompatActivity(), CoroutineScope { fun setDrawerItems() { d("NavDebug", "setDrawerItems() app.profile = ${app.profile}") val drawerItems = arrayListOf>() + val drawerItemsMore = arrayListOf>() + val drawerItemsBottom = arrayListOf>() val drawerProfiles = arrayListOf() - val supportedFragments = app.profile.supportedFragments + for (target in NavTarget.values()) { + if (target.devModeOnly && !App.devMode) + continue + if (target.featureType != null && !app.profile.hasUIFeature(target.featureType)) + continue - targetPopToHomeList.clear() - - var separatorAdded = false - - for (target in navTargetList) { - if (target.isInDrawer && target.isBelowSeparator && !separatorAdded) { - separatorAdded = true - drawerItems += DividerDrawerItem() - } - - if (target.popToHome) - targetPopToHomeList += target.id - - if (target.isInDrawer && ( - target.isStatic - || supportedFragments.isEmpty() - || supportedFragments.contains(target.id)) - ) { - drawerItems += createDrawerItem(target) - if (target.id == 1) { - targetHomeId = target.id + when (target.location) { + NavTargetLocation.DRAWER -> { + drawerItems += createDrawerItem(target, level = 1) } - } - - if (target.isInProfileList) { - drawerProfiles += ProfileSettingDrawerItem().apply { - identifier = target.id.toLong() - nameRes = target.name - if (target.description != null) - descriptionRes = target.description!! - if (target.icon != null) - withIcon(target.icon!!) + NavTargetLocation.DRAWER_MORE -> { + drawerItemsMore += createDrawerItem(target, level = 2) } + NavTargetLocation.DRAWER_BOTTOM -> { + drawerItemsBottom += createDrawerItem(target, level = 1) + } + NavTargetLocation.PROFILE_LIST -> { + drawerProfiles += ProfileSettingDrawerItem().also { + it.identifier = target.id.toLong() + it.nameRes = target.nameRes + it.descriptionRes = target.descriptionRes ?: -1 + it.icon = target.icon?.toImageHolder() + } + } + else -> continue } } + drawerItems += ExpandableDrawerItem().also { + it.identifier = -1L + it.nameRes = R.string.menu_more + it.icon = CommunityMaterial.Icon.cmd_dots_horizontal.toImageHolder() + it.subItems = drawerItemsMore.toMutableList() + it.isSelectedBackgroundAnimated = false + it.isSelectable = false + } + drawerItems += DividerDrawerItem() + drawerItems += drawerItemsBottom + // seems that this cannot be open, because the itemAdapter has Profile items // instead of normal Drawer items... drawer.profileSelectionClose() - drawer.setItems(*drawerItems.toTypedArray()) drawer.removeAllProfileSettings() drawer.addProfileSettings(*drawerProfiles.toTypedArray()) } - private val targetPopToHomeList = arrayListOf() - private var targetHomeId: Int = -1 @Deprecated("Deprecated in Java") override fun onBackPressed() { - if (!b.navView.onBackPressed()) { - if (App.config.ui.openDrawerOnBackPressed && ((navTarget.popTo == null && navTarget.popToHome) - || navTarget.id == DRAWER_ITEM_HOME) - ) { - b.navView.drawer.toggle() - } else { + super.onBackPressed() + if (App.config.ui.openDrawerOnBackPressed) { + if (drawer.isOpen) + navigateUp() + else if (!navView.onBackPressed()) + drawer.open() + } else { + if (!navView.onBackPressed()) navigateUp() - } } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/AbstractConfig.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/AbstractConfig.kt deleted file mode 100644 index d597a119..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/config/AbstractConfig.kt +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright (c) Kuba Szczodrzyński 2019-11-27. - */ - -package pl.szczodrzynski.edziennik.config - -interface AbstractConfig { - fun set(key: String, value: String?) -} \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/AppData.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/AppData.kt new file mode 100644 index 00000000..a0fa7c27 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/AppData.kt @@ -0,0 +1,70 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2022-10-21. + */ + +package pl.szczodrzynski.edziennik.config + +import com.google.gson.Gson +import com.google.gson.JsonObject +import com.google.gson.JsonParser +import com.google.gson.stream.JsonReader +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.data.db.enums.LoginType +import pl.szczodrzynski.edziennik.ext.getJsonObject +import pl.szczodrzynski.edziennik.ext.mergeWith +import pl.szczodrzynski.edziennik.utils.managers.TextStylingManager.HtmlMode + +data class AppData( + val configOverrides: Map, + val messagesConfig: MessagesConfig, + val uiConfig: UIConfig, + val eventTypes: List, +) { + companion object { + private var data: JsonObject? = null + private val appData = mutableMapOf() + + fun read(app: App) { + val res = app.resources.openRawResource(R.raw.app_data) + data = JsonParser.parseReader(JsonReader(res.reader())).asJsonObject + } + + fun get(loginType: LoginType): AppData { + if (loginType in appData) + return appData.getValue(loginType) + val json = data?.getJsonObject("base")?.deepCopy() + ?: throw NoSuchElementException("Base data not found") + val overrides = setOf(loginType, loginType.schoolType) + for (overrideType in overrides) { + val override = data?.getJsonObject(overrideType.name.lowercase()) ?: continue + json.mergeWith(override) + } + val value = Gson().fromJson(json, AppData::class.java) + appData[loginType] = value + return value + } + } + + data class MessagesConfig( + val subjectLength: Int?, + val bodyLength: Int?, + val textStyling: Boolean, + val syncRecipientList: Boolean, + val htmlMode: HtmlMode, + val needsReadStatus: Boolean, + ) + + data class UIConfig( + val lessonHeight: Int, + val enableMarkAsReadAnnouncements: Boolean, + val enableNoticePoints: Boolean, + val eventManualShowSubjectDropdown: Boolean, + ) + + data class EventType( + val id: Long, + val color: String, + val name: String, + ) +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/BaseConfig.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/BaseConfig.kt new file mode 100644 index 00000000..76d95b0b --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/BaseConfig.kt @@ -0,0 +1,48 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-11-27. + */ + +package pl.szczodrzynski.edziennik.config + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch +import pl.szczodrzynski.edziennik.config.db.ConfigEntry +import pl.szczodrzynski.edziennik.data.db.AppDb +import pl.szczodrzynski.edziennik.ext.takePositive +import kotlin.coroutines.CoroutineContext + +abstract class BaseConfig( + @Transient + val db: AppDb, + val profileId: Int? = null, + protected var entries: List? = null, +) : CoroutineScope { + + private val job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Default + + val values = hashMapOf() + + init { + if (entries == null) + entries = db.configDao().getAllNow() + values.clear() + for ((profileId, key, value) in entries!!) { + if (profileId.takePositive() != this.profileId) + continue + values[key] = value + } + } + + fun set(key: String, value: String?) { + values[key] = value + launch(Dispatchers.IO) { + db.configDao().add(ConfigEntry(profileId ?: -1, key, value)) + } + } + + fun has(key: String) = values.containsKey(key) +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/Config.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/Config.kt index e29d93ad..2042e8b2 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/config/Config.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/Config.kt @@ -5,151 +5,59 @@ package pl.szczodrzynski.edziennik.config import com.google.gson.JsonObject -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.launch import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.BuildConfig -import pl.szczodrzynski.edziennik.config.db.ConfigEntry import pl.szczodrzynski.edziennik.config.utils.* import pl.szczodrzynski.edziennik.data.api.szkolny.response.Update import pl.szczodrzynski.edziennik.data.db.AppDb -import kotlin.coroutines.CoroutineContext -class Config(val db: AppDb) : CoroutineScope, AbstractConfig { +@Suppress("RemoveExplicitTypeArguments") +class Config(db: AppDb) : BaseConfig(db) { companion object { const val DATA_VERSION = 12 } - private val job = Job() - override val coroutineContext: CoroutineContext - get() = job + Dispatchers.Default - - val values: HashMap = hashMapOf() + private val profileConfigs: HashMap = hashMapOf() val ui by lazy { ConfigUI(this) } val sync by lazy { ConfigSync(this) } val timetable by lazy { ConfigTimetable(this) } val grades by lazy { ConfigGrades(this) } - private var mDataVersion: Int? = null - var dataVersion: Int - get() { mDataVersion = mDataVersion ?: values.get("dataVersion", 0); return mDataVersion ?: 0 } - set(value) { set("dataVersion", value); mDataVersion = value } + var dataVersion by config(DATA_VERSION) + var hash by config("") - private var mHash: String? = null - var hash: String - get() { mHash = mHash ?: values.get("hash", ""); return mHash ?: "" } - set(value) { set("hash", value); mHash = value } + var lastProfileId by config(0) + var loginFinished by config(false) + var privacyPolicyAccepted by config(false) + var update by config(null) + var updatesChannel by config("release") - private var mLastProfileId: Int? = null - var lastProfileId: Int - get() { mLastProfileId = mLastProfileId ?: values.get("lastProfileId", 0); return mLastProfileId ?: 0 } - set(value) { set("lastProfileId", value); mLastProfileId = value } + var devMode by config("debugMode", null) + var devModePassword by config(null) + var enableChucker by config(null) - private var mUpdatesChannel: String? = null - var updatesChannel: String - get() { mUpdatesChannel = mUpdatesChannel ?: values.get("updatesChannel", "release"); return mUpdatesChannel ?: "release" } - set(value) { set("updatesChannel", value); mUpdatesChannel = value } - private var mUpdate: Update? = null - var update: Update? - get() { mUpdate = mUpdate ?: values.get("update", null as Update?); return mUpdate ?: null as Update? } - set(value) { set("update", value); mUpdate = value } + var apiAvailabilityCheck by config(true) + var apiInvalidCert by config(null) + var apiKeyCustom by config(null) + var appInstalledTime by config(0L) + var appRateSnackbarTime by config(0L) + var appVersion by config(BuildConfig.VERSION_CODE) + var validation by config(null, "buildValidation") - private var mAppVersion: Int? = null - var appVersion: Int - get() { mAppVersion = mAppVersion ?: values.get("appVersion", BuildConfig.VERSION_CODE); return mAppVersion ?: BuildConfig.VERSION_CODE } - set(value) { set("appVersion", value); mAppVersion = value } + var archiverEnabled by config(true) + var runSync by config(false) + var widgetConfigs by config { JsonObject() } - private var mLoginFinished: Boolean? = null - var loginFinished: Boolean - get() { mLoginFinished = mLoginFinished ?: values.get("loginFinished", false); return mLoginFinished ?: false } - set(value) { set("loginFinished", value); mLoginFinished = value } - - private var mPrivacyPolicyAccepted: Boolean? = null - var privacyPolicyAccepted: Boolean - get() { mPrivacyPolicyAccepted = mPrivacyPolicyAccepted ?: values.get("privacyPolicyAccepted", false); return mPrivacyPolicyAccepted ?: false } - set(value) { set("privacyPolicyAccepted", value); mPrivacyPolicyAccepted = value } - - private var mDevMode: Boolean? = null - var devMode: Boolean? - get() { mDevMode = mDevMode ?: values.getBooleanOrNull("debugMode"); return mDevMode } - set(value) { set("debugMode", value?.toString()); mDevMode = value } - - private var mEnableChucker: Boolean? = null - var enableChucker: Boolean? - get() { mEnableChucker = mEnableChucker ?: values.getBooleanOrNull("enableChucker"); return mEnableChucker } - set(value) { set("enableChucker", value?.toString()); mEnableChucker = value } - - private var mDevModePassword: String? = null - var devModePassword: String? - get() { mDevModePassword = mDevModePassword ?: values.get("devModePassword", null as String?); return mDevModePassword } - set(value) { set("devModePassword", value); mDevModePassword = value } - - private var mAppInstalledTime: Long? = null - var appInstalledTime: Long - get() { mAppInstalledTime = mAppInstalledTime ?: values.get("appInstalledTime", 0L); return mAppInstalledTime ?: 0L } - set(value) { set("appInstalledTime", value); mAppInstalledTime = value } - - private var mAppRateSnackbarTime: Long? = null - var appRateSnackbarTime: Long - get() { mAppRateSnackbarTime = mAppRateSnackbarTime ?: values.get("appRateSnackbarTime", 0L); return mAppRateSnackbarTime ?: 0L } - set(value) { set("appRateSnackbarTime", value); mAppRateSnackbarTime = value } - - private var mRunSync: Boolean? = null - var runSync: Boolean - get() { mRunSync = mRunSync ?: values.get("runSync", false); return mRunSync ?: false } - set(value) { set("runSync", value); mRunSync = value } - - private var mWidgetConfigs: JsonObject? = null - var widgetConfigs: JsonObject - get() { mWidgetConfigs = mWidgetConfigs ?: values.get("widgetConfigs", JsonObject()); return mWidgetConfigs ?: JsonObject() } - set(value) { set("widgetConfigs", value); mWidgetConfigs = value } - - private var mArchiverEnabled: Boolean? = null - var archiverEnabled: Boolean - get() { mArchiverEnabled = mArchiverEnabled ?: values.get("archiverEnabled", true); return mArchiverEnabled ?: true } - set(value) { set("archiverEnabled", value); mArchiverEnabled = value } - - private var mValidation: String? = null - var validation: String? - get() { mValidation = mValidation ?: values["buildValidation"]; return mValidation } - set(value) { set("buildValidation", value); mValidation = value } - - private var mApiInvalidCert: String? = null - var apiInvalidCert: String? - get() { mApiInvalidCert = mApiInvalidCert ?: values["apiInvalidCert"]; return mApiInvalidCert } - set(value) { set("apiInvalidCert", value); mApiInvalidCert = value } - - private var mApiAvailabilityCheck: Boolean? = null - var apiAvailabilityCheck: Boolean - get() { mApiAvailabilityCheck = mApiAvailabilityCheck ?: values.get("apiAvailabilityCheck", true); return mApiAvailabilityCheck ?: true } - set(value) { set("apiAvailabilityCheck", value); mApiAvailabilityCheck = value } - - private var rawEntries: List = db.configDao().getAllNow() - private val profileConfigs: HashMap = hashMapOf() - init { - rawEntries.toHashMap(-1, values) - } fun migrate(app: App) { - if (dataVersion < DATA_VERSION) + if (dataVersion < DATA_VERSION || hash == "") + // migrate old data version OR freshly installed app (or updated from 3.x) ConfigMigration(app, this) } - fun getFor(profileId: Int): ProfileConfig { - return profileConfigs[profileId] ?: ProfileConfig(db, profileId, db.configDao().getAllNow(profileId)).also { + + operator fun get(profileId: Int): ProfileConfig { + return profileConfigs[profileId] ?: ProfileConfig(db, profileId, entries).also { profileConfigs[profileId] = it } } - fun forProfile() = getFor(App.profileId) - - fun setProfile(profileId: Int) { - } - - override fun set(key: String, value: String?) { - values[key] = value - launch { - db.configDao().add(ConfigEntry(-1, key, value)) - } - } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigGrades.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigGrades.kt index 267ec322..a28dcc3d 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigGrades.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigGrades.kt @@ -4,13 +4,10 @@ package pl.szczodrzynski.edziennik.config -import pl.szczodrzynski.edziennik.config.utils.get -import pl.szczodrzynski.edziennik.config.utils.set -import pl.szczodrzynski.edziennik.utils.managers.GradesManager +import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.ORDER_BY_DATE_DESC -class ConfigGrades(private val config: Config) { - private var mOrderBy: Int? = null - var orderBy: Int - get() { mOrderBy = mOrderBy ?: config.values.get("gradesOrderBy", 0); return mOrderBy ?: GradesManager.ORDER_BY_DATE_DESC } - set(value) { config.set("gradesOrderBy", value); mOrderBy = value } +@Suppress("RemoveExplicitTypeArguments") +class ConfigGrades(base: Config) { + + var orderBy by base.config("gradesOrderBy", ORDER_BY_DATE_DESC) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigSync.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigSync.kt index 387e5cf7..6688838b 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigSync.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigSync.kt @@ -4,139 +4,53 @@ package pl.szczodrzynski.edziennik.config -import com.google.gson.Gson -import com.google.gson.reflect.TypeToken import pl.szczodrzynski.edziennik.BuildConfig -import pl.szczodrzynski.edziennik.config.utils.get -import pl.szczodrzynski.edziennik.config.utils.getIntList -import pl.szczodrzynski.edziennik.config.utils.set -import pl.szczodrzynski.edziennik.config.utils.setMap import pl.szczodrzynski.edziennik.data.api.szkolny.response.RegisterAvailabilityStatus +import pl.szczodrzynski.edziennik.ext.HOUR import pl.szczodrzynski.edziennik.utils.models.Time -class ConfigSync(private val config: Config) { - private val gson = Gson() +@Suppress("RemoveExplicitTypeArguments") +class ConfigSync(base: Config) { - private var mDontShowAppManagerDialog: Boolean? = null - var dontShowAppManagerDialog: Boolean - get() { mDontShowAppManagerDialog = mDontShowAppManagerDialog ?: config.values.get("dontShowAppManagerDialog", false); return mDontShowAppManagerDialog ?: false } - set(value) { config.set("dontShowAppManagerDialog", value); mDontShowAppManagerDialog = value } + var enabled by base.config("syncEnabled", true) + var interval by base.config("syncInterval", 1 * HOUR.toInt()) + var onlyWifi by base.config("syncOnlyWifi", false) - private var mSyncEnabled: Boolean? = null - var enabled: Boolean - get() { mSyncEnabled = mSyncEnabled ?: config.values.get("syncEnabled", true); return mSyncEnabled ?: true } - set(value) { config.set("syncEnabled", value); mSyncEnabled = value } + var dontShowAppManagerDialog by base.config(false) + var lastAppSync by base.config(0L) + var notifyAboutUpdates by base.config(true) + var webPushEnabled by base.config(true) - private var mWebPushEnabled: Boolean? = null - var webPushEnabled: Boolean - get() { mWebPushEnabled = mWebPushEnabled ?: config.values.get("webPushEnabled", true); return mWebPushEnabled ?: true } - set(value) { config.set("webPushEnabled", value); mWebPushEnabled = value } + // Quiet Hours + var quietHoursEnabled by base.config(false) + var quietHoursStart by base.config(null) + var quietHoursEnd by base.config(null) + var quietDuringLessons by base.config(false) - private var mSyncOnlyWifi: Boolean? = null - var onlyWifi: Boolean - get() { mSyncOnlyWifi = mSyncOnlyWifi ?: config.values.get("syncOnlyWifi", false); return mSyncOnlyWifi ?: notifyAboutUpdates } - set(value) { config.set("syncOnlyWifi", value); mSyncOnlyWifi = value } + // FCM Tokens + var tokenApp by base.config(null) + var tokenMobidziennik by base.config(null) + var tokenLibrus by base.config(null) + var tokenVulcan by base.config(null) + var tokenVulcanHebe by base.config(null) - private var mSyncInterval: Int? = null - var interval: Int - get() { mSyncInterval = mSyncInterval ?: config.values.get("syncInterval", 60*60); return mSyncInterval ?: 60*60 } - set(value) { config.set("syncInterval", value); mSyncInterval = value } + var tokenMobidziennikList by base.config> { listOf() } + var tokenLibrusList by base.config> { listOf() } + var tokenVulcanList by base.config> { listOf() } + var tokenVulcanHebeList by base.config> { listOf() } - private var mNotifyAboutUpdates: Boolean? = null - var notifyAboutUpdates: Boolean - get() { mNotifyAboutUpdates = mNotifyAboutUpdates ?: config.values.get("notifyAboutUpdates", true); return mNotifyAboutUpdates ?: true } - set(value) { config.set("notifyAboutUpdates", value); mNotifyAboutUpdates = value } + // Register Availability + private var registerAvailabilityMap by base.config>("registerAvailability") { mapOf() } + private var registerAvailabilityFlavor by base.config(null) - private var mLastAppSync: Long? = null - var lastAppSync: Long - get() { mLastAppSync = mLastAppSync ?: config.values.get("lastAppSync", 0L); return mLastAppSync ?: 0L } - set(value) { config.set("lastAppSync", value); mLastAppSync = value } - - /* ____ _ _ _ - / __ \ (_) | | | | - | | | |_ _ _ ___| |_ | |__ ___ _ _ _ __ ___ - | | | | | | | |/ _ \ __| | '_ \ / _ \| | | | '__/ __| - | |__| | |_| | | __/ |_ | | | | (_) | |_| | | \__ \ - \___\_\\__,_|_|\___|\__| |_| |_|\___/ \__,_|_| |__*/ - private var mQuietHoursEnabled: Boolean? = null - var quietHoursEnabled: Boolean - get() { mQuietHoursEnabled = mQuietHoursEnabled ?: config.values.get("quietHoursEnabled", false); return mQuietHoursEnabled ?: false } - set(value) { config.set("quietHoursEnabled", value); mQuietHoursEnabled = value } - - private var mQuietHoursStart: Time? = null - var quietHoursStart: Time? - get() { mQuietHoursStart = mQuietHoursStart ?: config.values.get("quietHoursStart", null as Time?); return mQuietHoursStart } - set(value) { config.set("quietHoursStart", value); mQuietHoursStart = value } - - private var mQuietHoursEnd: Time? = null - var quietHoursEnd: Time? - get() { mQuietHoursEnd = mQuietHoursEnd ?: config.values.get("quietHoursEnd", null as Time?); return mQuietHoursEnd } - set(value) { config.set("quietHoursEnd", value); mQuietHoursEnd = value } - - private var mQuietDuringLessons: Boolean? = null - var quietDuringLessons: Boolean - get() { mQuietDuringLessons = mQuietDuringLessons ?: config.values.get("quietDuringLessons", false); return mQuietDuringLessons ?: false } - set(value) { config.set("quietDuringLessons", value); mQuietDuringLessons = value } - - /* ______ _____ __ __ _______ _ - | ____/ ____| \/ | |__ __| | | - | |__ | | | \ / | | | ___ | | _____ _ __ ___ - | __|| | | |\/| | | |/ _ \| |/ / _ \ '_ \/ __| - | | | |____| | | | | | (_) | < __/ | | \__ \ - |_| \_____|_| |_| |_|\___/|_|\_\___|_| |_|__*/ - private var mTokenApp: String? = null - var tokenApp: String? - get() { mTokenApp = mTokenApp ?: config.values.get("tokenApp", null as String?); return mTokenApp } - set(value) { config.set("tokenApp", value); mTokenApp = value } - private var mTokenMobidziennik: String? = null - var tokenMobidziennik: String? - get() { mTokenMobidziennik = mTokenMobidziennik ?: config.values.get("tokenMobidziennik", null as String?); return mTokenMobidziennik } - set(value) { config.set("tokenMobidziennik", value); mTokenMobidziennik = value } - private var mTokenLibrus: String? = null - var tokenLibrus: String? - get() { mTokenLibrus = mTokenLibrus ?: config.values.get("tokenLibrus", null as String?); return mTokenLibrus } - set(value) { config.set("tokenLibrus", value); mTokenLibrus = value } - private var mTokenVulcan: String? = null - var tokenVulcan: String? - get() { mTokenVulcan = mTokenVulcan ?: config.values.get("tokenVulcan", null as String?); return mTokenVulcan } - set(value) { config.set("tokenVulcan", value); mTokenVulcan = value } - private var mTokenVulcanHebe: String? = null - var tokenVulcanHebe: String? - get() { mTokenVulcanHebe = mTokenVulcanHebe ?: config.values.get("tokenVulcanHebe", null as String?); return mTokenVulcanHebe } - set(value) { config.set("tokenVulcanHebe", value); mTokenVulcanHebe = value } - - private var mTokenMobidziennikList: List? = null - var tokenMobidziennikList: List - get() { mTokenMobidziennikList = mTokenMobidziennikList ?: config.values.getIntList("tokenMobidziennikList", listOf()); return mTokenMobidziennikList ?: listOf() } - set(value) { config.set("tokenMobidziennikList", value); mTokenMobidziennikList = value } - private var mTokenLibrusList: List? = null - var tokenLibrusList: List - get() { mTokenLibrusList = mTokenLibrusList ?: config.values.getIntList("tokenLibrusList", listOf()); return mTokenLibrusList ?: listOf() } - set(value) { config.set("tokenLibrusList", value); mTokenLibrusList = value } - private var mTokenVulcanList: List? = null - var tokenVulcanList: List - get() { mTokenVulcanList = mTokenVulcanList ?: config.values.getIntList("tokenVulcanList", listOf()); return mTokenVulcanList ?: listOf() } - set(value) { config.set("tokenVulcanList", value); mTokenVulcanList = value } - private var mTokenVulcanHebeList: List? = null - var tokenVulcanHebeList: List - get() { mTokenVulcanHebeList = mTokenVulcanHebeList ?: config.values.getIntList("tokenVulcanHebeList", listOf()); return mTokenVulcanHebeList ?: listOf() } - set(value) { config.set("tokenVulcanHebeList", value); mTokenVulcanHebeList = value } - - private var mRegisterAvailability: Map? = null var registerAvailability: Map get() { - val flavor = config.values.get("registerAvailabilityFlavor", null as String?) - if (BuildConfig.FLAVOR != flavor) + if (BuildConfig.FLAVOR != registerAvailabilityFlavor) return mapOf() - - mRegisterAvailability = mRegisterAvailability ?: config.values.get("registerAvailability", null as String?)?.let { it -> - gson.fromJson(it, object: TypeToken>(){}.type) - } - return mRegisterAvailability ?: mapOf() + return registerAvailabilityMap } set(value) { - config.setMap("registerAvailability", value) - config.set("registerAvailabilityFlavor", BuildConfig.FLAVOR) - mRegisterAvailability = value + registerAvailabilityMap = value + registerAvailabilityFlavor = BuildConfig.FLAVOR } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigTimetable.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigTimetable.kt index 2418f1d3..9f60534b 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigTimetable.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigTimetable.kt @@ -4,23 +4,12 @@ package pl.szczodrzynski.edziennik.config -import pl.szczodrzynski.edziennik.config.utils.get -import pl.szczodrzynski.edziennik.config.utils.set import pl.szczodrzynski.edziennik.utils.models.Time -class ConfigTimetable(private val config: Config) { - private var mBellSyncMultiplier: Int? = null - var bellSyncMultiplier: Int - get() { mBellSyncMultiplier = mBellSyncMultiplier ?: config.values.get("bellSyncMultiplier", 0); return mBellSyncMultiplier ?: 0 } - set(value) { config.set("bellSyncMultiplier", value); mBellSyncMultiplier = value } +@Suppress("RemoveExplicitTypeArguments") +class ConfigTimetable(base: Config) { - private var mBellSyncDiff: Time? = null - var bellSyncDiff: Time? - get() { mBellSyncDiff = mBellSyncDiff ?: config.values.get("bellSyncDiff", null as Time?); return mBellSyncDiff } - set(value) { config.set("bellSyncDiff", value); mBellSyncDiff = value } - - private var mCountInSeconds: Boolean? = null - var countInSeconds: Boolean - get() { mCountInSeconds = mCountInSeconds ?: config.values.get("countInSeconds", false); return mCountInSeconds ?: false } - set(value) { config.set("countInSeconds", value); mCountInSeconds = value } -} \ No newline at end of file + var bellSyncMultiplier by base.config(0) + var bellSyncDiff by base.config(null) + var countInSeconds by base.config(false) +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigUI.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigUI.kt index f36652a1..a8a6ba33 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigUI.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigUI.kt @@ -4,58 +4,32 @@ package pl.szczodrzynski.edziennik.config -import pl.szczodrzynski.edziennik.config.utils.get -import pl.szczodrzynski.edziennik.config.utils.getIntList -import pl.szczodrzynski.edziennik.config.utils.set +import pl.szczodrzynski.edziennik.ui.base.enums.NavTarget -class ConfigUI(private val config: Config) { - private var mTheme: Int? = null - var theme: Int - get() { mTheme = mTheme ?: config.values.get("theme", 1); return mTheme ?: 1 } - set(value) { config.set("theme", value); mTheme = value } +@Suppress("RemoveExplicitTypeArguments") +class ConfigUI(base: Config) { - private var mLanguage: String? = null - var language: String? - get() { mLanguage = mLanguage ?: config.values.get("language", null as String?); return mLanguage } - set(value) { config.set("language", value); mLanguage = value } + var theme by base.config(1) + var language by base.config(null) - private var mHeaderBackground: String? = null - var headerBackground: String? - get() { mHeaderBackground = mHeaderBackground ?: config.values.get("headerBg", null as String?); return mHeaderBackground } - set(value) { config.set("headerBg", value); mHeaderBackground = value } + var appBackground by base.config("appBg", null) + var headerBackground by base.config("headerBg", null) - private var mAppBackground: String? = null - var appBackground: String? - get() { mAppBackground = mAppBackground ?: config.values.get("appBg", null as String?); return mAppBackground } - set(value) { config.set("appBg", value); mAppBackground = value } + var miniMenuVisible by base.config(false) + var miniMenuButtons by base.config> { + setOf( + NavTarget.HOME, + NavTarget.TIMETABLE, + NavTarget.AGENDA, + NavTarget.GRADES, + NavTarget.MESSAGES, + NavTarget.HOMEWORK, + NavTarget.SETTINGS + ) + } + var openDrawerOnBackPressed by base.config(false) - private var mMiniMenuVisible: Boolean? = null - var miniMenuVisible: Boolean - get() { mMiniMenuVisible = mMiniMenuVisible ?: config.values.get("miniMenuVisible", false); return mMiniMenuVisible ?: false } - set(value) { config.set("miniMenuVisible", value); mMiniMenuVisible = value } - - private var mMiniMenuButtons: List? = null - var miniMenuButtons: List - get() { mMiniMenuButtons = mMiniMenuButtons ?: config.values.getIntList("miniMenuButtons", listOf()); return mMiniMenuButtons ?: listOf() } - set(value) { config.set("miniMenuButtons", value); mMiniMenuButtons = value } - - private var mOpenDrawerOnBackPressed: Boolean? = null - var openDrawerOnBackPressed: Boolean - get() { mOpenDrawerOnBackPressed = mOpenDrawerOnBackPressed ?: config.values.get("openDrawerOnBackPressed", false); return mOpenDrawerOnBackPressed ?: false } - set(value) { config.set("openDrawerOnBackPressed", value); mOpenDrawerOnBackPressed = value } - - private var mSnowfall: Boolean? = null - var snowfall: Boolean - get() { mSnowfall = mSnowfall ?: config.values.get("snowfall", false); return mSnowfall ?: false } - set(value) { config.set("snowfall", value); mSnowfall = value } - - private var mEggfall: Boolean? = null - var eggfall: Boolean - get() { mEggfall = mEggfall ?: config.values.get("eggfall", false); return mEggfall ?: false } - set(value) { config.set("eggfall", value); mEggfall = value } - - private var mBottomSheetOpened: Boolean? = null - var bottomSheetOpened: Boolean - get() { mBottomSheetOpened = mBottomSheetOpened ?: config.values.get("bottomSheetOpened", false); return mBottomSheetOpened ?: false } - set(value) { config.set("bottomSheetOpened", value); mBottomSheetOpened = value } + var bottomSheetOpened by base.config(false) + var snowfall by base.config(false) + var eggfall by base.config(false) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/DelegateConfig.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/DelegateConfig.kt new file mode 100644 index 00000000..91dac668 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/DelegateConfig.kt @@ -0,0 +1,175 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2022-10-21. + */ + +package pl.szczodrzynski.edziennik.config + +import com.google.gson.Gson +import com.google.gson.JsonArray +import com.google.gson.JsonObject +import com.google.gson.reflect.TypeToken +import pl.szczodrzynski.edziennik.ext.* +import pl.szczodrzynski.edziennik.utils.models.Date +import pl.szczodrzynski.edziennik.utils.models.Time +import java.lang.reflect.ParameterizedType +import java.lang.reflect.WildcardType +import kotlin.reflect.KProperty + +private val gson = Gson() + +inline fun BaseConfig.config(name: String? = null, noinline default: () -> T) = ConfigDelegate( + config = this, + type = T::class.java, + nullable = null is T, + typeToken = object : TypeToken() {}, + defaultFunc = default, + defaultValue = null, + fieldName = name, +) + +inline fun BaseConfig.config(default: T) = ConfigDelegate( + config = this, + type = T::class.java, + nullable = null is T, + typeToken = object : TypeToken() {}, + defaultFunc = null, + defaultValue = default, + fieldName = null, +) + +inline fun BaseConfig.config(name: String? = null, default: T) = ConfigDelegate( + config = this, + type = T::class.java, + nullable = null is T, + typeToken = object : TypeToken() {}, + defaultFunc = null, + defaultValue = default, + fieldName = name, +) + +@Suppress("UNCHECKED_CAST") +class ConfigDelegate( + private val config: BaseConfig, + private val type: Class, + private val nullable: Boolean, + private val typeToken: TypeToken, + private val defaultFunc: (() -> T)?, + private val defaultValue: T?, + private val fieldName: String?, +) { + private var value: T? = null + private var isInitialized = false + + private fun getDefault(): T = when { + defaultFunc != null -> defaultFunc.invoke() + else -> defaultValue as T + } + + private fun getGenericType(index: Int = 0): Class<*> { + val parameterizedType = typeToken.type as ParameterizedType + val typeArgument = parameterizedType.actualTypeArguments[index] as WildcardType + return typeArgument.upperBounds[0] as Class<*> + } + + operator fun setValue(_thisRef: Any, property: KProperty<*>, newValue: T) { + value = newValue + isInitialized = true + config.set(fieldName ?: property.name, serialize(newValue)?.toString()) + } + + operator fun getValue(_thisRef: Any, property: KProperty<*>): T { + if (isInitialized) + return value as T + val key = fieldName ?: property.name + + if (key !in config.values) { + value = getDefault() + isInitialized = true + return value as T + } + val str = config.values[key] + + value = if (str == null && nullable) + null as T + else if (str == null) + getDefault() + else + deserialize(str) + + isInitialized = true + return value as T + } + + private fun serialize(value: I?, serializeObjects: Boolean = true): Any? { + if (value == null) + return null + + return when (value) { + is String -> value + is Date -> value.stringY_m_d + is Time -> value.stringValue + is JsonObject -> value + is JsonArray -> value + // primitives + is Number -> value + is Boolean -> value + // enums, maps & collections + is Enum<*> -> value.toInt() + is Collection<*> -> value.map { + if (it is Number || it is Boolean) it else serialize(it, serializeObjects = false) + }.toJsonElement() + is Map<*, *> -> gson.toJson(value.mapValues { (_, it) -> + if (it is Number || it is Boolean) it else serialize(it, serializeObjects = false) + }) + // objects or else + else -> if (serializeObjects) gson.toJson(value) else value + } + } + + private fun deserialize(value: String?, type: Class<*> = this.type): I? { + if (value == null) + return null + + @Suppress("TYPE_MISMATCH_WARNING") + return when (type) { + String::class.java -> value + Date::class.java -> Date.fromY_m_d(value) + Time::class.java -> Time.fromHms(value) + JsonObject::class.java -> value.toJsonObject() + JsonArray::class.java -> value.toJsonArray() + // primitives + java.lang.Integer::class.java -> value.toIntOrNull() + java.lang.Boolean::class.java -> value.toBooleanStrictOrNull() + java.lang.Long::class.java -> value.toLongOrNull() + java.lang.Float::class.java -> value.toFloatOrNull() + // enums, maps & collections + else -> when { + Enum::class.java.isAssignableFrom(type) -> value.toIntOrNull()?.toEnum(type) as Enum<*> + Collection::class.java.isAssignableFrom(type) -> { + val array = value.toJsonArray() + val genericType = getGenericType() + val list = array?.map { + val str = if (it.isJsonPrimitive) it.asString else it.toString() + deserialize(str, genericType) + } + when { + List::class.java.isAssignableFrom(type) -> list + Set::class.java.isAssignableFrom(type) -> list?.toSet() + else -> list?.toTypedArray() + } + } + Map::class.java.isAssignableFrom(type) -> { + val obj = value.toJsonObject() + val genericType = getGenericType(index = 1) + val map = obj?.entrySet()?.associate { (key, it) -> + val str = if (it.isJsonPrimitive) it.asString else it.toString() + key to deserialize(str, genericType) + } + map + } + // objects or else + else -> gson.fromJson(value, type) + } + } as? I + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfig.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfig.kt index 6482634c..c36b6fae 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfig.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfig.kt @@ -4,29 +4,20 @@ package pl.szczodrzynski.edziennik.config -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.launch import pl.szczodrzynski.edziennik.config.db.ConfigEntry import pl.szczodrzynski.edziennik.config.utils.ProfileConfigMigration -import pl.szczodrzynski.edziennik.config.utils.get -import pl.szczodrzynski.edziennik.config.utils.set -import pl.szczodrzynski.edziennik.config.utils.toHashMap import pl.szczodrzynski.edziennik.data.db.AppDb -import kotlin.coroutines.CoroutineContext -class ProfileConfig(val db: AppDb, val profileId: Int, rawEntries: List) : CoroutineScope, AbstractConfig { +@Suppress("RemoveExplicitTypeArguments") +class ProfileConfig( + db: AppDb, + profileId: Int, + entries: List?, +) : BaseConfig(db, profileId, entries) { companion object { - const val DATA_VERSION = 3 + const val DATA_VERSION = 5 } - private val job = Job() - override val coroutineContext: CoroutineContext - get() = job + Dispatchers.Default - - val values: HashMap = hashMapOf() - val grades by lazy { ProfileConfigGrades(this) } val ui by lazy { ProfileConfigUI(this) } val sync by lazy { ProfileConfigSync(this) } @@ -35,26 +26,13 @@ class ProfileConfig(val db: AppDb, val profileId: Int, rawEntries: List(DATA_VERSION) + var hash by config("") - private var mHash: String? = null - var hash: String - get() { mHash = mHash ?: values.get("hash", ""); return mHash ?: "" } - set(value) { set("hash", value); mHash = value } + var shareByDefault by config(false) init { - rawEntries.toHashMap(profileId, values) if (dataVersion < DATA_VERSION) ProfileConfigMigration(this) } - - override fun set(key: String, value: String?) { - values[key] = value - launch { - db.configDao().add(ConfigEntry(profileId, key, value)) - } - } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfigAttendance.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfigAttendance.kt index 326f5b16..ba4f9deb 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfigAttendance.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfigAttendance.kt @@ -4,27 +4,11 @@ package pl.szczodrzynski.edziennik.config -import pl.szczodrzynski.edziennik.config.utils.get -import pl.szczodrzynski.edziennik.config.utils.set +@Suppress("RemoveExplicitTypeArguments") +class ProfileConfigAttendance(base: ProfileConfig) { -class ProfileConfigAttendance(private val config: ProfileConfig) { - private var mAttendancePageSelection: Int? = null - var attendancePageSelection: Int - get() { mAttendancePageSelection = mAttendancePageSelection ?: config.values.get("attendancePageSelection", 1); return mAttendancePageSelection ?: 1 } - set(value) { config.set("attendancePageSelection", value); mAttendancePageSelection = value } - - private var mUseSymbols: Boolean? = null - var useSymbols: Boolean - get() { mUseSymbols = mUseSymbols ?: config.values.get("useSymbols", false); return mUseSymbols ?: false } - set(value) { config.set("useSymbols", value); mUseSymbols = value } - - private var mGroupConsecutiveDays: Boolean? = null - var groupConsecutiveDays: Boolean - get() { mGroupConsecutiveDays = mGroupConsecutiveDays ?: config.values.get("groupConsecutiveDays", true); return mGroupConsecutiveDays ?: true } - set(value) { config.set("groupConsecutiveDays", value); mGroupConsecutiveDays = value } - - private var mShowPresenceInMonth: Boolean? = null - var showPresenceInMonth: Boolean - get() { mShowPresenceInMonth = mShowPresenceInMonth ?: config.values.get("showPresenceInMonth", false); return mShowPresenceInMonth ?: false } - set(value) { config.set("showPresenceInMonth", value); mShowPresenceInMonth = value } + var attendancePageSelection by base.config(1) + var groupConsecutiveDays by base.config(true) + var showPresenceInMonth by base.config(false) + var useSymbols by base.config(false) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfigGrades.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfigGrades.kt index 6434aac7..cfe4bc3e 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfigGrades.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfigGrades.kt @@ -4,54 +4,19 @@ package pl.szczodrzynski.edziennik.config -import pl.szczodrzynski.edziennik.config.utils.get -import pl.szczodrzynski.edziennik.config.utils.getFloat -import pl.szczodrzynski.edziennik.config.utils.set import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.COLOR_MODE_WEIGHTED import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_ALL_GRADES -class ProfileConfigGrades(private val config: ProfileConfig) { - private var mColorMode: Int? = null - var colorMode: Int - get() { mColorMode = mColorMode ?: config.values.get("gradesColorMode", COLOR_MODE_WEIGHTED); return mColorMode ?: COLOR_MODE_WEIGHTED } - set(value) { config.set("gradesColorMode", value); mColorMode = value } +@Suppress("RemoveExplicitTypeArguments") +class ProfileConfigGrades(base: ProfileConfig) { - private var mYearAverageMode: Int? = null - var yearAverageMode: Int - get() { mYearAverageMode = mYearAverageMode ?: config.values.get("yearAverageMode", YEAR_ALL_GRADES); return mYearAverageMode ?: YEAR_ALL_GRADES } - set(value) { config.set("yearAverageMode", value); mYearAverageMode = value } - - private var mHideImproved: Boolean? = null - var hideImproved: Boolean - get() { mHideImproved = mHideImproved ?: config.values.get("hideImproved", false); return mHideImproved ?: false } - set(value) { config.set("hideImproved", value); mHideImproved = value } - - private var mAverageWithoutWeight: Boolean? = null - var averageWithoutWeight: Boolean - get() { mAverageWithoutWeight = mAverageWithoutWeight ?: config.values.get("averageWithoutWeight", true); return mAverageWithoutWeight ?: true } - set(value) { config.set("averageWithoutWeight", value); mAverageWithoutWeight = value } - - private var mPlusValue: Float? = null - var plusValue: Float? - get() { mPlusValue = mPlusValue ?: config.values.getFloat("plusValue"); return mPlusValue } - set(value) { config.set("plusValue", value); mPlusValue = value } - private var mMinusValue: Float? = null - var minusValue: Float? - get() { mMinusValue = mMinusValue ?: config.values.getFloat("minusValue"); return mMinusValue } - set(value) { config.set("minusValue", value); mMinusValue = value } - - private var mDontCountEnabled: Boolean? = null - var dontCountEnabled: Boolean - get() { mDontCountEnabled = mDontCountEnabled ?: config.values.get("dontCountEnabled", false); return mDontCountEnabled ?: false } - set(value) { config.set("dontCountEnabled", value); mDontCountEnabled = value } - - private var mDontCountGrades: List? = null - var dontCountGrades: List - get() { mDontCountGrades = mDontCountGrades ?: config.values.get("dontCountGrades", listOf()); return mDontCountGrades ?: listOf() } - set(value) { config.set("dontCountGrades", value); mDontCountGrades = value } - - private var mHideSticksFromOld: Boolean? = null - var hideSticksFromOld: Boolean - get() { mHideSticksFromOld = mHideSticksFromOld ?: config.values.get("hideSticksFromOld", false); return mHideSticksFromOld ?: false } - set(value) { config.set("hideSticksFromOld", value); mHideSticksFromOld = value } + var averageWithoutWeight by base.config(true) + var colorMode by base.config(COLOR_MODE_WEIGHTED) + var dontCountEnabled by base.config(false) + var dontCountGrades by base.config> { listOf() } + var hideImproved by base.config(false) + var hideSticksFromOld by base.config(false) + var minusValue by base.config(null) + var plusValue by base.config(null) + var yearAverageMode by base.config(YEAR_ALL_GRADES) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfigSync.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfigSync.kt index 2012bc4b..91b6394e 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfigSync.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfigSync.kt @@ -4,12 +4,14 @@ package pl.szczodrzynski.edziennik.config -import pl.szczodrzynski.edziennik.config.utils.getIntList -import pl.szczodrzynski.edziennik.config.utils.set +import pl.szczodrzynski.edziennik.data.db.enums.NotificationType -class ProfileConfigSync(private val config: ProfileConfig) { - private var mNotificationFilter: List? = null - var notificationFilter: List - get() { mNotificationFilter = mNotificationFilter ?: config.values.getIntList("notificationFilter", listOf()); return mNotificationFilter ?: listOf() } - set(value) { config.set("notificationFilter", value); mNotificationFilter = value } +@Suppress("RemoveExplicitTypeArguments") +class ProfileConfigSync(base: ProfileConfig) { + + var notificationFilter by base.config> { + NotificationType.values() + .filter { it.enabledByDefault == false } + .toSet() + } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfigUI.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfigUI.kt index 187cb6bd..35951082 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfigUI.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfigUI.kt @@ -4,69 +4,30 @@ package pl.szczodrzynski.edziennik.config -import pl.szczodrzynski.edziennik.config.utils.get -import pl.szczodrzynski.edziennik.config.utils.set import pl.szczodrzynski.edziennik.data.db.entity.Profile.Companion.AGENDA_DEFAULT import pl.szczodrzynski.edziennik.ui.home.HomeCardModel -class ProfileConfigUI(private val config: ProfileConfig) { - private var mAgendaViewType: Int? = null - var agendaViewType: Int - get() { mAgendaViewType = mAgendaViewType ?: config.values.get("agendaViewType", 0); return mAgendaViewType ?: AGENDA_DEFAULT } - set(value) { config.set("agendaViewType", value); mAgendaViewType = value } +@Suppress("RemoveExplicitTypeArguments") +class ProfileConfigUI(base: ProfileConfig) { - private var mAgendaCompactMode: Boolean? = null - var agendaCompactMode: Boolean - get() { mAgendaCompactMode = mAgendaCompactMode ?: config.values.get("agendaCompactMode", false); return mAgendaCompactMode ?: false } - set(value) { config.set("agendaCompactMode", value); mAgendaCompactMode = value } + var agendaViewType by base.config(AGENDA_DEFAULT) + var agendaCompactMode by base.config(false) + var agendaGroupByType by base.config(false) + var agendaLessonChanges by base.config(true) + var agendaTeacherAbsence by base.config(true) + var agendaSubjectImportant by base.config(false) + var agendaElearningMark by base.config(false) + var agendaElearningGroup by base.config(true) - private var mAgendaGroupByType: Boolean? = null - var agendaGroupByType: Boolean - get() { mAgendaGroupByType = mAgendaGroupByType ?: config.values.get("agendaGroupByType", false); return mAgendaGroupByType ?: false } - set(value) { config.set("agendaGroupByType", value); mAgendaGroupByType = value } + var homeCards by base.config> { listOf() } - private var mAgendaLessonChanges: Boolean? = null - var agendaLessonChanges: Boolean - get() { mAgendaLessonChanges = mAgendaLessonChanges ?: config.values.get("agendaLessonChanges", true); return mAgendaLessonChanges ?: true } - set(value) { config.set("agendaLessonChanges", value); mAgendaLessonChanges = value } + var messagesGreetingOnCompose by base.config(true) + var messagesGreetingOnReply by base.config(true) + var messagesGreetingOnForward by base.config(false) + var messagesGreetingText by base.config(null) - private var mAgendaTeacherAbsence: Boolean? = null - var agendaTeacherAbsence: Boolean - get() { mAgendaTeacherAbsence = mAgendaTeacherAbsence ?: config.values.get("agendaTeacherAbsence", true); return mAgendaTeacherAbsence ?: true } - set(value) { config.set("agendaTeacherAbsence", value); mAgendaTeacherAbsence = value } - - private var mAgendaElearningMark: Boolean? = null - var agendaElearningMark: Boolean - get() { mAgendaElearningMark = mAgendaElearningMark ?: config.values.get("agendaElearningMark", false); return mAgendaElearningMark ?: false } - set(value) { config.set("agendaElearningMark", value); mAgendaElearningMark = value } - - private var mAgendaElearningGroup: Boolean? = null - var agendaElearningGroup: Boolean - get() { mAgendaElearningGroup = mAgendaElearningGroup ?: config.values.get("agendaElearningGroup", true); return mAgendaElearningGroup ?: true } - set(value) { config.set("agendaElearningGroup", value); mAgendaElearningGroup = value } - - private var mHomeCards: List? = null - var homeCards: List - get() { mHomeCards = mHomeCards ?: config.values.get("homeCards", listOf(), HomeCardModel::class.java); return mHomeCards ?: listOf() } - set(value) { config.set("homeCards", value); mHomeCards = value } - - private var mMessagesGreetingOnCompose: Boolean? = null - var messagesGreetingOnCompose: Boolean - get() { mMessagesGreetingOnCompose = mMessagesGreetingOnCompose ?: config.values.get("messagesGreetingOnCompose", true); return mMessagesGreetingOnCompose ?: true } - set(value) { config.set("messagesGreetingOnCompose", value); mMessagesGreetingOnCompose = value } - - private var mMessagesGreetingOnReply: Boolean? = null - var messagesGreetingOnReply: Boolean - get() { mMessagesGreetingOnReply = mMessagesGreetingOnReply ?: config.values.get("messagesGreetingOnReply", true); return mMessagesGreetingOnReply ?: true } - set(value) { config.set("messagesGreetingOnReply", value); mMessagesGreetingOnReply = value } - - private var mMessagesGreetingOnForward: Boolean? = null - var messagesGreetingOnForward: Boolean - get() { mMessagesGreetingOnForward = mMessagesGreetingOnForward ?: config.values.get("messagesGreetingOnForward", false); return mMessagesGreetingOnForward ?: false } - set(value) { config.set("messagesGreetingOnForward", value); mMessagesGreetingOnForward = value } - - private var mMessagesGreetingText: String? = null - var messagesGreetingText: String? - get() { mMessagesGreetingText = mMessagesGreetingText ?: config.values["messagesGreetingText"]; return mMessagesGreetingText } - set(value) { config.set("messagesGreetingText", value); mMessagesGreetingText = value } + var timetableShowAttendance by base.config(true) + var timetableShowEvents by base.config(true) + var timetableTrimHourRange by base.config(false) + var timetableColorSubjectName by base.config(false) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/db/ConfigDao.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/db/ConfigDao.kt index f55df857..21c6dc7c 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/config/db/ConfigDao.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/db/ConfigDao.kt @@ -17,7 +17,7 @@ interface ConfigDao { @Insert(onConflict = OnConflictStrategy.REPLACE) fun addAll(list: List) - @Query("SELECT * FROM config WHERE profileId = -1") + @Query("SELECT * FROM config") fun getAllNow(): List @Query("SELECT * FROM config WHERE profileId = :profileId") @@ -25,4 +25,4 @@ interface ConfigDao { @Query("DELETE FROM config WHERE profileId = :profileId") fun clear(profileId: Int) -} \ No newline at end of file +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/AppConfigMigrationV3.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/AppConfigMigrationV3.kt index 78a1d8a6..d99de89d 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/AppConfigMigrationV3.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/AppConfigMigrationV3.kt @@ -8,38 +8,35 @@ import android.content.SharedPreferences import com.google.gson.Gson import com.google.gson.reflect.TypeToken import pl.szczodrzynski.edziennik.BuildConfig -import pl.szczodrzynski.edziennik.MainActivity import pl.szczodrzynski.edziennik.config.Config -import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_LIBRUS -import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_MOBIDZIENNIK -import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_VULCAN +import pl.szczodrzynski.edziennik.data.db.enums.LoginType +import pl.szczodrzynski.edziennik.ext.asNavTargetOrNull +import pl.szczodrzynski.edziennik.ui.base.enums.NavTarget import pl.szczodrzynski.edziennik.utils.models.Time import kotlin.math.abs class AppConfigMigrationV3(p: SharedPreferences, config: Config) { - init { config.apply { + init { val s = "app.appConfig" - if (dataVersion < 1) { + config.apply { ui.theme = p.getString("$s.appTheme", null)?.toIntOrNull() ?: 1 sync.enabled = p.getString("$s.registerSyncEnabled", null)?.toBoolean() ?: true sync.interval = p.getString("$s.registerSyncInterval", null)?.toIntOrNull() ?: 3600 val oldButtons = p.getString("$s.miniDrawerButtonIds", null)?.let { str -> str.replace("[\\[\\]]*".toRegex(), "") .split(",\\s?".toRegex()) - .mapNotNull { it.toIntOrNull() } + .mapNotNull { it.toIntOrNull().asNavTargetOrNull() } + .toSet() } - ui.miniMenuButtons = oldButtons ?: listOf( - MainActivity.DRAWER_ITEM_HOME, - MainActivity.DRAWER_ITEM_TIMETABLE, - MainActivity.DRAWER_ITEM_AGENDA, - MainActivity.DRAWER_ITEM_GRADES, - MainActivity.DRAWER_ITEM_MESSAGES, - MainActivity.DRAWER_ITEM_HOMEWORK, - MainActivity.DRAWER_ITEM_SETTINGS + ui.miniMenuButtons = oldButtons ?: setOf( + NavTarget.HOME, + NavTarget.TIMETABLE, + NavTarget.AGENDA, + NavTarget.GRADES, + NavTarget.MESSAGES, + NavTarget.HOMEWORK, + NavTarget.SETTINGS ) - dataVersion = 1 - } - if (dataVersion < 2) { devModePassword = p.getString("$s.devModePassword", null).fix() sync.tokenApp = p.getString("$s.fcmToken", null).fix() timetable.bellSyncMultiplier = p.getString("$s.bellSyncMultiplier", null)?.toIntOrNull() ?: 0 @@ -81,14 +78,13 @@ class AppConfigMigrationV3(p: SharedPreferences, config: Config) { tokens?.forEach { val token = it.value.first when (it.key) { - LOGIN_TYPE_MOBIDZIENNIK -> sync.tokenMobidziennik = token - LOGIN_TYPE_VULCAN -> sync.tokenVulcan = token - LOGIN_TYPE_LIBRUS -> sync.tokenLibrus = token + LoginType.MOBIDZIENNIK.id -> sync.tokenMobidziennik = token + LoginType.VULCAN.id -> sync.tokenVulcan = token + LoginType.LIBRUS.id -> sync.tokenLibrus = token } } - dataVersion = 2 } - }} + } private fun String?.fix(): String? { return this?.replace("\"", "")?.let { if (it == "null") null else it } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/ConfigExtensions.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/ConfigExtensions.kt deleted file mode 100644 index 94bb87e6..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/ConfigExtensions.kt +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (c) Kuba Szczodrzyński 2019-11-27. - */ - -package pl.szczodrzynski.edziennik.config.utils - -import com.google.gson.* -import com.google.gson.reflect.TypeToken -import pl.szczodrzynski.edziennik.config.AbstractConfig -import pl.szczodrzynski.edziennik.config.db.ConfigEntry -import pl.szczodrzynski.edziennik.utils.models.Date -import pl.szczodrzynski.edziennik.utils.models.Time - -private val gson = Gson() - -fun AbstractConfig.set(key: String, value: Int) { - set(key, value.toString()) -} -fun AbstractConfig.set(key: String, value: Boolean) { - set(key, value.toString()) -} -fun AbstractConfig.set(key: String, value: Long) { - set(key, value.toString()) -} -fun AbstractConfig.set(key: String, value: Float) { - set(key, value.toString()) -} -fun AbstractConfig.set(key: String, value: Date?) { - set(key, value?.stringY_m_d) -} -fun AbstractConfig.set(key: String, value: Time?) { - set(key, value?.stringValue) -} -fun AbstractConfig.set(key: String, value: JsonElement?) { - set(key, value?.toString()) -} -fun AbstractConfig.set(key: String, value: List?) { - set(key, value?.let { gson.toJson(it) }) -} -fun AbstractConfig.set(key: String, value: Any?) { - set(key, value?.let { gson.toJson(it) }) -} -fun AbstractConfig.setStringList(key: String, value: List?) { - set(key, value?.let { gson.toJson(it) }) -} -fun AbstractConfig.setIntList(key: String, value: List?) { - set(key, value?.let { gson.toJson(it) }) -} -fun AbstractConfig.setLongList(key: String, value: List?) { - set(key, value?.let { gson.toJson(it) }) -} -fun AbstractConfig.setMap(key: String, value: Map?) { - set(key, value?.let { gson.toJson(it) }) -} - -fun HashMap.get(key: String, default: String?): String? { - return this[key] ?: default -} -fun HashMap.get(key: String, default: Boolean): Boolean { - return this[key]?.toBoolean() ?: default -} -fun HashMap.getBooleanOrNull(key: String): Boolean? { - return this[key]?.toBooleanStrictOrNull() -} -fun HashMap.get(key: String, default: Int): Int { - return this[key]?.toIntOrNull() ?: default -} -fun HashMap.get(key: String, default: Long): Long { - return this[key]?.toLongOrNull() ?: default -} -fun HashMap.get(key: String, default: Float): Float { - return this[key]?.toFloatOrNull() ?: default -} -fun HashMap.get(key: String, default: Date?): Date? { - return this[key]?.let { Date.fromY_m_d(it) } ?: default -} -fun HashMap.get(key: String, default: Time?): Time? { - return this[key]?.let { Time.fromHms(it) } ?: default -} -fun HashMap.get(key: String, default: JsonObject?): JsonObject? { - return this[key]?.let { JsonParser().parse(it)?.asJsonObject } ?: default -} -fun HashMap.get(key: String, default: JsonArray?): JsonArray? { - return this[key]?.let { JsonParser().parse(it)?.asJsonArray } ?: default -} -inline fun HashMap.get(key: String, default: T?): T? { - return this[key]?.let { Gson().fromJson(it, T::class.java) } ?: default -} -/* !!! cannot use mutable list here - modifying it will not update the DB */ -fun HashMap.get(key: String, default: List?, classOfT: Class): List? { - return this[key]?.let { ConfigGsonUtils().deserializeList(gson, it, classOfT) } ?: default -} -fun HashMap.getStringList(key: String, default: List?): List? { - return this[key]?.let { gson.fromJson>(it, object: TypeToken>(){}.type) } ?: default -} -fun HashMap.getIntList(key: String, default: List?): List? { - return this[key]?.let { gson.fromJson>(it, object: TypeToken>(){}.type) } ?: default -} -fun HashMap.getLongList(key: String, default: List?): List? { - return this[key]?.let { gson.fromJson>(it, object: TypeToken>(){}.type) } ?: default -} - -fun HashMap.getFloat(key: String): Float? { - return this[key]?.toFloatOrNull() -} - -fun List.toHashMap(profileId: Int, map: HashMap) { - map.clear() - forEach { - if (it.profileId == profileId) - map[it.key] = it.value - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/ConfigGsonUtils.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/ConfigGsonUtils.kt deleted file mode 100644 index 0b0a634e..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/ConfigGsonUtils.kt +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (c) Kuba Szczodrzyński 2019-12-2. - */ -package pl.szczodrzynski.edziennik.config.utils - -import com.google.gson.Gson -import com.google.gson.JsonParser -import pl.szczodrzynski.edziennik.ext.getInt -import pl.szczodrzynski.edziennik.ui.home.HomeCardModel -import pl.szczodrzynski.edziennik.utils.models.Time - -class ConfigGsonUtils { - @Suppress("UNCHECKED_CAST") - fun deserializeList(gson: Gson, str: String?, classOfT: Class): List { - val json = JsonParser.parseString(str) - val list: MutableList = mutableListOf() - if (!json.isJsonArray) - return list - - json.asJsonArray.forEach { e -> - when (classOfT) { - String::class.java -> { - list += e.asString as T - } - HomeCardModel::class.java -> { - val o = e.asJsonObject - list += HomeCardModel( - o.getInt("profileId", 0), - o.getInt("cardId", 0) - ) as T - } - Time::class.java -> { - val o = e.asJsonObject - list += Time( - o.getInt("hour", 0), - o.getInt("minute", 0), - o.getInt("second", 0) - ) as T - } - } - } - - return list - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/ConfigMigration.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/ConfigMigration.kt index dcb4ab94..f44d6e56 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/ConfigMigration.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/ConfigMigration.kt @@ -5,12 +5,9 @@ package pl.szczodrzynski.edziennik.config.utils import android.content.Context +import androidx.core.content.edit import pl.szczodrzynski.edziennik.App -import pl.szczodrzynski.edziennik.BuildConfig -import pl.szczodrzynski.edziennik.MainActivity import pl.szczodrzynski.edziennik.config.Config -import pl.szczodrzynski.edziennik.ext.HOUR -import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.ORDER_BY_DATE_DESC import pl.szczodrzynski.edziennik.utils.models.Time import kotlin.math.abs @@ -22,74 +19,14 @@ class ConfigMigration(app: App, config: Config) { // migrate appConfig from app version 3.x and lower. // Updates dataVersion to level 2. AppConfigMigrationV3(p, config) - } - - if (dataVersion < 2) { - appVersion = BuildConfig.VERSION_CODE - loginFinished = false - ui.language = null - ui.theme = 1 - ui.appBackground = null - ui.headerBackground = null - ui.miniMenuVisible = false - ui.miniMenuButtons = listOf( - MainActivity.DRAWER_ITEM_HOME, - MainActivity.DRAWER_ITEM_TIMETABLE, - MainActivity.DRAWER_ITEM_AGENDA, - MainActivity.DRAWER_ITEM_GRADES, - MainActivity.DRAWER_ITEM_MESSAGES, - MainActivity.DRAWER_ITEM_HOMEWORK, - MainActivity.DRAWER_ITEM_SETTINGS - ) - sync.enabled = true - sync.interval = 1* HOUR.toInt() - sync.notifyAboutUpdates = true - sync.onlyWifi = false - sync.quietHoursEnabled = false - sync.quietHoursStart = null - sync.quietHoursEnd = null - sync.quietDuringLessons = false - sync.tokenApp = null - sync.tokenMobidziennik = null - sync.tokenMobidziennikList = listOf() - sync.tokenLibrus = null - sync.tokenLibrusList = listOf() - sync.tokenVulcan = null - sync.tokenVulcanList = listOf() - timetable.bellSyncMultiplier = 0 - timetable.bellSyncDiff = null - timetable.countInSeconds = false - grades.orderBy = ORDER_BY_DATE_DESC - - dataVersion = 2 - } - - if (dataVersion < 3) { - update = null - privacyPolicyAccepted = false - devMode = null - devModePassword = null - appInstalledTime = 0L - appRateSnackbarTime = 0L - - dataVersion = 3 - } - - if (dataVersion < 10) { - ui.openDrawerOnBackPressed = false - ui.snowfall = false - ui.bottomSheetOpened = false - sync.dontShowAppManagerDialog = false - sync.webPushEnabled = true - sync.lastAppSync = 0L - - - dataVersion = 10 + p.edit { + remove("app.appConfig.appTheme") + } } if (dataVersion < 11) { - val startMillis = config.values.get("quietHoursStart", 0L) - val endMillis = config.values.get("quietHoursEnd", 0L) + val startMillis = config.values["quietHoursStart"]?.toLongOrNull() ?: 0L + val endMillis = config.values["quietHoursEnd"]?.toLongOrNull() ?: 0L if (startMillis > 0) { try { sync.quietHoursStart = Time.fromMillis(abs(startMillis)) @@ -106,5 +43,7 @@ class ConfigMigration(app: App, config: Config) { dataVersion = 11 } + + hash = "invalid" }} } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/ProfileConfigMigration.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/ProfileConfigMigration.kt index 57177899..ab915b1d 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/ProfileConfigMigration.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/ProfileConfigMigration.kt @@ -5,8 +5,9 @@ package pl.szczodrzynski.edziennik.config.utils import pl.szczodrzynski.edziennik.config.ProfileConfig -import pl.szczodrzynski.edziennik.data.db.entity.Notification import pl.szczodrzynski.edziennik.data.db.entity.Profile.Companion.AGENDA_DEFAULT +import pl.szczodrzynski.edziennik.data.db.enums.NotificationType +import pl.szczodrzynski.edziennik.data.db.enums.SchoolType import pl.szczodrzynski.edziennik.ui.home.HomeCard import pl.szczodrzynski.edziennik.ui.home.HomeCardModel import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.COLOR_MODE_WEIGHTED @@ -15,35 +16,46 @@ import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_AL class ProfileConfigMigration(config: ProfileConfig) { init { config.apply { - if (dataVersion < 1) { - grades.colorMode = COLOR_MODE_WEIGHTED - grades.yearAverageMode = YEAR_ALL_GRADES - grades.hideImproved = false - grades.averageWithoutWeight = true - grades.plusValue = null - grades.minusValue = null - grades.dontCountEnabled = false - grades.dontCountGrades = listOf() - ui.agendaViewType = AGENDA_DEFAULT - // no migration for ui.homeCards - - dataVersion = 1 - } + val profile = db.profileDao().getByIdNow(profileId ?: -1) if (dataVersion < 2) { - sync.notificationFilter = sync.notificationFilter + Notification.TYPE_TEACHER_ABSENCE + sync.notificationFilter = sync.notificationFilter + NotificationType.TEACHER_ABSENCE dataVersion = 2 } if (dataVersion < 3) { if (ui.homeCards.isNotEmpty()) { - ui.homeCards = ui.homeCards.toMutableList().also { - it.add(HomeCardModel(config.profileId, HomeCard.CARD_NOTES)) - } + ui.homeCards = ui.homeCards + HomeCardModel( + profileId = config.profileId ?: -1, + cardId = HomeCard.CARD_NOTES, + ) } dataVersion = 3 } + + if (dataVersion < 4) { + // switch to new event types (USOS) + dataVersion = 4 + + if (profile?.loginStoreType?.schoolType == SchoolType.UNIVERSITY) { + db.eventTypeDao().clear(profileId ?: -1) + db.eventTypeDao().addDefaultTypes(profile) + } + } + + if (dataVersion < 5) { + // update USOS event types and the appropriate events (2022-12-25) + dataVersion = 5 + + if (profile?.loginStoreType?.schoolType == SchoolType.UNIVERSITY) { + db.eventTypeDao().getAllWithDefaults(profile) + // wejściówka (4) -> kartkówka (3) + db.eventDao().getRawNow("UPDATE events SET eventType = 3 WHERE profileId = $profileId AND eventType = 4;") + // zadanie (6) -> zadanie domowe (-1) + db.eventDao().getRawNow("UPDATE events SET eventType = -1 WHERE profileId = $profileId AND eventType = 6;") + } + } }} } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/ApiService.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/ApiService.kt index f68e8840..0d9fe92c 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/ApiService.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/ApiService.kt @@ -84,19 +84,21 @@ class ApiService : Service() { runTask() } + override fun onRequiresUserAction(event: UserActionRequiredEvent) { + app.userActionManager.sendToUser(event) + taskRunning?.cancel() + clearTask() + runTask() + } + override fun onError(apiError: ApiError) { lastEventTime = System.currentTimeMillis() d(TAG, "Task $taskRunningId threw an error - $apiError") apiError.profileId = taskProfileId - if (app.userActionManager.requiresUserAction(apiError)) { - app.userActionManager.sendToUser(apiError) - } - else { - EventBus.getDefault().postSticky(ApiTaskErrorEvent(apiError)) - errorList.add(apiError) - apiError.throwable?.printStackTrace() - } + EventBus.getDefault().postSticky(ApiTaskErrorEvent(apiError)) + errorList.add(apiError) + apiError.throwable?.printStackTrace() if (apiError.isCritical) { taskRunning?.cancel() diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Constants.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Constants.kt index 3df74621..31fabaea 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Constants.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Constants.kt @@ -26,9 +26,10 @@ val LIBRUS_USER_AGENT = "${SYSTEM_USER_AGENT}LibrusMobileApp" const val SYNERGIA_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Gecko/20100101 Firefox/62.0" const val LIBRUS_CLIENT_ID = "VaItV6oRutdo8fnjJwysnTjVlvaswf52ZqmXsJGP" const val LIBRUS_REDIRECT_URL = "app://librus" -const val LIBRUS_AUTHORIZE_URL = "https://portal.librus.pl/oauth2/authorize?client_id=$LIBRUS_CLIENT_ID&redirect_uri=$LIBRUS_REDIRECT_URL&response_type=code" -const val LIBRUS_LOGIN_URL = "https://portal.librus.pl/rodzina/login/action" +const val LIBRUS_AUTHORIZE_URL = "https://portal.librus.pl/konto-librus/redirect/dru" +const val LIBRUS_LOGIN_URL = "https://portal.librus.pl/konto-librus/login/action" const val LIBRUS_TOKEN_URL = "https://portal.librus.pl/oauth2/access_token" +const val LIBRUS_HEADER = "pl.librus.synergiaDru2" const val LIBRUS_ACCOUNT_URL = "/v3/SynergiaAccounts/fresh/" // + login const val LIBRUS_ACCOUNTS_URL = "/v3/SynergiaAccounts" @@ -99,3 +100,12 @@ const val PODLASIE_API_VERSION = "1.0.62" const val PODLASIE_API_URL = "https://cpdklaser.zeto.bialystok.pl/api" const val PODLASIE_API_USER_ENDPOINT = "/pobierzDaneUcznia" const val PODLASIE_API_LOGOUT_DEVICES_ENDPOINT = "/wyczyscUrzadzenia" + +const val USOS_API_OAUTH_REDIRECT_URL = "szkolny://redirect/usos" + +val USOS_API_SCOPES by lazy { listOf( + "offline_access", + "studies", + "grades", + "events", +) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/EndpointChooser.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/EndpointChooser.kt index 7f882123..de834344 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/EndpointChooser.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/EndpointChooser.kt @@ -2,115 +2,116 @@ package pl.szczodrzynski.edziennik.data.api import pl.szczodrzynski.edziennik.data.api.models.Data import pl.szczodrzynski.edziennik.data.api.models.Feature -import pl.szczodrzynski.edziennik.data.api.models.LoginMethod import pl.szczodrzynski.edziennik.data.db.entity.EndpointTimer import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS import pl.szczodrzynski.edziennik.data.db.entity.SYNC_NEVER +import pl.szczodrzynski.edziennik.data.db.enums.FeatureType +import pl.szczodrzynski.edziennik.data.db.enums.LoginMethod +import pl.szczodrzynski.edziennik.ext.getFeatureTypesNecessary +import pl.szczodrzynski.edziennik.ext.getFeatureTypesUnnecessary +import pl.szczodrzynski.edziennik.ext.isNotNullNorEmpty -fun Data.prepare(loginMethods: List, features: List, featureIds: List, viewId: Int?, onlyEndpoints: List?) { - val data = this - - val possibleLoginMethods = data.loginMethods.toMutableList() - - for (loginMethod in loginMethods) { - if (loginMethod.isPossible(profile, loginStore)) - possibleLoginMethods += loginMethod.loginMethodId +fun Data.prepare( + features: List, + featureTypes: Set?, + onlyEndpoints: Set?, +) { + val loginType = this.loginStore.type + val possibleLoginMethods = this.loginMethods.toMutableList() + possibleLoginMethods += LoginMethod.values().filter { + it.loginType == loginType && it.isPossible?.invoke(profile, loginStore) != false } //var highestLoginMethod = 0 - var endpointList = mutableListOf() - val requiredLoginMethods = mutableListOf() + var possibleFeatures = mutableListOf() + val requiredLoginMethods = mutableListOf() - data.targetEndpointIds.clear() - data.targetLoginMethodIds.clear() + val syncFeatureTypes = when { + featureTypes.isNotNullNorEmpty() -> featureTypes!! + else -> getFeatureTypesUnnecessary() + } + getFeatureTypesNecessary() + val forceFeatureType = featureTypes?.singleOrNull() + + this.targetEndpoints.clear() + this.targetLoginMethods.clear() // get all endpoints for every feature, only if possible to login and possible/necessary to sync - for (featureId in featureIds) { - features.filter { - it.featureId == featureId // feature ID matches + for (featureId in syncFeatureTypes) { + possibleFeatures += features.filter { + it.featureType == featureId // feature ID matches && possibleLoginMethods.containsAll(it.requiredLoginMethods) // is possible to login - && it.shouldSync?.invoke(data) ?: true // is necessary/possible to sync - }.let { - endpointList.addAll(it) + && it.shouldSync?.invoke(this) ?: true // is necessary/possible to sync } } val timestamp = System.currentTimeMillis() - endpointList = endpointList - // sort the endpoint list by feature ID and priority - .sortedWith(compareBy(Feature::featureId, Feature::priority)) - // select only the most important endpoint for each feature - .distinctBy { it.featureId } - .toMutableList() - // add all endpoint IDs and required login methods, filtering using timers - .onEach { feature -> - feature.endpointIds.forEach { endpoint -> - if (onlyEndpoints?.contains(endpoint.first) == false) - return@forEach - (data.endpointTimers - .singleOrNull { it.endpointId == endpoint.first } ?: EndpointTimer(data.profile?.id - ?: -1, endpoint.first)) - .let { timer -> - if ( - onlyEndpoints?.contains(endpoint.first) == true || - timer.nextSync == SYNC_ALWAYS || - viewId != null && timer.viewId == viewId || - timer.nextSync != SYNC_NEVER && timer.nextSync < timestamp - ) { - data.targetEndpointIds[endpoint.first] = timer.lastSync - requiredLoginMethods.add(endpoint.second) - } - } - } + possibleFeatures = possibleFeatures + // sort the endpoint list by feature ID and priority + .sortedWith(compareBy(Feature::featureType, Feature::priority)) + // select only the most important endpoint for each feature + .distinctBy { it.featureType } + .toMutableList() + + for (feature in possibleFeatures) { + // add all endpoint IDs and required login methods, filtering using timers + feature.endpoints.forEach { endpoint -> + if (onlyEndpoints?.contains(endpoint.first) == false) + return@forEach + val timer = this.endpointTimers + .singleOrNull { it.endpointId == endpoint.first } + ?: EndpointTimer(this.profileId, endpoint.first) + if ( + onlyEndpoints?.contains(endpoint.first) == true || + timer.nextSync == SYNC_ALWAYS || + forceFeatureType != null && timer.featureType == forceFeatureType || + timer.nextSync != SYNC_NEVER && timer.nextSync < timestamp + ) { + this.targetEndpoints[endpoint.first] = timer.lastSync + requiredLoginMethods += endpoint.second } + } + } // check every login method for any dependencies - for (loginMethodId in requiredLoginMethods) { - var requiredLoginMethod: Int? = loginMethodId - while (requiredLoginMethod != LOGIN_METHOD_NOT_NEEDED) { - loginMethods.singleOrNull { it.loginMethodId == requiredLoginMethod }?.let { loginMethod -> - if (requiredLoginMethod != null) - data.targetLoginMethodIds.add(requiredLoginMethod!!) - requiredLoginMethod = loginMethod.requiredLoginMethod(data.profile, data.loginStore) - } + for (loginMethod in requiredLoginMethods) { + var requiredLoginMethod: LoginMethod? = loginMethod + while (requiredLoginMethod != null) { + this.targetLoginMethods += requiredLoginMethod + requiredLoginMethod = requiredLoginMethod.requiredLoginMethod?.invoke(this.profile, this.loginStore) } } // sort and distinct every login method and endpoint - data.targetLoginMethodIds = data.targetLoginMethodIds.toHashSet().toMutableList() - data.targetLoginMethodIds.sort() + this.targetLoginMethods = this.targetLoginMethods.toHashSet().toMutableList() + this.targetLoginMethods.sort() //data.targetEndpointIds = data.targetEndpointIds.toHashSet().toMutableList() //data.targetEndpointIds.sort() - progressCount = targetLoginMethodIds.size + targetEndpointIds.size + progressCount = targetLoginMethods.size + targetEndpoints.size progressStep = if (progressCount <= 0) 0f else 100f / progressCount.toFloat() } -fun Data.prepareFor(loginMethods: List, loginMethodId: Int) { +fun Data.prepareFor(loginMethod: LoginMethod) { + val loginType = loginStore.type val possibleLoginMethods = this.loginMethods.toMutableList() - - loginMethods.forEach { - if (it.isPossible(profile, loginStore)) - possibleLoginMethods += it.loginMethodId + possibleLoginMethods += LoginMethod.values().filter { + it.loginType == loginType && it.isPossible?.invoke(profile, loginStore) != false } - targetLoginMethodIds.clear() + this.targetLoginMethods.clear() // check the login method for any dependencies - var requiredLoginMethod: Int? = loginMethodId - while (requiredLoginMethod != LOGIN_METHOD_NOT_NEEDED) { - loginMethods.singleOrNull { it.loginMethodId == requiredLoginMethod }?.let { - if (requiredLoginMethod != null) - targetLoginMethodIds.add(requiredLoginMethod!!) - requiredLoginMethod = it.requiredLoginMethod(profile, loginStore) - } + var requiredLoginMethod: LoginMethod? = loginMethod + while (requiredLoginMethod != null) { + this.targetLoginMethods += requiredLoginMethod + requiredLoginMethod = requiredLoginMethod.requiredLoginMethod?.invoke(this.profile, this.loginStore) } // sort and distinct every login method - targetLoginMethodIds = targetLoginMethodIds.toHashSet().toMutableList() - targetLoginMethodIds.sort() + this.targetLoginMethods = this.targetLoginMethods.toHashSet().toMutableList() + this.targetLoginMethods.sort() progressCount = 0 progressStep = 0f diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Errors.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Errors.kt index 227561e6..93a7228f 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Errors.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Errors.kt @@ -58,11 +58,7 @@ const val ERROR_INVALID_LOGIN_MODE = 110 const val ERROR_LOGIN_METHOD_NOT_SATISFIED = 111 const val ERROR_NOT_IMPLEMENTED = 112 const val ERROR_FILE_DOWNLOAD = 113 - -const val ERROR_NO_STUDENTS_IN_ACCOUNT = 115 - -const val ERROR_CAPTCHA_NEEDED = 3000 -const val ERROR_CAPTCHA_LIBRUS_PORTAL = 3001 +const val ERROR_REQUIRES_USER_ACTION = 114 const val ERROR_API_PDO_ERROR = 5000 const val ERROR_API_INVALID_CLIENT = 5001 @@ -204,6 +200,12 @@ const val ERROR_PODLASIE_API_NO_TOKEN = 630 const val ERROR_PODLASIE_API_OTHER = 631 const val ERROR_PODLASIE_API_DATA_MISSING = 632 +const val ERROR_USOS_OAUTH_GOT_DIFFERENT_TOKEN = 702 +const val ERROR_USOS_OAUTH_INCOMPLETE_RESPONSE = 703 +const val ERROR_USOS_NO_STUDENT_PROGRAMMES = 704 +const val ERROR_USOS_API_INCOMPLETE_RESPONSE = 705 +const val ERROR_USOS_API_MISSING_RESPONSE = 706 + const val ERROR_TEMPLATE_WEB_OTHER = 801 const val EXCEPTION_API_TASK = 900 diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Features.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Features.kt deleted file mode 100644 index e38b37b5..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Features.kt +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (c) Kuba Szczodrzyński 2019-9-29. - */ - -package pl.szczodrzynski.edziennik.data.api - -import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_AGENDA -import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_ANNOUNCEMENTS -import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_ATTENDANCE -import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_BEHAVIOUR -import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_GRADES -import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_HOME -import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_HOMEWORK -import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_MESSAGES -import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_TIMETABLE -import pl.szczodrzynski.edziennik.data.db.entity.Message.Companion.TYPE_RECEIVED -import pl.szczodrzynski.edziennik.data.db.entity.Message.Companion.TYPE_SENT - -internal const val FEATURE_TIMETABLE = 1 -internal const val FEATURE_AGENDA = 2 -internal const val FEATURE_GRADES = 3 -internal const val FEATURE_HOMEWORK = 4 -internal const val FEATURE_BEHAVIOUR = 5 -internal const val FEATURE_ATTENDANCE = 6 -internal const val FEATURE_MESSAGES_INBOX = 7 -internal const val FEATURE_MESSAGES_SENT = 8 -internal const val FEATURE_ANNOUNCEMENTS = 9 - -internal const val FEATURE_ALWAYS_NEEDED = 100 -internal const val FEATURE_STUDENT_INFO = 101 -internal const val FEATURE_STUDENT_NUMBER = 109 -internal const val FEATURE_SCHOOL_INFO = 102 -internal const val FEATURE_CLASS_INFO = 103 -internal const val FEATURE_TEAM_INFO = 104 -internal const val FEATURE_LUCKY_NUMBER = 105 -internal const val FEATURE_TEACHERS = 106 -internal const val FEATURE_SUBJECTS = 107 -internal const val FEATURE_CLASSROOMS = 108 -internal const val FEATURE_PUSH_CONFIG = 120 - -object Features { - private fun getAllNecessary(): List = listOf( - FEATURE_ALWAYS_NEEDED, - FEATURE_PUSH_CONFIG, - FEATURE_STUDENT_INFO, - FEATURE_STUDENT_NUMBER, - FEATURE_SCHOOL_INFO, - FEATURE_CLASS_INFO, - FEATURE_TEAM_INFO, - FEATURE_LUCKY_NUMBER, - FEATURE_TEACHERS, - FEATURE_SUBJECTS, - FEATURE_CLASSROOMS) - - private fun getAllFeatures(): List = listOf( - FEATURE_TIMETABLE, - FEATURE_AGENDA, - FEATURE_GRADES, - FEATURE_HOMEWORK, - FEATURE_BEHAVIOUR, - FEATURE_ATTENDANCE, - FEATURE_MESSAGES_INBOX, - FEATURE_MESSAGES_SENT, - FEATURE_ANNOUNCEMENTS) - - fun getAllIds(): List = getAllFeatures() + getAllNecessary() - - fun getIdsByView(targetId: Int, targetType: Int): List { - return (when (targetId) { - DRAWER_ITEM_HOME -> getAllFeatures() - DRAWER_ITEM_TIMETABLE -> listOf(FEATURE_TIMETABLE) - DRAWER_ITEM_AGENDA -> listOf(FEATURE_AGENDA) - DRAWER_ITEM_GRADES -> listOf(FEATURE_GRADES) - DRAWER_ITEM_MESSAGES -> when (targetType) { - TYPE_RECEIVED -> listOf(FEATURE_MESSAGES_INBOX) - TYPE_SENT -> listOf(FEATURE_MESSAGES_SENT) - else -> listOf(FEATURE_MESSAGES_INBOX, FEATURE_MESSAGES_SENT) - } - DRAWER_ITEM_HOMEWORK -> listOf(FEATURE_HOMEWORK) - DRAWER_ITEM_BEHAVIOUR -> listOf(FEATURE_BEHAVIOUR) - DRAWER_ITEM_ATTENDANCE -> listOf(FEATURE_ATTENDANCE) - DRAWER_ITEM_ANNOUNCEMENTS -> listOf(FEATURE_ANNOUNCEMENTS) - else -> getAllFeatures() - } + getAllNecessary()).sorted() - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/LoginMethods.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/LoginMethods.kt deleted file mode 100644 index 0f0c07ae..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/LoginMethods.kt +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright (c) Kuba Szczodrzyński 2019-9-20. - */ - -package pl.szczodrzynski.edziennik.data.api - -import pl.szczodrzynski.edziennik.data.api.edziennik.librus.login.LibrusLoginApi -import pl.szczodrzynski.edziennik.data.api.edziennik.librus.login.LibrusLoginMessages -import pl.szczodrzynski.edziennik.data.api.edziennik.librus.login.LibrusLoginPortal -import pl.szczodrzynski.edziennik.data.api.edziennik.librus.login.LibrusLoginSynergia -import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.login.MobidziennikLoginApi2 -import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.login.MobidziennikLoginWeb -import pl.szczodrzynski.edziennik.data.api.edziennik.podlasie.login.PodlasieLoginApi -import pl.szczodrzynski.edziennik.data.api.edziennik.template.login.TemplateLoginApi -import pl.szczodrzynski.edziennik.data.api.edziennik.template.login.TemplateLoginWeb -import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.login.VulcanLoginHebe -import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.login.VulcanLoginWebMain -import pl.szczodrzynski.edziennik.data.api.models.LoginMethod - -// librus -// mobidziennik -// idziennik [*] -// vulcan -// mobireg - -const val SYNERGIA_API_ENABLED = false - -// the graveyard -const val LOGIN_TYPE_IDZIENNIK = 3 -const val LOGIN_TYPE_EDUDZIENNIK = 5 - -const val LOGIN_TYPE_TEMPLATE = 21 - -// LOGIN MODES -const val LOGIN_MODE_TEMPLATE_WEB = 0 - -// LOGIN METHODS -const val LOGIN_METHOD_NOT_NEEDED = -1 -const val LOGIN_METHOD_TEMPLATE_WEB = 100 -const val LOGIN_METHOD_TEMPLATE_API = 200 - -const val LOGIN_TYPE_LIBRUS = 2 -const val LOGIN_MODE_LIBRUS_EMAIL = 0 -const val LOGIN_MODE_LIBRUS_SYNERGIA = 1 -const val LOGIN_MODE_LIBRUS_JST = 2 -const val LOGIN_METHOD_LIBRUS_PORTAL = 100 -const val LOGIN_METHOD_LIBRUS_API = 200 -const val LOGIN_METHOD_LIBRUS_SYNERGIA = 300 -const val LOGIN_METHOD_LIBRUS_MESSAGES = 400 -val librusLoginMethods = listOf( - LoginMethod(LOGIN_TYPE_LIBRUS, LOGIN_METHOD_LIBRUS_PORTAL, LibrusLoginPortal::class.java) - .withIsPossible { _, loginStore -> - loginStore.mode == LOGIN_MODE_LIBRUS_EMAIL - } - .withRequiredLoginMethod { _, _ -> LOGIN_METHOD_NOT_NEEDED }, - - LoginMethod(LOGIN_TYPE_LIBRUS, LOGIN_METHOD_LIBRUS_API, LibrusLoginApi::class.java) - .withIsPossible { _, loginStore -> - loginStore.mode != LOGIN_MODE_LIBRUS_SYNERGIA || SYNERGIA_API_ENABLED - } - .withRequiredLoginMethod { _, loginStore -> - if (loginStore.mode == LOGIN_MODE_LIBRUS_EMAIL) LOGIN_METHOD_LIBRUS_PORTAL else LOGIN_METHOD_NOT_NEEDED - }, - - LoginMethod(LOGIN_TYPE_LIBRUS, LOGIN_METHOD_LIBRUS_SYNERGIA, LibrusLoginSynergia::class.java) - .withIsPossible { _, loginStore -> !loginStore.hasLoginData("fakeLogin") } - .withRequiredLoginMethod { profile, _ -> - if (profile?.hasStudentData("accountPassword") == false || true) LOGIN_METHOD_LIBRUS_API else LOGIN_METHOD_NOT_NEEDED - }, - - LoginMethod(LOGIN_TYPE_LIBRUS, LOGIN_METHOD_LIBRUS_MESSAGES, LibrusLoginMessages::class.java) - .withIsPossible { _, loginStore -> !loginStore.hasLoginData("fakeLogin") } - .withRequiredLoginMethod { profile, _ -> - if (profile?.hasStudentData("accountPassword") == false || true) LOGIN_METHOD_LIBRUS_SYNERGIA else LOGIN_METHOD_NOT_NEEDED - } -) - -const val LOGIN_TYPE_MOBIDZIENNIK = 1 -const val LOGIN_MODE_MOBIDZIENNIK_WEB = 0 -const val LOGIN_METHOD_MOBIDZIENNIK_WEB = 100 -const val LOGIN_METHOD_MOBIDZIENNIK_API2 = 300 -val mobidziennikLoginMethods = listOf( - LoginMethod(LOGIN_TYPE_MOBIDZIENNIK, LOGIN_METHOD_MOBIDZIENNIK_WEB, MobidziennikLoginWeb::class.java) - .withIsPossible { _, _ -> true } - .withRequiredLoginMethod { _, _ -> LOGIN_METHOD_NOT_NEEDED }, - - LoginMethod(LOGIN_TYPE_MOBIDZIENNIK, LOGIN_METHOD_MOBIDZIENNIK_API2, MobidziennikLoginApi2::class.java) - .withIsPossible { profile, _ -> profile?.getStudentData("email", null) != null } - .withRequiredLoginMethod { _, _ -> LOGIN_METHOD_NOT_NEEDED } -) - -const val LOGIN_TYPE_VULCAN = 4 -const val LOGIN_MODE_VULCAN_API = 0 -const val LOGIN_MODE_VULCAN_WEB = 1 -const val LOGIN_MODE_VULCAN_HEBE = 2 -const val LOGIN_METHOD_VULCAN_WEB_MAIN = 100 -const val LOGIN_METHOD_VULCAN_WEB_NEW = 200 -const val LOGIN_METHOD_VULCAN_WEB_OLD = 300 -const val LOGIN_METHOD_VULCAN_WEB_MESSAGES = 400 -const val LOGIN_METHOD_VULCAN_HEBE = 600 -val vulcanLoginMethods = listOf( - LoginMethod(LOGIN_TYPE_VULCAN, LOGIN_METHOD_VULCAN_WEB_MAIN, VulcanLoginWebMain::class.java) - .withIsPossible { _, loginStore -> loginStore.hasLoginData("webHost") } - .withRequiredLoginMethod { _, _ -> LOGIN_METHOD_NOT_NEEDED }, - - /*LoginMethod(LOGIN_TYPE_VULCAN, LOGIN_METHOD_VULCAN_WEB_NEW, VulcanLoginWebNew::class.java) - .withIsPossible { _, _ -> false } - .withRequiredLoginMethod { _, _ -> LOGIN_METHOD_VULCAN_WEB_MAIN }, - - LoginMethod(LOGIN_TYPE_VULCAN, LOGIN_METHOD_VULCAN_WEB_OLD, VulcanLoginWebOld::class.java) - .withIsPossible { _, _ -> false } - .withRequiredLoginMethod { _, _ -> LOGIN_METHOD_VULCAN_WEB_MAIN },*/ - - LoginMethod(LOGIN_TYPE_VULCAN, LOGIN_METHOD_VULCAN_HEBE, VulcanLoginHebe::class.java) - .withIsPossible { _, loginStore -> - loginStore.mode != LOGIN_MODE_VULCAN_API - } - .withRequiredLoginMethod { _, _ -> LOGIN_METHOD_NOT_NEEDED } -) - -const val LOGIN_TYPE_PODLASIE = 6 -const val LOGIN_MODE_PODLASIE_API = 0 -const val LOGIN_METHOD_PODLASIE_API = 100 -val podlasieLoginMethods = listOf( - LoginMethod(LOGIN_TYPE_PODLASIE, LOGIN_METHOD_PODLASIE_API, PodlasieLoginApi::class.java) - .withIsPossible { _, _ -> true } - .withRequiredLoginMethod { _, _ -> LOGIN_METHOD_NOT_NEEDED } -) - -val templateLoginMethods = listOf( - LoginMethod(LOGIN_TYPE_TEMPLATE, LOGIN_METHOD_TEMPLATE_WEB, TemplateLoginWeb::class.java) - .withIsPossible { _, _ -> true } - .withRequiredLoginMethod { _, _ -> LOGIN_METHOD_NOT_NEEDED }, - - LoginMethod(LOGIN_TYPE_TEMPLATE, LOGIN_METHOD_TEMPLATE_API, TemplateLoginApi::class.java) - .withIsPossible { _, _ -> true } - .withRequiredLoginMethod { _, _ -> LOGIN_METHOD_TEMPLATE_WEB } -) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Regexes.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Regexes.kt index f3d5a297..70388abf 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Regexes.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Regexes.kt @@ -24,6 +24,25 @@ object Regexes { """^\[META:([A-z0-9-&=]+)]""".toRegex() } + val HTML_INPUT_HIDDEN by lazy { + """""".toRegex() + } + val HTML_INPUT_NAME by lazy { + """name="(.+?)"""".toRegex() + } + val HTML_INPUT_VALUE by lazy { + """value="(.+?)"""".toRegex() + } + val HTML_CSRF_TOKEN by lazy { + """name="csrf-token" content="([A-z0-9=+/\-_]+?)"""".toRegex() + } + val HTML_FORM_ACTION by lazy { + """
>? = null, onlyEndpoints: List? = null, arguments: JsonObject? = null) = EdziennikTask(profileId, SyncProfileRequest(viewIds, onlyEndpoints, arguments)) - fun syncProfileList(profileList: List) = EdziennikTask(-1, SyncProfileListRequest(profileList)) + fun syncProfile(profileId: Int, featureTypes: Set? = null, onlyEndpoints: Set? = null, arguments: JsonObject? = null) = EdziennikTask(profileId, SyncProfileRequest(featureTypes, onlyEndpoints, arguments)) + fun syncProfileList(profileList: Set) = EdziennikTask(-1, SyncProfileListRequest(profileList)) fun messageGet(profileId: Int, message: MessageFull) = EdziennikTask(profileId, MessageGetRequest(message)) - fun messageSend(profileId: Int, recipients: List, subject: String, text: String) = EdziennikTask(profileId, MessageSendRequest(recipients, subject, text)) + fun messageSend(profileId: Int, recipients: Set, subject: String, text: String) = EdziennikTask(profileId, MessageSendRequest(recipients, subject, text)) fun announcementsRead(profileId: Int) = EdziennikTask(profileId, AnnouncementsReadRequest()) fun announcementGet(profileId: Int, announcement: AnnouncementFull) = EdziennikTask(profileId, AnnouncementGetRequest(announcement)) fun attachmentGet(profileId: Int, owner: Any, attachmentId: Long, attachmentName: String) = EdziennikTask(profileId, AttachmentGetRequest(owner, attachmentId, attachmentName)) @@ -108,11 +114,12 @@ open class EdziennikTask(override val profileId: Int, val request: Any) : IApiTa } edziennikInterface = when (loginStore.type) { - LOGIN_TYPE_LIBRUS -> Librus(app, profile, loginStore, taskCallback) - LOGIN_TYPE_MOBIDZIENNIK -> Mobidziennik(app, profile, loginStore, taskCallback) - LOGIN_TYPE_VULCAN -> Vulcan(app, profile, loginStore, taskCallback) - LOGIN_TYPE_PODLASIE -> Podlasie(app, profile, loginStore, taskCallback) - LOGIN_TYPE_TEMPLATE -> Template(app, profile, loginStore, taskCallback) + LoginType.LIBRUS -> Librus(app, profile, loginStore, taskCallback) + LoginType.MOBIDZIENNIK -> Mobidziennik(app, profile, loginStore, taskCallback) + LoginType.VULCAN -> Vulcan(app, profile, loginStore, taskCallback) + LoginType.PODLASIE -> Podlasie(app, profile, loginStore, taskCallback) + LoginType.TEMPLATE -> Template(app, profile, loginStore, taskCallback) + LoginType.USOS -> Usos(app, profile, loginStore, taskCallback) else -> null } if (edziennikInterface == null) { @@ -121,9 +128,7 @@ open class EdziennikTask(override val profileId: Int, val request: Any) : IApiTa when (request) { is SyncProfileRequest -> edziennikInterface?.sync( - featureIds = request.viewIds?.flatMap { Features.getIdsByView(it.first, it.second) } - ?: Features.getAllIds(), - viewId = request.viewIds?.get(0)?.first, + featureTypes = request.featureTypes, onlyEndpoints = request.onlyEndpoints, arguments = request.arguments) is MessageGetRequest -> edziennikInterface?.getMessage(request.message) @@ -148,10 +153,10 @@ open class EdziennikTask(override val profileId: Int, val request: Any) : IApiTa data class FirstLoginRequest(val loginStore: LoginStore) class SyncRequest - data class SyncProfileRequest(val viewIds: List>? = null, val onlyEndpoints: List? = null, val arguments: JsonObject? = null) - data class SyncProfileListRequest(val profileList: List) + data class SyncProfileRequest(val featureTypes: Set? = null, val onlyEndpoints: Set? = null, val arguments: JsonObject? = null) + data class SyncProfileListRequest(val profileList: Set) data class MessageGetRequest(val message: MessageFull) - data class MessageSendRequest(val recipients: List, val subject: String, val text: String) + data class MessageSendRequest(val recipients: Set, val subject: String, val text: String) class AnnouncementsReadRequest data class AnnouncementGetRequest(val announcement: AnnouncementFull) data class AttachmentGetRequest(val owner: Any, val attachmentId: Long, val attachmentName: String) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/ProfileArchiver.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/ProfileArchiver.kt index 27c61abc..f4f8db2a 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/ProfileArchiver.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/ProfileArchiver.kt @@ -6,8 +6,8 @@ package pl.szczodrzynski.edziennik.data.api.edziennik import android.content.Intent import pl.szczodrzynski.edziennik.App -import pl.szczodrzynski.edziennik.data.api.* import pl.szczodrzynski.edziennik.data.db.entity.Profile +import pl.szczodrzynski.edziennik.data.db.enums.LoginType import pl.szczodrzynski.edziennik.ext.Intent import pl.szczodrzynski.edziennik.utils.Utils.d import pl.szczodrzynski.edziennik.utils.models.Date @@ -51,35 +51,32 @@ class ProfileArchiver(val app: App, val profile: Profile) { d(TAG, "New profile ID for ${profile.name}: ${profile.id}") when (profile.loginStoreType) { - LOGIN_TYPE_LIBRUS -> { - profile.removeStudentData("isPremium") - profile.removeStudentData("pushDeviceId") - profile.removeStudentData("startPointsSemester1") - profile.removeStudentData("startPointsSemester2") - profile.removeStudentData("enablePointGrades") - profile.removeStudentData("enableDescriptiveGrades") + LoginType.LIBRUS -> { + profile.studentData.remove("isPremium") + profile.studentData.remove("pushDeviceId") + profile.studentData.remove("startPointsSemester1") + profile.studentData.remove("startPointsSemester2") + profile.studentData.remove("enablePointGrades") + profile.studentData.remove("enableDescriptiveGrades") } - LOGIN_TYPE_MOBIDZIENNIK -> { - - } - LOGIN_TYPE_VULCAN -> { + LoginType.MOBIDZIENNIK -> {} + LoginType.VULCAN -> { // DataVulcan.isApiLoginValid() returns false so it will update the semester - profile.removeStudentData("currentSemesterEndDate") - profile.removeStudentData("studentSemesterId") - profile.removeStudentData("studentSemesterNumber") - profile.removeStudentData("semester1Id") - profile.removeStudentData("semester2Id") - profile.removeStudentData("studentClassId") + profile.studentData.remove("currentSemesterEndDate") + profile.studentData.remove("studentSemesterId") + profile.studentData.remove("studentSemesterNumber") + profile.studentData.remove("semester1Id") + profile.studentData.remove("semester2Id") + profile.studentData.remove("studentClassId") } - LOGIN_TYPE_IDZIENNIK -> { - profile.removeStudentData("schoolYearId") - } - LOGIN_TYPE_EDUDZIENNIK -> { - - } - LOGIN_TYPE_PODLASIE -> { - + LoginType.IDZIENNIK -> { + profile.studentData.remove("schoolYearId") } + LoginType.EDUDZIENNIK -> {} + LoginType.PODLASIE -> {} + LoginType.USOS -> {} + LoginType.DEMO -> {} + LoginType.TEMPLATE -> {} } d(TAG, "Processed student data: ${profile.studentData}") diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/DataLibrus.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/DataLibrus.kt index 5d82191d..0702c203 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/DataLibrus.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/DataLibrus.kt @@ -5,15 +5,14 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.librus import pl.szczodrzynski.edziennik.App -import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_LIBRUS_API -import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_LIBRUS_MESSAGES -import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_LIBRUS_PORTAL -import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_LIBRUS_SYNERGIA import pl.szczodrzynski.edziennik.data.api.models.Data import pl.szczodrzynski.edziennik.data.db.entity.LoginStore import pl.szczodrzynski.edziennik.data.db.entity.Profile +import pl.szczodrzynski.edziennik.data.db.enums.LoginMethod import pl.szczodrzynski.edziennik.ext.currentTimeUnix +import pl.szczodrzynski.edziennik.ext.getStudentData import pl.szczodrzynski.edziennik.ext.isNotNullNorEmpty +import pl.szczodrzynski.edziennik.ext.set class DataLibrus(app: App, profile: Profile?, loginStore: LoginStore) : Data(app, profile, loginStore) { @@ -25,15 +24,15 @@ class DataLibrus(app: App, profile: Profile?, loginStore: LoginStore) : Data(app override fun satisfyLoginMethods() { loginMethods.clear() if (isPortalLoginValid()) - loginMethods += LOGIN_METHOD_LIBRUS_PORTAL + loginMethods += LoginMethod.LIBRUS_PORTAL if (isApiLoginValid()) - loginMethods += LOGIN_METHOD_LIBRUS_API + loginMethods += LoginMethod.LIBRUS_API if (isSynergiaLoginValid()) { - loginMethods += LOGIN_METHOD_LIBRUS_SYNERGIA + loginMethods += LoginMethod.LIBRUS_SYNERGIA app.cookieJar.set("synergia.librus.pl", "DZIENNIKSID", synergiaSessionId) } if (isMessagesLoginValid()) { - loginMethods += LOGIN_METHOD_LIBRUS_MESSAGES + loginMethods += LoginMethod.LIBRUS_MESSAGES app.cookieJar.set("wiadomosci.librus.pl", "DZIENNIKSID", messagesSessionId) } } @@ -120,7 +119,7 @@ class DataLibrus(app: App, profile: Profile?, loginStore: LoginStore) : Data(app private var mApiLogin: String? = null var apiLogin: String? get() { mApiLogin = mApiLogin ?: profile?.getStudentData("accountLogin", null); return mApiLogin } - set(value) { profile?.putStudentData("accountLogin", value); mApiLogin = value } + set(value) { profile["accountLogin"] = value; mApiLogin = value } /** * A Synergia password. * Used: for login (API Login Method) in Synergia mode. @@ -129,7 +128,7 @@ class DataLibrus(app: App, profile: Profile?, loginStore: LoginStore) : Data(app private var mApiPassword: String? = null var apiPassword: String? get() { mApiPassword = mApiPassword ?: profile?.getStudentData("accountPassword", null); return mApiPassword } - set(value) { profile?.putStudentData("accountPassword", value); mApiPassword = value } + set(value) { profile["accountPassword"] = value; mApiPassword = value } /** * A JST login Code. @@ -138,7 +137,7 @@ class DataLibrus(app: App, profile: Profile?, loginStore: LoginStore) : Data(app private var mApiCode: String? = null var apiCode: String? get() { mApiCode = mApiCode ?: loginStore.getLoginData("accountCode", null); return mApiCode } - set(value) { profile?.putStudentData("accountCode", value); mApiCode = value } + set(value) { profile["accountCode"] = value; mApiCode = value } /** * A JST login PIN. * Used only during first login in JST mode. @@ -146,7 +145,7 @@ class DataLibrus(app: App, profile: Profile?, loginStore: LoginStore) : Data(app private var mApiPin: String? = null var apiPin: String? get() { mApiPin = mApiPin ?: loginStore.getLoginData("accountPin", null); return mApiPin } - set(value) { profile?.putStudentData("accountPin", value); mApiPin = value } + set(value) { profile["accountPin"] = value; mApiPin = value } /** * A Synergia API access token. @@ -157,7 +156,7 @@ class DataLibrus(app: App, profile: Profile?, loginStore: LoginStore) : Data(app private var mApiAccessToken: String? = null var apiAccessToken: String? get() { mApiAccessToken = mApiAccessToken ?: profile?.getStudentData("accountToken", null); return mApiAccessToken } - set(value) { mApiAccessToken = value; profile?.putStudentData("accountToken", value) ?: return; } + set(value) { mApiAccessToken = value; profile["accountToken"] = value ?: return; } /** * A Synergia API refresh token. * Used when refreshing the [apiAccessToken] in JST, Synergia modes. @@ -165,7 +164,7 @@ class DataLibrus(app: App, profile: Profile?, loginStore: LoginStore) : Data(app private var mApiRefreshToken: String? = null var apiRefreshToken: String? get() { mApiRefreshToken = mApiRefreshToken ?: profile?.getStudentData("accountRefreshToken", null); return mApiRefreshToken } - set(value) { mApiRefreshToken = value; profile?.putStudentData("accountRefreshToken", value) ?: return; } + set(value) { mApiRefreshToken = value; profile["accountRefreshToken"] = value ?: return; } /** * The expiry time for [apiAccessToken], as a UNIX timestamp. * Used when refreshing the [apiAccessToken] in JST, Synergia modes. @@ -174,7 +173,7 @@ class DataLibrus(app: App, profile: Profile?, loginStore: LoginStore) : Data(app private var mApiTokenExpiryTime: Long? = null var apiTokenExpiryTime: Long get() { mApiTokenExpiryTime = mApiTokenExpiryTime ?: profile?.getStudentData("accountTokenTime", 0L); return mApiTokenExpiryTime ?: 0L } - set(value) { mApiTokenExpiryTime = value; profile?.putStudentData("accountTokenTime", value) ?: return; } + set(value) { mApiTokenExpiryTime = value; profile["accountTokenTime"] = value; } /** * A push device ID, generated by Librus when registering @@ -184,7 +183,7 @@ class DataLibrus(app: App, profile: Profile?, loginStore: LoginStore) : Data(app private var mPushDeviceId: Int? = null var pushDeviceId: Int get() { mPushDeviceId = mPushDeviceId ?: profile?.getStudentData("pushDeviceId", 0); return mPushDeviceId ?: 0 } - set(value) { mPushDeviceId = value; profile?.putStudentData("pushDeviceId", value) ?: return; } + set(value) { mPushDeviceId = value; profile["pushDeviceId"] = value; } /* _____ _ / ____| (_) @@ -201,7 +200,7 @@ class DataLibrus(app: App, profile: Profile?, loginStore: LoginStore) : Data(app private var mSynergiaSessionId: String? = null var synergiaSessionId: String? get() { mSynergiaSessionId = mSynergiaSessionId ?: profile?.getStudentData("accountSID", null); return mSynergiaSessionId } - set(value) { profile?.putStudentData("accountSID", value) ?: return; mSynergiaSessionId = value } + set(value) { profile["accountSID"] = value; mSynergiaSessionId = value } /** * The expiry time for [synergiaSessionId], as a UNIX timestamp. * Used in endpoints with Synergia login method. @@ -210,7 +209,7 @@ class DataLibrus(app: App, profile: Profile?, loginStore: LoginStore) : Data(app private var mSynergiaSessionIdExpiryTime: Long? = null var synergiaSessionIdExpiryTime: Long get() { mSynergiaSessionIdExpiryTime = mSynergiaSessionIdExpiryTime ?: profile?.getStudentData("accountSIDTime", 0L); return mSynergiaSessionIdExpiryTime ?: 0L } - set(value) { profile?.putStudentData("accountSIDTime", value) ?: return; mSynergiaSessionIdExpiryTime = value } + set(value) { profile["accountSIDTime"] = value; mSynergiaSessionIdExpiryTime = value } /** @@ -220,7 +219,7 @@ class DataLibrus(app: App, profile: Profile?, loginStore: LoginStore) : Data(app private var mMessagesSessionId: String? = null var messagesSessionId: String? get() { mMessagesSessionId = mMessagesSessionId ?: profile?.getStudentData("messagesSID", null); return mMessagesSessionId } - set(value) { profile?.putStudentData("messagesSID", value) ?: return; mMessagesSessionId = value } + set(value) { profile["messagesSID"] = value; mMessagesSessionId = value } /** * The expiry time for [messagesSessionId], as a UNIX timestamp. * Used in endpoints with Messages login method. @@ -229,7 +228,7 @@ class DataLibrus(app: App, profile: Profile?, loginStore: LoginStore) : Data(app private var mMessagesSessionIdExpiryTime: Long? = null var messagesSessionIdExpiryTime: Long get() { mMessagesSessionIdExpiryTime = mMessagesSessionIdExpiryTime ?: profile?.getStudentData("messagesSIDTime", 0L); return mMessagesSessionIdExpiryTime ?: 0L } - set(value) { profile?.putStudentData("messagesSIDTime", value) ?: return; mMessagesSessionIdExpiryTime = value } + set(value) { profile["messagesSIDTime"] = value; mMessagesSessionIdExpiryTime = value } /* ____ _ _ / __ \| | | | @@ -239,42 +238,42 @@ class DataLibrus(app: App, profile: Profile?, loginStore: LoginStore) : Data(app \____/ \__|_| |_|\___|*/ var isPremium get() = profile?.getStudentData("isPremium", false) ?: false - set(value) { profile?.putStudentData("isPremium", value) } + set(value) { profile["isPremium"] = value } private var mSchoolName: String? = null var schoolName: String? get() { mSchoolName = mSchoolName ?: profile?.getStudentData("schoolName", null); return mSchoolName } - set(value) { profile?.putStudentData("schoolName", value) ?: return; mSchoolName = value } + set(value) { profile["schoolName"] = value; mSchoolName = value } private var mUnitId: Long? = null var unitId: Long get() { mUnitId = mUnitId ?: profile?.getStudentData("unitId", 0L); return mUnitId ?: 0L } - set(value) { profile?.putStudentData("unitId", value) ?: return; mUnitId = value } + set(value) { profile["unitId"] = value; mUnitId = value } private var mStartPointsSemester1: Int? = null var startPointsSemester1: Int get() { mStartPointsSemester1 = mStartPointsSemester1 ?: profile?.getStudentData("startPointsSemester1", 0); return mStartPointsSemester1 ?: 0 } - set(value) { profile?.putStudentData("startPointsSemester1", value) ?: return; mStartPointsSemester1 = value } + set(value) { profile["startPointsSemester1"] = value; mStartPointsSemester1 = value } private var mStartPointsSemester2: Int? = null var startPointsSemester2: Int get() { mStartPointsSemester2 = mStartPointsSemester2 ?: profile?.getStudentData("startPointsSemester2", 0); return mStartPointsSemester2 ?: 0 } - set(value) { profile?.putStudentData("startPointsSemester2", value) ?: return; mStartPointsSemester2 = value } + set(value) { profile["startPointsSemester2"] = value; mStartPointsSemester2 = value } private var mEnablePointGrades: Boolean? = null var enablePointGrades: Boolean get() { mEnablePointGrades = mEnablePointGrades ?: profile?.getStudentData("enablePointGrades", true); return mEnablePointGrades ?: true } - set(value) { profile?.putStudentData("enablePointGrades", value) ?: return; mEnablePointGrades = value } + set(value) { profile["enablePointGrades"] = value; mEnablePointGrades = value } private var mEnableDescriptiveGrades: Boolean? = null var enableDescriptiveGrades: Boolean get() { mEnableDescriptiveGrades = mEnableDescriptiveGrades ?: profile?.getStudentData("enableDescriptiveGrades", true); return mEnableDescriptiveGrades ?: true } - set(value) { profile?.putStudentData("enableDescriptiveGrades", value) ?: return; mEnableDescriptiveGrades = value } + set(value) { profile["enableDescriptiveGrades"] = value; mEnableDescriptiveGrades = value } private var mTimetableNotPublic: Boolean? = null var timetableNotPublic: Boolean get() { mTimetableNotPublic = mTimetableNotPublic ?: profile?.getStudentData("timetableNotPublic", false); return mTimetableNotPublic ?: false } - set(value) { profile?.putStudentData("timetableNotPublic", value) ?: return; mTimetableNotPublic = value } + set(value) { profile["timetableNotPublic"] = value; mTimetableNotPublic = value } /** * Set to false when Recaptcha helper doesn't provide a working token. diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/Librus.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/Librus.kt index abf3ac5f..5b29b9fe 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/Librus.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/Librus.kt @@ -16,6 +16,7 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.messages.Librus import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.synergia.* import pl.szczodrzynski.edziennik.data.api.edziennik.librus.firstlogin.LibrusFirstLogin import pl.szczodrzynski.edziennik.data.api.edziennik.librus.login.LibrusLogin +import pl.szczodrzynski.edziennik.data.api.events.UserActionRequiredEvent import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikCallback import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikInterface import pl.szczodrzynski.edziennik.data.api.models.ApiError @@ -23,6 +24,8 @@ import pl.szczodrzynski.edziennik.data.db.entity.LoginStore import pl.szczodrzynski.edziennik.data.db.entity.Message import pl.szczodrzynski.edziennik.data.db.entity.Profile import pl.szczodrzynski.edziennik.data.db.entity.Teacher +import pl.szczodrzynski.edziennik.data.db.enums.FeatureType +import pl.szczodrzynski.edziennik.data.db.enums.LoginMethod import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull import pl.szczodrzynski.edziennik.data.db.full.EventFull import pl.szczodrzynski.edziennik.data.db.full.MessageFull @@ -57,19 +60,19 @@ class Librus(val app: App, val profile: Profile?, val loginStore: LoginStore, va |_| |_| |_|\___| /_/ \_\_|\__, |\___/|_| |_|\__|_| |_|_| |_| |_| __/ | |__*/ - override fun sync(featureIds: List, viewId: Int?, onlyEndpoints: List?, arguments: JsonObject?) { + override fun sync(featureTypes: Set?, onlyEndpoints: Set?, arguments: JsonObject?) { data.arguments = arguments - data.prepare(librusLoginMethods, LibrusFeatures, featureIds, viewId, onlyEndpoints) + data.prepare(LibrusFeatures, featureTypes, onlyEndpoints) login() } - private fun login(loginMethodId: Int? = null, afterLogin: (() -> Unit)? = null) { - d(TAG, "Trying to login with ${data.targetLoginMethodIds}") + private fun login(loginMethod: LoginMethod? = null, afterLogin: (() -> Unit)? = null) { + d(TAG, "Trying to login with ${data.targetLoginMethods}") if (internalErrorList.isNotEmpty()) { d(TAG, " - Internal errors:") internalErrorList.forEach { d(TAG, " - code $it") } } - loginMethodId?.let { data.prepareFor(librusLoginMethods, it) } + loginMethod?.let { data.prepareFor(it) } afterLogin?.let { this.afterLogin = it } LibrusLogin(data) { data() @@ -77,7 +80,7 @@ class Librus(val app: App, val profile: Profile?, val loginStore: LoginStore, va } private fun data() { - d(TAG, "Endpoint IDs: ${data.targetEndpointIds}") + d(TAG, "Endpoint IDs: ${data.targetEndpoints}") if (internalErrorList.isNotEmpty()) { d(TAG, " - Internal errors:") internalErrorList.forEach { d(TAG, " - code $it") } @@ -88,14 +91,14 @@ class Librus(val app: App, val profile: Profile?, val loginStore: LoginStore, va } override fun getMessage(message: MessageFull) { - login(LOGIN_METHOD_LIBRUS_MESSAGES) { + login(LoginMethod.LIBRUS_MESSAGES) { if (data.messagesLoginSuccessful) LibrusMessagesGetMessage(data, message) { completed() } else LibrusSynergiaGetMessage(data, message) { completed() } } } - override fun sendMessage(recipients: List, subject: String, text: String) { - login(LOGIN_METHOD_LIBRUS_MESSAGES) { + override fun sendMessage(recipients: Set, subject: String, text: String) { + login(LoginMethod.LIBRUS_MESSAGES) { LibrusMessagesSendMessage(data, recipients, subject, text) { completed() } @@ -103,7 +106,7 @@ class Librus(val app: App, val profile: Profile?, val loginStore: LoginStore, va } override fun markAllAnnouncementsAsRead() { - login(LOGIN_METHOD_LIBRUS_SYNERGIA) { + login(LoginMethod.LIBRUS_SYNERGIA) { LibrusSynergiaMarkAllAnnouncementsAsRead(data) { completed() } @@ -111,7 +114,7 @@ class Librus(val app: App, val profile: Profile?, val loginStore: LoginStore, va } override fun getAnnouncement(announcement: AnnouncementFull) { - login(LOGIN_METHOD_LIBRUS_API) { + login(LoginMethod.LIBRUS_API) { LibrusApiAnnouncementMarkAsRead(data, announcement) { completed() } @@ -121,13 +124,13 @@ class Librus(val app: App, val profile: Profile?, val loginStore: LoginStore, va override fun getAttachment(owner: Any, attachmentId: Long, attachmentName: String) { when (owner) { is Message -> { - login(LOGIN_METHOD_LIBRUS_SYNERGIA) { + login(LoginMethod.LIBRUS_SYNERGIA) { if (data.messagesLoginSuccessful) LibrusMessagesGetAttachment(data, owner, attachmentId, attachmentName) { completed() } LibrusSynergiaGetAttachment(data, owner, attachmentId, attachmentName) { completed() } } } is EventFull -> { - login(LOGIN_METHOD_LIBRUS_SYNERGIA) { + login(LoginMethod.LIBRUS_SYNERGIA) { LibrusSynergiaHomeworkGetAttachment(data, owner, attachmentId, attachmentName) { completed() } @@ -138,7 +141,7 @@ class Librus(val app: App, val profile: Profile?, val loginStore: LoginStore, va } override fun getRecipientList() { - login(LOGIN_METHOD_LIBRUS_MESSAGES) { + login(LoginMethod.LIBRUS_MESSAGES) { LibrusMessagesGetRecipientList(data) { completed() } @@ -146,7 +149,7 @@ class Librus(val app: App, val profile: Profile?, val loginStore: LoginStore, va } override fun getEvent(eventFull: EventFull) { - login(LOGIN_METHOD_LIBRUS_SYNERGIA) { + login(LoginMethod.LIBRUS_SYNERGIA) { LibrusSynergiaGetHomework(data, eventFull) { completed() } @@ -162,6 +165,7 @@ class Librus(val app: App, val profile: Profile?, val loginStore: LoginStore, va private fun wrapCallback(callback: EdziennikCallback): EdziennikCallback { return object : EdziennikCallback { override fun onCompleted() { callback.onCompleted() } + override fun onRequiresUserAction(event: UserActionRequiredEvent) { callback.onRequiresUserAction(event) } override fun onProgress(step: Float) { callback.onProgress(step) } override fun onStartProgress(stringRes: Int) { callback.onStartProgress(stringRes) } override fun onError(apiError: ApiError) { @@ -173,27 +177,27 @@ class Librus(val app: App, val profile: Profile?, val loginStore: LoginStore, va internalErrorList.add(apiError.errorCode) when (apiError.errorCode) { ERROR_LIBRUS_PORTAL_ACCESS_DENIED -> { - data.loginMethods.remove(LOGIN_METHOD_LIBRUS_PORTAL) - data.prepareFor(librusLoginMethods, LOGIN_METHOD_LIBRUS_PORTAL) + data.loginMethods.remove(LoginMethod.LIBRUS_PORTAL) + data.prepareFor(LoginMethod.LIBRUS_PORTAL) data.portalTokenExpiryTime = 0 login() } ERROR_LIBRUS_API_ACCESS_DENIED, ERROR_LIBRUS_API_TOKEN_EXPIRED -> { - data.loginMethods.remove(LOGIN_METHOD_LIBRUS_API) - data.prepareFor(librusLoginMethods, LOGIN_METHOD_LIBRUS_API) + data.loginMethods.remove(LoginMethod.LIBRUS_API) + data.prepareFor(LoginMethod.LIBRUS_API) data.apiTokenExpiryTime = 0 login() } ERROR_LIBRUS_SYNERGIA_ACCESS_DENIED -> { - data.loginMethods.remove(LOGIN_METHOD_LIBRUS_SYNERGIA) - data.prepareFor(librusLoginMethods, LOGIN_METHOD_LIBRUS_SYNERGIA) + data.loginMethods.remove(LoginMethod.LIBRUS_SYNERGIA) + data.prepareFor(LoginMethod.LIBRUS_SYNERGIA) data.synergiaSessionIdExpiryTime = 0 login() } ERROR_LIBRUS_MESSAGES_ACCESS_DENIED -> { - data.loginMethods.remove(LOGIN_METHOD_LIBRUS_MESSAGES) - data.prepareFor(librusLoginMethods, LOGIN_METHOD_LIBRUS_MESSAGES) + data.loginMethods.remove(LoginMethod.LIBRUS_MESSAGES) + data.prepareFor(LoginMethod.LIBRUS_MESSAGES) data.messagesSessionIdExpiryTime = 0 login() } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/LibrusFeatures.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/LibrusFeatures.kt index c14a00df..eaa05b99 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/LibrusFeatures.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/LibrusFeatures.kt @@ -4,8 +4,10 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.librus -import pl.szczodrzynski.edziennik.data.api.* import pl.szczodrzynski.edziennik.data.api.models.Feature +import pl.szczodrzynski.edziennik.data.db.enums.FeatureType +import pl.szczodrzynski.edziennik.data.db.enums.LoginMethod +import pl.szczodrzynski.edziennik.data.db.enums.LoginType const val ENDPOINT_LIBRUS_API_ME = 1001 const val ENDPOINT_LIBRUS_API_SCHOOLS = 1002 @@ -58,14 +60,14 @@ const val ENDPOINT_LIBRUS_MESSAGES_TRASH = 3030 val LibrusFeatures = listOf( - Feature(LOGIN_TYPE_LIBRUS, FEATURE_ALWAYS_NEEDED, listOf( - ENDPOINT_LIBRUS_API_LESSONS to LOGIN_METHOD_LIBRUS_API - ), listOf(LOGIN_METHOD_LIBRUS_API)), + Feature(LoginType.LIBRUS, FeatureType.ALWAYS_NEEDED, listOf( + ENDPOINT_LIBRUS_API_LESSONS to LoginMethod.LIBRUS_API + )), // push config - Feature(LOGIN_TYPE_LIBRUS, FEATURE_PUSH_CONFIG, listOf( - ENDPOINT_LIBRUS_API_PUSH_CONFIG to LOGIN_METHOD_LIBRUS_API - ), listOf(LOGIN_METHOD_LIBRUS_API)).withShouldSync { data -> + Feature(LoginType.LIBRUS, FeatureType.PUSH_CONFIG, listOf( + ENDPOINT_LIBRUS_API_PUSH_CONFIG to LoginMethod.LIBRUS_API + )).withShouldSync { data -> (data as DataLibrus).isPremium && !data.app.config.sync.tokenLibrusList.contains(data.profileId) }, @@ -76,72 +78,72 @@ val LibrusFeatures = listOf( /** * Timetable - using API. */ - Feature(LOGIN_TYPE_LIBRUS, FEATURE_TIMETABLE, listOf( - ENDPOINT_LIBRUS_API_TIMETABLES to LOGIN_METHOD_LIBRUS_API, - ENDPOINT_LIBRUS_API_SUBSTITUTIONS to LOGIN_METHOD_LIBRUS_API - ), listOf(LOGIN_METHOD_LIBRUS_API)), + Feature(LoginType.LIBRUS, FeatureType.TIMETABLE, listOf( + ENDPOINT_LIBRUS_API_TIMETABLES to LoginMethod.LIBRUS_API, + ENDPOINT_LIBRUS_API_SUBSTITUTIONS to LoginMethod.LIBRUS_API + )), /** * Agenda - using API. * Events, Parent-teacher meetings, free days (teacher/school/class). */ - Feature(LOGIN_TYPE_LIBRUS, FEATURE_AGENDA, listOf( - ENDPOINT_LIBRUS_API_EVENTS to LOGIN_METHOD_LIBRUS_API, - ENDPOINT_LIBRUS_API_EVENT_TYPES to LOGIN_METHOD_LIBRUS_API, - ENDPOINT_LIBRUS_API_PT_MEETINGS to LOGIN_METHOD_LIBRUS_API, - ENDPOINT_LIBRUS_API_TEACHER_FREE_DAY_TYPES to LOGIN_METHOD_LIBRUS_API, - ENDPOINT_LIBRUS_API_TEACHER_FREE_DAYS to LOGIN_METHOD_LIBRUS_API, - ENDPOINT_LIBRUS_API_SCHOOL_FREE_DAYS to LOGIN_METHOD_LIBRUS_API, - ENDPOINT_LIBRUS_API_CLASS_FREE_DAYS to LOGIN_METHOD_LIBRUS_API - ), listOf(LOGIN_METHOD_LIBRUS_API)), + Feature(LoginType.LIBRUS, FeatureType.AGENDA, listOf( + ENDPOINT_LIBRUS_API_EVENTS to LoginMethod.LIBRUS_API, + ENDPOINT_LIBRUS_API_EVENT_TYPES to LoginMethod.LIBRUS_API, + ENDPOINT_LIBRUS_API_PT_MEETINGS to LoginMethod.LIBRUS_API, + ENDPOINT_LIBRUS_API_TEACHER_FREE_DAY_TYPES to LoginMethod.LIBRUS_API, + ENDPOINT_LIBRUS_API_TEACHER_FREE_DAYS to LoginMethod.LIBRUS_API, + ENDPOINT_LIBRUS_API_SCHOOL_FREE_DAYS to LoginMethod.LIBRUS_API, + ENDPOINT_LIBRUS_API_CLASS_FREE_DAYS to LoginMethod.LIBRUS_API + )), /** * Grades - using API. * All grades + categories. */ - Feature(LOGIN_TYPE_LIBRUS, FEATURE_GRADES, listOf( - ENDPOINT_LIBRUS_API_NORMAL_GRADE_CATEGORIES to LOGIN_METHOD_LIBRUS_API, - ENDPOINT_LIBRUS_API_POINT_GRADE_CATEGORIES to LOGIN_METHOD_LIBRUS_API, - ENDPOINT_LIBRUS_API_DESCRIPTIVE_GRADE_CATEGORIES to LOGIN_METHOD_LIBRUS_API, + Feature(LoginType.LIBRUS, FeatureType.GRADES, listOf( + ENDPOINT_LIBRUS_API_NORMAL_GRADE_CATEGORIES to LoginMethod.LIBRUS_API, + ENDPOINT_LIBRUS_API_POINT_GRADE_CATEGORIES to LoginMethod.LIBRUS_API, + ENDPOINT_LIBRUS_API_DESCRIPTIVE_GRADE_CATEGORIES to LoginMethod.LIBRUS_API, // Commented out, because TextGrades/Categories is the same as Grades/Categories - /* ENDPOINT_LIBRUS_API_TEXT_GRADE_CATEGORIES to LOGIN_METHOD_LIBRUS_API, */ - ENDPOINT_LIBRUS_API_DESCRIPTIVE_TEXT_GRADE_CATEGORIES to LOGIN_METHOD_LIBRUS_API, - ENDPOINT_LIBRUS_API_BEHAVIOUR_GRADE_CATEGORIES to LOGIN_METHOD_LIBRUS_API, - ENDPOINT_LIBRUS_API_NORMAL_GRADE_COMMENTS to LOGIN_METHOD_LIBRUS_API, - ENDPOINT_LIBRUS_API_BEHAVIOUR_GRADE_COMMENTS to LOGIN_METHOD_LIBRUS_API, - ENDPOINT_LIBRUS_API_NORMAL_GRADES to LOGIN_METHOD_LIBRUS_API, - ENDPOINT_LIBRUS_API_POINT_GRADES to LOGIN_METHOD_LIBRUS_API, - ENDPOINT_LIBRUS_API_DESCRIPTIVE_GRADES to LOGIN_METHOD_LIBRUS_API, - ENDPOINT_LIBRUS_API_TEXT_GRADES to LOGIN_METHOD_LIBRUS_API, - ENDPOINT_LIBRUS_API_DESCRIPTIVE_TEXT_GRADES to LOGIN_METHOD_LIBRUS_API, - ENDPOINT_LIBRUS_API_BEHAVIOUR_GRADES to LOGIN_METHOD_LIBRUS_API - ), listOf(LOGIN_METHOD_LIBRUS_API)), + /* ENDPOINT_LIBRUS_API_TEXT_GRADE_CATEGORIES to LoginMethod.LIBRUS_API, */ + ENDPOINT_LIBRUS_API_DESCRIPTIVE_TEXT_GRADE_CATEGORIES to LoginMethod.LIBRUS_API, + ENDPOINT_LIBRUS_API_BEHAVIOUR_GRADE_CATEGORIES to LoginMethod.LIBRUS_API, + ENDPOINT_LIBRUS_API_NORMAL_GRADE_COMMENTS to LoginMethod.LIBRUS_API, + ENDPOINT_LIBRUS_API_BEHAVIOUR_GRADE_COMMENTS to LoginMethod.LIBRUS_API, + ENDPOINT_LIBRUS_API_NORMAL_GRADES to LoginMethod.LIBRUS_API, + ENDPOINT_LIBRUS_API_POINT_GRADES to LoginMethod.LIBRUS_API, + ENDPOINT_LIBRUS_API_DESCRIPTIVE_GRADES to LoginMethod.LIBRUS_API, + ENDPOINT_LIBRUS_API_TEXT_GRADES to LoginMethod.LIBRUS_API, + ENDPOINT_LIBRUS_API_DESCRIPTIVE_TEXT_GRADES to LoginMethod.LIBRUS_API, + ENDPOINT_LIBRUS_API_BEHAVIOUR_GRADES to LoginMethod.LIBRUS_API + )), /** * Homework - using API. * Sync only if account has premium access. */ - /*Feature(LOGIN_TYPE_LIBRUS, FEATURE_HOMEWORK, listOf( - ENDPOINT_LIBRUS_API_HOMEWORK to LOGIN_METHOD_LIBRUS_API - ), listOf(LOGIN_METHOD_LIBRUS_API)).withShouldSync { data -> + /*Feature(LoginType.LIBRUS, FeatureType.HOMEWORK, listOf( + ENDPOINT_LIBRUS_API_HOMEWORK to LoginMethod.LIBRUS_API + )).withShouldSync { data -> (data as DataLibrus).isPremium },*/ /** * Behaviour - using API. */ - Feature(LOGIN_TYPE_LIBRUS, FEATURE_BEHAVIOUR, listOf( - ENDPOINT_LIBRUS_API_NOTICES to LOGIN_METHOD_LIBRUS_API - ), listOf(LOGIN_METHOD_LIBRUS_API)), + Feature(LoginType.LIBRUS, FeatureType.BEHAVIOUR, listOf( + ENDPOINT_LIBRUS_API_NOTICES to LoginMethod.LIBRUS_API + )), /** * Attendance - using API. */ - Feature(LOGIN_TYPE_LIBRUS, FEATURE_ATTENDANCE, listOf( - ENDPOINT_LIBRUS_API_ATTENDANCE_TYPES to LOGIN_METHOD_LIBRUS_API, - ENDPOINT_LIBRUS_API_ATTENDANCES to LOGIN_METHOD_LIBRUS_API - ), listOf(LOGIN_METHOD_LIBRUS_API)), + Feature(LoginType.LIBRUS, FeatureType.ATTENDANCE, listOf( + ENDPOINT_LIBRUS_API_ATTENDANCE_TYPES to LoginMethod.LIBRUS_API, + ENDPOINT_LIBRUS_API_ATTENDANCES to LoginMethod.LIBRUS_API + )), /** * Announcements - using API. */ - Feature(LOGIN_TYPE_LIBRUS, FEATURE_ANNOUNCEMENTS, listOf( - ENDPOINT_LIBRUS_API_ANNOUNCEMENTS to LOGIN_METHOD_LIBRUS_API - ), listOf(LOGIN_METHOD_LIBRUS_API)), + Feature(LoginType.LIBRUS, FeatureType.ANNOUNCEMENTS, listOf( + ENDPOINT_LIBRUS_API_ANNOUNCEMENTS to LoginMethod.LIBRUS_API + )), @@ -150,99 +152,99 @@ val LibrusFeatures = listOf( /** * Student info - using API. */ - Feature(LOGIN_TYPE_LIBRUS, FEATURE_STUDENT_INFO, listOf( - ENDPOINT_LIBRUS_API_ME to LOGIN_METHOD_LIBRUS_API - ), listOf(LOGIN_METHOD_LIBRUS_API)), + Feature(LoginType.LIBRUS, FeatureType.STUDENT_INFO, listOf( + ENDPOINT_LIBRUS_API_ME to LoginMethod.LIBRUS_API + )), /** * School info - using API. */ - Feature(LOGIN_TYPE_LIBRUS, FEATURE_SCHOOL_INFO, listOf( - ENDPOINT_LIBRUS_API_SCHOOLS to LOGIN_METHOD_LIBRUS_API, - ENDPOINT_LIBRUS_API_UNITS to LOGIN_METHOD_LIBRUS_API - ), listOf(LOGIN_METHOD_LIBRUS_API)), + Feature(LoginType.LIBRUS, FeatureType.SCHOOL_INFO, listOf( + ENDPOINT_LIBRUS_API_SCHOOLS to LoginMethod.LIBRUS_API, + ENDPOINT_LIBRUS_API_UNITS to LoginMethod.LIBRUS_API + )), /** * Class info - using API. */ - Feature(LOGIN_TYPE_LIBRUS, FEATURE_CLASS_INFO, listOf( - ENDPOINT_LIBRUS_API_CLASSES to LOGIN_METHOD_LIBRUS_API - ), listOf(LOGIN_METHOD_LIBRUS_API)), + Feature(LoginType.LIBRUS, FeatureType.CLASS_INFO, listOf( + ENDPOINT_LIBRUS_API_CLASSES to LoginMethod.LIBRUS_API + )), /** * Team info - using API. */ - Feature(LOGIN_TYPE_LIBRUS, FEATURE_TEAM_INFO, listOf( - ENDPOINT_LIBRUS_API_VIRTUAL_CLASSES to LOGIN_METHOD_LIBRUS_API - ), listOf(LOGIN_METHOD_LIBRUS_API)), + Feature(LoginType.LIBRUS, FeatureType.TEAM_INFO, listOf( + ENDPOINT_LIBRUS_API_VIRTUAL_CLASSES to LoginMethod.LIBRUS_API + )), /** * Lucky number - using API. */ - Feature(LOGIN_TYPE_LIBRUS, FEATURE_LUCKY_NUMBER, listOf( - ENDPOINT_LIBRUS_API_LUCKY_NUMBER to LOGIN_METHOD_LIBRUS_API - ), listOf(LOGIN_METHOD_LIBRUS_API)).withShouldSync { data -> data.shouldSyncLuckyNumber() }, + Feature(LoginType.LIBRUS, FeatureType.LUCKY_NUMBER, listOf( + ENDPOINT_LIBRUS_API_LUCKY_NUMBER to LoginMethod.LIBRUS_API + )).withShouldSync { data -> data.shouldSyncLuckyNumber() }, /** * Teacher list - using API. */ - Feature(LOGIN_TYPE_LIBRUS, FEATURE_TEACHERS, listOf( - ENDPOINT_LIBRUS_API_USERS to LOGIN_METHOD_LIBRUS_API - ), listOf(LOGIN_METHOD_LIBRUS_API)), + Feature(LoginType.LIBRUS, FeatureType.TEACHERS, listOf( + ENDPOINT_LIBRUS_API_USERS to LoginMethod.LIBRUS_API + )), /** * Subject list - using API. */ - Feature(LOGIN_TYPE_LIBRUS, FEATURE_SUBJECTS, listOf( - ENDPOINT_LIBRUS_API_SUBJECTS to LOGIN_METHOD_LIBRUS_API - ), listOf(LOGIN_METHOD_LIBRUS_API)), + Feature(LoginType.LIBRUS, FeatureType.SUBJECTS, listOf( + ENDPOINT_LIBRUS_API_SUBJECTS to LoginMethod.LIBRUS_API + )), /** * Classroom list - using API. */ - Feature(LOGIN_TYPE_LIBRUS, FEATURE_CLASSROOMS, listOf( - ENDPOINT_LIBRUS_API_CLASSROOMS to LOGIN_METHOD_LIBRUS_API - ), listOf(LOGIN_METHOD_LIBRUS_API)), + Feature(LoginType.LIBRUS, FeatureType.CLASSROOMS, listOf( + ENDPOINT_LIBRUS_API_CLASSROOMS to LoginMethod.LIBRUS_API + )), /** * Student info - using synergia scrapper. */ - Feature(LOGIN_TYPE_LIBRUS, FEATURE_STUDENT_INFO, listOf( - ENDPOINT_LIBRUS_SYNERGIA_INFO to LOGIN_METHOD_LIBRUS_SYNERGIA - ), listOf(LOGIN_METHOD_LIBRUS_SYNERGIA)), + Feature(LoginType.LIBRUS, FeatureType.STUDENT_INFO, listOf( + ENDPOINT_LIBRUS_SYNERGIA_INFO to LoginMethod.LIBRUS_SYNERGIA + )), /** * Student number - using synergia scrapper. */ - Feature(LOGIN_TYPE_LIBRUS, FEATURE_STUDENT_NUMBER, listOf( - ENDPOINT_LIBRUS_SYNERGIA_INFO to LOGIN_METHOD_LIBRUS_SYNERGIA - ), listOf(LOGIN_METHOD_LIBRUS_SYNERGIA)), + Feature(LoginType.LIBRUS, FeatureType.STUDENT_NUMBER, listOf( + ENDPOINT_LIBRUS_SYNERGIA_INFO to LoginMethod.LIBRUS_SYNERGIA + )), /** * Grades - using API + synergia scrapper. */ - /*Feature(LOGIN_TYPE_LIBRUS, FEATURE_GRADES, listOf( - ENDPOINT_LIBRUS_API_NORMAL_GC to LOGIN_METHOD_LIBRUS_API, - ENDPOINT_LIBRUS_API_NORMAL_GRADES to LOGIN_METHOD_LIBRUS_API, - ENDPOINT_LIBRUS_SYNERGIA_GRADES to LOGIN_METHOD_LIBRUS_SYNERGIA - ), listOf(LOGIN_METHOD_LIBRUS_API, LOGIN_METHOD_LIBRUS_SYNERGIA)),*/ - /*Endpoint(LOGIN_TYPE_LIBRUS, FEATURE_GRADES, listOf( - ENDPOINT_LIBRUS_SYNERGIA_GRADES to LOGIN_METHOD_LIBRUS_SYNERGIA - ), listOf(LOGIN_METHOD_LIBRUS_SYNERGIA)),*/ + /*Feature(LoginType.LIBRUS, FeatureType.GRADES, listOf( + ENDPOINT_LIBRUS_API_NORMAL_GC to LoginMethod.LIBRUS_API, + ENDPOINT_LIBRUS_API_NORMAL_GRADES to LoginMethod.LIBRUS_API, + ENDPOINT_LIBRUS_SYNERGIA_GRADES to LoginMethod.LIBRUS_SYNERGIA + )),*/ + /*Endpoint(LoginType.LIBRUS, FeatureType.GRADES, listOf( + ENDPOINT_LIBRUS_SYNERGIA_GRADES to LoginMethod.LIBRUS_SYNERGIA + )),*/ /** * Homework - using scrapper. * Sync only if account has not premium access. */ - Feature(LOGIN_TYPE_LIBRUS, FEATURE_HOMEWORK, listOf( - ENDPOINT_LIBRUS_SYNERGIA_HOMEWORK to LOGIN_METHOD_LIBRUS_SYNERGIA - ), listOf(LOGIN_METHOD_LIBRUS_SYNERGIA))/*.withShouldSync { data -> + Feature(LoginType.LIBRUS, FeatureType.HOMEWORK, listOf( + ENDPOINT_LIBRUS_SYNERGIA_HOMEWORK to LoginMethod.LIBRUS_SYNERGIA + ))/*.withShouldSync { data -> !(data as DataLibrus).isPremium }*/, /** * Messages inbox - using messages website. */ - Feature(LOGIN_TYPE_LIBRUS, FEATURE_MESSAGES_INBOX, listOf( - ENDPOINT_LIBRUS_MESSAGES_RECEIVED to LOGIN_METHOD_LIBRUS_MESSAGES - ), listOf(LOGIN_METHOD_LIBRUS_MESSAGES)), + Feature(LoginType.LIBRUS, FeatureType.MESSAGES_INBOX, listOf( + ENDPOINT_LIBRUS_MESSAGES_RECEIVED to LoginMethod.LIBRUS_MESSAGES + )), /** * Messages sent - using messages website. */ - Feature(LOGIN_TYPE_LIBRUS, FEATURE_MESSAGES_SENT, listOf( - ENDPOINT_LIBRUS_MESSAGES_SENT to LOGIN_METHOD_LIBRUS_MESSAGES - ), listOf(LOGIN_METHOD_LIBRUS_MESSAGES)) + Feature(LoginType.LIBRUS, FeatureType.MESSAGES_SENT, listOf( + ENDPOINT_LIBRUS_MESSAGES_SENT to LoginMethod.LIBRUS_MESSAGES + )) ) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/LibrusData.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/LibrusData.kt index db72ddcc..a5d9256e 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/LibrusData.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/LibrusData.kt @@ -24,7 +24,7 @@ class LibrusData(val data: DataLibrus, val onSuccess: () -> Unit) { } private fun nextEndpoint(onSuccess: () -> Unit) { - if (data.targetEndpointIds.isEmpty()) { + if (data.targetEndpoints.isEmpty()) { onSuccess() return } @@ -32,8 +32,8 @@ class LibrusData(val data: DataLibrus, val onSuccess: () -> Unit) { onSuccess() return } - val id = data.targetEndpointIds.firstKey() - val lastSync = data.targetEndpointIds.remove(id) + val id = data.targetEndpoints.firstKey() + val lastSync = data.targetEndpoints.remove(id) useEndpoint(id, lastSync) { endpointId -> data.progress(data.progressStep) nextEndpoint(onSuccess) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiAnnouncementMarkAsRead.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiAnnouncementMarkAsRead.kt index 23cfceb4..8f45f9df 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiAnnouncementMarkAsRead.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiAnnouncementMarkAsRead.kt @@ -12,6 +12,7 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi import pl.szczodrzynski.edziennik.data.api.events.AnnouncementGetEvent import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull class LibrusApiAnnouncementMarkAsRead(override val data: DataLibrus, @@ -34,7 +35,7 @@ class LibrusApiAnnouncementMarkAsRead(override val data: DataLibrus, data.setSeenMetadataList.add(Metadata( profileId, - Metadata.TYPE_ANNOUNCEMENT, + MetadataType.ANNOUNCEMENT, announcement.id, announcement.seen, announcement.notified diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiAnnouncements.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiAnnouncements.kt index fcf68cad..ad7c0df8 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiAnnouncements.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiAnnouncements.kt @@ -11,6 +11,7 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi import pl.szczodrzynski.edziennik.data.db.entity.Announcement import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType import pl.szczodrzynski.edziennik.ext.* import pl.szczodrzynski.edziennik.utils.models.Date @@ -54,7 +55,7 @@ class LibrusApiAnnouncements(override val data: DataLibrus, data.announcementList.add(announcementObject) data.setSeenMetadataList.add(Metadata( profileId, - Metadata.TYPE_ANNOUNCEMENT, + MetadataType.ANNOUNCEMENT, id, read, profile.empty || read diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiAttendances.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiAttendances.kt index 314a0716..b9bb6152 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiAttendances.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiAttendances.kt @@ -12,6 +12,7 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi import pl.szczodrzynski.edziennik.data.db.entity.Attendance import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType import pl.szczodrzynski.edziennik.ext.* import pl.szczodrzynski.edziennik.utils.models.Date @@ -76,7 +77,7 @@ class LibrusApiAttendances(override val data: DataLibrus, if(type?.baseType != Attendance.TYPE_PRESENT) { data.metadataList.add(Metadata( profileId, - Metadata.TYPE_ATTENDANCE, + MetadataType.ATTENDANCE, id, profile?.empty ?: false || type?.baseType == Attendance.TYPE_PRESENT_CUSTOM || type?.baseType == Attendance.TYPE_UNKNOWN, profile?.empty ?: false || type?.baseType == Attendance.TYPE_PRESENT_CUSTOM || type?.baseType == Attendance.TYPE_UNKNOWN diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiBehaviourGrades.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiBehaviourGrades.kt index 7a528407..9d9e3015 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiBehaviourGrades.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiBehaviourGrades.kt @@ -14,6 +14,7 @@ import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_POINT_SUM import pl.szczodrzynski.edziennik.data.db.entity.GradeCategory import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType import pl.szczodrzynski.edziennik.ext.* import pl.szczodrzynski.edziennik.utils.models.Date import java.text.DecimalFormat @@ -63,7 +64,7 @@ class LibrusApiBehaviourGrades(override val data: DataLibrus, data.gradeList.add(semester1StartGradeObject) data.metadataList.add(Metadata( profileId, - Metadata.TYPE_GRADE, + MetadataType.GRADE, semester1StartGradeObject.id, true, true @@ -91,7 +92,7 @@ class LibrusApiBehaviourGrades(override val data: DataLibrus, data.gradeList.add(semester2StartGradeObject) data.metadataList.add(Metadata( profileId, - Metadata.TYPE_GRADE, + MetadataType.GRADE, semester2StartGradeObject.id, true, true @@ -165,7 +166,7 @@ class LibrusApiBehaviourGrades(override val data: DataLibrus, data.gradeList.add(gradeObject) data.metadataList.add(Metadata( profileId, - Metadata.TYPE_GRADE, + MetadataType.GRADE, id, profile.empty, profile.empty diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiDescriptiveGrades.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiDescriptiveGrades.kt index 9b214d04..d77d1bf4 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiDescriptiveGrades.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiDescriptiveGrades.kt @@ -15,6 +15,7 @@ import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_TEXT import pl.szczodrzynski.edziennik.data.db.entity.GradeCategory import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType import pl.szczodrzynski.edziennik.ext.* import pl.szczodrzynski.edziennik.utils.models.Date @@ -73,7 +74,7 @@ class LibrusApiDescriptiveGrades(override val data: DataLibrus, data.gradeList.add(gradeObject) data.metadataList.add(Metadata( profileId, - Metadata.TYPE_GRADE, + MetadataType.GRADE, id, profile.empty, profile.empty diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiEvents.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiEvents.kt index 82a0e267..79722a8b 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiEvents.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiEvents.kt @@ -13,6 +13,7 @@ import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel import pl.szczodrzynski.edziennik.data.db.entity.Event import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType import pl.szczodrzynski.edziennik.ext.* import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Time @@ -71,7 +72,7 @@ class LibrusApiEvents(override val data: DataLibrus, data.metadataList.add( Metadata( profileId, - Metadata.TYPE_EVENT, + MetadataType.EVENT, id, profile?.empty ?: false, profile?.empty ?: false diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiGradeComments.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiGradeComments.kt index 96225b67..0e9ea16c 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiGradeComments.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiGradeComments.kt @@ -4,7 +4,6 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api -import pl.szczodrzynski.edziennik.* import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_NORMAL_GRADE_COMMENTS import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiGrades.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiGrades.kt index 58621a0e..7c2d60f3 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiGrades.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiGrades.kt @@ -16,6 +16,7 @@ import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_YEAR_PROPO import pl.szczodrzynski.edziennik.data.db.entity.GradeCategory import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType import pl.szczodrzynski.edziennik.ext.* import pl.szczodrzynski.edziennik.utils.Utils import pl.szczodrzynski.edziennik.utils.models.Date @@ -97,7 +98,7 @@ class LibrusApiGrades(override val data: DataLibrus, data.metadataList.add( Metadata( profileId, - Metadata.TYPE_GRADE, + MetadataType.GRADE, id, profile.empty, profile.empty diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiHomework.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiHomework.kt index f304aaad..1c7741ce 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiHomework.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiHomework.kt @@ -12,6 +12,7 @@ import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel import pl.szczodrzynski.edziennik.data.db.entity.Event import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType import pl.szczodrzynski.edziennik.ext.* import pl.szczodrzynski.edziennik.utils.models.Date @@ -51,7 +52,7 @@ class LibrusApiHomework(override val data: DataLibrus, data.eventList.add(eventObject) data.metadataList.add(Metadata( profileId, - Metadata.TYPE_HOMEWORK, + MetadataType.HOMEWORK, id, profile?.empty ?: false, profile?.empty ?: false diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiLessons.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiLessons.kt index 66075008..72605991 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiLessons.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiLessons.kt @@ -4,7 +4,6 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api -import pl.szczodrzynski.edziennik.* import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_LESSONS import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiLuckyNumber.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiLuckyNumber.kt index 6ebe9e63..f93a06a3 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiLuckyNumber.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiLuckyNumber.kt @@ -9,6 +9,7 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_ import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi import pl.szczodrzynski.edziennik.data.db.entity.LuckyNumber import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType import pl.szczodrzynski.edziennik.ext.* import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Time @@ -47,7 +48,7 @@ class LibrusApiLuckyNumber(override val data: DataLibrus, data.metadataList.add( Metadata( profileId, - Metadata.TYPE_LUCKY_NUMBER, + MetadataType.LUCKY_NUMBER, luckyNumberObject.date.value.toLong(), true, profile?.empty ?: false diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiNotices.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiNotices.kt index ea61428e..83536977 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiNotices.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiNotices.kt @@ -12,6 +12,7 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.data.db.entity.Notice import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType import pl.szczodrzynski.edziennik.ext.* import pl.szczodrzynski.edziennik.utils.models.Date @@ -35,7 +36,7 @@ class LibrusApiNotices(override val data: DataLibrus, val id = note.getLong("Id") ?: return@forEach val text = note.getString("Text") ?: "" val categoryId = note.getJsonObject("Category")?.getLong("Id") ?: -1 - val teacherId = note.getJsonObject("AddedBy")?.getLong("Id") ?: -1 + val teacherId = note.getJsonObject("Teacher")?.getLong("Id") ?: -1 val addedDate = note.getString("Date")?.let { Date.fromY_m_d(it) } ?: return@forEach val type = when (note.getInt("Positive")) { @@ -62,7 +63,7 @@ class LibrusApiNotices(override val data: DataLibrus, data.metadataList.add( Metadata( profileId, - Metadata.TYPE_NOTICE, + MetadataType.NOTICE, id, profile?.empty ?: false, profile?.empty ?: false diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiPointGrades.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiPointGrades.kt index 8a7ad29f..ee9fdfdf 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiPointGrades.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiPointGrades.kt @@ -14,6 +14,7 @@ import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_POINT_AVG import pl.szczodrzynski.edziennik.data.db.entity.GradeCategory import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType import pl.szczodrzynski.edziennik.ext.* import pl.szczodrzynski.edziennik.utils.models.Date @@ -66,7 +67,7 @@ class LibrusApiPointGrades(override val data: DataLibrus, data.gradeList.add(gradeObject) data.metadataList.add(Metadata( profileId, - Metadata.TYPE_GRADE, + MetadataType.GRADE, id, profile.empty, profile.empty diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiPtMeetings.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiPtMeetings.kt index 170c6135..35818ac0 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiPtMeetings.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiPtMeetings.kt @@ -11,6 +11,7 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel import pl.szczodrzynski.edziennik.data.db.entity.Event import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType import pl.szczodrzynski.edziennik.ext.* import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Time @@ -56,7 +57,7 @@ class LibrusApiPtMeetings(override val data: DataLibrus, data.metadataList.add( Metadata( profileId, - Metadata.TYPE_EVENT, + MetadataType.EVENT, id, profile?.empty ?: false, profile?.empty ?: false diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiSchools.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiSchools.kt index e42ab949..c5581e4d 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiSchools.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiSchools.kt @@ -4,14 +4,12 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api -import pl.szczodrzynski.edziennik.* import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_SCHOOLS import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi import pl.szczodrzynski.edziennik.data.db.entity.LessonRange import pl.szczodrzynski.edziennik.ext.* import pl.szczodrzynski.edziennik.utils.models.Time -import java.util.* class LibrusApiSchools(override val data: DataLibrus, override val lastSync: Long?, diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiSubjects.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiSubjects.kt index 17e30559..b615d379 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiSubjects.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiSubjects.kt @@ -4,7 +4,6 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api -import pl.szczodrzynski.edziennik.* import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_SUBJECTS import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiTeacherFreeDays.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiTeacherFreeDays.kt index 09ed5416..68053aeb 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiTeacherFreeDays.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiTeacherFreeDays.kt @@ -5,13 +5,13 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api import androidx.core.util.isEmpty -import pl.szczodrzynski.edziennik.* -import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_AGENDA import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_TEACHER_FREE_DAYS import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.data.db.entity.TeacherAbsence +import pl.szczodrzynski.edziennik.data.db.enums.FeatureType +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType import pl.szczodrzynski.edziennik.ext.* import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Time @@ -56,14 +56,14 @@ class LibrusApiTeacherFreeDays(override val data: DataLibrus, data.teacherAbsenceList.add(teacherAbsenceObject) data.metadataList.add(Metadata( profileId, - Metadata.TYPE_TEACHER_ABSENCE, + MetadataType.TEACHER_ABSENCE, id, true, profile?.empty ?: false )) } - data.setSyncNext(ENDPOINT_LIBRUS_API_TEACHER_FREE_DAYS, 6* HOUR, DRAWER_ITEM_AGENDA) + data.setSyncNext(ENDPOINT_LIBRUS_API_TEACHER_FREE_DAYS, 6* HOUR, FeatureType.AGENDA) onSuccess(ENDPOINT_LIBRUS_API_TEACHER_FREE_DAYS) } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiTextGrades.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiTextGrades.kt index bb19f11f..ce4cbcc2 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiTextGrades.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiTextGrades.kt @@ -14,6 +14,7 @@ import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_DESCRIPTIV import pl.szczodrzynski.edziennik.data.db.entity.GradeCategory import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType import pl.szczodrzynski.edziennik.ext.* import pl.szczodrzynski.edziennik.utils.models.Date @@ -68,7 +69,7 @@ class LibrusApiTextGrades(override val data: DataLibrus, data.gradeList.add(gradeObject) data.metadataList.add(Metadata( profileId, - Metadata.TYPE_GRADE, + MetadataType.GRADE, id, profile.empty, profile.empty diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiTimetables.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiTimetables.kt index b224a381..abd62272 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiTimetables.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiTimetables.kt @@ -14,6 +14,7 @@ import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel import pl.szczodrzynski.edziennik.data.db.entity.Lesson import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType import pl.szczodrzynski.edziennik.ext.* import pl.szczodrzynski.edziennik.utils.Utils.d import pl.szczodrzynski.edziennik.utils.models.Date @@ -189,6 +190,7 @@ class LibrusApiTimetables(override val data: DataLibrus, } lessonObject.id = lessonObject.buildId() + lessonObject.ownerId = lessonObject.buildOwnerId() val seen = profile.empty || lessonDate < Date.getToday() @@ -196,7 +198,7 @@ class LibrusApiTimetables(override val data: DataLibrus, data.metadataList.add( Metadata( profileId, - Metadata.TYPE_LESSON_CHANGE, + MetadataType.LESSON_CHANGE, lessonObject.id, seen, seen diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusMessagesGetList.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusMessagesGetList.kt index 7d0cfeef..1535e759 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusMessagesGetList.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusMessagesGetList.kt @@ -4,7 +4,6 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.messages -import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_MESSAGES import pl.szczodrzynski.edziennik.data.api.ERROR_NOT_IMPLEMENTED import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_MESSAGES_RECEIVED @@ -12,6 +11,8 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_MESS import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusMessages import pl.szczodrzynski.edziennik.data.db.entity.* import pl.szczodrzynski.edziennik.data.db.entity.Message.Companion.TYPE_RECEIVED +import pl.szczodrzynski.edziennik.data.db.enums.FeatureType +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType import pl.szczodrzynski.edziennik.ext.DAY import pl.szczodrzynski.edziennik.ext.fixName import pl.szczodrzynski.edziennik.ext.singleOrNull @@ -65,16 +66,17 @@ class LibrusMessagesGetList(override val data: DataLibrus, val recipientId = data.teacherList.singleOrNull { it.name == recipientFirstName && it.surname == recipientLastName - }?.id ?: { + }?.id ?: run { val teacherObject = Teacher( - profileId, - -1 * Utils.crc16("$recipientFirstName $recipientLastName".toByteArray()).toLong(), - recipientFirstName, - recipientLastName + profileId, + -1 * Utils.crc16("$recipientFirstName $recipientLastName".toByteArray()) + .toLong(), + recipientFirstName, + recipientLastName ) data.teacherList.put(teacherObject.id, teacherObject) teacherObject.id - }.invoke() + } val senderId = when (type) { TYPE_RECEIVED -> recipientId @@ -118,7 +120,7 @@ class LibrusMessagesGetList(override val data: DataLibrus, data.messageRecipientList.add(messageRecipientObject) data.setSeenMetadataList.add(Metadata( profileId, - Metadata.TYPE_MESSAGE, + MetadataType.MESSAGE, id, notified, notified @@ -127,7 +129,7 @@ class LibrusMessagesGetList(override val data: DataLibrus, when (type) { TYPE_RECEIVED -> data.setSyncNext(ENDPOINT_LIBRUS_MESSAGES_RECEIVED, SYNC_ALWAYS) - Message.TYPE_SENT -> data.setSyncNext(ENDPOINT_LIBRUS_MESSAGES_SENT, DAY, DRAWER_ITEM_MESSAGES) + Message.TYPE_SENT -> data.setSyncNext(ENDPOINT_LIBRUS_MESSAGES_SENT, DAY, FeatureType.MESSAGES_SENT) } onSuccess(endpointId) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusMessagesGetMessage.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusMessagesGetMessage.kt index 8e6435c6..e8a1e1a6 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusMessagesGetMessage.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusMessagesGetMessage.kt @@ -13,6 +13,7 @@ import pl.szczodrzynski.edziennik.data.db.entity.Message.Companion.TYPE_RECEIVED import pl.szczodrzynski.edziennik.data.db.entity.Message.Companion.TYPE_SENT import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.data.db.entity.Teacher +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType import pl.szczodrzynski.edziennik.data.db.full.MessageFull import pl.szczodrzynski.edziennik.data.db.full.MessageRecipientFull import pl.szczodrzynski.edziennik.ext.fixName @@ -124,7 +125,7 @@ class LibrusMessagesGetMessage(override val data: DataLibrus, val receiverId = teacher?.id ?: -1 teacher?.loginId = receiverLoginId - val readDateText = message.select("readed").text() + val readDateText = receiver.select("readed").text() val readDate = when (readDateText.isNotNullNorEmpty()) { true -> Date.fromIso(readDateText) else -> 0 @@ -147,7 +148,7 @@ class LibrusMessagesGetMessage(override val data: DataLibrus, if (!messageObject.seen) { data.setSeenMetadataList.add(Metadata( messageObject.profileId, - Metadata.TYPE_MESSAGE, + MetadataType.MESSAGE, messageObject.id, true, true diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusMessagesSendMessage.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusMessagesSendMessage.kt index 02248e99..bcd01311 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusMessagesSendMessage.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusMessagesSendMessage.kt @@ -16,7 +16,7 @@ import pl.szczodrzynski.edziennik.ext.getLong import pl.szczodrzynski.edziennik.ext.getString class LibrusMessagesSendMessage(override val data: DataLibrus, - val recipients: List, + val recipients: Set, val subject: String, val text: String, val onSuccess: () -> Unit diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaGetMessage.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaGetMessage.kt index b9df2af1..36cadfaa 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaGetMessage.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaGetMessage.kt @@ -8,6 +8,7 @@ import pl.szczodrzynski.edziennik.data.api.events.MessageGetEvent import pl.szczodrzynski.edziennik.data.db.entity.Message import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.data.db.entity.Teacher +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType import pl.szczodrzynski.edziennik.data.db.full.MessageFull import pl.szczodrzynski.edziennik.data.db.full.MessageRecipientFull import pl.szczodrzynski.edziennik.ext.get @@ -139,7 +140,7 @@ class LibrusSynergiaGetMessage(override val data: DataLibrus, if (!messageObject.seen) { data.setSeenMetadataList.add(Metadata( messageObject.profileId, - Metadata.TYPE_MESSAGE, + MetadataType.MESSAGE, messageObject.id, true, true diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaGetMessages.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaGetMessages.kt index 01cc69f6..626aec61 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaGetMessages.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaGetMessages.kt @@ -1,12 +1,13 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.synergia import org.jsoup.Jsoup -import pl.szczodrzynski.edziennik.* import pl.szczodrzynski.edziennik.data.api.ERROR_NOT_IMPLEMENTED import pl.szczodrzynski.edziennik.data.api.Regexes import pl.szczodrzynski.edziennik.data.api.edziennik.librus.* import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusSynergia import pl.szczodrzynski.edziennik.data.db.entity.* +import pl.szczodrzynski.edziennik.data.db.enums.FeatureType +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType import pl.szczodrzynski.edziennik.ext.* import pl.szczodrzynski.edziennik.utils.Utils import pl.szczodrzynski.edziennik.utils.models.Date @@ -96,7 +97,7 @@ class LibrusSynergiaGetMessages(override val data: DataLibrus, data.messageRecipientList.add(messageRecipientObject) data.setSeenMetadataList.add(Metadata( profileId, - Metadata.TYPE_MESSAGE, + MetadataType.MESSAGE, id, notified, notified @@ -105,7 +106,7 @@ class LibrusSynergiaGetMessages(override val data: DataLibrus, when (type) { Message.TYPE_RECEIVED -> data.setSyncNext(ENDPOINT_LIBRUS_MESSAGES_RECEIVED, SYNC_ALWAYS) - Message.TYPE_SENT -> data.setSyncNext(ENDPOINT_LIBRUS_MESSAGES_SENT, DAY, MainActivity.DRAWER_ITEM_MESSAGES) + Message.TYPE_SENT -> data.setSyncNext(ENDPOINT_LIBRUS_MESSAGES_SENT, DAY, FeatureType.MESSAGES_SENT) } onSuccess(endpointId) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaHomework.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaHomework.kt index ddf6635c..be9b4efc 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaHomework.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaHomework.kt @@ -5,7 +5,6 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.synergia import org.jsoup.Jsoup -import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_HOMEWORK import pl.szczodrzynski.edziennik.data.api.POST import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_SYNERGIA_HOMEWORK @@ -13,8 +12,11 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusSynergia import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel import pl.szczodrzynski.edziennik.data.db.entity.Event import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.enums.FeatureType +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType import pl.szczodrzynski.edziennik.ext.HOUR import pl.szczodrzynski.edziennik.ext.get +import pl.szczodrzynski.edziennik.ext.getSemesterStart import pl.szczodrzynski.edziennik.ext.singleOrNull import pl.szczodrzynski.edziennik.utils.models.Date @@ -84,7 +86,7 @@ class LibrusSynergiaHomework(override val data: DataLibrus, data.eventList.add(eventObject) data.metadataList.add(Metadata( profileId, - Metadata.TYPE_HOMEWORK, + MetadataType.HOMEWORK, id, seen, seen @@ -95,7 +97,7 @@ class LibrusSynergiaHomework(override val data: DataLibrus, data.toRemove.add(DataRemoveModel.Events.futureWithType(Event.TYPE_HOMEWORK)) // because this requires a synergia login (2 more requests!!!) sync this every few hours or if explicit :D - data.setSyncNext(ENDPOINT_LIBRUS_SYNERGIA_HOMEWORK, 5 * HOUR, DRAWER_ITEM_HOMEWORK) + data.setSyncNext(ENDPOINT_LIBRUS_SYNERGIA_HOMEWORK, 5 * HOUR, FeatureType.HOMEWORK) onSuccess(ENDPOINT_LIBRUS_SYNERGIA_HOMEWORK) } } ?: onSuccess(ENDPOINT_LIBRUS_SYNERGIA_HOMEWORK) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaMarkAllAnnouncementsAsRead.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaMarkAllAnnouncementsAsRead.kt index 04a7f5b6..2cb06942 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaMarkAllAnnouncementsAsRead.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaMarkAllAnnouncementsAsRead.kt @@ -7,6 +7,7 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.synergia import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusSynergia import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType class LibrusSynergiaMarkAllAnnouncementsAsRead(override val data: DataLibrus, val onSuccess: () -> Unit @@ -17,7 +18,7 @@ class LibrusSynergiaMarkAllAnnouncementsAsRead(override val data: DataLibrus, init { synergiaGet(TAG, "ogloszenia") { - data.app.db.metadataDao().setAllSeen(profileId, Metadata.TYPE_ANNOUNCEMENT, true) + data.app.db.metadataDao().setAllSeen(profileId, MetadataType.ANNOUNCEMENT, true) onSuccess() } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/firstlogin/LibrusFirstLogin.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/firstlogin/LibrusFirstLogin.kt index 43e5bda9..c29d3a85 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/firstlogin/LibrusFirstLogin.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/firstlogin/LibrusFirstLogin.kt @@ -11,6 +11,8 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.librus.login.LibrusLoginPor import pl.szczodrzynski.edziennik.data.api.events.FirstLoginFinishedEvent import pl.szczodrzynski.edziennik.data.api.models.ApiError import pl.szczodrzynski.edziennik.data.db.entity.Profile +import pl.szczodrzynski.edziennik.data.db.enums.LoginMode +import pl.szczodrzynski.edziennik.data.db.enums.LoginType import pl.szczodrzynski.edziennik.ext.* class LibrusFirstLogin(val data: DataLibrus, val onSuccess: () -> Unit) { @@ -23,11 +25,9 @@ class LibrusFirstLogin(val data: DataLibrus, val onSuccess: () -> Unit) { private val profileList = mutableListOf() init { - val loginStoreId = data.loginStore.id - val loginStoreType = LOGIN_TYPE_LIBRUS - var firstProfileId = loginStoreId + var firstProfileId = data.loginStore.id - if (data.loginStore.mode == LOGIN_MODE_LIBRUS_EMAIL) { + if (data.loginStore.mode == LoginMode.LIBRUS_EMAIL) { // email login: use Portal for account list LibrusLoginPortal(data) { portal.portalGet(TAG, if (data.fakeLogin) FAKE_LIBRUS_ACCOUNTS else LIBRUS_ACCOUNTS_URL) { json, response -> @@ -66,8 +66,8 @@ class LibrusFirstLogin(val data: DataLibrus, val onSuccess: () -> Unit) { val profile = Profile( firstProfileId++, - loginStoreId, - loginStoreType, + data.loginStore.id, + LoginType.LIBRUS, studentNameLong, data.portalEmail, studentNameLong, @@ -107,8 +107,8 @@ class LibrusFirstLogin(val data: DataLibrus, val onSuccess: () -> Unit) { val profile = Profile( firstProfileId++, - loginStoreId, - loginStoreType, + data.loginStore.id, + LoginType.LIBRUS, studentNameLong, login, studentNameLong, diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLogin.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLogin.kt index c79acd2e..6af1398b 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLogin.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLogin.kt @@ -5,11 +5,8 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.librus.login import pl.szczodrzynski.edziennik.R -import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_LIBRUS_API -import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_LIBRUS_MESSAGES -import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_LIBRUS_PORTAL -import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_LIBRUS_SYNERGIA import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.db.enums.LoginMethod import pl.szczodrzynski.edziennik.utils.Utils class LibrusLogin(val data: DataLibrus, val onSuccess: () -> Unit) { @@ -24,7 +21,7 @@ class LibrusLogin(val data: DataLibrus, val onSuccess: () -> Unit) { } private fun nextLoginMethod(onSuccess: () -> Unit) { - if (data.targetLoginMethodIds.isEmpty()) { + if (data.targetLoginMethods.isEmpty()) { onSuccess() return } @@ -32,38 +29,39 @@ class LibrusLogin(val data: DataLibrus, val onSuccess: () -> Unit) { onSuccess() return } - useLoginMethod(data.targetLoginMethodIds.removeAt(0)) { usedMethodId -> + useLoginMethod(data.targetLoginMethods.removeAt(0)) { usedMethod -> data.progress(data.progressStep) - if (usedMethodId != -1) - data.loginMethods.add(usedMethodId) + if (usedMethod != null) + data.loginMethods.add(usedMethod) nextLoginMethod(onSuccess) } } - private fun useLoginMethod(loginMethodId: Int, onSuccess: (usedMethodId: Int) -> Unit) { + private fun useLoginMethod(loginMethod: LoginMethod, onSuccess: (usedMethod: LoginMethod?) -> Unit) { // this should never be true - if (data.loginMethods.contains(loginMethodId)) { - onSuccess(-1) + if (data.loginMethods.contains(loginMethod)) { + onSuccess(null) return } - Utils.d(TAG, "Using login method $loginMethodId") - when (loginMethodId) { - LOGIN_METHOD_LIBRUS_PORTAL -> { + Utils.d(TAG, "Using login method $loginMethod") + when (loginMethod) { + LoginMethod.LIBRUS_PORTAL -> { data.startProgress(R.string.edziennik_progress_login_librus_portal) - LibrusLoginPortal(data) { onSuccess(loginMethodId) } + LibrusLoginPortal(data) { onSuccess(loginMethod) } } - LOGIN_METHOD_LIBRUS_API -> { + LoginMethod.LIBRUS_API -> { data.startProgress(R.string.edziennik_progress_login_librus_api) - LibrusLoginApi(data) { onSuccess(loginMethodId) } + LibrusLoginApi(data) { onSuccess(loginMethod) } } - LOGIN_METHOD_LIBRUS_SYNERGIA -> { + LoginMethod.LIBRUS_SYNERGIA -> { data.startProgress(R.string.edziennik_progress_login_librus_synergia) - LibrusLoginSynergia(data) { onSuccess(loginMethodId) } + LibrusLoginSynergia(data) { onSuccess(loginMethod) } } - LOGIN_METHOD_LIBRUS_MESSAGES -> { + LoginMethod.LIBRUS_MESSAGES -> { data.startProgress(R.string.edziennik_progress_login_librus_messages) - LibrusLoginMessages(data) { onSuccess(loginMethodId) } + LibrusLoginMessages(data) { onSuccess(loginMethod) } } + else -> {} } } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLoginApi.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLoginApi.kt index 58f7181d..d2ef2336 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLoginApi.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLoginApi.kt @@ -12,6 +12,8 @@ import im.wangchao.mhttp.callback.JsonCallbackHandler import pl.szczodrzynski.edziennik.data.api.* import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.data.db.enums.LoginMethod +import pl.szczodrzynski.edziennik.data.db.enums.LoginMode import pl.szczodrzynski.edziennik.ext.getInt import pl.szczodrzynski.edziennik.ext.getString import pl.szczodrzynski.edziennik.ext.getUnixDate @@ -32,7 +34,7 @@ class LibrusLoginApi { this.data = data this.onSuccess = onSuccess - if (data.loginStore.mode == LOGIN_MODE_LIBRUS_EMAIL && data.profile == null) { + if (data.loginStore.mode == LoginMode.LIBRUS_EMAIL && data.profile == null) { data.error(ApiError(TAG, ERROR_PROFILE_MISSING)) return } @@ -42,9 +44,9 @@ class LibrusLoginApi { } else { when (data.loginStore.mode) { - LOGIN_MODE_LIBRUS_EMAIL -> loginWithPortal() - LOGIN_MODE_LIBRUS_SYNERGIA -> loginWithSynergia() - LOGIN_MODE_LIBRUS_JST -> loginWithJst() + LoginMode.LIBRUS_EMAIL -> loginWithPortal() + LoginMode.LIBRUS_SYNERGIA -> loginWithSynergia() + LoginMode.LIBRUS_JST -> loginWithJst() else -> { data.error(ApiError(TAG, ERROR_INVALID_LOGIN_MODE)) } @@ -53,7 +55,7 @@ class LibrusLoginApi { } private fun loginWithPortal() { - if (!data.loginMethods.contains(LOGIN_METHOD_LIBRUS_PORTAL)) { + if (!data.loginMethods.contains(LoginMethod.LIBRUS_PORTAL)) { data.error(ApiError(TAG, ERROR_LOGIN_METHOD_NOT_SATISFIED)) return } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLoginMessages.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLoginMessages.kt index 2a61f641..eeefe0f8 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLoginMessages.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLoginMessages.kt @@ -12,6 +12,7 @@ import pl.szczodrzynski.edziennik.data.api.* import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus import pl.szczodrzynski.edziennik.data.api.edziennik.librus.LibrusRecaptchaHelper import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.data.db.enums.LoginMethod import pl.szczodrzynski.edziennik.ext.getUnixDate import pl.szczodrzynski.edziennik.utils.Utils.d import java.io.StringWriter @@ -91,7 +92,7 @@ class LibrusLoginMessages(val data: DataLibrus, val onSuccess: () -> Unit) { } else { data.app.cookieJar.clear("wiadomosci.librus.pl") - if (data.loginMethods.contains(LOGIN_METHOD_LIBRUS_SYNERGIA)) { + if (data.loginMethods.contains(LoginMethod.LIBRUS_SYNERGIA)) { loginWithSynergia() } else if (data.apiLogin != null && data.apiPassword != null && false) { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLoginPortal.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLoginPortal.kt index ed01d01f..92741bb0 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLoginPortal.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLoginPortal.kt @@ -10,7 +10,9 @@ import im.wangchao.mhttp.callback.TextCallbackHandler import pl.szczodrzynski.edziennik.* import pl.szczodrzynski.edziennik.data.api.* import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.events.UserActionRequiredEvent import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.data.db.enums.LoginMode import pl.szczodrzynski.edziennik.ext.* import pl.szczodrzynski.edziennik.utils.Utils.d import java.net.HttpURLConnection.* @@ -22,8 +24,11 @@ class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) { private const val TAG = "LoginLibrusPortal" } + // loop failsafe + private var loginPerformed = false + init { run { - if (data.loginStore.mode != LOGIN_MODE_LIBRUS_EMAIL) { + if (data.loginStore.mode != LoginMode.LIBRUS_EMAIL) { data.error(ApiError(TAG, ERROR_INVALID_LOGIN_MODE)) return@run } @@ -31,6 +36,7 @@ class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) { data.error(ApiError(TAG, ERROR_LOGIN_DATA_MISSING)) return@run } + loginPerformed = false // succeed having a non-expired access token and a refresh token if (data.isPortalLoginValid()) { @@ -56,18 +62,23 @@ class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) { } }} - private fun authorize(url: String?) { + private fun authorize(url: String, referer: String? = null) { d(TAG, "Request: Librus/Login/Portal - $url") Request.builder() .url(url) .userAgent(LIBRUS_USER_AGENT) + .also { + if (referer != null) + it.addHeader("Referer", referer) + } + .addHeader("X-Requested-With", LIBRUS_HEADER) .withClient(data.app.httpLazy) .callback(object : TextCallbackHandler() { override fun onSuccess(text: String, response: Response) { val location = response.headers().get("Location") if (location != null) { - val authMatcher = Pattern.compile("$LIBRUS_REDIRECT_URL\\?code=([A-z0-9]+?)$", Pattern.DOTALL or Pattern.MULTILINE).matcher(location) + val authMatcher = Pattern.compile("$LIBRUS_REDIRECT_URL\\?code=([^&?]+)", Pattern.DOTALL or Pattern.MULTILINE).matcher(location) when { authMatcher.find() -> { accessToken(authMatcher.group(1), null) @@ -81,16 +92,31 @@ class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) { authorize(location) } } - } else { - val csrfMatcher = Pattern.compile("name=\"csrf-token\" content=\"([A-z0-9=+/\\-_]+?)\"", Pattern.DOTALL).matcher(text) - if (csrfMatcher.find()) { - login(csrfMatcher.group(1) ?: "") - } else { - data.error(ApiError(TAG, ERROR_LOGIN_LIBRUS_PORTAL_CSRF_MISSING) - .withResponse(response) - .withApiResponse(text)) + return + } + + if (checkError(text, response)) + return + + var loginUrl = if (data.fakeLogin) FAKE_LIBRUS_LOGIN else LIBRUS_LOGIN_URL + val csrfToken = Regexes.HTML_CSRF_TOKEN.find(text)?.get(1) ?: "" + + for (match in Regexes.HTML_FORM_ACTION.findAll(text)) { + val form = match.value.lowercase() + if ("login" in form && "post" in form) { + loginUrl = match[1] } } + + val params = mutableMapOf() + for (match in Regexes.HTML_INPUT_HIDDEN.findAll(text)) { + val input = match.value + val name = Regexes.HTML_INPUT_NAME.find(input)?.get(1) ?: continue + val value = Regexes.HTML_INPUT_VALUE.find(input)?.get(1) ?: continue + params[name] = value + } + + login(url = loginUrl, referer = url, csrfToken, params) } override fun onFailure(response: Response, throwable: Throwable) { @@ -103,8 +129,54 @@ class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) { .enqueue() } - private fun login(csrfToken: String) { - d(TAG, "Request: Librus/Login/Portal - ${if (data.fakeLogin) FAKE_LIBRUS_LOGIN else LIBRUS_LOGIN_URL}") + private fun checkError(text: String, response: Response): Boolean { + when { + text.contains("librus_account_settings_main") -> return false + text.contains("Sesja logowania wygasła") -> ERROR_LOGIN_LIBRUS_PORTAL_CSRF_EXPIRED + text.contains("Upewnij się, że nie") -> ERROR_LOGIN_LIBRUS_PORTAL_INVALID_LOGIN + text.contains("Podany adres e-mail jest nieprawidłowy.") -> ERROR_LOGIN_LIBRUS_PORTAL_INVALID_LOGIN + else -> null // no error for now + }?.let { errorCode -> + data.error(ApiError(TAG, errorCode) + .withApiResponse(text) + .withResponse(response)) + return true + } + + if ("robotem" in text || "g-recaptcha" in text || "captchaValidate" in text) { + val siteKey = Regexes.HTML_RECAPTCHA_KEY.find(text)?.get(1) + if (siteKey == null) { + data.error(ApiError(TAG, ERROR_LOGIN_LIBRUS_PORTAL_ACTION_ERROR) + .withApiResponse(text) + .withResponse(response)) + return true + } + data.requireUserAction( + type = UserActionRequiredEvent.Type.RECAPTCHA, + params = Bundle( + "siteKey" to siteKey, + "referer" to response.request().url().toString(), + "userAgent" to LIBRUS_USER_AGENT, + ), + errorText = R.string.notification_user_action_required_captcha_librus, + ) + return true + } + return false + } + + private fun login( + url: String, + referer: String, + csrfToken: String?, + params: Map, + ) { + if (loginPerformed) { + data.error(ApiError(TAG, ERROR_LOGIN_LIBRUS_PORTAL_ACTION_ERROR)) + return + } + + d(TAG, "Request: Librus/Login/Portal - $url") val recaptchaCode = data.arguments?.getString("recaptchaCode") ?: data.loginStore.getLoginData("recaptchaCode", null) val recaptchaTime = data.arguments?.getLong("recaptchaTime") ?: data.loginStore.getLoginData("recaptchaTime", 0L) @@ -114,62 +186,46 @@ class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) { Request.builder() .url(if (data.fakeLogin) FAKE_LIBRUS_LOGIN else LIBRUS_LOGIN_URL) .userAgent(LIBRUS_USER_AGENT) + .addHeader("X-Requested-With", LIBRUS_HEADER) + .addHeader("Referer", referer) + .withClient(data.app.httpLazy) .addParameter("email", data.portalEmail) .addParameter("password", data.portalPassword) .also { if (recaptchaCode != null && System.currentTimeMillis() - recaptchaTime < 2*60*1000 /* 2 minutes */) it.addParameter("g-recaptcha-response", recaptchaCode) + if (csrfToken != null) + it.addHeader("X-CSRF-TOKEN", csrfToken) + for ((key, value) in params) { + it.addParameter(key, value) + } } - .addHeader("X-CSRF-TOKEN", csrfToken) - .allowErrorCode(HTTP_BAD_REQUEST) - .allowErrorCode(HTTP_FORBIDDEN) - .contentType(MediaTypeUtils.APPLICATION_JSON) + .contentType(MediaTypeUtils.APPLICATION_FORM) .post() - .callback(object : JsonCallbackHandler() { - override fun onSuccess(json: JsonObject?, response: Response) { + .callback(object : TextCallbackHandler() { + override fun onSuccess(text: String?, response: Response) { + loginPerformed = true val location = response.headers()?.get("Location") if (location == "$LIBRUS_REDIRECT_URL?command=close") { data.error(ApiError(TAG, ERROR_LIBRUS_PORTAL_MAINTENANCE) - .withApiResponse(json) + .withApiResponse(text) .withResponse(response)) return } - - if (json == null) { - if (response.parserErrorBody?.contains("wciąż nieaktywne") == true) { - data.error(ApiError(TAG, ERROR_LOGIN_LIBRUS_PORTAL_NOT_ACTIVATED) - .withResponse(response)) - return - } + if (text == null) { data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY) .withResponse(response)) return } - val error = if (response.code() == 200) null else - json.getJsonArray("errors")?.getString(0) - ?: json.getJsonObject("errors")?.entrySet()?.firstOrNull()?.value?.asString - error?.let { code -> - when { - code.contains("Sesja logowania wygasła") -> ERROR_LOGIN_LIBRUS_PORTAL_CSRF_EXPIRED - code.contains("Upewnij się, że nie") -> ERROR_LOGIN_LIBRUS_PORTAL_INVALID_LOGIN - // this doesn't work anyway: `errors` is an object with `g-recaptcha-response` set - code.contains("robotem") -> ERROR_CAPTCHA_LIBRUS_PORTAL - code.contains("Podany adres e-mail jest nieprawidłowy.") -> ERROR_LOGIN_LIBRUS_PORTAL_INVALID_LOGIN - else -> ERROR_LOGIN_LIBRUS_PORTAL_ACTION_ERROR - }.let { errorCode -> - data.error(ApiError(TAG, errorCode) - .withApiResponse(json) - .withResponse(response)) - return - } - } - if (json.getBoolean("captchaRequired") == true) { - data.error(ApiError(TAG, ERROR_CAPTCHA_LIBRUS_PORTAL) - .withResponse(response) - .withApiResponse(json)) - return - } - authorize(json.getString("redirect", LIBRUS_AUTHORIZE_URL)) + + authorize( + url = location + ?: if (data.fakeLogin) + FAKE_LIBRUS_AUTHORIZE + else + LIBRUS_AUTHORIZE_URL, + referer = referer, + ) } override fun onFailure(response: Response, throwable: Throwable) { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLoginSynergia.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLoginSynergia.kt index a95af893..5f53890c 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLoginSynergia.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLoginSynergia.kt @@ -12,6 +12,7 @@ import pl.szczodrzynski.edziennik.data.api.* import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.data.db.enums.LoginMethod import pl.szczodrzynski.edziennik.ext.getString import pl.szczodrzynski.edziennik.ext.getUnixDate import pl.szczodrzynski.edziennik.utils.Utils.d @@ -34,7 +35,7 @@ class LibrusLoginSynergia(override val data: DataLibrus, val onSuccess: () -> Un } else { data.app.cookieJar.clear("synergia.librus.pl") - if (data.loginMethods.contains(LOGIN_METHOD_LIBRUS_API)) { + if (data.loginMethods.contains(LoginMethod.LIBRUS_API)) { loginWithApi() } else if (data.apiLogin != null && data.apiPassword != null && false) { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/SynergiaTokenExtractor.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/SynergiaTokenExtractor.kt index 807bb131..7e1dc012 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/SynergiaTokenExtractor.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/SynergiaTokenExtractor.kt @@ -7,6 +7,7 @@ import pl.szczodrzynski.edziennik.data.api.* import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusPortal import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.data.db.enums.LoginMode import pl.szczodrzynski.edziennik.ext.* import pl.szczodrzynski.edziennik.utils.Utils.d @@ -16,7 +17,7 @@ class SynergiaTokenExtractor(override val data: DataLibrus, val onSuccess: () -> } init { run { - if (data.loginStore.mode != LOGIN_MODE_LIBRUS_EMAIL) { + if (data.loginStore.mode != LoginMode.LIBRUS_EMAIL) { data.error(ApiError(TAG, ERROR_INVALID_LOGIN_MODE)) return@run } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/DataMobidziennik.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/DataMobidziennik.kt index cee3cda5..64507362 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/DataMobidziennik.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/DataMobidziennik.kt @@ -6,14 +6,14 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik import android.util.LongSparseArray import pl.szczodrzynski.edziennik.App -import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_MOBIDZIENNIK_WEB -import pl.szczodrzynski.edziennik.data.api.Regexes import pl.szczodrzynski.edziennik.data.api.models.Data import pl.szczodrzynski.edziennik.data.db.entity.LoginStore import pl.szczodrzynski.edziennik.data.db.entity.Profile +import pl.szczodrzynski.edziennik.data.db.enums.LoginMethod import pl.szczodrzynski.edziennik.ext.currentTimeUnix -import pl.szczodrzynski.edziennik.ext.get +import pl.szczodrzynski.edziennik.ext.getStudentData import pl.szczodrzynski.edziennik.ext.isNotNullNorEmpty +import pl.szczodrzynski.edziennik.ext.set import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Time @@ -31,7 +31,7 @@ class DataMobidziennik(app: App, profile: Profile?, loginStore: LoginStore) : Da override fun satisfyLoginMethods() { loginMethods.clear() if (isWebLoginValid()) { - loginMethods += LOGIN_METHOD_MOBIDZIENNIK_WEB + loginMethods += LoginMethod.MOBIDZIENNIK_WEB } } @@ -87,7 +87,7 @@ class DataMobidziennik(app: App, profile: Profile?, loginStore: LoginStore) : Da private var mStudentId: Int? = null var studentId: Int get() { mStudentId = mStudentId ?: profile?.getStudentData("studentId", 0); return mStudentId ?: 0 } - set(value) { profile?.putStudentData("studentId", value) ?: return; mStudentId = value } + set(value) { profile["studentId"] = value; mStudentId = value } /* __ __ _ \ \ / / | | @@ -127,7 +127,7 @@ class DataMobidziennik(app: App, profile: Profile?, loginStore: LoginStore) : Da */ var globalId: String? get() { mGlobalId = mGlobalId ?: profile?.getStudentData("globalId", null); return mGlobalId } - set(value) { profile?.putStudentData("globalId", value) ?: return; mGlobalId = value } + set(value) { profile["globalId"] = value; mGlobalId = value } private var mGlobalId: String? = null /** @@ -137,7 +137,7 @@ class DataMobidziennik(app: App, profile: Profile?, loginStore: LoginStore) : Da */ var loginEmail: String? get() { mLoginEmail = mLoginEmail ?: profile?.getStudentData("email", null); return mLoginEmail } - set(value) { profile?.putStudentData("email", value); mLoginEmail = value } + set(value) { profile["email"] = value; mLoginEmail = value } private var mLoginEmail: String? = null /** @@ -146,7 +146,7 @@ class DataMobidziennik(app: App, profile: Profile?, loginStore: LoginStore) : Da */ var loginId: String? get() { mLoginId = mLoginId ?: profile?.getStudentData("loginId", null); return mLoginId } - set(value) { profile?.putStudentData("loginId", value) ?: return; mLoginId = value } + set(value) { profile["loginId"] = value; mLoginId = value } private var mLoginId: String? = null /** @@ -154,7 +154,7 @@ class DataMobidziennik(app: App, profile: Profile?, loginStore: LoginStore) : Da */ var ciasteczkoAutoryzacji: String? get() { mCiasteczkoAutoryzacji = mCiasteczkoAutoryzacji ?: profile?.getStudentData("ciasteczkoAutoryzacji", null); return mCiasteczkoAutoryzacji } - set(value) { profile?.putStudentData("ciasteczkoAutoryzacji", value) ?: return; mCiasteczkoAutoryzacji = value } + set(value) { profile["ciasteczkoAutoryzacji"] = value; mCiasteczkoAutoryzacji = value } private var mCiasteczkoAutoryzacji: String? = null diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/Mobidziennik.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/Mobidziennik.kt index e19fabca..6d53a6c7 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/Mobidziennik.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/Mobidziennik.kt @@ -11,12 +11,15 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.Mobidzien import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.web.* import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.firstlogin.MobidziennikFirstLogin import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.login.MobidziennikLogin +import pl.szczodrzynski.edziennik.data.api.events.UserActionRequiredEvent import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikCallback import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikInterface import pl.szczodrzynski.edziennik.data.api.models.ApiError import pl.szczodrzynski.edziennik.data.db.entity.LoginStore import pl.szczodrzynski.edziennik.data.db.entity.Profile import pl.szczodrzynski.edziennik.data.db.entity.Teacher +import pl.szczodrzynski.edziennik.data.db.enums.FeatureType +import pl.szczodrzynski.edziennik.data.db.enums.LoginMethod import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull import pl.szczodrzynski.edziennik.data.db.full.EventFull import pl.szczodrzynski.edziennik.data.db.full.MessageFull @@ -53,19 +56,19 @@ class Mobidziennik(val app: App, val profile: Profile?, val loginStore: LoginSto |_| |_| |_|\___| /_/ \_\_|\__, |\___/|_| |_|\__|_| |_|_| |_| |_| __/ | |__*/ - override fun sync(featureIds: List, viewId: Int?, onlyEndpoints: List?, arguments: JsonObject?) { + override fun sync(featureTypes: Set?, onlyEndpoints: Set?, arguments: JsonObject?) { data.arguments = arguments - data.prepare(mobidziennikLoginMethods, MobidziennikFeatures, featureIds, viewId, onlyEndpoints) + data.prepare(MobidziennikFeatures, featureTypes, onlyEndpoints) login() } - private fun login(loginMethodId: Int? = null, afterLogin: (() -> Unit)? = null) { - d(TAG, "Trying to login with ${data.targetLoginMethodIds}") + private fun login(loginMethod: LoginMethod? = null, afterLogin: (() -> Unit)? = null) { + d(TAG, "Trying to login with ${data.targetLoginMethods}") if (internalErrorList.isNotEmpty()) { d(TAG, " - Internal errors:") internalErrorList.forEach { d(TAG, " - code $it") } } - loginMethodId?.let { data.prepareFor(mobidziennikLoginMethods, it) } + loginMethod?.let { data.prepareFor(it) } afterLogin?.let { this.afterLogin = it } MobidziennikLogin(data) { data() @@ -73,7 +76,7 @@ class Mobidziennik(val app: App, val profile: Profile?, val loginStore: LoginSto } private fun data() { - d(TAG, "Endpoint IDs: ${data.targetEndpointIds}") + d(TAG, "Endpoint IDs: ${data.targetEndpoints}") if (internalErrorList.isNotEmpty()) { d(TAG, " - Internal errors:") internalErrorList.forEach { d(TAG, " - code $it") } @@ -84,15 +87,15 @@ class Mobidziennik(val app: App, val profile: Profile?, val loginStore: LoginSto } override fun getMessage(message: MessageFull) { - login(LOGIN_METHOD_MOBIDZIENNIK_WEB) { + login(LoginMethod.MOBIDZIENNIK_WEB) { MobidziennikWebGetMessage(data, message) { completed() } } } - override fun sendMessage(recipients: List, subject: String, text: String) { - login(LOGIN_METHOD_MOBIDZIENNIK_WEB) { + override fun sendMessage(recipients: Set, subject: String, text: String) { + login(LoginMethod.MOBIDZIENNIK_WEB) { MobidziennikWebSendMessage(data, recipients, subject, text) { completed() } @@ -103,7 +106,7 @@ class Mobidziennik(val app: App, val profile: Profile?, val loginStore: LoginSto override fun getAnnouncement(announcement: AnnouncementFull) {} override fun getAttachment(owner: Any, attachmentId: Long, attachmentName: String) { - login(LOGIN_METHOD_MOBIDZIENNIK_WEB) { + login(LoginMethod.MOBIDZIENNIK_WEB) { MobidziennikWebGetAttachment(data, owner, attachmentId, attachmentName) { completed() } @@ -111,7 +114,7 @@ class Mobidziennik(val app: App, val profile: Profile?, val loginStore: LoginSto } override fun getRecipientList() { - login(LOGIN_METHOD_MOBIDZIENNIK_WEB) { + login(LoginMethod.MOBIDZIENNIK_WEB) { MobidziennikWebGetRecipientList(data) { completed() } @@ -119,7 +122,7 @@ class Mobidziennik(val app: App, val profile: Profile?, val loginStore: LoginSto } override fun getEvent(eventFull: EventFull) { - login(LOGIN_METHOD_MOBIDZIENNIK_WEB) { + login(LoginMethod.MOBIDZIENNIK_WEB) { if (eventFull.isHomework) { MobidziennikWebGetHomework(data, eventFull) { completed() @@ -142,6 +145,7 @@ class Mobidziennik(val app: App, val profile: Profile?, val loginStore: LoginSto private fun wrapCallback(callback: EdziennikCallback): EdziennikCallback { return object : EdziennikCallback { override fun onCompleted() { callback.onCompleted() } + override fun onRequiresUserAction(event: UserActionRequiredEvent) { callback.onRequiresUserAction(event) } override fun onProgress(step: Float) { callback.onProgress(step) } override fun onStartProgress(stringRes: Int) { callback.onStartProgress(stringRes) } override fun onError(apiError: ApiError) { @@ -156,8 +160,8 @@ class Mobidziennik(val app: App, val profile: Profile?, val loginStore: LoginSto ERROR_MOBIDZIENNIK_WEB_NO_SESSION_KEY, ERROR_MOBIDZIENNIK_WEB_NO_SESSION_VALUE, ERROR_MOBIDZIENNIK_WEB_NO_SERVER_ID -> { - data.loginMethods.remove(LOGIN_METHOD_MOBIDZIENNIK_WEB) - data.prepareFor(mobidziennikLoginMethods, LOGIN_METHOD_MOBIDZIENNIK_WEB) + data.loginMethods.remove(LoginMethod.MOBIDZIENNIK_WEB) + data.prepareFor(LoginMethod.MOBIDZIENNIK_WEB) data.webSessionIdExpiryTime = 0 login() } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/MobidziennikFeatures.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/MobidziennikFeatures.kt index 7b39e455..cd2c99ec 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/MobidziennikFeatures.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/MobidziennikFeatures.kt @@ -4,8 +4,10 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik -import pl.szczodrzynski.edziennik.data.api.* import pl.szczodrzynski.edziennik.data.api.models.Feature +import pl.szczodrzynski.edziennik.data.db.enums.FeatureType +import pl.szczodrzynski.edziennik.data.db.enums.LoginMethod +import pl.szczodrzynski.edziennik.data.db.enums.LoginType const val ENDPOINT_MOBIDZIENNIK_API_MAIN = 1000 const val ENDPOINT_MOBIDZIENNIK_WEB_MESSAGES_INBOX = 2011 @@ -23,15 +25,15 @@ const val ENDPOINT_MOBIDZIENNIK_API2_MAIN = 3000 val MobidziennikFeatures = listOf( // always synced - Feature(LOGIN_TYPE_MOBIDZIENNIK, FEATURE_ALWAYS_NEEDED, listOf( - ENDPOINT_MOBIDZIENNIK_API_MAIN to LOGIN_METHOD_MOBIDZIENNIK_WEB, - ENDPOINT_MOBIDZIENNIK_WEB_ACCOUNT_EMAIL to LOGIN_METHOD_MOBIDZIENNIK_WEB - ), listOf(LOGIN_METHOD_MOBIDZIENNIK_WEB)), // TODO divide features into separate view IDs (all with API_MAIN) + Feature(LoginType.MOBIDZIENNIK, FeatureType.ALWAYS_NEEDED, listOf( + ENDPOINT_MOBIDZIENNIK_API_MAIN to LoginMethod.MOBIDZIENNIK_WEB, + ENDPOINT_MOBIDZIENNIK_WEB_ACCOUNT_EMAIL to LoginMethod.MOBIDZIENNIK_WEB + )), // TODO divide features into separate view IDs (all with API_MAIN) // push config - Feature(LOGIN_TYPE_MOBIDZIENNIK, FEATURE_PUSH_CONFIG, listOf( - ENDPOINT_MOBIDZIENNIK_API2_MAIN to LOGIN_METHOD_MOBIDZIENNIK_API2 - ), listOf(LOGIN_METHOD_MOBIDZIENNIK_API2)).withShouldSync { data -> + Feature(LoginType.MOBIDZIENNIK, FeatureType.PUSH_CONFIG, listOf( + ENDPOINT_MOBIDZIENNIK_API2_MAIN to LoginMethod.MOBIDZIENNIK_API2 + )).withShouldSync { data -> !data.app.config.sync.tokenMobidziennikList.contains(data.profileId) }, @@ -42,36 +44,36 @@ val MobidziennikFeatures = listOf( /** * Timetable - web scraping - does nothing if the API_MAIN timetable is enough. */ - Feature(LOGIN_TYPE_MOBIDZIENNIK, FEATURE_TIMETABLE, listOf( - ENDPOINT_MOBIDZIENNIK_WEB_TIMETABLE to LOGIN_METHOD_MOBIDZIENNIK_WEB - ), listOf(LOGIN_METHOD_MOBIDZIENNIK_WEB, LOGIN_METHOD_MOBIDZIENNIK_WEB)), + Feature(LoginType.MOBIDZIENNIK, FeatureType.TIMETABLE, listOf( + ENDPOINT_MOBIDZIENNIK_WEB_TIMETABLE to LoginMethod.MOBIDZIENNIK_WEB + )), /** * Agenda - "API" + web scraping. */ - Feature(LOGIN_TYPE_MOBIDZIENNIK, FEATURE_AGENDA, listOf( - ENDPOINT_MOBIDZIENNIK_API_MAIN to LOGIN_METHOD_MOBIDZIENNIK_WEB, - ENDPOINT_MOBIDZIENNIK_WEB_CALENDAR to LOGIN_METHOD_MOBIDZIENNIK_WEB - ), listOf(LOGIN_METHOD_MOBIDZIENNIK_WEB, LOGIN_METHOD_MOBIDZIENNIK_WEB)), + Feature(LoginType.MOBIDZIENNIK, FeatureType.AGENDA, listOf( + ENDPOINT_MOBIDZIENNIK_API_MAIN to LoginMethod.MOBIDZIENNIK_WEB, + ENDPOINT_MOBIDZIENNIK_WEB_CALENDAR to LoginMethod.MOBIDZIENNIK_WEB + )), /** * Grades - "API" + web scraping. */ - Feature(LOGIN_TYPE_MOBIDZIENNIK, FEATURE_GRADES, listOf( - ENDPOINT_MOBIDZIENNIK_API_MAIN to LOGIN_METHOD_MOBIDZIENNIK_WEB, - ENDPOINT_MOBIDZIENNIK_WEB_GRADES to LOGIN_METHOD_MOBIDZIENNIK_WEB - ), listOf(LOGIN_METHOD_MOBIDZIENNIK_WEB, LOGIN_METHOD_MOBIDZIENNIK_WEB)), + Feature(LoginType.MOBIDZIENNIK, FeatureType.GRADES, listOf( + ENDPOINT_MOBIDZIENNIK_API_MAIN to LoginMethod.MOBIDZIENNIK_WEB, + ENDPOINT_MOBIDZIENNIK_WEB_GRADES to LoginMethod.MOBIDZIENNIK_WEB + )), /** * Behaviour - "API" + web scraping. */ - Feature(LOGIN_TYPE_MOBIDZIENNIK, FEATURE_BEHAVIOUR, listOf( - ENDPOINT_MOBIDZIENNIK_API_MAIN to LOGIN_METHOD_MOBIDZIENNIK_WEB, - ENDPOINT_MOBIDZIENNIK_WEB_NOTICES to LOGIN_METHOD_MOBIDZIENNIK_WEB - ), listOf(LOGIN_METHOD_MOBIDZIENNIK_WEB, LOGIN_METHOD_MOBIDZIENNIK_WEB)), + Feature(LoginType.MOBIDZIENNIK, FeatureType.BEHAVIOUR, listOf( + ENDPOINT_MOBIDZIENNIK_API_MAIN to LoginMethod.MOBIDZIENNIK_WEB, + ENDPOINT_MOBIDZIENNIK_WEB_NOTICES to LoginMethod.MOBIDZIENNIK_WEB + )), /** * Attendance - only web scraping. */ - Feature(LOGIN_TYPE_MOBIDZIENNIK, FEATURE_ATTENDANCE, listOf( - ENDPOINT_MOBIDZIENNIK_WEB_ATTENDANCE to LOGIN_METHOD_MOBIDZIENNIK_WEB - ), listOf(LOGIN_METHOD_MOBIDZIENNIK_WEB)), + Feature(LoginType.MOBIDZIENNIK, FeatureType.ATTENDANCE, listOf( + ENDPOINT_MOBIDZIENNIK_WEB_ATTENDANCE to LoginMethod.MOBIDZIENNIK_WEB + )), @@ -80,38 +82,38 @@ val MobidziennikFeatures = listOf( /** * Messages inbox - using web scraper. */ - Feature(LOGIN_TYPE_MOBIDZIENNIK, FEATURE_MESSAGES_INBOX, listOf( - ENDPOINT_MOBIDZIENNIK_WEB_MESSAGES_INBOX to LOGIN_METHOD_MOBIDZIENNIK_WEB, - ENDPOINT_MOBIDZIENNIK_WEB_MESSAGES_ALL to LOGIN_METHOD_MOBIDZIENNIK_WEB - ), listOf(LOGIN_METHOD_MOBIDZIENNIK_WEB)), + Feature(LoginType.MOBIDZIENNIK, FeatureType.MESSAGES_INBOX, listOf( + ENDPOINT_MOBIDZIENNIK_WEB_MESSAGES_INBOX to LoginMethod.MOBIDZIENNIK_WEB, + ENDPOINT_MOBIDZIENNIK_WEB_MESSAGES_ALL to LoginMethod.MOBIDZIENNIK_WEB + )), /** * Messages sent - using web scraper. */ - Feature(LOGIN_TYPE_MOBIDZIENNIK, FEATURE_MESSAGES_SENT, listOf( - ENDPOINT_MOBIDZIENNIK_WEB_MESSAGES_SENT to LOGIN_METHOD_MOBIDZIENNIK_WEB, - ENDPOINT_MOBIDZIENNIK_WEB_MESSAGES_ALL to LOGIN_METHOD_MOBIDZIENNIK_WEB - ), listOf(LOGIN_METHOD_MOBIDZIENNIK_WEB)) + Feature(LoginType.MOBIDZIENNIK, FeatureType.MESSAGES_SENT, listOf( + ENDPOINT_MOBIDZIENNIK_WEB_MESSAGES_SENT to LoginMethod.MOBIDZIENNIK_WEB, + ENDPOINT_MOBIDZIENNIK_WEB_MESSAGES_ALL to LoginMethod.MOBIDZIENNIK_WEB + )) // lucky number possibilities // all endpoints that may supply the lucky number - /*Feature(LOGIN_TYPE_MOBIDZIENNIK, FEATURE_LUCKY_NUMBER, listOf( - ENDPOINT_MOBIDZIENNIK_WEB_MANUALS to LOGIN_METHOD_MOBIDZIENNIK_WEB - ), listOf(LOGIN_METHOD_MOBIDZIENNIK_WEB)).apply { priority = 10 }, + /*Feature(LoginType.MOBIDZIENNIK, FeatureType.LUCKY_NUMBER, listOf( + ENDPOINT_MOBIDZIENNIK_WEB_MANUALS to LoginMethod.MOBIDZIENNIK_WEB + )).apply { priority = 10 }, - Feature(LOGIN_TYPE_MOBIDZIENNIK, FEATURE_LUCKY_NUMBER, listOf( - ENDPOINT_MOBIDZIENNIK_WEB_CALENDAR to LOGIN_METHOD_MOBIDZIENNIK_WEB - ), listOf(LOGIN_METHOD_MOBIDZIENNIK_WEB)).apply { priority = 3 }, + Feature(LoginType.MOBIDZIENNIK, FeatureType.LUCKY_NUMBER, listOf( + ENDPOINT_MOBIDZIENNIK_WEB_CALENDAR to LoginMethod.MOBIDZIENNIK_WEB + )).apply { priority = 3 }, - Feature(LOGIN_TYPE_MOBIDZIENNIK, FEATURE_LUCKY_NUMBER, listOf( - ENDPOINT_MOBIDZIENNIK_WEB_GRADES to LOGIN_METHOD_MOBIDZIENNIK_WEB - ), listOf(LOGIN_METHOD_MOBIDZIENNIK_WEB)).apply { priority = 2 }, + Feature(LoginType.MOBIDZIENNIK, FeatureType.LUCKY_NUMBER, listOf( + ENDPOINT_MOBIDZIENNIK_WEB_GRADES to LoginMethod.MOBIDZIENNIK_WEB + )).apply { priority = 2 }, - Feature(LOGIN_TYPE_MOBIDZIENNIK, FEATURE_LUCKY_NUMBER, listOf( - ENDPOINT_MOBIDZIENNIK_WEB_NOTICES to LOGIN_METHOD_MOBIDZIENNIK_WEB - ), listOf(LOGIN_METHOD_MOBIDZIENNIK_WEB)).apply { priority = 1 }, + Feature(LoginType.MOBIDZIENNIK, FeatureType.LUCKY_NUMBER, listOf( + ENDPOINT_MOBIDZIENNIK_WEB_NOTICES to LoginMethod.MOBIDZIENNIK_WEB + )).apply { priority = 1 }, - Feature(LOGIN_TYPE_MOBIDZIENNIK, FEATURE_LUCKY_NUMBER, listOf( - ENDPOINT_MOBIDZIENNIK_WEB_ATTENDANCE to LOGIN_METHOD_MOBIDZIENNIK_WEB - ), listOf(LOGIN_METHOD_MOBIDZIENNIK_WEB)).apply { priority = 4 }*/ + Feature(LoginType.MOBIDZIENNIK, FeatureType.LUCKY_NUMBER, listOf( + ENDPOINT_MOBIDZIENNIK_WEB_ATTENDANCE to LoginMethod.MOBIDZIENNIK_WEB + )).apply { priority = 4 }*/ ) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/MobidziennikData.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/MobidziennikData.kt index b82f6355..4b58d203 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/MobidziennikData.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/MobidziennikData.kt @@ -21,7 +21,7 @@ class MobidziennikData(val data: DataMobidziennik, val onSuccess: () -> Unit) { } private fun nextEndpoint(onSuccess: () -> Unit) { - if (data.targetEndpointIds.isEmpty()) { + if (data.targetEndpoints.isEmpty()) { onSuccess() return } @@ -29,8 +29,8 @@ class MobidziennikData(val data: DataMobidziennik, val onSuccess: () -> Unit) { onSuccess() return } - val id = data.targetEndpointIds.firstKey() - val lastSync = data.targetEndpointIds.remove(id) + val id = data.targetEndpoints.firstKey() + val lastSync = data.targetEndpoints.remove(id) useEndpoint(id, lastSync) { endpointId -> data.progress(data.progressStep) nextEndpoint(onSuccess) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiAttendance.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiAttendance.kt index 473b3691..26079f44 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiAttendance.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiAttendance.kt @@ -11,6 +11,8 @@ import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_ABSEN import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_PRESENT import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_RELEASED import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType +import pl.szczodrzynski.edziennik.ext.dateToSemester class MobidziennikApiAttendance(val data: DataMobidziennik, rows: List) { init { run { @@ -70,7 +72,7 @@ class MobidziennikApiAttendance(val data: DataMobidziennik, rows: List) data.metadataList.add( Metadata( data.profileId, - Metadata.TYPE_ATTENDANCE, + MetadataType.ATTENDANCE, id, data.profile?.empty ?: false || baseType == Attendance.TYPE_PRESENT_CUSTOM || baseType == Attendance.TYPE_UNKNOWN, data.profile?.empty ?: false || baseType == Attendance.TYPE_PRESENT_CUSTOM || baseType == Attendance.TYPE_UNKNOWN diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiEvents.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiEvents.kt index 7a72d3de..d8d9d7c7 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiEvents.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiEvents.kt @@ -10,6 +10,7 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.DataMobidzienn import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel import pl.szczodrzynski.edziennik.data.db.entity.Event import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Time import java.text.ParseException @@ -68,7 +69,7 @@ class MobidziennikApiEvents(val data: DataMobidziennik, rows: List) { data.metadataList.add( Metadata( data.profileId, - Metadata.TYPE_EVENT, + MetadataType.EVENT, id, data.profile?.empty ?: false, data.profile?.empty ?: false diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiGrades.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiGrades.kt index c3c1b449..91ddedf0 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiGrades.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiGrades.kt @@ -15,6 +15,7 @@ import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_SEMESTER2_ import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_YEAR_FINAL import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_YEAR_PROPOSED import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType class MobidziennikApiGrades(val data: DataMobidziennik, rows: List) { init { data.profile?.also { profile -> run { @@ -91,7 +92,7 @@ class MobidziennikApiGrades(val data: DataMobidziennik, rows: List) { data.metadataList.add( Metadata( data.profileId, - Metadata.TYPE_GRADE, + MetadataType.GRADE, id, data.profile?.empty ?: false, data.profile?.empty ?: false diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiHomework.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiHomework.kt index a79eaded..52f7cdc4 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiHomework.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiHomework.kt @@ -9,6 +9,7 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.DataMobidzienn import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel import pl.szczodrzynski.edziennik.data.db.entity.Event import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType import pl.szczodrzynski.edziennik.utils.html.BetterHtml import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Time @@ -47,7 +48,7 @@ class MobidziennikApiHomework(val data: DataMobidziennik, rows: List) { data.metadataList.add( Metadata( data.profileId, - Metadata.TYPE_HOMEWORK, + MetadataType.HOMEWORK, id, data.profile?.empty ?: false, data.profile?.empty ?: false diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiNotices.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiNotices.kt index 66bfe1fd..c765e1ec 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiNotices.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiNotices.kt @@ -7,6 +7,7 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.api import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.DataMobidziennik import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.data.db.entity.Notice +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType import pl.szczodrzynski.edziennik.utils.models.Date class MobidziennikApiNotices(val data: DataMobidziennik, rows: List) { @@ -48,7 +49,7 @@ class MobidziennikApiNotices(val data: DataMobidziennik, rows: List) { data.metadataList.add( Metadata( data.profileId, - Metadata.TYPE_NOTICE, + MetadataType.NOTICE, id, data.profile?.empty ?: false, data.profile?.empty ?: false diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiTimetable.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiTimetable.kt index 8e490832..8aeca3f9 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiTimetable.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiTimetable.kt @@ -11,6 +11,7 @@ import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel import pl.szczodrzynski.edziennik.data.db.entity.Lesson import pl.szczodrzynski.edziennik.data.db.entity.LessonRange import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType import pl.szczodrzynski.edziennik.ext.fixName import pl.szczodrzynski.edziennik.ext.keys import pl.szczodrzynski.edziennik.ext.singleOrNull @@ -87,6 +88,7 @@ class MobidziennikApiTimetable(val data: DataMobidziennik, rows: List) { } it.id = it.buildId() + it.ownerId = it.buildOwnerId() val seen = profile.empty || date < Date.getToday() @@ -94,7 +96,7 @@ class MobidziennikApiTimetable(val data: DataMobidziennik, rows: List) { data.metadataList.add( Metadata( data.profileId, - Metadata.TYPE_LESSON_CHANGE, + MetadataType.LESSON_CHANGE, it.id, seen, seen diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikLuckyNumberExtractor.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikLuckyNumberExtractor.kt index ece23d7f..2ad8a61f 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikLuckyNumberExtractor.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikLuckyNumberExtractor.kt @@ -8,6 +8,7 @@ import pl.szczodrzynski.edziennik.data.api.Regexes import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.DataMobidziennik import pl.szczodrzynski.edziennik.data.db.entity.LuckyNumber import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType import pl.szczodrzynski.edziennik.utils.models.Date class MobidziennikLuckyNumberExtractor(val data: DataMobidziennik, text: String) { @@ -26,7 +27,7 @@ class MobidziennikLuckyNumberExtractor(val data: DataMobidziennik, text: String) data.metadataList.add( Metadata( data.profileId, - Metadata.TYPE_LUCKY_NUMBER, + MetadataType.LUCKY_NUMBER, luckyNumberObject.date.value.toLong(), true, data.profile?.empty ?: false diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebAccountEmail.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebAccountEmail.kt index 9dc3878c..6c252678 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebAccountEmail.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebAccountEmail.kt @@ -10,6 +10,7 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.ENDPOINT_MOBID import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.MobidziennikWeb import pl.szczodrzynski.edziennik.ext.DAY import pl.szczodrzynski.edziennik.ext.get +import pl.szczodrzynski.edziennik.ext.isNotNullNorBlank class MobidziennikWebAccountEmail(override val data: DataMobidziennik, override val lastSync: Long?, @@ -24,7 +25,8 @@ class MobidziennikWebAccountEmail(override val data: DataMobidziennik, MobidziennikLuckyNumberExtractor(data, text) val email = Regexes.MOBIDZIENNIK_ACCOUNT_EMAIL.find(text)?.let { it[1] } - data.loginEmail = email + if (email.isNotNullNorBlank()) + data.loginEmail = email data.setSyncNext(ENDPOINT_MOBIDZIENNIK_WEB_ACCOUNT_EMAIL, if (email == null) 3* DAY else 7* DAY) onSuccess(ENDPOINT_MOBIDZIENNIK_WEB_ACCOUNT_EMAIL) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebAttendance.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebAttendance.kt index 9160e4d6..08ce6457 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebAttendance.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebAttendance.kt @@ -20,6 +20,8 @@ import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_RELEA import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_UNKNOWN import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType +import pl.szczodrzynski.edziennik.ext.dateToSemester import pl.szczodrzynski.edziennik.ext.fixName import pl.szczodrzynski.edziennik.ext.get import pl.szczodrzynski.edziennik.ext.singleOrNull @@ -246,7 +248,7 @@ class MobidziennikWebAttendance(override val data: DataMobidziennik, data.metadataList.add( Metadata( data.profileId, - Metadata.TYPE_ATTENDANCE, + MetadataType.ATTENDANCE, id, data.profile?.empty ?: false || baseType == Attendance.TYPE_PRESENT_CUSTOM || baseType == TYPE_UNKNOWN, data.profile?.empty ?: false || baseType == Attendance.TYPE_PRESENT_CUSTOM || baseType == TYPE_UNKNOWN diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebCalendar.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebCalendar.kt index b6a10335..11cfa69d 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebCalendar.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebCalendar.kt @@ -12,6 +12,7 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.Mobidzien import pl.szczodrzynski.edziennik.data.db.entity.Event import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType import pl.szczodrzynski.edziennik.ext.getString import pl.szczodrzynski.edziennik.utils.Utils.crc16 import pl.szczodrzynski.edziennik.utils.models.Date @@ -86,7 +87,7 @@ class MobidziennikWebCalendar(override val data: DataMobidziennik, data.metadataList.add( Metadata( profileId, - Metadata.TYPE_EVENT, + MetadataType.EVENT, eventObject.id, profile?.empty ?: false, profile?.empty ?: false diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebGetMessage.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebGetMessage.kt index 3e196ec0..23650982 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebGetMessage.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebGetMessage.kt @@ -11,6 +11,7 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.DataMobidzienn import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.MobidziennikWeb import pl.szczodrzynski.edziennik.data.api.events.MessageGetEvent import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType import pl.szczodrzynski.edziennik.data.db.full.MessageFull import pl.szczodrzynski.edziennik.data.db.full.MessageRecipientFull import pl.szczodrzynski.edziennik.ext.fixName @@ -133,7 +134,7 @@ class MobidziennikWebGetMessage(override val data: DataMobidziennik, if (!message.seen) { // TODO discover why this monstrosity instead of MetadataDao.setSeen data.setSeenMetadataList.add(Metadata( message.profileId, - Metadata.TYPE_MESSAGE, + MetadataType.MESSAGE, message.id, true, true diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebGrades.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebGrades.kt index 285441b6..dcf77fee 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebGrades.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebGrades.kt @@ -14,6 +14,8 @@ import pl.szczodrzynski.edziennik.data.db.entity.Grade import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_NORMAL import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType +import pl.szczodrzynski.edziennik.ext.dateToSemester import pl.szczodrzynski.edziennik.ext.fixWhiteSpaces import pl.szczodrzynski.edziennik.ext.get import pl.szczodrzynski.edziennik.ext.singleOrNull @@ -135,7 +137,7 @@ class MobidziennikWebGrades(override val data: DataMobidziennik, data.metadataList.add( Metadata( profileId, - Metadata.TYPE_GRADE, + MetadataType.GRADE, gradeObject.id, profile.empty, profile.empty diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebMessagesAll.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebMessagesAll.kt index aebff41e..99715831 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebMessagesAll.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebMessagesAll.kt @@ -13,6 +13,7 @@ import pl.szczodrzynski.edziennik.data.db.entity.Message.Companion.TYPE_RECEIVED import pl.szczodrzynski.edziennik.data.db.entity.Message.Companion.TYPE_SENT import pl.szczodrzynski.edziennik.data.db.entity.MessageRecipient import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType import pl.szczodrzynski.edziennik.ext.DAY import pl.szczodrzynski.edziennik.ext.fixName import pl.szczodrzynski.edziennik.ext.singleOrNull @@ -84,7 +85,7 @@ class MobidziennikWebMessagesAll(override val data: DataMobidziennik, ) data.messageList.add(message) - data.metadataList.add(Metadata(profileId, Metadata.TYPE_MESSAGE, message.id, true, true)) + data.metadataList.add(Metadata(profileId, MetadataType.MESSAGE, message.id, true, true)) } // sync every 7 days as we probably don't expect more than diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebMessagesInbox.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebMessagesInbox.kt index b3819784..698f125f 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebMessagesInbox.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebMessagesInbox.kt @@ -12,6 +12,7 @@ import pl.szczodrzynski.edziennik.data.db.entity.Message import pl.szczodrzynski.edziennik.data.db.entity.MessageRecipient import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType import pl.szczodrzynski.edziennik.ext.fixName import pl.szczodrzynski.edziennik.ext.singleOrNull import pl.szczodrzynski.edziennik.utils.models.Date @@ -83,7 +84,7 @@ class MobidziennikWebMessagesInbox(override val data: DataMobidziennik, data.setSeenMetadataList.add( Metadata( profileId, - Metadata.TYPE_MESSAGE, + MetadataType.MESSAGE, message.id, isRead, isRead || profile?.empty ?: false diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebMessagesSent.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebMessagesSent.kt index 7f249cf6..2e15751c 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebMessagesSent.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebMessagesSent.kt @@ -5,7 +5,6 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.web import org.jsoup.Jsoup -import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_MESSAGES import pl.szczodrzynski.edziennik.data.api.Regexes import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.DataMobidziennik import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.ENDPOINT_MOBIDZIENNIK_WEB_MESSAGES_SENT @@ -14,6 +13,8 @@ import pl.szczodrzynski.edziennik.data.db.entity.Message import pl.szczodrzynski.edziennik.data.db.entity.MessageRecipient import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.data.db.enums.FeatureType +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType import pl.szczodrzynski.edziennik.ext.DAY import pl.szczodrzynski.edziennik.ext.fixName import pl.szczodrzynski.edziennik.ext.get @@ -100,14 +101,14 @@ class MobidziennikWebMessagesSent(override val data: DataMobidziennik, data.setSeenMetadataList.add( Metadata( profileId, - Metadata.TYPE_MESSAGE, + MetadataType.MESSAGE, message.id, true, true )) } - data.setSyncNext(ENDPOINT_MOBIDZIENNIK_WEB_MESSAGES_SENT, 1* DAY, DRAWER_ITEM_MESSAGES) + data.setSyncNext(ENDPOINT_MOBIDZIENNIK_WEB_MESSAGES_SENT, 1* DAY, FeatureType.MESSAGES_SENT) onSuccess(ENDPOINT_MOBIDZIENNIK_WEB_MESSAGES_SENT) } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebSendMessage.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebSendMessage.kt index b5a6c5ee..d4965b36 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebSendMessage.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebSendMessage.kt @@ -11,9 +11,10 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.Mobidzien import pl.szczodrzynski.edziennik.data.api.events.MessageSentEvent import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.data.db.entity.Teacher +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType class MobidziennikWebSendMessage(override val data: DataMobidziennik, - val recipients: List, + val recipients: Set, val subject: String, val text: String, val onSuccess: () -> Unit @@ -43,7 +44,7 @@ class MobidziennikWebSendMessage(override val data: DataMobidziennik, // TODO create MobidziennikWebMessagesSent and replace this MobidziennikWebMessagesAll(data, null) { val message = data.messageList.firstOrNull { it.isSent && it.subject == subject } - val metadata = data.metadataList.firstOrNull { it.thingType == Metadata.TYPE_MESSAGE && it.thingId == message?.id } + val metadata = data.metadataList.firstOrNull { it.thingType == MetadataType.MESSAGE && it.thingId == message?.id } val event = MessageSentEvent(data.profileId, message, message?.addedDate) EventBus.getDefault().postSticky(event) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebTimetable.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebTimetable.kt index dd0ff03f..206c517e 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebTimetable.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebTimetable.kt @@ -15,6 +15,7 @@ import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel.Timetable.Comp import pl.szczodrzynski.edziennik.data.db.entity.Lesson import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType import pl.szczodrzynski.edziennik.ext.DAY import pl.szczodrzynski.edziennik.ext.MS import pl.szczodrzynski.edziennik.ext.get @@ -335,6 +336,7 @@ class MobidziennikWebTimetable( } it.id = it.buildId() + it.ownerId = it.buildOwnerId() it.isExtra = isExtra val seen = profile?.empty == false || lessonDate < Date.getToday() @@ -343,7 +345,7 @@ class MobidziennikWebTimetable( data.metadataList.add( Metadata( data.profileId, - Metadata.TYPE_LESSON_CHANGE, + MetadataType.LESSON_CHANGE, it.id, seen, seen diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/firstlogin/MobidziennikFirstLogin.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/firstlogin/MobidziennikFirstLogin.kt index 91390c5c..f1e67115 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/firstlogin/MobidziennikFirstLogin.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/firstlogin/MobidziennikFirstLogin.kt @@ -1,12 +1,12 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.firstlogin import org.greenrobot.eventbus.EventBus -import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_MOBIDZIENNIK import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.DataMobidziennik import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.MobidziennikWeb import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.login.MobidziennikLoginWeb import pl.szczodrzynski.edziennik.data.api.events.FirstLoginFinishedEvent import pl.szczodrzynski.edziennik.data.db.entity.Profile +import pl.szczodrzynski.edziennik.data.db.enums.LoginType import pl.szczodrzynski.edziennik.ext.fixName import pl.szczodrzynski.edziennik.ext.set import pl.szczodrzynski.edziennik.utils.models.Date @@ -20,9 +20,7 @@ class MobidziennikFirstLogin(val data: DataMobidziennik, val onSuccess: () -> Un private val profileList = mutableListOf() init { - val loginStoreId = data.loginStore.id - val loginStoreType = LOGIN_TYPE_MOBIDZIENNIK - var firstProfileId = loginStoreId + var firstProfileId = data.loginStore.id MobidziennikLoginWeb(data) { web.webGet(TAG, "/api/zrzutbazy") { text -> @@ -66,8 +64,8 @@ class MobidziennikFirstLogin(val data: DataMobidziennik, val onSuccess: () -> Un val profile = Profile( firstProfileId++, - loginStoreId, - loginStoreType, + data.loginStore.id, + LoginType.MOBIDZIENNIK, studentNameLong, data.loginUsername, studentNameLong, diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/login/MobidziennikLogin.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/login/MobidziennikLogin.kt index 7bc79a7b..308087fb 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/login/MobidziennikLogin.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/login/MobidziennikLogin.kt @@ -5,9 +5,8 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.login import pl.szczodrzynski.edziennik.R -import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_MOBIDZIENNIK_API2 -import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_MOBIDZIENNIK_WEB import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.DataMobidziennik +import pl.szczodrzynski.edziennik.data.db.enums.LoginMethod import pl.szczodrzynski.edziennik.utils.Utils class MobidziennikLogin(val data: DataMobidziennik, val onSuccess: () -> Unit) { @@ -22,7 +21,7 @@ class MobidziennikLogin(val data: DataMobidziennik, val onSuccess: () -> Unit) { } private fun nextLoginMethod(onSuccess: () -> Unit) { - if (data.targetLoginMethodIds.isEmpty()) { + if (data.targetLoginMethods.isEmpty()) { onSuccess() return } @@ -30,30 +29,31 @@ class MobidziennikLogin(val data: DataMobidziennik, val onSuccess: () -> Unit) { onSuccess() return } - useLoginMethod(data.targetLoginMethodIds.removeAt(0)) { usedMethodId -> + useLoginMethod(data.targetLoginMethods.removeAt(0)) { usedMethod -> data.progress(data.progressStep) - if (usedMethodId != -1) - data.loginMethods.add(usedMethodId) + if (usedMethod != null) + data.loginMethods.add(usedMethod) nextLoginMethod(onSuccess) } } - private fun useLoginMethod(loginMethodId: Int, onSuccess: (usedMethodId: Int) -> Unit) { + private fun useLoginMethod(loginMethod: LoginMethod, onSuccess: (usedMethod: LoginMethod?) -> Unit) { // this should never be true - if (data.loginMethods.contains(loginMethodId)) { - onSuccess(-1) + if (data.loginMethods.contains(loginMethod)) { + onSuccess(null) return } - Utils.d(TAG, "Using login method $loginMethodId") - when (loginMethodId) { - LOGIN_METHOD_MOBIDZIENNIK_WEB -> { + Utils.d(TAG, "Using login method $loginMethod") + when (loginMethod) { + LoginMethod.MOBIDZIENNIK_WEB -> { data.startProgress(R.string.edziennik_progress_login_mobidziennik_web) - MobidziennikLoginWeb(data) { onSuccess(loginMethodId) } + MobidziennikLoginWeb(data) { onSuccess(loginMethod) } } - LOGIN_METHOD_MOBIDZIENNIK_API2 -> { + LoginMethod.MOBIDZIENNIK_API2 -> { data.startProgress(R.string.edziennik_progress_login_mobidziennik_api2) - MobidziennikLoginApi2(data) { onSuccess(loginMethodId) } + MobidziennikLoginApi2(data) { onSuccess(loginMethod) } } + else -> {} } } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/login/MobidziennikLoginApi2.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/login/MobidziennikLoginApi2.kt index 9258886b..01d7b9b7 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/login/MobidziennikLoginApi2.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/login/MobidziennikLoginApi2.kt @@ -16,6 +16,7 @@ import pl.szczodrzynski.edziennik.data.api.models.ApiError import pl.szczodrzynski.edziennik.ext.JsonObject import pl.szczodrzynski.edziennik.ext.getJsonObject import pl.szczodrzynski.edziennik.ext.getString +import pl.szczodrzynski.edziennik.ext.isNotNullNorBlank import pl.szczodrzynski.edziennik.ext.isNotNullNorEmpty import pl.szczodrzynski.edziennik.utils.Utils @@ -77,7 +78,9 @@ class MobidziennikLoginApi2(val data: DataMobidziennik, val onSuccess: () -> Uni } } - data.loginEmail = json.getString("email") + val email = json.getString("email") + if (email.isNotNullNorBlank()) + data.loginEmail = email data.globalId = json.getString("id_global") data.loginId = json.getString("login") onSuccess() diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/DataPodlasie.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/DataPodlasie.kt index 0a5cce84..afbe53eb 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/DataPodlasie.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/DataPodlasie.kt @@ -5,12 +5,14 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.podlasie import pl.szczodrzynski.edziennik.App -import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_PODLASIE_API import pl.szczodrzynski.edziennik.data.api.models.Data import pl.szczodrzynski.edziennik.data.db.entity.LoginStore import pl.szczodrzynski.edziennik.data.db.entity.Profile +import pl.szczodrzynski.edziennik.data.db.enums.LoginMethod import pl.szczodrzynski.edziennik.ext.crc32 +import pl.szczodrzynski.edziennik.ext.getStudentData import pl.szczodrzynski.edziennik.ext.isNotNullNorEmpty +import pl.szczodrzynski.edziennik.ext.set class DataPodlasie(app: App, profile: Profile?, loginStore: LoginStore) : Data(app, profile, loginStore) { @@ -19,7 +21,7 @@ class DataPodlasie(app: App, profile: Profile?, loginStore: LoginStore) : Data(a override fun satisfyLoginMethods() { loginMethods.clear() if (isApiLoginValid()) - loginMethods += LOGIN_METHOD_PODLASIE_API + loginMethods += LoginMethod.PODLASIE_API } override fun generateUserCode(): String = "$schoolShortName:$loginShort:${studentId?.crc32()}" @@ -40,7 +42,7 @@ class DataPodlasie(app: App, profile: Profile?, loginStore: LoginStore) : Data(a private var mApiUrl: String? = null var apiUrl: String? get() { mApiUrl = mApiUrl ?: profile?.getStudentData("apiUrl", null); return mApiUrl } - set(value) { profile?.putStudentData("apiUrl", value) ?: return; mApiUrl = value } + set(value) { profile["apiUrl"] = value; mApiUrl = value } /* ____ _ _ / __ \| | | | @@ -51,32 +53,32 @@ class DataPodlasie(app: App, profile: Profile?, loginStore: LoginStore) : Data(a private var mStudentId: String? = null var studentId: String? get() { mStudentId = mStudentId ?: profile?.getStudentData("studentId", null); return mStudentId } - set(value) { profile?.putStudentData("studentId", value) ?: return; mStudentId = value } + set(value) { profile["studentId"] = value; mStudentId = value } private var mStudentLogin: String? = null var studentLogin: String? get() { mStudentLogin = mStudentLogin ?: profile?.getStudentData("studentLogin", null); return mStudentLogin } - set(value) { profile?.putStudentData("studentLogin", value) ?: return; mStudentLogin = value } + set(value) { profile["studentLogin"] = value; mStudentLogin = value } private var mSchoolName: String? = null var schoolName: String? get() { mSchoolName = mSchoolName ?: profile?.getStudentData("schoolName", null); return mSchoolName } - set(value) { profile?.putStudentData("schoolName", value) ?: return; mSchoolName = value } + set(value) { profile["schoolName"] = value; mSchoolName = value } private var mClassName: String? = null var className: String? get() { mClassName = mClassName ?: profile?.getStudentData("className", null); return mClassName } - set(value) { profile?.putStudentData("className", value) ?: return; mClassName = value } + set(value) { profile["className"] = value; mClassName = value } private var mSchoolYear: String? = null var schoolYear: String? get() { mSchoolYear = mSchoolYear ?: profile?.getStudentData("schoolYear", null); return mSchoolYear } - set(value) { profile?.putStudentData("schoolYear", value) ?: return; mSchoolYear = value } + set(value) { profile["schoolYear"] = value; mSchoolYear = value } private var mCurrentSemester: Int? = null var currentSemester: Int get() { mCurrentSemester = mCurrentSemester ?: profile?.getStudentData("currentSemester", 0); return mCurrentSemester ?: 0 } - set(value) { profile?.putStudentData("currentSemester", value) ?: return; mCurrentSemester = value } + set(value) { profile["currentSemester"] = value; mCurrentSemester = value } val schoolShortName: String? get() = studentLogin?.split('@')?.get(1)?.replace(".podlaskie.pl", "") diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/Podlasie.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/Podlasie.kt index d7ec441f..660ba2e9 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/Podlasie.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/Podlasie.kt @@ -12,14 +12,15 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.podlasie.data.PodlasieData import pl.szczodrzynski.edziennik.data.api.edziennik.podlasie.firstlogin.PodlasieFirstLogin import pl.szczodrzynski.edziennik.data.api.edziennik.podlasie.login.PodlasieLogin import pl.szczodrzynski.edziennik.data.api.events.AttachmentGetEvent +import pl.szczodrzynski.edziennik.data.api.events.UserActionRequiredEvent import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikCallback import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikInterface import pl.szczodrzynski.edziennik.data.api.models.ApiError -import pl.szczodrzynski.edziennik.data.api.podlasieLoginMethods import pl.szczodrzynski.edziennik.data.api.prepare import pl.szczodrzynski.edziennik.data.db.entity.LoginStore import pl.szczodrzynski.edziennik.data.db.entity.Profile import pl.szczodrzynski.edziennik.data.db.entity.Teacher +import pl.szczodrzynski.edziennik.data.db.enums.FeatureType import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull import pl.szczodrzynski.edziennik.data.db.full.EventFull import pl.szczodrzynski.edziennik.data.db.full.MessageFull @@ -54,11 +55,11 @@ class Podlasie(val app: App, val profile: Profile?, val loginStore: LoginStore, |_| |_| |_|\___| /_/ \_\_|\__, |\___/|_| |_|\__|_| |_|_| |_| |_| __/ | |__*/ - override fun sync(featureIds: List, viewId: Int?, onlyEndpoints: List?, arguments: JsonObject?) { + override fun sync(featureTypes: Set?, onlyEndpoints: Set?, arguments: JsonObject?) { data.arguments = arguments - data.prepare(podlasieLoginMethods, PodlasieFeatures, featureIds, viewId, onlyEndpoints) - Utils.d(TAG, "LoginMethod IDs: ${data.targetLoginMethodIds}") - Utils.d(TAG, "Endpoint IDs: ${data.targetEndpointIds}") + data.prepare(PodlasieFeatures, featureTypes, onlyEndpoints) + Utils.d(TAG, "LoginMethod IDs: ${data.targetLoginMethods}") + Utils.d(TAG, "Endpoint IDs: ${data.targetEndpoints}") PodlasieLogin(data) { PodlasieData(data) { completed() @@ -70,7 +71,7 @@ class Podlasie(val app: App, val profile: Profile?, val loginStore: LoginStore, } - override fun sendMessage(recipients: List, subject: String, text: String) { + override fun sendMessage(recipients: Set, subject: String, text: String) { } @@ -142,6 +143,10 @@ class Podlasie(val app: App, val profile: Profile?, val loginStore: LoginStore, callback.onCompleted() } + override fun onRequiresUserAction(event: UserActionRequiredEvent) { + callback.onRequiresUserAction(event) + } + override fun onProgress(step: Float) { callback.onProgress(step) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/PodlasieFeatures.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/PodlasieFeatures.kt index 82c6f659..6d131990 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/PodlasieFeatures.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/PodlasieFeatures.kt @@ -4,15 +4,15 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.podlasie -import pl.szczodrzynski.edziennik.data.api.FEATURE_ALWAYS_NEEDED -import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_PODLASIE_API -import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_PODLASIE import pl.szczodrzynski.edziennik.data.api.models.Feature +import pl.szczodrzynski.edziennik.data.db.enums.FeatureType +import pl.szczodrzynski.edziennik.data.db.enums.LoginMethod +import pl.szczodrzynski.edziennik.data.db.enums.LoginType const val ENDPOINT_PODLASIE_API_MAIN = 1001 val PodlasieFeatures = listOf( - Feature(LOGIN_TYPE_PODLASIE, FEATURE_ALWAYS_NEEDED, listOf( - ENDPOINT_PODLASIE_API_MAIN to LOGIN_METHOD_PODLASIE_API - ), listOf(LOGIN_METHOD_PODLASIE_API)) + Feature(LoginType.PODLASIE, FeatureType.ALWAYS_NEEDED, listOf( + ENDPOINT_PODLASIE_API_MAIN to LoginMethod.PODLASIE_API + )) ) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/PodlasieData.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/PodlasieData.kt index ce1197b8..f1117537 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/PodlasieData.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/PodlasieData.kt @@ -20,7 +20,7 @@ class PodlasieData(val data: DataPodlasie, val onSuccess: () -> Unit) { } private fun nextEndpoint(onSuccess: () -> Unit) { - if (data.targetEndpointIds.isEmpty()) { + if (data.targetEndpoints.isEmpty()) { onSuccess() return } @@ -28,8 +28,8 @@ class PodlasieData(val data: DataPodlasie, val onSuccess: () -> Unit) { onSuccess() return } - val id = data.targetEndpointIds.firstKey() - val lastSync = data.targetEndpointIds.remove(id) + val id = data.targetEndpoints.firstKey() + val lastSync = data.targetEndpoints.remove(id) useEndpoint(id, lastSync) { data.progress(data.progressStep) nextEndpoint(onSuccess) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/api/PodlasieApiEvents.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/api/PodlasieApiEvents.kt index f51c7477..25735d3d 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/api/PodlasieApiEvents.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/api/PodlasieApiEvents.kt @@ -9,6 +9,7 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.podlasie.DataPodlasie import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel import pl.szczodrzynski.edziennik.data.db.entity.Event import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType import pl.szczodrzynski.edziennik.ext.getLong import pl.szczodrzynski.edziennik.ext.getString import pl.szczodrzynski.edziennik.utils.models.Date @@ -64,7 +65,7 @@ class PodlasieApiEvents(val data: DataPodlasie, val rows: List) { data.metadataList.add( Metadata( data.profileId, - if (type == Event.TYPE_HOMEWORK) Metadata.TYPE_HOMEWORK else Metadata.TYPE_EVENT, + if (type == Event.TYPE_HOMEWORK) MetadataType.HOMEWORK else MetadataType.EVENT, id, data.profile?.empty ?: false, data.profile?.empty ?: false diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/api/PodlasieApiFinalGrades.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/api/PodlasieApiFinalGrades.kt index 94dadfc8..04023f5f 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/api/PodlasieApiFinalGrades.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/api/PodlasieApiFinalGrades.kt @@ -15,7 +15,9 @@ import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_SEMESTER2_ import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_YEAR_FINAL import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_YEAR_PROPOSED import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType import pl.szczodrzynski.edziennik.ext.getLong +import pl.szczodrzynski.edziennik.ext.getSemesterStart import pl.szczodrzynski.edziennik.ext.getString class PodlasieApiFinalGrades(val data: DataPodlasie, val rows: List) { @@ -62,7 +64,7 @@ class PodlasieApiFinalGrades(val data: DataPodlasie, val rows: List) data.metadataList.add( Metadata( data.profileId, - Metadata.TYPE_GRADE, + MetadataType.GRADE, id, profile.empty, profile.empty @@ -99,7 +101,7 @@ class PodlasieApiFinalGrades(val data: DataPodlasie, val rows: List) data.metadataList.add( Metadata( data.profileId, - Metadata.TYPE_GRADE, + MetadataType.GRADE, proposedGradeObject.id, profile.empty, profile.empty diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/api/PodlasieApiGrades.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/api/PodlasieApiGrades.kt index 4aa749ef..9a049313 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/api/PodlasieApiGrades.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/api/PodlasieApiGrades.kt @@ -10,6 +10,7 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.podlasie.DataPodlasie import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel import pl.szczodrzynski.edziennik.data.db.entity.Grade import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType import pl.szczodrzynski.edziennik.ext.getFloat import pl.szczodrzynski.edziennik.ext.getInt import pl.szczodrzynski.edziennik.ext.getLong @@ -60,7 +61,7 @@ class PodlasieApiGrades(val data: DataPodlasie, val rows: List) { data.metadataList.add( Metadata( data.profileId, - Metadata.TYPE_GRADE, + MetadataType.GRADE, id, data.profile?.empty ?: false, data.profile?.empty ?: false diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/api/PodlasieApiHomework.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/api/PodlasieApiHomework.kt index be2fd062..8a8e851b 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/api/PodlasieApiHomework.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/api/PodlasieApiHomework.kt @@ -9,6 +9,7 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.podlasie.DataPodlasie import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel import pl.szczodrzynski.edziennik.data.db.entity.Event import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType import pl.szczodrzynski.edziennik.ext.crc32 import pl.szczodrzynski.edziennik.ext.get import pl.szczodrzynski.edziennik.ext.getString @@ -53,7 +54,7 @@ class PodlasieApiHomework(val data: DataPodlasie, val rows: List) { data.metadataList.add( Metadata( data.profileId, - Metadata.TYPE_HOMEWORK, + MetadataType.HOMEWORK, id, data.profile?.empty ?: false, data.profile?.empty ?: false diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/api/PodlasieApiLuckyNumber.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/api/PodlasieApiLuckyNumber.kt index 725ec727..87b8dcc8 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/api/PodlasieApiLuckyNumber.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/api/PodlasieApiLuckyNumber.kt @@ -7,6 +7,7 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.podlasie.data.api import pl.szczodrzynski.edziennik.data.api.edziennik.podlasie.DataPodlasie import pl.szczodrzynski.edziennik.data.db.entity.LuckyNumber import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType import pl.szczodrzynski.edziennik.utils.models.Date class PodlasieApiLuckyNumber(val data: DataPodlasie, val luckyNumber: Int) { @@ -21,7 +22,7 @@ class PodlasieApiLuckyNumber(val data: DataPodlasie, val luckyNumber: Int) { data.metadataList.add( Metadata( data.profileId, - Metadata.TYPE_LUCKY_NUMBER, + MetadataType.LUCKY_NUMBER, luckyNumberObject.date.value.toLong(), true, data.profile?.empty ?: false diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/api/PodlasieApiTimetable.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/api/PodlasieApiTimetable.kt index 8c95d054..9abb47fe 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/api/PodlasieApiTimetable.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/api/PodlasieApiTimetable.kt @@ -72,6 +72,7 @@ class PodlasieApiTimetable(val data: DataPodlasie, rows: List) { it.classroom = classroom it.id = it.buildId() + it.ownerId = it.buildOwnerId() data.lessonList += it } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/firstlogin/PodlasieFirstLogin.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/firstlogin/PodlasieFirstLogin.kt index a99ea2ee..3b978cae 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/firstlogin/PodlasieFirstLogin.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/firstlogin/PodlasieFirstLogin.kt @@ -5,7 +5,6 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.podlasie.firstlogin import org.greenrobot.eventbus.EventBus -import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_PODLASIE import pl.szczodrzynski.edziennik.data.api.PODLASIE_API_LOGOUT_DEVICES_ENDPOINT import pl.szczodrzynski.edziennik.data.api.PODLASIE_API_USER_ENDPOINT import pl.szczodrzynski.edziennik.data.api.edziennik.podlasie.DataPodlasie @@ -13,6 +12,7 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.podlasie.data.PodlasieApi import pl.szczodrzynski.edziennik.data.api.edziennik.podlasie.login.PodlasieLoginApi import pl.szczodrzynski.edziennik.data.api.events.FirstLoginFinishedEvent import pl.szczodrzynski.edziennik.data.db.entity.Profile +import pl.szczodrzynski.edziennik.data.db.enums.LoginType import pl.szczodrzynski.edziennik.ext.fixName import pl.szczodrzynski.edziennik.ext.getShortName import pl.szczodrzynski.edziennik.ext.getString @@ -32,9 +32,6 @@ class PodlasieFirstLogin(val data: DataPodlasie, val onSuccess: () -> Unit) { } private fun doLogin() { - val loginStoreId = data.loginStore.id - val loginStoreType = LOGIN_TYPE_PODLASIE - if (data.loginStore.getLoginData("logoutDevices", false)) { data.loginStore.removeLoginData("logoutDevices") api.apiGet(TAG, PODLASIE_API_LOGOUT_DEVICES_ENDPOINT) { @@ -57,9 +54,9 @@ class PodlasieFirstLogin(val data: DataPodlasie, val onSuccess: () -> Unit) { val apiUrl = json.getString("URL") val profile = Profile( - loginStoreId, - loginStoreId, - loginStoreType, + data.loginStore.id, + data.loginStore.id, + LoginType.PODLASIE, studentNameLong, login, studentNameLong, diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/login/PodlasieLogin.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/login/PodlasieLogin.kt index 406bb997..73594d41 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/login/PodlasieLogin.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/login/PodlasieLogin.kt @@ -5,8 +5,8 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.podlasie.login import pl.szczodrzynski.edziennik.R -import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_PODLASIE_API import pl.szczodrzynski.edziennik.data.api.edziennik.podlasie.DataPodlasie +import pl.szczodrzynski.edziennik.data.db.enums.LoginMethod import pl.szczodrzynski.edziennik.utils.Utils class PodlasieLogin(val data: DataPodlasie, val onSuccess: () -> Unit) { @@ -21,7 +21,7 @@ class PodlasieLogin(val data: DataPodlasie, val onSuccess: () -> Unit) { } private fun nextLoginMethod(onSuccess: () -> Unit) { - if (data.targetLoginMethodIds.isEmpty()) { + if (data.targetLoginMethods.isEmpty()) { onSuccess() return } @@ -29,26 +29,27 @@ class PodlasieLogin(val data: DataPodlasie, val onSuccess: () -> Unit) { onSuccess() return } - useLoginMethod(data.targetLoginMethodIds.removeAt(0)) { usedMethodId -> + useLoginMethod(data.targetLoginMethods.removeAt(0)) { usedMethod -> data.progress(data.progressStep) - if (usedMethodId != -1) - data.loginMethods.add(usedMethodId) + if (usedMethod != null) + data.loginMethods.add(usedMethod) nextLoginMethod(onSuccess) } } - private fun useLoginMethod(loginMethodId: Int, onSuccess: (usedMethodId: Int) -> Unit) { + private fun useLoginMethod(loginMethod: LoginMethod, onSuccess: (usedMethod: LoginMethod?) -> Unit) { // this should never be true - if (data.loginMethods.contains(loginMethodId)) { - onSuccess(-1) + if (data.loginMethods.contains(loginMethod)) { + onSuccess(null) return } - Utils.d(TAG, "Using login method $loginMethodId") - when (loginMethodId) { - LOGIN_METHOD_PODLASIE_API -> { + Utils.d(TAG, "Using login method $loginMethod") + when (loginMethod) { + LoginMethod.PODLASIE_API -> { data.startProgress(R.string.edziennik_progress_login_podlasie_api) - PodlasieLoginApi(data) { onSuccess(loginMethodId) } + PodlasieLoginApi(data) { onSuccess(loginMethod) } } + else -> {} } } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/template/DataTemplate.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/template/DataTemplate.kt index 17644978..f0a1785e 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/template/DataTemplate.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/template/DataTemplate.kt @@ -5,13 +5,14 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.template import pl.szczodrzynski.edziennik.App -import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_TEMPLATE_API -import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_TEMPLATE_WEB import pl.szczodrzynski.edziennik.data.api.models.Data import pl.szczodrzynski.edziennik.data.db.entity.LoginStore import pl.szczodrzynski.edziennik.data.db.entity.Profile +import pl.szczodrzynski.edziennik.data.db.enums.LoginMethod import pl.szczodrzynski.edziennik.ext.currentTimeUnix +import pl.szczodrzynski.edziennik.ext.getStudentData import pl.szczodrzynski.edziennik.ext.isNotNullNorEmpty +import pl.szczodrzynski.edziennik.ext.set /** * Use http://patorjk.com/software/taag/#p=display&f=Big for the ascii art @@ -26,11 +27,11 @@ class DataTemplate(app: App, profile: Profile?, loginStore: LoginStore) : Data(a override fun satisfyLoginMethods() { loginMethods.clear() if (isWebLoginValid()) { - loginMethods += LOGIN_METHOD_TEMPLATE_WEB + loginMethods += LoginMethod.TEMPLATE_WEB app.cookieJar.set("eregister.example.com", "AuthCookie", webCookie) } if (isApiLoginValid()) - loginMethods += LOGIN_METHOD_TEMPLATE_API + loginMethods += LoginMethod.TEMPLATE_API } override fun generateUserCode() = "TEMPLATE:DO_NOT_USE" @@ -44,12 +45,12 @@ class DataTemplate(app: App, profile: Profile?, loginStore: LoginStore) : Data(a private var mWebCookie: String? = null var webCookie: String? get() { mWebCookie = mWebCookie ?: profile?.getStudentData("webCookie", null); return mWebCookie } - set(value) { profile?.putStudentData("webCookie", value) ?: return; mWebCookie = value } + set(value) { profile["webCookie"] = value; mWebCookie = value } private var mWebExpiryTime: Long? = null var webExpiryTime: Long get() { mWebExpiryTime = mWebExpiryTime ?: profile?.getStudentData("webExpiryTime", 0L); return mWebExpiryTime ?: 0L } - set(value) { profile?.putStudentData("webExpiryTime", value) ?: return; mWebExpiryTime = value } + set(value) { profile["webExpiryTime"] = value; mWebExpiryTime = value } /* _ /\ (_) @@ -62,10 +63,10 @@ class DataTemplate(app: App, profile: Profile?, loginStore: LoginStore) : Data(a private var mApiToken: String? = null var apiToken: String? get() { mApiToken = mApiToken ?: profile?.getStudentData("apiToken", null); return mApiToken } - set(value) { profile?.putStudentData("apiToken", value) ?: return; mApiToken = value } + set(value) { profile["apiToken"] = value; mApiToken = value } private var mApiExpiryTime: Long? = null var apiExpiryTime: Long get() { mApiExpiryTime = mApiExpiryTime ?: profile?.getStudentData("apiExpiryTime", 0L); return mApiExpiryTime ?: 0L } - set(value) { profile?.putStudentData("apiExpiryTime", value) ?: return; mApiExpiryTime = value } + set(value) { profile["apiExpiryTime"] = value; mApiExpiryTime = value } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/template/Template.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/template/Template.kt index 843d36e9..7b0ddfd6 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/template/Template.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/template/Template.kt @@ -10,14 +10,15 @@ import pl.szczodrzynski.edziennik.data.api.CODE_INTERNAL_LIBRUS_ACCOUNT_410 import pl.szczodrzynski.edziennik.data.api.edziennik.template.data.TemplateData import pl.szczodrzynski.edziennik.data.api.edziennik.template.firstlogin.TemplateFirstLogin import pl.szczodrzynski.edziennik.data.api.edziennik.template.login.TemplateLogin +import pl.szczodrzynski.edziennik.data.api.events.UserActionRequiredEvent import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikCallback import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikInterface import pl.szczodrzynski.edziennik.data.api.models.ApiError import pl.szczodrzynski.edziennik.data.api.prepare -import pl.szczodrzynski.edziennik.data.api.templateLoginMethods import pl.szczodrzynski.edziennik.data.db.entity.LoginStore import pl.szczodrzynski.edziennik.data.db.entity.Profile import pl.szczodrzynski.edziennik.data.db.entity.Teacher +import pl.szczodrzynski.edziennik.data.db.enums.FeatureType import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull import pl.szczodrzynski.edziennik.data.db.full.EventFull import pl.szczodrzynski.edziennik.data.db.full.MessageFull @@ -51,11 +52,11 @@ class Template(val app: App, val profile: Profile?, val loginStore: LoginStore, |_| |_| |_|\___| /_/ \_\_|\__, |\___/|_| |_|\__|_| |_|_| |_| |_| __/ | |__*/ - override fun sync(featureIds: List, viewId: Int?, onlyEndpoints: List?, arguments: JsonObject?) { + override fun sync(featureTypes: Set?, onlyEndpoints: Set?, arguments: JsonObject?) { data.arguments = arguments - data.prepare(templateLoginMethods, TemplateFeatures, featureIds, viewId, onlyEndpoints) - d(TAG, "LoginMethod IDs: ${data.targetLoginMethodIds}") - d(TAG, "Endpoint IDs: ${data.targetEndpointIds}") + data.prepare(TemplateFeatures, featureTypes, onlyEndpoints) + d(TAG, "LoginMethod IDs: ${data.targetLoginMethods}") + d(TAG, "Endpoint IDs: ${data.targetEndpoints}") TemplateLogin(data) { TemplateData(data) { completed() @@ -67,7 +68,7 @@ class Template(val app: App, val profile: Profile?, val loginStore: LoginStore, } - override fun sendMessage(recipients: List, subject: String, text: String) { + override fun sendMessage(recipients: Set, subject: String, text: String) { } @@ -108,6 +109,10 @@ class Template(val app: App, val profile: Profile?, val loginStore: LoginStore, callback.onCompleted() } + override fun onRequiresUserAction(event: UserActionRequiredEvent) { + callback.onRequiresUserAction(event) + } + override fun onProgress(step: Float) { callback.onProgress(step) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/template/TemplateFeatures.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/template/TemplateFeatures.kt index c12e2bcc..b389ea59 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/template/TemplateFeatures.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/template/TemplateFeatures.kt @@ -4,21 +4,23 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.template -import pl.szczodrzynski.edziennik.data.api.* import pl.szczodrzynski.edziennik.data.api.models.Feature +import pl.szczodrzynski.edziennik.data.db.enums.FeatureType +import pl.szczodrzynski.edziennik.data.db.enums.LoginMethod +import pl.szczodrzynski.edziennik.data.db.enums.LoginType const val ENDPOINT_TEMPLATE_WEB_SAMPLE = 9991 const val ENDPOINT_TEMPLATE_WEB_SAMPLE_2 = 9992 const val ENDPOINT_TEMPLATE_API_SAMPLE = 9993 val TemplateFeatures = listOf( - Feature(LOGIN_TYPE_TEMPLATE, FEATURE_STUDENT_INFO, listOf( - ENDPOINT_TEMPLATE_WEB_SAMPLE to LOGIN_METHOD_TEMPLATE_WEB - ), listOf(LOGIN_METHOD_TEMPLATE_WEB)), - Feature(LOGIN_TYPE_TEMPLATE, FEATURE_SCHOOL_INFO, listOf( - ENDPOINT_TEMPLATE_WEB_SAMPLE_2 to LOGIN_METHOD_TEMPLATE_WEB - ), listOf(LOGIN_METHOD_TEMPLATE_WEB)), - Feature(LOGIN_TYPE_TEMPLATE, FEATURE_GRADES, listOf( - ENDPOINT_TEMPLATE_API_SAMPLE to LOGIN_METHOD_TEMPLATE_API - ), listOf(LOGIN_METHOD_TEMPLATE_API)) + Feature(LoginType.TEMPLATE, FeatureType.STUDENT_INFO, listOf( + ENDPOINT_TEMPLATE_WEB_SAMPLE to LoginMethod.TEMPLATE_WEB + )), + Feature(LoginType.TEMPLATE, FeatureType.SCHOOL_INFO, listOf( + ENDPOINT_TEMPLATE_WEB_SAMPLE_2 to LoginMethod.TEMPLATE_WEB + )), + Feature(LoginType.TEMPLATE, FeatureType.GRADES, listOf( + ENDPOINT_TEMPLATE_API_SAMPLE to LoginMethod.TEMPLATE_API + )) ) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/template/data/TemplateData.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/template/data/TemplateData.kt index 3489555f..4b759e34 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/template/data/TemplateData.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/template/data/TemplateData.kt @@ -24,7 +24,7 @@ class TemplateData(val data: DataTemplate, val onSuccess: () -> Unit) { } private fun nextEndpoint(onSuccess: () -> Unit) { - if (data.targetEndpointIds.isEmpty()) { + if (data.targetEndpoints.isEmpty()) { onSuccess() return } @@ -32,8 +32,8 @@ class TemplateData(val data: DataTemplate, val onSuccess: () -> Unit) { onSuccess() return } - val id = data.targetEndpointIds.firstKey() - val lastSync = data.targetEndpointIds.remove(id) + val id = data.targetEndpoints.firstKey() + val lastSync = data.targetEndpoints.remove(id) useEndpoint(id, lastSync) { endpointId -> data.progress(data.progressStep) nextEndpoint(onSuccess) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/template/data/api/TemplateApiSample.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/template/data/api/TemplateApiSample.kt index d61dc8f9..62df262e 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/template/data/api/TemplateApiSample.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/template/data/api/TemplateApiSample.kt @@ -4,11 +4,11 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.template.data.api -import pl.szczodrzynski.edziennik.MainActivity import pl.szczodrzynski.edziennik.data.api.edziennik.template.DataTemplate import pl.szczodrzynski.edziennik.data.api.edziennik.template.ENDPOINT_TEMPLATE_API_SAMPLE import pl.szczodrzynski.edziennik.data.api.edziennik.template.data.TemplateApi import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.data.db.enums.FeatureType import pl.szczodrzynski.edziennik.ext.DAY class TemplateApiSample(override val data: DataTemplate, @@ -29,9 +29,7 @@ class TemplateApiSample(override val data: DataTemplate, // not sooner than two days later data.setSyncNext(ENDPOINT_TEMPLATE_API_SAMPLE, 2 * DAY) // in two days OR on explicit "grades" sync - data.setSyncNext(ENDPOINT_TEMPLATE_API_SAMPLE, 2 * DAY, MainActivity.DRAWER_ITEM_GRADES) - // only if sync is executed on Home view - data.setSyncNext(ENDPOINT_TEMPLATE_API_SAMPLE, syncIn = null, viewId = MainActivity.DRAWER_ITEM_HOME) + data.setSyncNext(ENDPOINT_TEMPLATE_API_SAMPLE, 2 * DAY, FeatureType.GRADES) // always, in every sync data.setSyncNext(ENDPOINT_TEMPLATE_API_SAMPLE, SYNC_ALWAYS) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/template/data/web/TemplateWebSample.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/template/data/web/TemplateWebSample.kt index 0732710a..5d6d457b 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/template/data/web/TemplateWebSample.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/template/data/web/TemplateWebSample.kt @@ -4,12 +4,11 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.template.data.web -import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_GRADES -import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_HOME import pl.szczodrzynski.edziennik.data.api.edziennik.template.DataTemplate import pl.szczodrzynski.edziennik.data.api.edziennik.template.ENDPOINT_TEMPLATE_WEB_SAMPLE import pl.szczodrzynski.edziennik.data.api.edziennik.template.data.TemplateWeb import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.data.db.enums.FeatureType import pl.szczodrzynski.edziennik.ext.DAY class TemplateWebSample(override val data: DataTemplate, @@ -30,9 +29,7 @@ class TemplateWebSample(override val data: DataTemplate, // not sooner than two days later data.setSyncNext(ENDPOINT_TEMPLATE_WEB_SAMPLE, 2 * DAY) // in two days OR on explicit "grades" sync - data.setSyncNext(ENDPOINT_TEMPLATE_WEB_SAMPLE, 2 * DAY, DRAWER_ITEM_GRADES) - // only if sync is executed on Home view - data.setSyncNext(ENDPOINT_TEMPLATE_WEB_SAMPLE, syncIn = null, viewId = DRAWER_ITEM_HOME) + data.setSyncNext(ENDPOINT_TEMPLATE_WEB_SAMPLE, 2 * DAY, FeatureType.GRADES) // always, in every sync data.setSyncNext(ENDPOINT_TEMPLATE_WEB_SAMPLE, SYNC_ALWAYS) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/template/data/web/TemplateWebSample2.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/template/data/web/TemplateWebSample2.kt index ecf41b64..0125635d 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/template/data/web/TemplateWebSample2.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/template/data/web/TemplateWebSample2.kt @@ -4,11 +4,11 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.template.data.web -import pl.szczodrzynski.edziennik.MainActivity import pl.szczodrzynski.edziennik.data.api.edziennik.template.DataTemplate import pl.szczodrzynski.edziennik.data.api.edziennik.template.ENDPOINT_TEMPLATE_WEB_SAMPLE_2 import pl.szczodrzynski.edziennik.data.api.edziennik.template.data.TemplateWeb import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.data.db.enums.FeatureType import pl.szczodrzynski.edziennik.ext.DAY class TemplateWebSample2(override val data: DataTemplate, @@ -29,9 +29,7 @@ class TemplateWebSample2(override val data: DataTemplate, // not sooner than two days later data.setSyncNext(ENDPOINT_TEMPLATE_WEB_SAMPLE_2, 2 * DAY) // in two days OR on explicit "grades" sync - data.setSyncNext(ENDPOINT_TEMPLATE_WEB_SAMPLE_2, 2 * DAY, MainActivity.DRAWER_ITEM_GRADES) - // only if sync is executed on Home view - data.setSyncNext(ENDPOINT_TEMPLATE_WEB_SAMPLE_2, syncIn = null, viewId = MainActivity.DRAWER_ITEM_HOME) + data.setSyncNext(ENDPOINT_TEMPLATE_WEB_SAMPLE_2, 2 * DAY, FeatureType.GRADES) // always, in every sync data.setSyncNext(ENDPOINT_TEMPLATE_WEB_SAMPLE_2, SYNC_ALWAYS) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/template/login/TemplateLogin.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/template/login/TemplateLogin.kt index b11eef13..82ee669a 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/template/login/TemplateLogin.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/template/login/TemplateLogin.kt @@ -5,9 +5,8 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.template.login import pl.szczodrzynski.edziennik.R -import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_TEMPLATE_API -import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_TEMPLATE_WEB import pl.szczodrzynski.edziennik.data.api.edziennik.template.DataTemplate +import pl.szczodrzynski.edziennik.data.db.enums.LoginMethod import pl.szczodrzynski.edziennik.utils.Utils class TemplateLogin(val data: DataTemplate, val onSuccess: () -> Unit) { @@ -22,7 +21,7 @@ class TemplateLogin(val data: DataTemplate, val onSuccess: () -> Unit) { } private fun nextLoginMethod(onSuccess: () -> Unit) { - if (data.targetLoginMethodIds.isEmpty()) { + if (data.targetLoginMethods.isEmpty()) { onSuccess() return } @@ -30,30 +29,31 @@ class TemplateLogin(val data: DataTemplate, val onSuccess: () -> Unit) { onSuccess() return } - useLoginMethod(data.targetLoginMethodIds.removeAt(0)) { usedMethodId -> + useLoginMethod(data.targetLoginMethods.removeAt(0)) { usedMethod -> data.progress(data.progressStep) - if (usedMethodId != -1) - data.loginMethods.add(usedMethodId) + if (usedMethod != null) + data.loginMethods.add(usedMethod) nextLoginMethod(onSuccess) } } - private fun useLoginMethod(loginMethodId: Int, onSuccess: (usedMethodId: Int) -> Unit) { + private fun useLoginMethod(loginMethod: LoginMethod, onSuccess: (usedMethod: LoginMethod?) -> Unit) { // this should never be true - if (data.loginMethods.contains(loginMethodId)) { - onSuccess(-1) + if (data.loginMethods.contains(loginMethod)) { + onSuccess(null) return } - Utils.d(TAG, "Using login method $loginMethodId") - when (loginMethodId) { - LOGIN_METHOD_TEMPLATE_WEB -> { + Utils.d(TAG, "Using login method $loginMethod") + when (loginMethod) { + LoginMethod.TEMPLATE_WEB -> { data.startProgress(R.string.edziennik_progress_login_template_web) - TemplateLoginWeb(data) { onSuccess(loginMethodId) } + TemplateLoginWeb(data) { onSuccess(loginMethod) } } - LOGIN_METHOD_TEMPLATE_API -> { + LoginMethod.TEMPLATE_API -> { data.startProgress(R.string.edziennik_progress_login_template_api) - TemplateLoginApi(data) { onSuccess(loginMethodId) } + TemplateLoginApi(data) { onSuccess(loginMethod) } } + else -> {} } } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/DataUsos.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/DataUsos.kt new file mode 100644 index 00000000..8ebafb51 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/DataUsos.kt @@ -0,0 +1,76 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2022-10-11. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.usos + +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.data.api.models.Data +import pl.szczodrzynski.edziennik.data.db.entity.LoginStore +import pl.szczodrzynski.edziennik.data.db.entity.Profile +import pl.szczodrzynski.edziennik.data.db.enums.LoginMethod +import pl.szczodrzynski.edziennik.ext.getStudentData +import pl.szczodrzynski.edziennik.ext.set + +class DataUsos( + app: App, + profile: Profile?, + loginStore: LoginStore, +) : Data(app, profile, loginStore) { + + fun isApiLoginValid() = oauthTokenKey != null && oauthTokenSecret != null && oauthTokenIsUser + + override fun satisfyLoginMethods() { + loginMethods.clear() + if (isApiLoginValid()) { + loginMethods += LoginMethod.USOS_API + } + } + + override fun generateUserCode() = "$schoolId:${profile?.studentNumber ?: studentId}" + + var schoolId: String? + get() { mSchoolId = mSchoolId ?: loginStore.getLoginData("schoolId", null); return mSchoolId } + set(value) { loginStore.putLoginData("schoolId", value); mSchoolId = value } + private var mSchoolId: String? = null + + var instanceUrl: String? + get() { mInstanceUrl = mInstanceUrl ?: loginStore.getLoginData("instanceUrl", null); return mInstanceUrl } + set(value) { loginStore.putLoginData("instanceUrl", value); mInstanceUrl = value } + private var mInstanceUrl: String? = null + + var oauthLoginResponse: String? + get() { mOauthLoginResponse = mOauthLoginResponse ?: loginStore.getLoginData("oauthLoginResponse", null); return mOauthLoginResponse } + set(value) { loginStore.putLoginData("oauthLoginResponse", value); mOauthLoginResponse = value } + private var mOauthLoginResponse: String? = null + + var oauthConsumerKey: String? + get() { mOauthConsumerKey = mOauthConsumerKey ?: loginStore.getLoginData("oauthConsumerKey", null); return mOauthConsumerKey } + set(value) { loginStore.putLoginData("oauthConsumerKey", value); mOauthConsumerKey = value } + private var mOauthConsumerKey: String? = null + + var oauthConsumerSecret: String? + get() { mOauthConsumerSecret = mOauthConsumerSecret ?: loginStore.getLoginData("oauthConsumerSecret", null); return mOauthConsumerSecret } + set(value) { loginStore.putLoginData("oauthConsumerSecret", value); mOauthConsumerSecret = value } + private var mOauthConsumerSecret: String? = null + + var oauthTokenKey: String? + get() { mOauthTokenKey = mOauthTokenKey ?: loginStore.getLoginData("oauthTokenKey", null); return mOauthTokenKey } + set(value) { loginStore.putLoginData("oauthTokenKey", value); mOauthTokenKey = value } + private var mOauthTokenKey: String? = null + + var oauthTokenSecret: String? + get() { mOauthTokenSecret = mOauthTokenSecret ?: loginStore.getLoginData("oauthTokenSecret", null); return mOauthTokenSecret } + set(value) { loginStore.putLoginData("oauthTokenSecret", value); mOauthTokenSecret = value } + private var mOauthTokenSecret: String? = null + + var oauthTokenIsUser: Boolean + get() { mOauthTokenIsUser = mOauthTokenIsUser ?: loginStore.getLoginData("oauthTokenIsUser", false); return mOauthTokenIsUser ?: false } + set(value) { loginStore.putLoginData("oauthTokenIsUser", value); mOauthTokenIsUser = value } + private var mOauthTokenIsUser: Boolean? = null + + var studentId: Int + get() { mStudentId = mStudentId ?: profile?.getStudentData("studentId", 0); return mStudentId ?: 0 } + set(value) { profile["studentId"] = value; mStudentId = value } + private var mStudentId: Int? = null +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/Usos.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/Usos.kt new file mode 100644 index 00000000..61cc0fda --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/Usos.kt @@ -0,0 +1,115 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2022-10-11. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.usos + +import com.google.gson.JsonObject +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.data.api.edziennik.usos.data.UsosData +import pl.szczodrzynski.edziennik.data.api.edziennik.usos.firstlogin.UsosFirstLogin +import pl.szczodrzynski.edziennik.data.api.edziennik.usos.login.UsosLogin +import pl.szczodrzynski.edziennik.data.api.events.UserActionRequiredEvent +import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikCallback +import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikInterface +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.data.api.prepare +import pl.szczodrzynski.edziennik.data.db.entity.LoginStore +import pl.szczodrzynski.edziennik.data.db.entity.Profile +import pl.szczodrzynski.edziennik.data.db.entity.Teacher +import pl.szczodrzynski.edziennik.data.db.enums.FeatureType +import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull +import pl.szczodrzynski.edziennik.data.db.full.EventFull +import pl.szczodrzynski.edziennik.data.db.full.MessageFull +import pl.szczodrzynski.edziennik.utils.Utils.d + +class Usos( + val app: App, + val profile: Profile?, + val loginStore: LoginStore, + val callback: EdziennikCallback, +) : EdziennikInterface { + companion object { + private const val TAG = "Usos" + } + + val internalErrorList = mutableListOf() + val data: DataUsos + + init { + data = DataUsos(app, profile, loginStore).apply { + callback = wrapCallback(this@Usos.callback) + satisfyLoginMethods() + } + } + + private fun completed() { + data.saveData() + callback.onCompleted() + } + + override fun sync( + featureTypes: Set?, + onlyEndpoints: Set?, + arguments: JsonObject?, + ) { + data.arguments = arguments + data.prepare(UsosFeatures, featureTypes, onlyEndpoints) + d(TAG, "LoginMethod IDs: ${data.targetLoginMethods}") + d(TAG, "Endpoint IDs: ${data.targetEndpoints}") + UsosLogin(data) { + UsosData(data) { + completed() + } + } + } + + override fun getMessage(message: MessageFull) {} + override fun sendMessage(recipients: Set, subject: String, text: String) {} + override fun markAllAnnouncementsAsRead() {} + override fun getAnnouncement(announcement: AnnouncementFull) {} + override fun getAttachment(owner: Any, attachmentId: Long, attachmentName: String) {} + override fun getRecipientList() {} + override fun getEvent(eventFull: EventFull) {} + + override fun firstLogin() { + UsosFirstLogin(data) { + completed() + } + } + + override fun cancel() { + d(TAG, "Cancelled") + data.cancel() + } + + private fun wrapCallback(callback: EdziennikCallback): EdziennikCallback { + return object : EdziennikCallback { + override fun onCompleted() { + callback.onCompleted() + } + + override fun onRequiresUserAction(event: UserActionRequiredEvent) { + callback.onRequiresUserAction(event) + } + + override fun onProgress(step: Float) { + callback.onProgress(step) + } + + override fun onStartProgress(stringRes: Int) { + callback.onStartProgress(stringRes) + } + + override fun onError(apiError: ApiError) { + when (apiError.errorCode) { + in internalErrorList -> { + // finish immediately if the same error occurs twice during the same sync + callback.onError(apiError) + } + else -> callback.onError(apiError) + } + } + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/UsosFeatures.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/UsosFeatures.kt new file mode 100644 index 00000000..f36580fb --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/UsosFeatures.kt @@ -0,0 +1,42 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2022-10-11. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.usos + +import pl.szczodrzynski.edziennik.data.api.* +import pl.szczodrzynski.edziennik.data.api.models.Feature +import pl.szczodrzynski.edziennik.data.db.enums.FeatureType +import pl.szczodrzynski.edziennik.data.db.enums.LoginMethod +import pl.szczodrzynski.edziennik.data.db.enums.LoginType + +const val ENDPOINT_USOS_API_USER = 7000 +const val ENDPOINT_USOS_API_TERMS = 7010 +const val ENDPOINT_USOS_API_COURSES = 7020 +const val ENDPOINT_USOS_API_TIMETABLE = 7030 + +val UsosFeatures = listOf( + /* + * Student information + */ + Feature(LoginType.USOS, FeatureType.STUDENT_INFO, listOf( + ENDPOINT_USOS_API_USER to LoginMethod.USOS_API, + )), + + /* + * Terms & courses + */ + Feature(LoginType.USOS, FeatureType.SCHOOL_INFO, listOf( + ENDPOINT_USOS_API_TERMS to LoginMethod.USOS_API, + )), + Feature(LoginType.USOS, FeatureType.TEAM_INFO, listOf( + ENDPOINT_USOS_API_COURSES to LoginMethod.USOS_API, + )), + + /* + * Timetable + */ + Feature(LoginType.USOS, FeatureType.TIMETABLE, listOf( + ENDPOINT_USOS_API_TIMETABLE to LoginMethod.USOS_API, + )), +) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/data/UsosApi.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/data/UsosApi.kt new file mode 100644 index 00000000..9f716d50 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/data/UsosApi.kt @@ -0,0 +1,209 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2022-10-13. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.usos.data + +import com.google.gson.JsonArray +import com.google.gson.JsonObject +import im.wangchao.mhttp.Request +import im.wangchao.mhttp.Response +import im.wangchao.mhttp.body.MediaTypeUtils +import im.wangchao.mhttp.callback.JsonArrayCallbackHandler +import im.wangchao.mhttp.callback.JsonCallbackHandler +import im.wangchao.mhttp.callback.TextCallbackHandler +import pl.szczodrzynski.edziennik.data.api.ERROR_REQUEST_FAILURE +import pl.szczodrzynski.edziennik.data.api.ERROR_USOS_API_MISSING_RESPONSE +import pl.szczodrzynski.edziennik.data.api.SERVER_USER_AGENT +import pl.szczodrzynski.edziennik.data.api.edziennik.usos.DataUsos +import pl.szczodrzynski.edziennik.data.api.edziennik.usos.login.UsosLoginApi +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.ext.* +import pl.szczodrzynski.edziennik.utils.Utils.d +import java.net.HttpURLConnection.* +import java.util.UUID + +open class UsosApi(open val data: DataUsos, open val lastSync: Long?) { + companion object { + private const val TAG = "UsosApi" + } + + enum class ResponseType { + OBJECT, + ARRAY, + PLAIN, + } + + val profileId + get() = data.profile?.id ?: -1 + + val profile + get() = data.profile + + protected fun JsonObject.getLangString(key: String) = + this.getJsonObject(key)?.getString("pl") + + protected fun JsonObject.getLecturerIds(key: String) = + this.getJsonArray(key)?.asJsonObjectList()?.mapNotNull { + val id = it.getLong("id") ?: return@mapNotNull null + val firstName = it.getString("first_name") ?: return@mapNotNull null + val lastName = it.getString("last_name") ?: return@mapNotNull null + data.getTeacher(firstName, lastName, id = id).id + } ?: listOf() + + private fun valueToString(value: Any) = when (value) { + is String -> value + is Number -> value.toString() + is List<*> -> listToString(value) + else -> value.toString() + } + + private fun listToString(list: List<*>): String { + return list.map { + if (it is Pair<*, *> && it.first is String && it.second is List<*>) + return@map "${it.first}[${listToString(it.second as List<*>)}]" + return@map valueToString(it ?: "") + }.joinToString("|") + } + + private fun buildSignature(method: String, url: String, params: Map): String { + val query = params.toQueryString() + val signatureString = listOf( + method.uppercase(), + url.urlEncode(), + query.urlEncode(), + ).joinToString("&") + val signingKey = listOf( + data.oauthConsumerSecret ?: "", + data.oauthTokenSecret ?: "", + ).joinToString("&") { it.urlEncode() } + return signatureString.hmacSHA1(signingKey) + } + + fun apiRequest( + tag: String, + service: String, + params: Map? = null, + fields: List? = null, + responseType: ResponseType, + onSuccess: (data: T, response: Response?) -> Unit, + ) { + val url = "${data.instanceUrl}services/$service" + d(tag, "Request: Usos/Api - $url") + + val formData = mutableMapOf() + if (params != null) + formData.putAll(params.mapValues { + valueToString(it.value) + }) + if (fields != null) + formData["fields"] = valueToString(fields) + + val auth = mutableMapOf( + "realm" to url, + "oauth_consumer_key" to (data.oauthConsumerKey ?: ""), + "oauth_nonce" to UUID.randomUUID().toString(), + "oauth_signature_method" to "HMAC-SHA1", + "oauth_timestamp" to currentTimeUnix().toString(), + "oauth_token" to (data.oauthTokenKey ?: ""), + "oauth_version" to "1.0", + ) + val signature = buildSignature( + method = "POST", + url = url, + params = formData + auth.filterKeys { it.startsWith("oauth_") }, + ) + auth["oauth_signature"] = signature + + val authString = auth.map { + """${it.key}="${it.value.urlEncode()}"""" + }.joinToString(", ") + + Request.builder() + .url(url) + .userAgent(SERVER_USER_AGENT) + .addHeader("Authorization", "OAuth $authString") + .post() + .setTextBody(formData.toQueryString(), MediaTypeUtils.APPLICATION_FORM) + .allowErrorCode(HTTP_BAD_REQUEST) + .allowErrorCode(HTTP_UNAUTHORIZED) + .allowErrorCode(HTTP_FORBIDDEN) + .allowErrorCode(HTTP_NOT_FOUND) + .allowErrorCode(HTTP_UNAVAILABLE) + .callback(getCallback(tag, responseType, onSuccess)) + .build() + .enqueue() + } + + @Suppress("UNCHECKED_CAST") + private fun getCallback( + tag: String, + responseType: ResponseType, + onSuccess: (data: T, response: Response?) -> Unit, + ) = when (responseType) { + ResponseType.OBJECT -> object : JsonCallbackHandler() { + override fun onSuccess(data: JsonObject?, response: Response) { + processResponse(tag, response, data as T?, onSuccess) + } + + override fun onFailure(response: Response?, throwable: Throwable?) { + processError(tag, response, throwable) + } + } + ResponseType.ARRAY -> object : JsonArrayCallbackHandler() { + override fun onSuccess(data: JsonArray?, response: Response) { + processResponse(tag, response, data as T?, onSuccess) + } + + override fun onFailure(response: Response?, throwable: Throwable?) { + processError(tag, response, throwable) + } + } + ResponseType.PLAIN -> object : TextCallbackHandler() { + override fun onSuccess(data: String?, response: Response) { + processResponse(tag, response, data as T?, onSuccess) + } + + override fun onFailure(response: Response?, throwable: Throwable?) { + processError(tag, response, throwable) + } + } + } + + private fun processResponse( + tag: String, + response: Response, + value: T?, + onSuccess: (data: T, response: Response?) -> Unit, + ) { + val errorCode = when { + response.code() == HTTP_UNAUTHORIZED -> { + data.oauthTokenKey = null + data.oauthTokenSecret = null + data.oauthTokenIsUser = false + data.oauthLoginResponse = null + UsosLoginApi(data) { } + return + } + value == null -> ERROR_USOS_API_MISSING_RESPONSE + response.code() == HTTP_OK -> { + onSuccess(value, response) + null + } + else -> response.toErrorCode() + } + if (errorCode != null) { + data.error(tag, errorCode, response, value.toString()) + } + } + + private fun processError( + tag: String, + response: Response?, + throwable: Throwable?, + ) { + data.error(ApiError(tag, ERROR_REQUEST_FAILURE) + .withResponse(response) + .withThrowable(throwable)) + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/data/UsosData.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/data/UsosData.kt new file mode 100644 index 00000000..dbccb16b --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/data/UsosData.kt @@ -0,0 +1,64 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2022-10-13. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.usos.data + +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.data.api.edziennik.template.data.web.TemplateWebSample +import pl.szczodrzynski.edziennik.data.api.edziennik.usos.* +import pl.szczodrzynski.edziennik.data.api.edziennik.usos.data.api.UsosApiCourses +import pl.szczodrzynski.edziennik.data.api.edziennik.usos.data.api.UsosApiTerms +import pl.szczodrzynski.edziennik.data.api.edziennik.usos.data.api.UsosApiTimetable +import pl.szczodrzynski.edziennik.data.api.edziennik.usos.data.api.UsosApiUser +import pl.szczodrzynski.edziennik.utils.Utils.d + +class UsosData(val data: DataUsos, val onSuccess: () -> Unit) { + companion object { + private const val TAG = "UsosData" + } + + init { + nextEndpoint(onSuccess) + } + + private fun nextEndpoint(onSuccess: () -> Unit) { + if (data.targetEndpoints.isEmpty()) { + onSuccess() + return + } + if (data.cancelled) { + onSuccess() + return + } + val id = data.targetEndpoints.firstKey() + val lastSync = data.targetEndpoints.remove(id) + useEndpoint(id, lastSync) { endpointId -> + data.progress(data.progressStep) + nextEndpoint(onSuccess) + } + } + + private fun useEndpoint(endpointId: Int, lastSync: Long?, onSuccess: (endpointId: Int) -> Unit) { + d(TAG, "Using endpoint $endpointId. Last sync time = $lastSync") + when (endpointId) { + ENDPOINT_USOS_API_USER -> { + data.startProgress(R.string.edziennik_progress_endpoint_student_info) + UsosApiUser(data, lastSync, onSuccess) + } + ENDPOINT_USOS_API_TERMS -> { + data.startProgress(R.string.edziennik_progress_endpoint_school_info) + UsosApiTerms(data, lastSync, onSuccess) + } + ENDPOINT_USOS_API_COURSES -> { + data.startProgress(R.string.edziennik_progress_endpoint_teams) + UsosApiCourses(data, lastSync, onSuccess) + } + ENDPOINT_USOS_API_TIMETABLE -> { + data.startProgress(R.string.edziennik_progress_endpoint_timetable) + UsosApiTimetable(data, lastSync, onSuccess) + } + else -> onSuccess(endpointId) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/data/api/UsosApiCourses.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/data/api/UsosApiCourses.kt new file mode 100644 index 00000000..e93c63dd --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/data/api/UsosApiCourses.kt @@ -0,0 +1,87 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2022-10-15. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.usos.data.api + +import com.google.gson.JsonObject +import pl.szczodrzynski.edziennik.data.api.ERROR_USOS_API_INCOMPLETE_RESPONSE +import pl.szczodrzynski.edziennik.data.api.edziennik.usos.DataUsos +import pl.szczodrzynski.edziennik.data.api.edziennik.usos.ENDPOINT_USOS_API_COURSES +import pl.szczodrzynski.edziennik.data.api.edziennik.usos.data.UsosApi +import pl.szczodrzynski.edziennik.data.db.entity.Team +import pl.szczodrzynski.edziennik.ext.* + +class UsosApiCourses( + override val data: DataUsos, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit, +) : UsosApi(data, lastSync) { + companion object { + const val TAG = "UsosApiCourses" + } + + init { + apiRequest( + tag = TAG, + service = "courses/user", + fields = listOf( + // "terms" to listOf("id", "name", "start_date", "end_date"), + "course_editions" to listOf( + "course_id", + "course_name", + // "term_id", + "user_groups" to listOf( + "course_unit_id", + "group_number", + // "class_type", + "class_type_id", + "lecturers", + ), + ), + ), + responseType = ResponseType.OBJECT, + ) { json, response -> + if (!processResponse(json)) { + data.error(TAG, ERROR_USOS_API_INCOMPLETE_RESPONSE, response) + return@apiRequest + } + + data.setSyncNext(ENDPOINT_USOS_API_COURSES, 2 * DAY) + onSuccess(ENDPOINT_USOS_API_COURSES) + } + } + + private fun processResponse(json: JsonObject): Boolean { + // val term = json.getJsonArray("terms")?.firstOrNull() ?: return false + val courseEditions = json.getJsonObject("course_editions") + ?.entrySet() + ?.flatMap { it.value.asJsonArray } + ?.map { it.asJsonObject } ?: return false + + var hasValidTeam = false + for (courseEdition in courseEditions) { + val courseId = courseEdition.getString("course_id") ?: continue + val courseName = courseEdition.getLangString("course_name") ?: continue + val userGroups = courseEdition.getJsonArray("user_groups")?.asJsonObjectList() ?: continue + for (userGroup in userGroups) { + val courseUnitId = userGroup.getLong("course_unit_id") ?: continue + val groupNumber = userGroup.getInt("group_number") ?: continue + // val classType = userGroup.getLangString("class_type") ?: continue + val classTypeId = userGroup.getString("class_type_id") ?: continue + val lecturers = userGroup.getLecturerIds("lecturers") + + data.teamList.put(courseUnitId, Team( + profileId, + courseUnitId, + "${profile?.studentClassName} $classTypeId$groupNumber - $courseName", + 2, + "${data.schoolId}:${courseId} $classTypeId$groupNumber", + lecturers.firstOrNull() ?: -1L, + )) + hasValidTeam = true + } + } + return hasValidTeam + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/data/api/UsosApiTerms.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/data/api/UsosApiTerms.kt new file mode 100644 index 00000000..b1b2fd88 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/data/api/UsosApiTerms.kt @@ -0,0 +1,58 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2022-10-15. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.usos.data.api + +import com.google.gson.JsonArray +import pl.szczodrzynski.edziennik.data.api.ERROR_USOS_API_INCOMPLETE_RESPONSE +import pl.szczodrzynski.edziennik.data.api.edziennik.usos.DataUsos +import pl.szczodrzynski.edziennik.data.api.edziennik.usos.ENDPOINT_USOS_API_TERMS +import pl.szczodrzynski.edziennik.data.api.edziennik.usos.data.UsosApi +import pl.szczodrzynski.edziennik.ext.* +import pl.szczodrzynski.edziennik.utils.models.Date + +class UsosApiTerms( + override val data: DataUsos, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit, +) : UsosApi(data, lastSync) { + companion object { + const val TAG = "UsosApiTerms" + } + + init { + apiRequest( + tag = TAG, + service = "terms/search", + params = mapOf( + "query" to Date.getToday().year.toString(), + ), + responseType = ResponseType.ARRAY, + ) { json, response -> + if (!processResponse(json)) { + data.error(TAG, ERROR_USOS_API_INCOMPLETE_RESPONSE, response) + return@apiRequest + } + + data.setSyncNext(ENDPOINT_USOS_API_TERMS, 7 * DAY) + onSuccess(ENDPOINT_USOS_API_TERMS) + } + } + + private fun processResponse(json: JsonArray): Boolean { + val today = Date.getToday() + for (term in json.asJsonObjectList()) { + if (!term.getBoolean("is_active", false)) + continue + val startDate = term.getString("start_date")?.let { Date.fromY_m_d(it) } ?: continue + val finishDate = term.getString("finish_date")?.let { Date.fromY_m_d(it) } ?: continue + if (today in startDate..finishDate) { + profile?.studentSchoolYearStart = startDate.year + profile?.dateSemester1Start = startDate + profile?.dateSemester2Start = finishDate + } + } + return true + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/data/api/UsosApiTimetable.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/data/api/UsosApiTimetable.kt new file mode 100644 index 00000000..251b2f4b --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/data/api/UsosApiTimetable.kt @@ -0,0 +1,143 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2022-10-16. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.usos.data.api + +import com.google.gson.JsonArray +import pl.szczodrzynski.edziennik.data.api.ERROR_USOS_API_INCOMPLETE_RESPONSE +import pl.szczodrzynski.edziennik.data.api.edziennik.usos.DataUsos +import pl.szczodrzynski.edziennik.data.api.edziennik.usos.ENDPOINT_USOS_API_TIMETABLE +import pl.szczodrzynski.edziennik.data.api.edziennik.usos.data.UsosApi +import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel +import pl.szczodrzynski.edziennik.data.db.entity.Lesson +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType +import pl.szczodrzynski.edziennik.ext.* +import pl.szczodrzynski.edziennik.utils.models.Date +import pl.szczodrzynski.edziennik.utils.models.Time +import pl.szczodrzynski.edziennik.utils.models.Week + +class UsosApiTimetable( + override val data: DataUsos, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit, +) : UsosApi(data, lastSync) { + companion object { + const val TAG = "UsosApiTimetable" + } + + init { + val currentWeekStart = Week.getWeekStart() + if (Date.getToday().weekDay > 4) + currentWeekStart.stepForward(0, 0, 7) + + val weekStart = data.arguments + ?.getString("weekStart") + ?.let { Date.fromY_m_d(it) } + ?: currentWeekStart + val weekEnd = weekStart.clone().stepForward(0, 0, 6) + + apiRequest( + tag = TAG, + service = "tt/user", + params = mapOf( + "start" to weekStart.stringY_m_d, + "days" to 7, + ), + fields = listOf( + "type", + "start_time", + "end_time", + "unit_id", + "course_id", + "course_name", + "lecturer_ids", + "building_id", + "room_number", + "classtype_id", + "group_number", + ), + responseType = ResponseType.ARRAY, + ) { json, response -> + if (!processResponse(json, weekStart..weekEnd)) { + data.error(TAG, ERROR_USOS_API_INCOMPLETE_RESPONSE, response) + return@apiRequest + } + + data.toRemove.add(DataRemoveModel.Timetable.between(weekStart, weekEnd)) + data.setSyncNext(ENDPOINT_USOS_API_TIMETABLE, SYNC_ALWAYS) + onSuccess(ENDPOINT_USOS_API_TIMETABLE) + } + } + + private fun processResponse(json: JsonArray, syncRange: ClosedRange): Boolean { + val foundDates = mutableSetOf() + + for (activity in json.asJsonObjectList()) { + val type = activity.getString("type") + if (type !in listOf("classgroup", "classgroup2")) + continue + + val startTime = activity.getString("start_time") ?: continue + val endTime = activity.getString("end_time") ?: continue + val unitId = activity.getLong("unit_id", -1) + val courseName = activity.getLangString("course_name") ?: continue + val courseId = activity.getString("course_id") ?: continue + val lecturerIds = activity.getJsonArray("lecturer_ids")?.map { it.asLong } + val buildingId = activity.getString("building_id") + val roomNumber = activity.getString("room_number") + val classTypeId = activity.getString("classtype_id") + val groupNumber = activity.getString("group_number") + + val lesson = Lesson(profileId, -1).also { + it.type = Lesson.TYPE_NORMAL + it.date = Date.fromY_m_d(startTime) + it.startTime = Time.fromY_m_d_H_m_s(startTime) + it.endTime = Time.fromY_m_d_H_m_s(endTime) + it.subjectId = data.getSubject( + id = null, + name = courseName, + shortName = courseId, + ).id + it.teacherId = lecturerIds?.firstOrNull() ?: -1L + it.teamId = unitId + val groupName = classTypeId?.plus(groupNumber)?.let { s -> "($s)" } + it.classroom = "Sala $roomNumber / bud. $buildingId ${groupName ?: ""}" + it.id = it.buildId() + it.ownerId = it.buildOwnerId() + + it.color = when (classTypeId) { + "WYK" -> 0xff0d6091 + "CW" -> 0xff54306e + "LAB" -> 0xff772747 + "KON" -> 0xff1e5128 + "^P?SEM" -> 0xff1e5128 // TODO make it regex + else -> 0xff08534c + }.toInt() + } + lesson.date?.let { foundDates += it } + + val seen = profile?.empty != false || lesson.date!! < Date.getToday() + data.lessonList.add(lesson) + if (lesson.type != Lesson.TYPE_NORMAL) + data.metadataList += Metadata( + profileId, + MetadataType.LESSON_CHANGE, + lesson.id, + seen, + seen, + ) + } + + val notFoundDates = syncRange.asSequence() - foundDates + for (date in notFoundDates) { + data.lessonList += Lesson(profileId, date.value.toLong()).also { + it.type = Lesson.TYPE_NO_LESSONS + it.date = date + } + } + return true + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/data/api/UsosApiUser.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/data/api/UsosApiUser.kt new file mode 100644 index 00000000..29a3e22b --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/data/api/UsosApiUser.kt @@ -0,0 +1,72 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2022-10-16. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.usos.data.api + +import com.google.gson.JsonObject +import pl.szczodrzynski.edziennik.data.api.ERROR_USOS_NO_STUDENT_PROGRAMMES +import pl.szczodrzynski.edziennik.data.api.edziennik.usos.DataUsos +import pl.szczodrzynski.edziennik.data.api.edziennik.usos.ENDPOINT_USOS_API_USER +import pl.szczodrzynski.edziennik.data.api.edziennik.usos.data.UsosApi +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.ext.* + +class UsosApiUser( + override val data: DataUsos, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit, +) : UsosApi(data, lastSync) { + companion object { + const val TAG = "UsosApiUser" + } + + init { + apiRequest( + tag = TAG, + service = "users/user", + params = mapOf( + "fields" to listOf( + "id", + "first_name", + "last_name", + "student_number", + "student_programmes" to listOf( + "programme" to listOf("id"), + ), + ), + ), + responseType = ResponseType.OBJECT, + ) { json, response -> + val programmes = json.getJsonArray("student_programmes") + if (programmes.isNullOrEmpty()) { + data.error(ApiError(TAG, ERROR_USOS_NO_STUDENT_PROGRAMMES) + .withApiResponse(json) + .withResponse(response)) + return@apiRequest + } + + val firstName = json.getString("first_name") + val lastName = json.getString("last_name") + val studentName = buildFullName(firstName, lastName) + + data.studentId = json.getInt("id") ?: data.studentId + profile?.studentNameLong = studentName + profile?.studentNameShort = studentName.getShortName() + profile?.studentNumber = json.getInt("student_number", -1) + profile?.studentClassName = programmes.getJsonObject(0).getJsonObject("programme").getString("id") + + profile?.studentClassName?.let { + data.getTeam( + id = null, + name = it, + schoolCode = data.schoolId ?: "", + isTeamClass = true, + ) + } + + data.setSyncNext(ENDPOINT_USOS_API_USER, 4 * DAY) + onSuccess(ENDPOINT_USOS_API_USER) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/firstlogin/UsosFirstLogin.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/firstlogin/UsosFirstLogin.kt new file mode 100644 index 00000000..f839511c --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/firstlogin/UsosFirstLogin.kt @@ -0,0 +1,82 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2022-10-14. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.usos.firstlogin + +import com.google.gson.JsonObject +import org.greenrobot.eventbus.EventBus +import pl.szczodrzynski.edziennik.data.api.ERROR_USOS_NO_STUDENT_PROGRAMMES +import pl.szczodrzynski.edziennik.data.api.edziennik.usos.DataUsos +import pl.szczodrzynski.edziennik.data.api.edziennik.usos.data.UsosApi +import pl.szczodrzynski.edziennik.data.api.edziennik.usos.login.UsosLoginApi +import pl.szczodrzynski.edziennik.data.api.events.FirstLoginFinishedEvent +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.data.db.entity.Profile +import pl.szczodrzynski.edziennik.data.db.enums.LoginType +import pl.szczodrzynski.edziennik.ext.* + +class UsosFirstLogin(val data: DataUsos, val onSuccess: () -> Unit) { + companion object { + private const val TAG = "UsosFirstLogin" + } + + private val api = UsosApi(data, null) + + init { + var firstProfileId = data.loginStore.id + + UsosLoginApi(data) { + api.apiRequest( + tag = TAG, + service = "users/user", + params = mapOf( + "fields" to listOf( + "id", + "first_name", + "last_name", + "student_number", + "student_programmes" to listOf( + "programme" to listOf("id"), + ), + ), + ), + responseType = UsosApi.ResponseType.OBJECT, + ) { json, response -> + val programmes = json.getJsonArray("student_programmes") + if (programmes.isNullOrEmpty()) { + data.error(ApiError(TAG, ERROR_USOS_NO_STUDENT_PROGRAMMES) + .withApiResponse(json) + .withResponse(response)) + return@apiRequest + } + + val firstName = json.getString("first_name") + val lastName = json.getString("last_name") + val studentName = buildFullName(firstName, lastName) + + val profile = Profile( + id = firstProfileId++, + loginStoreId = data.loginStore.id, + loginStoreType = LoginType.USOS, + name = studentName, + subname = data.schoolId, + studentNameLong = studentName, + studentNameShort = studentName.getShortName(), + accountName = null, // student account + studentData = JsonObject( + "studentId" to json.getInt("id"), + ), + ).also { + it.studentNumber = json.getInt("student_number", -1) + it.studentClassName = programmes.getJsonObject(0).getJsonObject("programme").getString("id") + } + + EventBus.getDefault().postSticky( + FirstLoginFinishedEvent(listOf(profile), data.loginStore), + ) + onSuccess() + } + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/login/UsosLogin.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/login/UsosLogin.kt new file mode 100644 index 00000000..3f745b34 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/login/UsosLogin.kt @@ -0,0 +1,55 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2022-10-11. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.usos.login + +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.data.api.edziennik.usos.DataUsos +import pl.szczodrzynski.edziennik.data.db.enums.LoginMethod +import pl.szczodrzynski.edziennik.utils.Utils.d + +class UsosLogin(val data: DataUsos, val onSuccess: () -> Unit) { + companion object { + private const val TAG = "UsosLogin" + } + + private var cancelled = false + + init { + nextLoginMethod(onSuccess) + } + + private fun nextLoginMethod(onSuccess: () -> Unit) { + if (data.targetLoginMethods.isEmpty()) { + onSuccess() + return + } + if (cancelled) { + onSuccess() + return + } + useLoginMethod(data.targetLoginMethods.removeAt(0)) { usedMethod -> + data.progress(data.progressStep) + if (usedMethod != null) + data.loginMethods.add(usedMethod) + nextLoginMethod(onSuccess) + } + } + + private fun useLoginMethod(loginMethod: LoginMethod, onSuccess: (usedMethod: LoginMethod?) -> Unit) { + // this should never be true + if (data.loginMethods.contains(loginMethod)) { + onSuccess(null) + return + } + d(TAG, "Using login method $loginMethod") + when (loginMethod) { + LoginMethod.USOS_API -> { + data.startProgress(R.string.edziennik_progress_login_usos_api) + UsosLoginApi(data) { onSuccess(loginMethod) } + } + else -> {} + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/login/UsosLoginApi.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/login/UsosLoginApi.kt new file mode 100644 index 00000000..06651477 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/login/UsosLoginApi.kt @@ -0,0 +1,113 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2022-10-11. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.usos.login + +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.data.api.* +import pl.szczodrzynski.edziennik.data.api.edziennik.usos.DataUsos +import pl.szczodrzynski.edziennik.data.api.edziennik.usos.data.UsosApi +import pl.szczodrzynski.edziennik.data.api.events.UserActionRequiredEvent +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.ext.* +import pl.szczodrzynski.edziennik.utils.Utils.d + +class UsosLoginApi(val data: DataUsos, val onSuccess: () -> Unit) { + companion object { + private const val TAG = "UsosLoginApi" + } + + private val api = UsosApi(data, null) + + init { + run { + data.arguments?.getString("oauthLoginResponse")?.let { + data.oauthLoginResponse = it + } + if (data.isApiLoginValid()) { + onSuccess() + } else if (data.oauthLoginResponse != null) { + login() + } else { + authorize() + } + } + } + + private fun authorize() { + data.oauthTokenKey = null + data.oauthTokenSecret = null + api.apiRequest( + tag = TAG, + service = "oauth/request_token", + params = mapOf( + "oauth_callback" to USOS_API_OAUTH_REDIRECT_URL, + "scopes" to USOS_API_SCOPES, + ), + responseType = UsosApi.ResponseType.PLAIN, + ) { text, _ -> + val authorizeData = text.fromQueryString() + data.oauthTokenKey = authorizeData["oauth_token"] + data.oauthTokenSecret = authorizeData["oauth_token_secret"] + data.oauthTokenIsUser = false + + val authUrl = "${data.instanceUrl}services/oauth/authorize" + val authParams = mapOf( + "interactivity" to "confirm_user", + "oauth_token" to (data.oauthTokenKey ?: ""), + ) + data.requireUserAction( + type = UserActionRequiredEvent.Type.OAUTH, + params = Bundle( + "authorizeUrl" to "$authUrl?${authParams.toQueryString()}", + "redirectUrl" to USOS_API_OAUTH_REDIRECT_URL, + "responseStoreKey" to "oauthLoginResponse", + "extras" to data.loginStore.data.toBundle(), + ), + errorText = R.string.notification_user_action_required_oauth_usos, + ) + } + } + + private fun login() { + d(TAG, "Login to ${data.schoolId} with ${data.oauthLoginResponse}") + + val authorizeResponse = data.oauthLoginResponse?.fromQueryString() + ?: return // checked in init {} + if (authorizeResponse["oauth_token"] != data.oauthTokenKey) { + // got different token + data.error(ApiError(TAG, ERROR_USOS_OAUTH_GOT_DIFFERENT_TOKEN) + .withApiResponse(data.oauthLoginResponse)) + return + } + val verifier = authorizeResponse["oauth_verifier"] + if (verifier.isNullOrBlank()) { + data.error(ApiError(TAG, ERROR_USOS_OAUTH_INCOMPLETE_RESPONSE) + .withApiResponse(data.oauthLoginResponse)) + return + } + + api.apiRequest( + tag = TAG, + service = "oauth/access_token", + params = mapOf( + "oauth_verifier" to verifier, + ), + responseType = UsosApi.ResponseType.PLAIN, + ) { text, response -> + val accessData = text.fromQueryString() + data.oauthTokenKey = accessData["oauth_token"] + data.oauthTokenSecret = accessData["oauth_token_secret"] + data.oauthTokenIsUser = data.oauthTokenKey != null && data.oauthTokenSecret != null + data.loginStore.removeLoginData("oauthLoginResponse") + + if (!data.oauthTokenIsUser) + data.error(ApiError(TAG, ERROR_USOS_OAUTH_INCOMPLETE_RESPONSE) + .withApiResponse(text) + .withResponse(response)) + else + onSuccess() + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/DataVulcan.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/DataVulcan.kt index 0c2bc91c..b4e1610b 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/DataVulcan.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/DataVulcan.kt @@ -6,14 +6,15 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan import com.google.gson.JsonObject import pl.szczodrzynski.edziennik.App -import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_VULCAN_HEBE -import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_VULCAN_WEB_MAIN import pl.szczodrzynski.edziennik.data.api.models.Data import pl.szczodrzynski.edziennik.data.db.entity.LoginStore import pl.szczodrzynski.edziennik.data.db.entity.Profile +import pl.szczodrzynski.edziennik.data.db.enums.LoginMethod import pl.szczodrzynski.edziennik.ext.crc16 import pl.szczodrzynski.edziennik.ext.currentTimeUnix +import pl.szczodrzynski.edziennik.ext.getStudentData import pl.szczodrzynski.edziennik.ext.isNotNullNorEmpty +import pl.szczodrzynski.edziennik.ext.set import pl.szczodrzynski.fslogin.realm.RealmData class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app, profile, loginStore) { @@ -28,10 +29,10 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app override fun satisfyLoginMethods() { loginMethods.clear() if (isWebMainLoginValid()) { - loginMethods += LOGIN_METHOD_VULCAN_WEB_MAIN + loginMethods += LoginMethod.VULCAN_WEB_MAIN } if (isHebeLoginValid()) { - loginMethods += LOGIN_METHOD_VULCAN_HEBE + loginMethods += LoginMethod.VULCAN_HEBE } } @@ -58,7 +59,7 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app private var mSymbol: String? = null var symbol: String? get() { mSymbol = mSymbol ?: profile?.getStudentData("symbol", null); return mSymbol } - set(value) { profile?.putStudentData("symbol", value); mSymbol = value } + set(value) { profile["symbol"] = value; mSymbol = value } /** * Group symbol/number of the student's school. @@ -70,7 +71,7 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app private var mSchoolSymbol: String? = null var schoolSymbol: String? get() { mSchoolSymbol = mSchoolSymbol ?: profile?.getStudentData("schoolSymbol", null); return mSchoolSymbol } - set(value) { profile?.putStudentData("schoolSymbol", value) ?: return; mSchoolSymbol = value } + set(value) { profile["schoolSymbol"] = value; mSchoolSymbol = value } /** * Short name of the school, used in some places. @@ -80,7 +81,7 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app private var mSchoolShort: String? = null var schoolShort: String? get() { mSchoolShort = mSchoolShort ?: profile?.getStudentData("schoolShort", null); return mSchoolShort } - set(value) { profile?.putStudentData("schoolShort", value) ?: return; mSchoolShort = value } + set(value) { profile["schoolShort"] = value; mSchoolShort = value } /** * A school code consisting of the [symbol] and [schoolSymbol]. @@ -92,7 +93,7 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app private var mSchoolCode: String? = null var schoolCode: String? get() { mSchoolCode = mSchoolCode ?: profile?.getStudentData("schoolName", null); return mSchoolCode } - set(value) { profile?.putStudentData("schoolName", value) ?: return; mSchoolCode = value } + set(value) { profile["schoolName"] = value; mSchoolCode = value } /** * ID of the student. @@ -102,7 +103,7 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app private var mStudentId: Int? = null var studentId: Int get() { mStudentId = mStudentId ?: profile?.getStudentData("studentId", 0); return mStudentId ?: 0 } - set(value) { profile?.putStudentData("studentId", value) ?: return; mStudentId = value } + set(value) { profile["studentId"] = value; mStudentId = value } /** * ID of the student's account. @@ -112,7 +113,7 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app private var mStudentLoginId: Int? = null var studentLoginId: Int get() { mStudentLoginId = mStudentLoginId ?: profile?.getStudentData("studentLoginId", 0); return mStudentLoginId ?: 0 } - set(value) { profile?.putStudentData("studentLoginId", value) ?: return; mStudentLoginId = value } + set(value) { profile["studentLoginId"] = value; mStudentLoginId = value } /** * ID of the student's class. @@ -122,7 +123,7 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app private var mStudentClassId: Int? = null var studentClassId: Int get() { mStudentClassId = mStudentClassId ?: profile?.getStudentData("studentClassId", 0); return mStudentClassId ?: 0 } - set(value) { profile?.putStudentData("studentClassId", value) ?: return; mStudentClassId = value } + set(value) { profile["studentClassId"] = value; mStudentClassId = value } /** * ListaUczniow/IdOkresKlasyfikacyjny, e.g. 321 @@ -130,26 +131,26 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app private var mStudentSemesterId: Int? = null var studentSemesterId: Int get() { mStudentSemesterId = mStudentSemesterId ?: profile?.getStudentData("studentSemesterId", 0); return mStudentSemesterId ?: 0 } - set(value) { profile?.putStudentData("studentSemesterId", value) ?: return; mStudentSemesterId = value } + set(value) { profile["studentSemesterId"] = value; mStudentSemesterId = value } private var mStudentUnitId: Int? = null var studentUnitId: Int get() { mStudentUnitId = mStudentUnitId ?: profile?.getStudentData("studentUnitId", 0); return mStudentUnitId ?: 0 } - set(value) { profile?.putStudentData("studentUnitId", value) ?: return; mStudentUnitId = value } + set(value) { profile["studentUnitId"] = value; mStudentUnitId = value } private var mStudentConstituentId: Int? = null var studentConstituentId: Int get() { mStudentConstituentId = mStudentConstituentId ?: profile?.getStudentData("studentConstituentId", 0); return mStudentConstituentId ?: 0 } - set(value) { profile?.putStudentData("studentConstituentId", value) ?: return; mStudentConstituentId = value } + set(value) { profile["studentConstituentId"] = value; mStudentConstituentId = value } private var mSemester1Id: Int? = null var semester1Id: Int get() { mSemester1Id = mSemester1Id ?: profile?.getStudentData("semester1Id", 0); return mSemester1Id ?: 0 } - set(value) { profile?.putStudentData("semester1Id", value) ?: return; mSemester1Id = value } + set(value) { profile["semester1Id"] = value; mSemester1Id = value } private var mSemester2Id: Int? = null var semester2Id: Int get() { mSemester2Id = mSemester2Id ?: profile?.getStudentData("semester2Id", 0); return mSemester2Id ?: 0 } - set(value) { profile?.putStudentData("semester2Id", value) ?: return; mSemester2Id = value } + set(value) { profile["semester2Id"] = value; mSemester2Id = value } /** * ListaUczniow/OkresNumer, e.g. 1 or 2 @@ -157,7 +158,7 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app private var mStudentSemesterNumber: Int? = null var studentSemesterNumber: Int get() { mStudentSemesterNumber = mStudentSemesterNumber ?: profile?.getStudentData("studentSemesterNumber", 0); return mStudentSemesterNumber ?: 0 } - set(value) { profile?.putStudentData("studentSemesterNumber", value) ?: return; mStudentSemesterNumber = value } + set(value) { profile["studentSemesterNumber"] = value; mStudentSemesterNumber = value } /** * Date of the end of the current semester ([studentSemesterNumber]). @@ -167,7 +168,7 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app private var mCurrentSemesterEndDate: Long? = null var currentSemesterEndDate: Long get() { mCurrentSemesterEndDate = mCurrentSemesterEndDate ?: profile?.getStudentData("currentSemesterEndDate", 0L); return mCurrentSemesterEndDate ?: 0L } - set(value) { profile?.putStudentData("currentSemesterEndDate", value) ?: return; mCurrentSemesterEndDate = value } + set(value) { profile["currentSemesterEndDate"] = value; mCurrentSemesterEndDate = value } /* _____ _____ ____ /\ | __ \_ _| |___ \ @@ -220,17 +221,17 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app private var mHebeContext: String? = null var hebeContext: String? get() { mHebeContext = mHebeContext ?: profile?.getStudentData("hebeContext", null); return mHebeContext } - set(value) { profile?.putStudentData("hebeContext", value) ?: return; mHebeContext = value } + set(value) { profile["hebeContext"] = value; mHebeContext = value } private var mMessageBoxKey: String? = null var messageBoxKey: String? get() { mMessageBoxKey = mMessageBoxKey ?: profile?.getStudentData("messageBoxKey", null); return mMessageBoxKey } - set(value) { profile?.putStudentData("messageBoxKey", value) ?: return; mMessageBoxKey = value } + set(value) { profile["messageBoxKey"] = value; mMessageBoxKey = value } private var mMessageBoxName: String? = null var messageBoxName: String? get() { mMessageBoxName = mMessageBoxName ?: profile?.getStudentData("messageBoxName", null); return mMessageBoxName } - set(value) { profile?.putStudentData("messageBoxName", value) ?: return; mMessageBoxName = value } + set(value) { profile["messageBoxName"] = value; mMessageBoxName = value } val apiUrl: String? get() { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/Vulcan.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/Vulcan.kt index 64c87bf6..ca7a2d21 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/Vulcan.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/Vulcan.kt @@ -7,7 +7,8 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan import com.google.gson.JsonObject import org.greenrobot.eventbus.EventBus import pl.szczodrzynski.edziennik.App -import pl.szczodrzynski.edziennik.data.api.* +import pl.szczodrzynski.edziennik.data.api.ERROR_ONEDRIVE_DOWNLOAD +import pl.szczodrzynski.edziennik.data.api.ERROR_VULCAN_API_DEPRECATED import pl.szczodrzynski.edziennik.data.api.edziennik.helper.OneDriveDownloadAttachment import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanData import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe.VulcanHebeMessagesChangeStatus @@ -17,12 +18,18 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.login.VulcanLogin import pl.szczodrzynski.edziennik.data.api.events.AttachmentGetEvent import pl.szczodrzynski.edziennik.data.api.events.EventGetEvent import pl.szczodrzynski.edziennik.data.api.events.MessageGetEvent +import pl.szczodrzynski.edziennik.data.api.events.UserActionRequiredEvent import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikCallback import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikInterface import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.data.api.prepare +import pl.szczodrzynski.edziennik.data.api.prepareFor import pl.szczodrzynski.edziennik.data.db.entity.LoginStore import pl.szczodrzynski.edziennik.data.db.entity.Profile import pl.szczodrzynski.edziennik.data.db.entity.Teacher +import pl.szczodrzynski.edziennik.data.db.enums.FeatureType +import pl.szczodrzynski.edziennik.data.db.enums.LoginMethod +import pl.szczodrzynski.edziennik.data.db.enums.LoginMode import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull import pl.szczodrzynski.edziennik.data.db.full.EventFull import pl.szczodrzynski.edziennik.data.db.full.MessageFull @@ -59,24 +66,24 @@ class Vulcan(val app: App, val profile: Profile?, val loginStore: LoginStore, va |_| |_| |_|\___| /_/ \_\_|\__, |\___/|_| |_|\__|_| |_|_| |_| |_| __/ | |__*/ - override fun sync(featureIds: List, viewId: Int?, onlyEndpoints: List?, arguments: JsonObject?) { + override fun sync(featureTypes: Set?, onlyEndpoints: Set?, arguments: JsonObject?) { data.arguments = arguments - data.prepare(vulcanLoginMethods, VulcanFeatures, featureIds, viewId, onlyEndpoints) + data.prepare(VulcanFeatures, featureTypes, onlyEndpoints) login() } - private fun login(loginMethodId: Int? = null, afterLogin: (() -> Unit)? = null) { - if (data.loginStore.mode == LOGIN_MODE_VULCAN_API) { + private fun login(loginMethod: LoginMethod? = null, afterLogin: (() -> Unit)? = null) { + if (data.loginStore.mode == LoginMode.VULCAN_API) { data.error(TAG, ERROR_VULCAN_API_DEPRECATED) return } - d(TAG, "Trying to login with ${data.targetLoginMethodIds}") + d(TAG, "Trying to login with ${data.targetLoginMethods}") if (internalErrorList.isNotEmpty()) { d(TAG, " - Internal errors:") internalErrorList.forEach { d(TAG, " - code $it") } } - loginMethodId?.let { data.prepareFor(vulcanLoginMethods, it) } + loginMethod?.let { data.prepareFor(it) } afterLogin?.let { this.afterLogin = it } VulcanLogin(data) { data() @@ -84,7 +91,7 @@ class Vulcan(val app: App, val profile: Profile?, val loginStore: LoginStore, va } private fun data() { - d(TAG, "Endpoint IDs: ${data.targetEndpointIds}") + d(TAG, "Endpoint IDs: ${data.targetEndpoints}") if (internalErrorList.isNotEmpty()) { d(TAG, " - Internal errors:") internalErrorList.forEach { d(TAG, " - code $it") } @@ -95,7 +102,7 @@ class Vulcan(val app: App, val profile: Profile?, val loginStore: LoginStore, va } override fun getMessage(message: MessageFull) { - login(LOGIN_METHOD_VULCAN_HEBE) { + login(LoginMethod.VULCAN_HEBE) { if (message.seen) { EventBus.getDefault().postSticky(MessageGetEvent(message)) completed() @@ -107,8 +114,8 @@ class Vulcan(val app: App, val profile: Profile?, val loginStore: LoginStore, va } } - override fun sendMessage(recipients: List, subject: String, text: String) { - login(LOGIN_METHOD_VULCAN_HEBE) { + override fun sendMessage(recipients: Set, subject: String, text: String) { + login(LoginMethod.VULCAN_HEBE) { VulcanHebeSendMessage(data, recipients, subject, text) { completed() } @@ -179,6 +186,7 @@ class Vulcan(val app: App, val profile: Profile?, val loginStore: LoginStore, va private fun wrapCallback(callback: EdziennikCallback): EdziennikCallback { return object : EdziennikCallback { override fun onCompleted() { callback.onCompleted() } + override fun onRequiresUserAction(event: UserActionRequiredEvent) { callback.onRequiresUserAction(event) } override fun onProgress(step: Float) { callback.onProgress(step) } override fun onStartProgress(stringRes: Int) { callback.onStartProgress(stringRes) } override fun onError(apiError: ApiError) { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/VulcanFeatures.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/VulcanFeatures.kt index 4def0214..206983a1 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/VulcanFeatures.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/VulcanFeatures.kt @@ -4,8 +4,10 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan -import pl.szczodrzynski.edziennik.data.api.* import pl.szczodrzynski.edziennik.data.api.models.Feature +import pl.szczodrzynski.edziennik.data.db.enums.FeatureType +import pl.szczodrzynski.edziennik.data.db.enums.LoginMethod +import pl.szczodrzynski.edziennik.data.db.enums.LoginType const val ENDPOINT_VULCAN_WEB_LUCKY_NUMBERS = 2010 @@ -28,67 +30,67 @@ const val ENDPOINT_VULCAN_HEBE_MESSAGES_SENT = 3520 val VulcanFeatures = listOf( // timetable - Feature(LOGIN_TYPE_VULCAN, FEATURE_TIMETABLE, listOf( - ENDPOINT_VULCAN_HEBE_TIMETABLE to LOGIN_METHOD_VULCAN_HEBE - ), listOf(LOGIN_METHOD_VULCAN_HEBE)), + Feature(LoginType.VULCAN, FeatureType.TIMETABLE, listOf( + ENDPOINT_VULCAN_HEBE_TIMETABLE to LoginMethod.VULCAN_HEBE + )), // agenda - Feature(LOGIN_TYPE_VULCAN, FEATURE_AGENDA, listOf( - ENDPOINT_VULCAN_HEBE_EXAMS to LOGIN_METHOD_VULCAN_HEBE - ), listOf(LOGIN_METHOD_VULCAN_HEBE)), + Feature(LoginType.VULCAN, FeatureType.AGENDA, listOf( + ENDPOINT_VULCAN_HEBE_EXAMS to LoginMethod.VULCAN_HEBE + )), // grades - Feature(LOGIN_TYPE_VULCAN, FEATURE_GRADES, listOf( - ENDPOINT_VULCAN_HEBE_GRADES to LOGIN_METHOD_VULCAN_HEBE, - ENDPOINT_VULCAN_HEBE_GRADE_SUMMARY to LOGIN_METHOD_VULCAN_HEBE - ), listOf(LOGIN_METHOD_VULCAN_HEBE)), + Feature(LoginType.VULCAN, FeatureType.GRADES, listOf( + ENDPOINT_VULCAN_HEBE_GRADES to LoginMethod.VULCAN_HEBE, + ENDPOINT_VULCAN_HEBE_GRADE_SUMMARY to LoginMethod.VULCAN_HEBE + )), // homework - Feature(LOGIN_TYPE_VULCAN, FEATURE_HOMEWORK, listOf( - ENDPOINT_VULCAN_HEBE_HOMEWORK to LOGIN_METHOD_VULCAN_HEBE - ), listOf(LOGIN_METHOD_VULCAN_HEBE)), + Feature(LoginType.VULCAN, FeatureType.HOMEWORK, listOf( + ENDPOINT_VULCAN_HEBE_HOMEWORK to LoginMethod.VULCAN_HEBE + )), // behaviour - Feature(LOGIN_TYPE_VULCAN, FEATURE_BEHAVIOUR, listOf( - ENDPOINT_VULCAN_HEBE_NOTICES to LOGIN_METHOD_VULCAN_HEBE - ), listOf(LOGIN_METHOD_VULCAN_HEBE)), + Feature(LoginType.VULCAN, FeatureType.BEHAVIOUR, listOf( + ENDPOINT_VULCAN_HEBE_NOTICES to LoginMethod.VULCAN_HEBE + )), // attendance - Feature(LOGIN_TYPE_VULCAN, FEATURE_ATTENDANCE, listOf( - ENDPOINT_VULCAN_HEBE_ATTENDANCE to LOGIN_METHOD_VULCAN_HEBE - ), listOf(LOGIN_METHOD_VULCAN_HEBE)), + Feature(LoginType.VULCAN, FeatureType.ATTENDANCE, listOf( + ENDPOINT_VULCAN_HEBE_ATTENDANCE to LoginMethod.VULCAN_HEBE + )), // messages - Feature(LOGIN_TYPE_VULCAN, FEATURE_MESSAGES_INBOX, listOf( - ENDPOINT_VULCAN_HEBE_MESSAGES_INBOX to LOGIN_METHOD_VULCAN_HEBE - ), listOf(LOGIN_METHOD_VULCAN_HEBE)), - Feature(LOGIN_TYPE_VULCAN, FEATURE_MESSAGES_SENT, listOf( - ENDPOINT_VULCAN_HEBE_MESSAGES_SENT to LOGIN_METHOD_VULCAN_HEBE - ), listOf(LOGIN_METHOD_VULCAN_HEBE)), + Feature(LoginType.VULCAN, FeatureType.MESSAGES_INBOX, listOf( + ENDPOINT_VULCAN_HEBE_MESSAGES_INBOX to LoginMethod.VULCAN_HEBE + )), + Feature(LoginType.VULCAN, FeatureType.MESSAGES_SENT, listOf( + ENDPOINT_VULCAN_HEBE_MESSAGES_SENT to LoginMethod.VULCAN_HEBE + )), // push config - Feature(LOGIN_TYPE_VULCAN, FEATURE_PUSH_CONFIG, listOf( - ENDPOINT_VULCAN_HEBE_PUSH_CONFIG to LOGIN_METHOD_VULCAN_HEBE - ), listOf(LOGIN_METHOD_VULCAN_HEBE)).withShouldSync { data -> + Feature(LoginType.VULCAN, FeatureType.PUSH_CONFIG, listOf( + ENDPOINT_VULCAN_HEBE_PUSH_CONFIG to LoginMethod.VULCAN_HEBE + )).withShouldSync { data -> !data.app.config.sync.tokenVulcanList.contains(data.profileId) }, /** * Lucky number - using WEB Main. */ - Feature(LOGIN_TYPE_VULCAN, FEATURE_LUCKY_NUMBER, listOf( - ENDPOINT_VULCAN_WEB_LUCKY_NUMBERS to LOGIN_METHOD_VULCAN_WEB_MAIN - ), listOf(LOGIN_METHOD_VULCAN_WEB_MAIN)) + Feature(LoginType.VULCAN, FeatureType.LUCKY_NUMBER, listOf( + ENDPOINT_VULCAN_WEB_LUCKY_NUMBERS to LoginMethod.VULCAN_WEB_MAIN + )) .withShouldSync { data -> data.shouldSyncLuckyNumber() } .withPriority(2), /** * Lucky number - using Hebe API */ - Feature(LOGIN_TYPE_VULCAN, FEATURE_LUCKY_NUMBER, listOf( - ENDPOINT_VULCAN_HEBE_LUCKY_NUMBER to LOGIN_METHOD_VULCAN_HEBE - ), listOf(LOGIN_METHOD_VULCAN_HEBE)) + Feature(LoginType.VULCAN, FeatureType.LUCKY_NUMBER, listOf( + ENDPOINT_VULCAN_HEBE_LUCKY_NUMBER to LoginMethod.VULCAN_HEBE + )) .withShouldSync { data -> data.shouldSyncLuckyNumber() } .withPriority(1), - Feature(LOGIN_TYPE_VULCAN, FEATURE_ALWAYS_NEEDED, listOf( - ENDPOINT_VULCAN_HEBE_MAIN to LOGIN_METHOD_VULCAN_HEBE, - ENDPOINT_VULCAN_HEBE_ADDRESSBOOK to LOGIN_METHOD_VULCAN_HEBE, - ENDPOINT_VULCAN_HEBE_TEACHERS to LOGIN_METHOD_VULCAN_HEBE, - ENDPOINT_VULCAN_HEBE_MESSAGE_BOXES to LOGIN_METHOD_VULCAN_HEBE, - ENDPOINT_VULCAN_HEBE_ADDRESSBOOK_2 to LOGIN_METHOD_VULCAN_HEBE, - ), listOf(LOGIN_METHOD_VULCAN_HEBE)) + Feature(LoginType.VULCAN, FeatureType.ALWAYS_NEEDED, listOf( + ENDPOINT_VULCAN_HEBE_MAIN to LoginMethod.VULCAN_HEBE, + ENDPOINT_VULCAN_HEBE_ADDRESSBOOK to LoginMethod.VULCAN_HEBE, + ENDPOINT_VULCAN_HEBE_TEACHERS to LoginMethod.VULCAN_HEBE, + ENDPOINT_VULCAN_HEBE_MESSAGE_BOXES to LoginMethod.VULCAN_HEBE, + ENDPOINT_VULCAN_HEBE_ADDRESSBOOK_2 to LoginMethod.VULCAN_HEBE, + )) ) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/VulcanData.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/VulcanData.kt index 4b3f961f..41014919 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/VulcanData.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/VulcanData.kt @@ -51,7 +51,7 @@ class VulcanData(val data: DataVulcan, val onSuccess: () -> Unit) { } private fun nextEndpoint(onSuccess: () -> Unit) { - if (data.targetEndpointIds.isEmpty()) { + if (data.targetEndpoints.isEmpty()) { onSuccess() return } @@ -59,8 +59,8 @@ class VulcanData(val data: DataVulcan, val onSuccess: () -> Unit) { onSuccess() return } - val id = data.targetEndpointIds.firstKey() - val lastSync = data.targetEndpointIds.remove(id) + val id = data.targetEndpoints.firstKey() + val lastSync = data.targetEndpoints.remove(id) useEndpoint(id, lastSync) { if (firstSemesterSync && id !in firstSemesterSyncExclude) { // sync 2nd semester after every endpoint diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/VulcanHebe.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/VulcanHebe.kt index e9196c20..2a11386c 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/VulcanHebe.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/VulcanHebe.kt @@ -26,6 +26,7 @@ import pl.szczodrzynski.edziennik.utils.Utils.d import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Time import java.net.HttpURLConnection +import java.net.HttpURLConnection.HTTP_NOT_FOUND import java.net.URLEncoder import java.time.Instant import java.time.LocalDateTime @@ -183,6 +184,7 @@ open class VulcanHebe(open val data: DataVulcan, open val lastSync: Long?) { payload: JsonElement? = null, baseUrl: Boolean = false, firebaseToken: String? = null, + allow404: Boolean = false, crossinline onSuccess: (json: T, response: Response?) -> Unit ) { val url = "${if (baseUrl) data.apiUrl else data.fullApiUrl}$endpoint" @@ -295,6 +297,19 @@ open class VulcanHebe(open val data: DataVulcan, open val lastSync: Long?) { } override fun onFailure(response: Response?, throwable: Throwable?) { + if (allow404 && response?.code() == HTTP_NOT_FOUND) { + try { + onSuccess(null as T, response) + } catch (e: Exception) { + data.error( + ApiError(tag, EXCEPTION_VULCAN_HEBE_REQUEST) + .withResponse(response) + .withThrowable(e) + ) + } + return + } + data.error( ApiError(tag, ERROR_REQUEST_FAILURE) .withResponse(response) @@ -338,6 +353,7 @@ open class VulcanHebe(open val data: DataVulcan, open val lastSync: Long?) { query: Map = mapOf(), baseUrl: Boolean = false, firebaseToken: String? = null, + allow404: Boolean = false, crossinline onSuccess: (json: T, response: Response?) -> Unit ) { val queryPath = query.map { @@ -348,6 +364,7 @@ open class VulcanHebe(open val data: DataVulcan, open val lastSync: Long?) { if (query.isNotEmpty()) "$endpoint?$queryPath" else endpoint, baseUrl = baseUrl, firebaseToken = firebaseToken, + allow404 = allow404, onSuccess = onSuccess ) } @@ -382,6 +399,7 @@ open class VulcanHebe(open val data: DataVulcan, open val lastSync: Long?) { messageBox: String? = null, params: Map = mapOf(), includeFilterType: Boolean = true, + allow404: Boolean = false, onSuccess: (data: List, response: Response?) -> Unit ) { val url = if (includeFilterType && filterType != null) @@ -429,8 +447,8 @@ open class VulcanHebe(open val data: DataVulcan, open val lastSync: Long?) { ) .format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) - apiGet(tag, url, query) { json: JsonArray, response -> - onSuccess(json.map { it.asJsonObject }, response) + apiGet(tag, url, query, allow404 = allow404) { json: JsonArray?, response -> + onSuccess(json?.map { it.asJsonObject } ?: listOf(), response) } } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeAddressbook.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeAddressbook.kt index d7bec22b..5893486b 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeAddressbook.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeAddressbook.kt @@ -19,6 +19,7 @@ import pl.szczodrzynski.edziennik.data.db.entity.Teacher.Companion.TYPE_PARENTS_ import pl.szczodrzynski.edziennik.data.db.entity.Teacher.Companion.TYPE_STUDENT import pl.szczodrzynski.edziennik.data.db.entity.Teacher.Companion.TYPE_TEACHER import pl.szczodrzynski.edziennik.ext.* +import java.net.HttpURLConnection.HTTP_NOT_FOUND class VulcanHebeAddressbook( override val data: DataVulcan, @@ -41,8 +42,15 @@ class VulcanHebeAddressbook( VULCAN_HEBE_ENDPOINT_ADDRESSBOOK, HebeFilterType.BY_PERSON, lastSync = lastSync, - includeFilterType = false - ) { list, _ -> + includeFilterType = false, + allow404 = true, + ) { list, response -> + if (response?.code() == HTTP_NOT_FOUND) { + data.setSyncNext(ENDPOINT_VULCAN_HEBE_ADDRESSBOOK, 2 * DAY) + onSuccess(ENDPOINT_VULCAN_HEBE_ADDRESSBOOK) + return@apiGetList + } + list.forEach { person -> val id = person.getString("Id") ?: return@forEach diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeAttendance.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeAttendance.kt index 9f3dcaea..d2a17039 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeAttendance.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeAttendance.kt @@ -13,6 +13,7 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanHebe import pl.szczodrzynski.edziennik.data.db.entity.Attendance import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType import pl.szczodrzynski.edziennik.ext.* class VulcanHebeAttendance( @@ -96,7 +97,7 @@ class VulcanHebeAttendance( data.metadataList.add( Metadata( profileId, - Metadata.TYPE_ATTENDANCE, + MetadataType.ATTENDANCE, attendanceObject.id, profile?.empty ?: true || baseType == Attendance.TYPE_PRESENT_CUSTOM diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeExams.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeExams.kt index 308a3e26..70ccc1f9 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeExams.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeExams.kt @@ -12,6 +12,7 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanHebe import pl.szczodrzynski.edziennik.data.db.entity.Event import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType import pl.szczodrzynski.edziennik.ext.getLong import pl.szczodrzynski.edziennik.ext.getString @@ -71,7 +72,7 @@ class VulcanHebeExams( data.metadataList.add( Metadata( profileId, - Metadata.TYPE_EVENT, + MetadataType.EVENT, id, profile?.empty ?: true, profile?.empty ?: true diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeGradeSummary.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeGradeSummary.kt index 390b0383..a2f561dc 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeGradeSummary.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeGradeSummary.kt @@ -10,6 +10,7 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_HEBE import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanHebe import pl.szczodrzynski.edziennik.data.db.entity.Grade import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType import pl.szczodrzynski.edziennik.ext.DAY import pl.szczodrzynski.edziennik.ext.getString import pl.szczodrzynski.edziennik.utils.Utils @@ -72,7 +73,7 @@ class VulcanHebeGradeSummary( data.metadataList.add( Metadata( profileId, - Metadata.TYPE_GRADE, + MetadataType.GRADE, id, profile?.empty ?: true, profile?.empty ?: true diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeGrades.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeGrades.kt index f345b9e5..fc53d8b2 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeGrades.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeGrades.kt @@ -12,6 +12,7 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanHebe import pl.szczodrzynski.edziennik.data.db.entity.Grade import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType import pl.szczodrzynski.edziennik.ext.* import java.text.DecimalFormat import kotlin.math.roundToInt @@ -111,7 +112,7 @@ class VulcanHebeGrades( data.metadataList.add( Metadata( profileId, - Metadata.TYPE_GRADE, + MetadataType.GRADE, id, profile?.empty ?: true, profile?.empty ?: true diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeHomework.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeHomework.kt index 06eea284..6b336b47 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeHomework.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeHomework.kt @@ -11,6 +11,7 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanHebe import pl.szczodrzynski.edziennik.data.db.entity.Event import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType import pl.szczodrzynski.edziennik.ext.asJsonObjectList import pl.szczodrzynski.edziennik.ext.getJsonArray import pl.szczodrzynski.edziennik.ext.getLong @@ -79,7 +80,7 @@ class VulcanHebeHomework( data.metadataList.add( Metadata( profileId, - Metadata.TYPE_HOMEWORK, + MetadataType.HOMEWORK, id, profile?.empty ?: true, profile?.empty ?: true diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeLuckyNumber.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeLuckyNumber.kt index bfa07399..993c1e30 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeLuckyNumber.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeLuckyNumber.kt @@ -12,6 +12,7 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanHebe import pl.szczodrzynski.edziennik.data.db.entity.LuckyNumber import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType import pl.szczodrzynski.edziennik.ext.getInt import pl.szczodrzynski.edziennik.ext.getString import pl.szczodrzynski.edziennik.utils.models.Date @@ -57,7 +58,7 @@ class VulcanHebeLuckyNumber( data.metadataList.add( Metadata( profileId, - Metadata.TYPE_LUCKY_NUMBER, + MetadataType.LUCKY_NUMBER, luckyNumberObject.date.value.toLong(), true, profile?.empty ?: false diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeMain.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeMain.kt index ea093d14..b729fd9c 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeMain.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeMain.kt @@ -5,13 +5,12 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe import com.google.gson.JsonArray -import pl.szczodrzynski.edziennik.* -import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_VULCAN import pl.szczodrzynski.edziennik.data.api.VULCAN_HEBE_ENDPOINT_MAIN import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_HEBE_MAIN import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanHebe import pl.szczodrzynski.edziennik.data.db.entity.Profile +import pl.szczodrzynski.edziennik.data.db.enums.LoginType import pl.szczodrzynski.edziennik.ext.* import pl.szczodrzynski.edziennik.utils.models.Date @@ -125,7 +124,7 @@ class VulcanHebeMain( val newProfile = profile ?: Profile( profileId++, loginStoreId!!, - LOGIN_TYPE_VULCAN, + LoginType.VULCAN, studentNameLong, userLogin, studentNameLong, diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeMessages.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeMessages.kt index affca1e1..8a48f65c 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeMessages.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeMessages.kt @@ -4,7 +4,6 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe -import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_MESSAGES import pl.szczodrzynski.edziennik.data.api.VULCAN_HEBE_ENDPOINT_MESSAGEBOX_MESSAGES import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_HEBE_MESSAGES_INBOX @@ -17,6 +16,8 @@ import pl.szczodrzynski.edziennik.data.db.entity.Message.Companion.TYPE_SENT import pl.szczodrzynski.edziennik.data.db.entity.MessageRecipient import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.data.db.enums.FeatureType +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType import pl.szczodrzynski.edziennik.ext.* import pl.szczodrzynski.edziennik.utils.Utils @@ -141,7 +142,7 @@ class VulcanHebeMessages( data.setSeenMetadataList.add( Metadata( profileId, - Metadata.TYPE_MESSAGE, + MetadataType.MESSAGE, id, readDate > 0 || messageType == TYPE_SENT, readDate > 0 || messageType == TYPE_SENT @@ -152,7 +153,7 @@ class VulcanHebeMessages( data.setSyncNext( endpointId, if (messageType == TYPE_RECEIVED) SYNC_ALWAYS else 1 * DAY, - if (messageType == TYPE_RECEIVED) null else DRAWER_ITEM_MESSAGES + if (messageType == TYPE_RECEIVED) null else FeatureType.MESSAGES_SENT ) onSuccess(endpointId) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeMessagesChangeStatus.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeMessagesChangeStatus.kt index 2144a768..bc8e9e9c 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeMessagesChangeStatus.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeMessagesChangeStatus.kt @@ -11,6 +11,7 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanHebe import pl.szczodrzynski.edziennik.data.api.events.MessageGetEvent import pl.szczodrzynski.edziennik.data.db.entity.MessageRecipient import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType import pl.szczodrzynski.edziennik.data.db.full.MessageFull import pl.szczodrzynski.edziennik.ext.JsonObject @@ -44,7 +45,7 @@ class VulcanHebeMessagesChangeStatus( data.setSeenMetadataList.add( Metadata( profileId, - Metadata.TYPE_MESSAGE, + MetadataType.MESSAGE, messageObject.id, true, true diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeNotices.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeNotices.kt index be1e0545..6e8d9176 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeNotices.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeNotices.kt @@ -12,6 +12,7 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanHebe import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.data.db.entity.Notice import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType import pl.szczodrzynski.edziennik.ext.* class VulcanHebeNotices( @@ -63,7 +64,7 @@ class VulcanHebeNotices( data.metadataList.add( Metadata( profileId, - Metadata.TYPE_NOTICE, + MetadataType.NOTICE, id, profile?.empty ?: true, profile?.empty ?: true diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeSendMessage.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeSendMessage.kt index 5e607caa..19d8f0a2 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeSendMessage.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeSendMessage.kt @@ -21,7 +21,7 @@ import java.util.UUID class VulcanHebeSendMessage( override val data: DataVulcan, - val recipients: List, + val recipients: Set, val subject: String, val text: String, val onSuccess: () -> Unit diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeTimetable.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeTimetable.kt index c20cf1bf..e3c3348b 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeTimetable.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeTimetable.kt @@ -20,6 +20,7 @@ import pl.szczodrzynski.edziennik.data.db.entity.Lesson.Companion.TYPE_SHIFTED_S import pl.szczodrzynski.edziennik.data.db.entity.Lesson.Companion.TYPE_SHIFTED_TARGET import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType import pl.szczodrzynski.edziennik.ext.getBoolean import pl.szczodrzynski.edziennik.ext.getInt import pl.szczodrzynski.edziennik.ext.getJsonObject @@ -237,7 +238,7 @@ class VulcanHebeTimetable( data.metadataList.add( Metadata( profileId, - Metadata.TYPE_LESSON_CHANGE, + MetadataType.LESSON_CHANGE, lesson.id, seen, seen @@ -246,7 +247,9 @@ class VulcanHebeTimetable( } lessonObject.id = lessonObject.buildId() + lessonObject.ownerId = lessonObject.buildOwnerId() lessonShift?.id = lessonShift?.buildId() ?: -1 + lessonShift?.ownerId = lessonShift?.buildOwnerId() ?: -1 lessonList.add(lessonObject) lessonShift?.let { lessonList.add(it) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/web/VulcanWebLuckyNumber.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/web/VulcanWebLuckyNumber.kt index adb4363d..9dc707f5 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/web/VulcanWebLuckyNumber.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/web/VulcanWebLuckyNumber.kt @@ -11,6 +11,7 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanWebMain import pl.szczodrzynski.edziennik.data.db.entity.LuckyNumber import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType import pl.szczodrzynski.edziennik.ext.DAY import pl.szczodrzynski.edziennik.ext.getJsonArray import pl.szczodrzynski.edziennik.utils.models.Date @@ -56,7 +57,7 @@ class VulcanWebLuckyNumber(override val data: DataVulcan, data.metadataList.add( Metadata( profileId, - Metadata.TYPE_LUCKY_NUMBER, + MetadataType.LUCKY_NUMBER, luckyNumberObject.date.value.toLong(), true, profile?.empty ?: false diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/firstlogin/VulcanFirstLogin.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/firstlogin/VulcanFirstLogin.kt index 61502488..16d7a569 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/firstlogin/VulcanFirstLogin.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/firstlogin/VulcanFirstLogin.kt @@ -17,6 +17,7 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.login.VulcanLoginWeb import pl.szczodrzynski.edziennik.data.api.events.FirstLoginFinishedEvent import pl.szczodrzynski.edziennik.data.api.models.ApiError import pl.szczodrzynski.edziennik.data.db.entity.Profile +import pl.szczodrzynski.edziennik.data.db.enums.LoginMode import pl.szczodrzynski.edziennik.ext.getJsonObject import pl.szczodrzynski.edziennik.ext.getString @@ -33,7 +34,7 @@ class VulcanFirstLogin(val data: DataVulcan, val onSuccess: () -> Unit) { private val tryingSymbols = mutableListOf() init { - if (data.loginStore.mode == LOGIN_MODE_VULCAN_WEB) { + if (data.loginStore.mode == LoginMode.VULCAN_WEB) { VulcanLoginWebMain(data) { val xml = web.readCertificate() ?: run { data.error(ApiError(TAG, ERROR_VULCAN_WEB_NO_CERTIFICATE)) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/login/VulcanLogin.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/login/VulcanLogin.kt index 3cbb7ea7..dc57acc9 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/login/VulcanLogin.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/login/VulcanLogin.kt @@ -5,9 +5,8 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.login import pl.szczodrzynski.edziennik.R -import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_VULCAN_HEBE -import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_VULCAN_WEB_MAIN import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan +import pl.szczodrzynski.edziennik.data.db.enums.LoginMethod import pl.szczodrzynski.edziennik.utils.Utils class VulcanLogin(val data: DataVulcan, val onSuccess: () -> Unit) { @@ -22,7 +21,7 @@ class VulcanLogin(val data: DataVulcan, val onSuccess: () -> Unit) { } private fun nextLoginMethod(onSuccess: () -> Unit) { - if (data.targetLoginMethodIds.isEmpty()) { + if (data.targetLoginMethods.isEmpty()) { onSuccess() return } @@ -30,30 +29,31 @@ class VulcanLogin(val data: DataVulcan, val onSuccess: () -> Unit) { onSuccess() return } - useLoginMethod(data.targetLoginMethodIds.removeAt(0)) { usedMethodId -> + useLoginMethod(data.targetLoginMethods.removeAt(0)) { usedMethod -> data.progress(data.progressStep) - if (usedMethodId != -1) - data.loginMethods.add(usedMethodId) + if (usedMethod != null) + data.loginMethods.add(usedMethod) nextLoginMethod(onSuccess) } } - private fun useLoginMethod(loginMethodId: Int, onSuccess: (usedMethodId: Int) -> Unit) { + private fun useLoginMethod(loginMethod: LoginMethod, onSuccess: (usedMethod: LoginMethod?) -> Unit) { // this should never be true - if (data.loginMethods.contains(loginMethodId)) { - onSuccess(-1) + if (data.loginMethods.contains(loginMethod)) { + onSuccess(null) return } - Utils.d(TAG, "Using login method $loginMethodId") - when (loginMethodId) { - LOGIN_METHOD_VULCAN_WEB_MAIN -> { + Utils.d(TAG, "Using login method $loginMethod") + when (loginMethod) { + LoginMethod.VULCAN_WEB_MAIN -> { data.startProgress(R.string.edziennik_progress_login_vulcan_web_main) - VulcanLoginWebMain(data) { onSuccess(loginMethodId) } + VulcanLoginWebMain(data) { onSuccess(loginMethod) } } - LOGIN_METHOD_VULCAN_HEBE -> { + LoginMethod.VULCAN_HEBE -> { data.startProgress(R.string.edziennik_progress_login_vulcan_api) - VulcanLoginHebe(data) { onSuccess(loginMethodId) } + VulcanLoginHebe(data) { onSuccess(loginMethod) } } + else -> {} } } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/events/UserActionRequiredEvent.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/events/UserActionRequiredEvent.kt index 49b510c2..3995842a 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/events/UserActionRequiredEvent.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/events/UserActionRequiredEvent.kt @@ -4,11 +4,16 @@ package pl.szczodrzynski.edziennik.data.api.events -data class UserActionRequiredEvent(val profileId: Int, val type: Int) { - companion object { - const val LOGIN_DATA_MOBIDZIENNIK = 101 - const val LOGIN_DATA_LIBRUS = 102 - const val LOGIN_DATA_VULCAN = 104 - const val CAPTCHA_LIBRUS = 202 +import android.os.Bundle + +data class UserActionRequiredEvent( + val profileId: Int?, + val type: Type, + val params: Bundle, + val errorText: Int, +) { + enum class Type { + RECAPTCHA, + OAUTH, } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/interfaces/EdziennikCallback.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/interfaces/EdziennikCallback.kt index c1c878b4..47c85096 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/interfaces/EdziennikCallback.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/interfaces/EdziennikCallback.kt @@ -4,8 +4,8 @@ package pl.szczodrzynski.edziennik.data.api.interfaces +import pl.szczodrzynski.edziennik.data.api.events.UserActionRequiredEvent import pl.szczodrzynski.edziennik.data.api.models.Feature -import pl.szczodrzynski.edziennik.data.api.models.LoginMethod /** * A callback passed only to an e-register class. @@ -14,4 +14,5 @@ import pl.szczodrzynski.edziennik.data.api.models.LoginMethod */ interface EdziennikCallback : EndpointCallback { fun onCompleted() + fun onRequiresUserAction(event: UserActionRequiredEvent) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/interfaces/EdziennikInterface.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/interfaces/EdziennikInterface.kt index 153bbe18..617e3336 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/interfaces/EdziennikInterface.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/interfaces/EdziennikInterface.kt @@ -6,14 +6,15 @@ package pl.szczodrzynski.edziennik.data.api.interfaces import com.google.gson.JsonObject import pl.szczodrzynski.edziennik.data.db.entity.Teacher +import pl.szczodrzynski.edziennik.data.db.enums.FeatureType import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull import pl.szczodrzynski.edziennik.data.db.full.EventFull import pl.szczodrzynski.edziennik.data.db.full.MessageFull interface EdziennikInterface { - fun sync(featureIds: List, viewId: Int? = null, onlyEndpoints: List? = null, arguments: JsonObject? = null) + fun sync(featureTypes: Set? = null, onlyEndpoints: Set? = null, arguments: JsonObject? = null) fun getMessage(message: MessageFull) - fun sendMessage(recipients: List, subject: String, text: String) + fun sendMessage(recipients: Set, subject: String, text: String) fun markAllAnnouncementsAsRead() fun getAnnouncement(announcement: AnnouncementFull) fun getAttachment(owner: Any, attachmentId: Long, attachmentName: String) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/interfaces/EndpointCallback.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/interfaces/EndpointCallback.kt index b44820c5..f8a65ac4 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/interfaces/EndpointCallback.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/interfaces/EndpointCallback.kt @@ -6,7 +6,6 @@ package pl.szczodrzynski.edziennik.data.api.interfaces import pl.szczodrzynski.edziennik.data.api.models.ApiError import pl.szczodrzynski.edziennik.data.api.models.Feature -import pl.szczodrzynski.edziennik.data.api.models.LoginMethod /** * A callback passed to all [Feature]s and [LoginMethod]s diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/models/ApiError.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/models/ApiError.kt index bb5d20cc..b631ed7d 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/models/ApiError.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/models/ApiError.kt @@ -5,6 +5,7 @@ package pl.szczodrzynski.edziennik.data.api.models import android.content.Context +import android.os.Bundle import com.google.gson.JsonObject import im.wangchao.mhttp.Request import im.wangchao.mhttp.Response @@ -30,6 +31,7 @@ class ApiError(val tag: String, var errorCode: Int) { var request: Request? = null var response: Response? = null var isCritical = true + var params: Bundle? = null fun withThrowable(throwable: Throwable?): ApiError { this.throwable = throwable @@ -58,6 +60,11 @@ class ApiError(val tag: String, var errorCode: Int) { return this } + fun withParams(bundle: Bundle): ApiError { + this.params = bundle + return this + } + fun getStringText(context: Context): String { return context.resources.getIdentifier("error_${errorCode}", "string", context.packageName).let { if (it != 0) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/models/Data.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/models/Data.kt index ea53112a..2198c46c 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/models/Data.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/models/Data.kt @@ -1,5 +1,6 @@ package pl.szczodrzynski.edziennik.data.api.models +import android.os.Bundle import android.util.LongSparseArray import android.util.SparseArray import androidx.core.util.set @@ -12,9 +13,12 @@ import pl.szczodrzynski.edziennik.BuildConfig import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.data.api.ERROR_REQUEST_FAILURE import pl.szczodrzynski.edziennik.data.api.Regexes.MESSAGE_META -import pl.szczodrzynski.edziennik.data.api.interfaces.EndpointCallback +import pl.szczodrzynski.edziennik.data.api.events.UserActionRequiredEvent +import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikCallback import pl.szczodrzynski.edziennik.data.db.AppDb import pl.szczodrzynski.edziennik.data.db.entity.* +import pl.szczodrzynski.edziennik.data.db.enums.FeatureType +import pl.szczodrzynski.edziennik.data.db.enums.LoginMethod import pl.szczodrzynski.edziennik.ext.* import pl.szczodrzynski.edziennik.utils.Utils import pl.szczodrzynski.edziennik.utils.models.Date @@ -37,7 +41,7 @@ abstract class Data(val app: App, val profile: Profile?, val loginStore: LoginSt /** * A callback passed to all [Feature]s and [LoginMethod]s */ - lateinit var callback: EndpointCallback + lateinit var callback: EdziennikCallback /** * A list of [LoginMethod]s *already fulfilled* during this sync. @@ -45,7 +49,7 @@ abstract class Data(val app: App, val profile: Profile?, val loginStore: LoginSt * A [LoginMethod] may add elements to this list only after a successful login * with that method. */ - val loginMethods = mutableListOf() + val loginMethods = mutableListOf() /** * A method which may be overridden in child Data* classes. @@ -59,12 +63,12 @@ abstract class Data(val app: App, val profile: Profile?, val loginStore: LoginSt * A list of Login method IDs that are still pending * to run. */ - var targetLoginMethodIds = mutableListOf() + var targetLoginMethods = mutableListOf() /** * A map of endpoint ID to last sync time, that are still pending * to run. */ - var targetEndpointIds = sortedMapOf() + var targetEndpoints = sortedMapOf() /** * A count of all network requests to do. */ @@ -311,7 +315,7 @@ abstract class Data(val app: App, val profile: Profile?, val loginStore: LoginSt d("Total save time: ${System.currentTimeMillis()-totalStart} ms") } - fun setSyncNext(endpointId: Int, syncIn: Long? = null, viewId: Int? = null, syncAt: Long? = null) { + fun setSyncNext(endpointId: Int, syncIn: Long? = null, forceFeatureType: FeatureType? = null, syncAt: Long? = null) { EndpointTimer(profile?.id ?: -1, endpointId).apply { syncedNow() @@ -325,8 +329,8 @@ abstract class Data(val app: App, val profile: Profile?, val loginStore: LoginSt if (syncAt != null) { nextSync = syncAt } - if (viewId != null) - syncWhenView(viewId) + if (forceFeatureType != null) + syncWithFeature(forceFeatureType) endpointTimers.add(this) } @@ -374,6 +378,15 @@ abstract class Data(val app: App, val profile: Profile?, val loginStore: LoginSt callback.onError(apiError) } + fun requireUserAction(type: UserActionRequiredEvent.Type, params: Bundle, errorText: Int) { + callback.onRequiresUserAction(UserActionRequiredEvent( + profileId = profile?.id, + type = type, + params = params, + errorText = errorText, + )) + } + fun progress(step: Float) { callback.onProgress(step) } @@ -438,14 +451,14 @@ abstract class Data(val app: App, val profile: Profile?, val loginStore: LoginSt return team } - fun getTeacher(firstName: String, lastName: String, loginId: String? = null): Teacher { + fun getTeacher(firstName: String, lastName: String, loginId: String? = null, id: Long? = null): Teacher { val teacher = teacherList.singleOrNull { it.fullName == "$firstName $lastName" } - return validateTeacher(teacher, firstName, lastName, loginId) + return validateTeacher(teacher, firstName, lastName, loginId, id) } fun getTeacher(firstNameChar: Char, lastName: String, loginId: String? = null): Teacher { val teacher = teacherList.singleOrNull { it.shortName == "$firstNameChar.$lastName" } - return validateTeacher(teacher, firstNameChar.toString(), lastName, loginId) + return validateTeacher(teacher, firstNameChar.toString(), lastName, loginId, null) } fun getTeacherByLastFirst(nameLastFirst: String, loginId: String? = null): Teacher { @@ -453,9 +466,9 @@ abstract class Data(val app: App, val profile: Profile?, val loginStore: LoginSt val teacher = teacherList.singleOrNull { it.fullNameLastFirst == nameLastFirst } val nameParts = nameLastFirst.split(" ", limit = 2) return if (nameParts.size == 1) - validateTeacher(teacher, nameParts[0], "", loginId) + validateTeacher(teacher, nameParts[0], "", loginId, null) else - validateTeacher(teacher, nameParts[1], nameParts[0], loginId) + validateTeacher(teacher, nameParts[1], nameParts[0], loginId, null) } fun getTeacherByFirstLast(nameFirstLast: String, loginId: String? = null): Teacher { @@ -463,9 +476,9 @@ abstract class Data(val app: App, val profile: Profile?, val loginStore: LoginSt val teacher = teacherList.singleOrNull { it.fullName == nameFirstLast } val nameParts = nameFirstLast.split(" ", limit = 2) return if (nameParts.size == 1) - validateTeacher(teacher, nameParts[0], "", loginId) + validateTeacher(teacher, nameParts[0], "", loginId, null) else - validateTeacher(teacher, nameParts[0], nameParts[1], loginId) + validateTeacher(teacher, nameParts[0], nameParts[1], loginId, null) } fun getTeacherByFDotLast(nameFDotLast: String, loginId: String? = null): Teacher { @@ -484,10 +497,16 @@ abstract class Data(val app: App, val profile: Profile?, val loginStore: LoginSt getTeacher(nameParts[0][0], nameParts[1], loginId) } - private fun validateTeacher(teacher: Teacher?, firstName: String, lastName: String, loginId: String?): Teacher { - val obj = teacher ?: Teacher(profileId, -1, firstName, lastName, loginId).apply { - id = fullName.crc32() - teacherList[id] = this + private fun validateTeacher( + teacher: Teacher?, + firstName: String, + lastName: String, + loginId: String?, + id: Long? + ): Teacher { + val obj = teacher ?: Teacher(profileId, -1, firstName, lastName, loginId).also { + it.id = id ?: it.fullName.crc32() + teacherList[it.id] = it } return obj.also { if (loginId != null) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/models/Feature.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/models/Feature.kt index fd7c7873..2007135b 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/models/Feature.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/models/Feature.kt @@ -1,25 +1,27 @@ package pl.szczodrzynski.edziennik.data.api.models +import pl.szczodrzynski.edziennik.data.db.enums.FeatureType +import pl.szczodrzynski.edziennik.data.db.enums.LoginMethod +import pl.szczodrzynski.edziennik.data.db.enums.LoginType + /** * A Endpoint descriptor class. * * The API runs appropriate endpoints in order to fulfill its * feature list. * An endpoint may have its [LoginMethod] dependencies which will be - * satisfied by the API before the [endpointClass]'s constructor is invoked. + * satisfied by the API before the endpoint class is invoked. * * @param loginType type of the e-register this endpoint handles - * @param featureId a feature ID - * @param endpointIds a [List] of [Feature]s that satisfy this feature ID - * @param requiredLoginMethod a required login method, which will have to be executed before this endpoint. + * @param featureType type of the feature + * @param endpoints a [List] of endpoints and their required login methods that satisfy this feature type */ data class Feature( - val loginType: Int, - val featureId: Int, - val endpointIds: List>, - val requiredLoginMethods: List + val loginType: LoginType, + val featureType: FeatureType, + val endpoints: List>, ) { - var priority = endpointIds.size + var priority = endpoints.size fun withPriority(priority: Int): Feature { this.priority = priority return this @@ -30,4 +32,6 @@ data class Feature( this.shouldSync = shouldSync return this } + + val requiredLoginMethods by lazy { endpoints.map { it.second } } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/models/LoginMethod.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/models/LoginMethod.kt deleted file mode 100644 index cf899cff..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/models/LoginMethod.kt +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) Kuba Szczodrzyński 2019-9-20. - */ - -package pl.szczodrzynski.edziennik.data.api.models - -import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_NOT_NEEDED -import pl.szczodrzynski.edziennik.data.db.entity.LoginStore -import pl.szczodrzynski.edziennik.data.db.entity.Profile - -/** - * A Login Method descriptor class. - * - * This is used by the API to satisfy all [Feature]s' dependencies. - * A login method may have its own dependencies which need to be - * satisfied before the [loginMethodClass]'s constructor is invoked. - * - * @param loginType type of the e-register this login method handles - * @param loginMethodId a unique ID of this login method - * @param loginMethodClass a [Class] which constructor will be invoked when a log in is needed - * @param requiredLoginMethod a required login method (which will be called before this). May differ depending on the [Profile] and/or [LoginStore]. - */ -class LoginMethod( - val loginType: Int, - val loginMethodId: Int, - val loginMethodClass: Class<*>, - private var mIsPossible: ((profile: Profile?, loginStore: LoginStore) -> Boolean)? = null, - private var mRequiredLoginMethod: ((profile: Profile?, loginStore: LoginStore) -> Int)? = null -) { - - fun withIsPossible(isPossible: (profile: Profile?, loginStore: LoginStore) -> Boolean): LoginMethod { - this.mIsPossible = isPossible - return this - } - fun withRequiredLoginMethod(requiredLoginMethod: (profile: Profile?, loginStore: LoginStore) -> Int): LoginMethod { - this.mRequiredLoginMethod = requiredLoginMethod - return this - } - - fun isPossible(profile: Profile?, loginStore: LoginStore): Boolean { - return mIsPossible?.invoke(profile, loginStore) ?: false - } - fun requiredLoginMethod(profile: Profile?, loginStore: LoginStore): Int { - return mRequiredLoginMethod?.invoke(profile, loginStore) ?: LOGIN_METHOD_NOT_NEEDED - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/SzkolnyApi.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/SzkolnyApi.kt index c86ae235..954d6bb1 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/SzkolnyApi.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/SzkolnyApi.kt @@ -13,7 +13,6 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.withContext import okhttp3.OkHttpClient -import org.greenrobot.eventbus.EventBus import pl.szczodrzynski.edziennik.* import pl.szczodrzynski.edziennik.data.api.ERROR_API_INVALID_SIGNATURE import pl.szczodrzynski.edziennik.data.api.szkolny.adapter.DateAdapter @@ -128,16 +127,10 @@ class SzkolnyApi(val app: App) : CoroutineScope { response: Response>, updateDeviceHash: Boolean = false, ): T { - app.config.update = response.body()?.update?.let { update -> - if (update.versionCode > BuildConfig.VERSION_CODE) { - if (update.updateMandatory - && EventBus.getDefault().hasSubscriberForEvent(update::class.java)) { - EventBus.getDefault().postSticky(update) - } - update - } - else - null + response.body()?.update?.let { update -> + // do not process "null" update, as it might not mean there's no update + // do not notify; silently check and show the home update card + app.updateManager.process(update, notify = false) } response.body()?.registerAvailability?.let { registerAvailability -> @@ -205,18 +198,17 @@ class SzkolnyApi(val app: App) : CoroutineScope { val teams = app.db.teamDao().allNow val users = profiles.mapNotNull { profile -> - val config = app.config.getFor(profile.id) val user = ServerSyncRequest.User( profile.userCode, profile.studentNameLong, profile.studentNameShort, - profile.loginStoreType, + profile.loginStoreType.id, teams.filter { it.profileId == profile.id }.map { it.code } ) val hash = user.toString().md5() - if (hash == config.hash) + if (hash == profile.config.hash) return@mapNotNull null - return@mapNotNull user to config + return@mapNotNull user to profile.config } val response = api.serverSync(ServerSyncRequest( @@ -225,7 +217,7 @@ class SzkolnyApi(val app: App) : CoroutineScope { userCodes = profiles.map { it.userCode }, users = users.keys(), lastSync = lastSyncTime, - notifications = notifications.map { ServerSyncRequest.Notification(it.profileName ?: "", it.type, it.text) } + notifications = notifications.map { ServerSyncRequest.Notification(it.profileName ?: "", it.type.id, it.text) } )).execute() val (events, notes, hasBrowsers) = parseResponse(response, updateDeviceHash = true) @@ -265,12 +257,10 @@ class SzkolnyApi(val app: App) : CoroutineScope { seen = profile.empty notified = profile.empty - if (profile.userCode == event.sharedBy) { - sharedBy = "self" - addedManually = true - } else { - sharedBy = eventSharedBy - } + sharedBy = if (profile.userCode == event.sharedBy) + "self" + else + eventSharedBy } } } @@ -334,11 +324,14 @@ class SzkolnyApi(val app: App) : CoroutineScope { } @Throws(Exception::class) - fun shareNote(note: Note) { + fun shareNote(note: Note, teamId: Long? = null) { val profile = app.db.profileDao().getByIdNow(note.profileId) ?: throw NullPointerException("Profile is not found") - val team = app.db.teamDao().getClassNow(note.profileId) - ?: throw NullPointerException("TeamClass is not found") + val team = if (teamId == null) + app.db.teamDao().getClassNow(note.profileId) + else + app.db.teamDao().getByIdNow(note.profileId, teamId) + team ?: throw NullPointerException("TeamClass is not found") val response = api.shareNote(NoteShareRequest( deviceId = app.deviceId, @@ -431,8 +424,8 @@ class SzkolnyApi(val app: App) : CoroutineScope { } @Throws(Exception::class) - fun getUpdate(channel: String): List { - val response = api.updates(channel).execute() + fun getUpdate(channel: Update.Type): List { + val response = api.updates(channel.name.lowercase()).execute() return parseResponse(response) } @@ -451,9 +444,9 @@ class SzkolnyApi(val app: App) : CoroutineScope { @Throws(Exception::class) fun getRealms(registerName: String): List { - val response = api.fsLoginRealms(registerName).execute() + val response = api.platforms(registerName).execute() if (response.isSuccessful && response.body() != null) { - return response.body()!! + return parseResponse(response) } throw SzkolnyApiException(null) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/SzkolnyService.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/SzkolnyService.kt index 5c60c3ff..cca9475a 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/SzkolnyService.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/SzkolnyService.kt @@ -45,6 +45,6 @@ interface SzkolnyService { @GET("registerAvailability") fun registerAvailability(): Call>> - @GET("https://szkolny-eu.github.io/FSLogin/realms/{registerName}.json") - fun fsLoginRealms(@Path("registerName") registerName: String): Call> + @GET("platforms/{registerName}") + fun platforms(@Path("registerName") registerName: String): Call>> } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/interceptor/SignatureInterceptor.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/interceptor/SignatureInterceptor.kt index b766cea5..275a1909 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/interceptor/SignatureInterceptor.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/interceptor/SignatureInterceptor.kt @@ -12,10 +12,11 @@ import pl.szczodrzynski.edziennik.ext.bodyToString import pl.szczodrzynski.edziennik.ext.currentTimeUnix import pl.szczodrzynski.edziennik.ext.hmacSHA1 import pl.szczodrzynski.edziennik.ext.md5 +import pl.szczodrzynski.edziennik.ext.takeValue class SignatureInterceptor(val app: App) : Interceptor { companion object { - private const val API_KEY = "szkolny_android_42a66f0842fc7da4e37c66732acf705a" + const val API_KEY = "szkolny_android_42a66f0842fc7da4e37c66732acf705a" } override fun intercept(chain: Interceptor.Chain): Response { @@ -27,12 +28,13 @@ class SignatureInterceptor(val app: App) : Interceptor { return chain.proceed( request.newBuilder() - .header("X-ApiKey", API_KEY) - .header("X-AppVersion", BuildConfig.VERSION_CODE.toString()) - .header("X-Timestamp", timestamp.toString()) - .header("X-Signature", sign(timestamp, body, url)) + .header("X-ApiKey", app.config.apiKeyCustom?.takeValue() ?: API_KEY) .header("X-AppBuild", BuildConfig.BUILD_TYPE) .header("X-AppFlavor", BuildConfig.FLAVOR) + .header("X-AppVersion", BuildConfig.VERSION_CODE.toString()) + .header("X-DeviceId", app.deviceId) + .header("X-Signature", sign(timestamp, body, url)) + .header("X-Timestamp", timestamp.toString()) .build()) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/interceptor/Signing.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/interceptor/Signing.kt index 24f28c3c..f520ac62 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/interceptor/Signing.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/interceptor/Signing.kt @@ -46,6 +46,6 @@ object Signing { /*fun provideKey(param1: String, param2: Long): ByteArray {*/ fun pleaseStopRightNow(param1: String, param2: Long): ByteArray { - return "$param1.MTIzNDU2Nzg5MDJgAI/q8c===.$param2".sha256() + return "$param1.MTIzNDU2Nzg5MD01uMP7oW===.$param2".sha256() } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/response/UpdateResponse.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/response/UpdateResponse.kt index 63a8ce9d..0b0433f3 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/response/UpdateResponse.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/response/UpdateResponse.kt @@ -5,12 +5,21 @@ package pl.szczodrzynski.edziennik.data.api.szkolny.response data class Update( - val versionCode: Int, - val versionName: String, - val releaseDate: String, - val releaseNotes: String?, - val releaseType: String, - val isOnGooglePlay: Boolean, - val downloadUrl: String?, - val updateMandatory: Boolean -) + val versionCode: Int, + val versionName: String, + val releaseDate: String, + val releaseNotes: String?, + val releaseType: String, + val isOnGooglePlay: Boolean, + val downloadUrl: String?, + val updateMandatory: Boolean, +) { + + enum class Type { + NIGHTLY, + DEV, + BETA, + RC, + RELEASE, + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/task/AppSync.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/task/AppSync.kt index 0b13c867..f076503a 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/task/AppSync.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/task/AppSync.kt @@ -11,6 +11,7 @@ import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApiException import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.data.db.entity.Notification import pl.szczodrzynski.edziennik.data.db.entity.Profile +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType import pl.szczodrzynski.edziennik.ext.toErrorCode import pl.szczodrzynski.edziennik.utils.models.Date @@ -50,7 +51,7 @@ class AppSync(val app: App, val notifications: MutableList, val pr val isPast = event.date < today Metadata( event.profileId, - Metadata.TYPE_EVENT, + MetadataType.EVENT, event.id, isPast || markAsSeen || event.seen, isPast || markAsSeen || event.notified diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/task/Notifications.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/task/Notifications.kt index 3913709a..bb4f0cb1 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/task/Notifications.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/task/Notifications.kt @@ -5,10 +5,11 @@ package pl.szczodrzynski.edziennik.data.api.task import pl.szczodrzynski.edziennik.App -import pl.szczodrzynski.edziennik.MainActivity import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.data.db.entity.* -import pl.szczodrzynski.edziennik.ext.getNotificationTitle +import pl.szczodrzynski.edziennik.data.db.enums.NotificationType +import pl.szczodrzynski.edziennik.ext.resolveString +import pl.szczodrzynski.edziennik.ui.base.enums.NavTarget import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Week @@ -55,14 +56,14 @@ class Notifications(val app: App, val notifications: MutableList, lesson.changeTeacherName ) notifications += Notification( - id = Notification.buildId(lesson.profileId, Notification.TYPE_TIMETABLE_LESSON_CHANGE, lesson.id), - title = app.getNotificationTitle(Notification.TYPE_TIMETABLE_LESSON_CHANGE), + id = Notification.buildId(lesson.profileId, NotificationType.TIMETABLE_LESSON_CHANGE, lesson.id), + title = NotificationType.TIMETABLE_LESSON_CHANGE.titleRes.resolveString(app), text = text, textLong = textLong, - type = Notification.TYPE_TIMETABLE_LESSON_CHANGE, + type = NotificationType.TIMETABLE_LESSON_CHANGE, profileId = lesson.profileId, profileName = profiles.singleOrNull { it.id == lesson.profileId }?.name, - viewId = MainActivity.DRAWER_ITEM_TIMETABLE, + navTarget = NavTarget.TIMETABLE, addedDate = System.currentTimeMillis() ).addExtra("timetableDate", lesson.displayDate?.stringY_m_d ?: "") } @@ -101,18 +102,18 @@ class Notifications(val app: App, val notifications: MutableList, event.topic.take(200) ) val type = if (event.isHomework) - Notification.TYPE_NEW_HOMEWORK + NotificationType.HOMEWORK else - Notification.TYPE_NEW_EVENT + NotificationType.EVENT notifications += Notification( id = Notification.buildId(event.profileId, type, event.id), - title = app.getNotificationTitle(type), + title = type.titleRes.resolveString(app), text = text, textLong = textLong, type = type, profileId = event.profileId, profileName = profiles.singleOrNull { it.id == event.profileId }?.name, - viewId = if (event.isHomework) MainActivity.DRAWER_ITEM_HOMEWORK else MainActivity.DRAWER_ITEM_AGENDA, + navTarget = if (event.isHomework) NavTarget.HOMEWORK else NavTarget.AGENDA, addedDate = event.addedDate ).addExtra("eventId", event.id).addExtra("eventDate", event.date.value.toLong()) } @@ -140,18 +141,18 @@ class Notifications(val app: App, val notifications: MutableList, event.topicHtml.take(200) ) val type = if (event.isHomework) - Notification.TYPE_NEW_HOMEWORK + NotificationType.HOMEWORK else - Notification.TYPE_NEW_EVENT + NotificationType.EVENT notifications += Notification( id = Notification.buildId(event.profileId, type, event.id), - title = app.getNotificationTitle(type), + title = type.titleRes.resolveString(app), text = text, textLong = textLong, type = type, profileId = event.profileId, profileName = profiles.singleOrNull { it.id == event.profileId }?.name, - viewId = if (event.isHomework) MainActivity.DRAWER_ITEM_HOMEWORK else MainActivity.DRAWER_ITEM_AGENDA, + navTarget = if (event.isHomework) NavTarget.HOMEWORK else NavTarget.AGENDA, addedDate = event.addedDate ).addExtra("eventId", event.id).addExtra("eventDate", event.date.value.toLong()) } @@ -181,14 +182,14 @@ class Notifications(val app: App, val notifications: MutableList, grade.teacherName ?: "-" ) notifications += Notification( - id = Notification.buildId(grade.profileId, Notification.TYPE_NEW_GRADE, grade.id), - title = app.getNotificationTitle(Notification.TYPE_NEW_GRADE), + id = Notification.buildId(grade.profileId, NotificationType.GRADE, grade.id), + title = NotificationType.GRADE.titleRes.resolveString(app), text = text, textLong = textLong, - type = Notification.TYPE_NEW_GRADE, + type = NotificationType.GRADE, profileId = grade.profileId, profileName = profiles.singleOrNull { it.id == grade.profileId }?.name, - viewId = MainActivity.DRAWER_ITEM_GRADES, + navTarget = NavTarget.GRADES, addedDate = grade.addedDate ).addExtra("gradeId", grade.id).addExtra("gradesSubjectId", grade.subjectId) } @@ -216,14 +217,14 @@ class Notifications(val app: App, val notifications: MutableList, notice.text.take(200) ) notifications += Notification( - id = Notification.buildId(notice.profileId, Notification.TYPE_NEW_NOTICE, notice.id), - title = app.getNotificationTitle(Notification.TYPE_NEW_NOTICE), + id = Notification.buildId(notice.profileId, NotificationType.NOTICE, notice.id), + title = NotificationType.NOTICE.titleRes.resolveString(app), text = text, textLong = textLong, - type = Notification.TYPE_NEW_NOTICE, + type = NotificationType.NOTICE, profileId = notice.profileId, profileName = profiles.singleOrNull { it.id == notice.profileId }?.name, - viewId = MainActivity.DRAWER_ITEM_BEHAVIOUR, + navTarget = NavTarget.BEHAVIOUR, addedDate = notice.addedDate ).addExtra("noticeId", notice.id) } @@ -262,14 +263,14 @@ class Notifications(val app: App, val notifications: MutableList, attendance.lessonTopic ?: "-" ) notifications += Notification( - id = Notification.buildId(attendance.profileId, Notification.TYPE_NEW_ATTENDANCE, attendance.id), - title = app.getNotificationTitle(Notification.TYPE_NEW_ATTENDANCE), + id = Notification.buildId(attendance.profileId, NotificationType.ATTENDANCE, attendance.id), + title = NotificationType.ATTENDANCE.titleRes.resolveString(app), text = text, textLong = textLong, - type = Notification.TYPE_NEW_ATTENDANCE, + type = NotificationType.ATTENDANCE, profileId = attendance.profileId, profileName = profiles.singleOrNull { it.id == attendance.profileId }?.name, - viewId = MainActivity.DRAWER_ITEM_ATTENDANCE, + navTarget = NavTarget.ATTENDANCE, addedDate = attendance.addedDate ).addExtra("attendanceId", attendance.id).addExtra("attendanceSubjectId", attendance.subjectId) } @@ -283,13 +284,13 @@ class Notifications(val app: App, val notifications: MutableList, announcement.subject ) notifications += Notification( - id = Notification.buildId(announcement.profileId, Notification.TYPE_NEW_ANNOUNCEMENT, announcement.id), - title = app.getNotificationTitle(Notification.TYPE_NEW_ANNOUNCEMENT), + id = Notification.buildId(announcement.profileId, NotificationType.ANNOUNCEMENT, announcement.id), + title = NotificationType.ANNOUNCEMENT.titleRes.resolveString(app), text = text, - type = Notification.TYPE_NEW_ANNOUNCEMENT, + type = NotificationType.ANNOUNCEMENT, profileId = announcement.profileId, profileName = profiles.singleOrNull { it.id == announcement.profileId }?.name, - viewId = MainActivity.DRAWER_ITEM_ANNOUNCEMENTS, + navTarget = NavTarget.ANNOUNCEMENTS, addedDate = announcement.addedDate ).addExtra("announcementId", announcement.id) } @@ -303,13 +304,13 @@ class Notifications(val app: App, val notifications: MutableList, message.subject ) notifications += Notification( - id = Notification.buildId(message.profileId, Notification.TYPE_NEW_MESSAGE, message.id), - title = app.getNotificationTitle(Notification.TYPE_NEW_MESSAGE), + id = Notification.buildId(message.profileId, NotificationType.MESSAGE, message.id), + title = NotificationType.MESSAGE.titleRes.resolveString(app), text = text, - type = Notification.TYPE_NEW_MESSAGE, + type = NotificationType.MESSAGE, profileId = message.profileId, profileName = profiles.singleOrNull { it.id == message.profileId }?.name, - viewId = MainActivity.DRAWER_ITEM_MESSAGES, + navTarget = NavTarget.MESSAGES, addedDate = message.addedDate ).addExtra("messageType", Message.TYPE_RECEIVED.toLong()).addExtra("messageId", message.id) } @@ -333,13 +334,13 @@ class Notifications(val app: App, val notifications: MutableList, } } notifications += Notification( - id = Notification.buildId(luckyNumber.profileId, Notification.TYPE_LUCKY_NUMBER, luckyNumber.date.value.toLong()), - title = app.getNotificationTitle(Notification.TYPE_LUCKY_NUMBER), + id = Notification.buildId(luckyNumber.profileId, NotificationType.LUCKY_NUMBER, luckyNumber.date.value.toLong()), + title = NotificationType.LUCKY_NUMBER.titleRes.resolveString(app), text = app.getString(text, luckyNumber.date.formattedString, luckyNumber.number), - type = Notification.TYPE_LUCKY_NUMBER, + type = NotificationType.LUCKY_NUMBER, profileId = luckyNumber.profileId, profileName = profile.name, - viewId = MainActivity.DRAWER_ITEM_HOME, + navTarget = NavTarget.HOME, addedDate = System.currentTimeMillis() ) } @@ -352,13 +353,13 @@ class Notifications(val app: App, val notifications: MutableList, teacherAbsence.teacherName ) notifications += Notification( - id = Notification.buildId(teacherAbsence.profileId, Notification.TYPE_TEACHER_ABSENCE, teacherAbsence.id), - title = app.getNotificationTitle(Notification.TYPE_TEACHER_ABSENCE), + id = Notification.buildId(teacherAbsence.profileId, NotificationType.TEACHER_ABSENCE, teacherAbsence.id), + title = NotificationType.TEACHER_ABSENCE.titleRes.resolveString(app), text = message, - type = Notification.TYPE_TEACHER_ABSENCE, + type = NotificationType.TEACHER_ABSENCE, profileId = teacherAbsence.profileId, profileName = profiles.singleOrNull { it.id == teacherAbsence.profileId }?.name, - viewId = MainActivity.DRAWER_ITEM_AGENDA, + navTarget = NavTarget.AGENDA, addedDate = teacherAbsence.addedDate ).addExtra("eventDate", teacherAbsence.dateFrom.value.toLong()) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/task/PostNotifications.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/task/PostNotifications.kt index 41614394..9dac8928 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/task/PostNotifications.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/task/PostNotifications.kt @@ -4,19 +4,19 @@ import android.app.NotificationManager import android.app.PendingIntent import android.content.Context import android.os.Build -import android.util.SparseIntArray import androidx.core.app.NotificationCompat -import androidx.core.util.forEach -import androidx.core.util.set import com.mikepenz.iconics.IconicsDrawable import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial -import com.mikepenz.iconics.utils.* -import pl.szczodrzynski.edziennik.* -import pl.szczodrzynski.edziennik.data.db.entity.Notification.Companion.TYPE_SERVER_MESSAGE +import com.mikepenz.iconics.utils.colorRes +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.MainActivity +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.data.db.enums.NotificationType import pl.szczodrzynski.edziennik.ext.Intent import pl.szczodrzynski.edziennik.ext.asBoldSpannable import pl.szczodrzynski.edziennik.ext.concat import pl.szczodrzynski.edziennik.ext.pendingIntentFlag +import pl.szczodrzynski.edziennik.ui.base.enums.NavTarget import pl.szczodrzynski.edziennik.utils.models.Time import pl.szczodrzynski.edziennik.data.db.entity.Notification as AppNotification @@ -54,26 +54,12 @@ class PostNotifications(val app: App, nList: List) { .setGroup(if (quiet) app.notificationChannelsManager.dataQuiet.key else app.notificationChannelsManager.data.key) } - private fun buildSummaryText(summaryCounts: SparseIntArray): CharSequence { + private fun buildSummaryText(summaryCounts: Map): CharSequence { val summaryTexts = mutableListOf() - summaryCounts.forEach { key, value -> + summaryCounts.forEach { (key, value) -> if (value <= 0) return@forEach - val pluralRes = when (key) { - AppNotification.TYPE_TIMETABLE_LESSON_CHANGE -> R.plurals.notification_new_timetable_change_format - AppNotification.TYPE_NEW_GRADE -> R.plurals.notification_new_grades_format - AppNotification.TYPE_NEW_EVENT -> R.plurals.notification_new_events_format - AppNotification.TYPE_NEW_HOMEWORK -> R.plurals.notification_new_homework_format - AppNotification.TYPE_NEW_SHARED_EVENT -> R.plurals.notification_new_shared_events_format - AppNotification.TYPE_NEW_SHARED_HOMEWORK -> R.plurals.notification_new_shared_homework_format - AppNotification.TYPE_NEW_MESSAGE -> R.plurals.notification_new_messages_format - AppNotification.TYPE_NEW_NOTICE -> R.plurals.notification_new_notices_format - AppNotification.TYPE_NEW_ATTENDANCE -> R.plurals.notification_new_attendance_format - AppNotification.TYPE_LUCKY_NUMBER -> R.plurals.notification_new_lucky_number_format - AppNotification.TYPE_NEW_ANNOUNCEMENT -> R.plurals.notification_new_announcements_format - else -> R.plurals.notification_other_format - } - summaryTexts += app.resources.getQuantityString(pluralRes, value, value) + summaryTexts += app.resources.getQuantityString(key.pluralRes, value, value) } return summaryTexts.concat(", ") } @@ -83,7 +69,7 @@ class PostNotifications(val app: App, nList: List) { if (count == 0) return@run val notificationManager = app.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - val summaryCounts = SparseIntArray() + val summaryCounts = mutableMapOf() val newNotificationsText = app.resources.getQuantityString(R.plurals.notification_count_format, count, count) val newNotificationsShortText = app.resources.getQuantityString(R.plurals.notification_count_short_format, count, count) @@ -91,7 +77,7 @@ class PostNotifications(val app: App, nList: List) { val intent = Intent( app, MainActivity::class.java, - "fragmentId" to MainActivity.DRAWER_ITEM_NOTIFICATIONS + "fragmentId" to NavTarget.NOTIFICATIONS.id ) val summaryIntent = PendingIntent.getActivity(app, app.notificationChannelsManager.data.id, intent, PendingIntent.FLAG_ONE_SHOT or pendingIntentFlag()) @@ -101,7 +87,7 @@ class PostNotifications(val app: App, nList: List) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N && count > 4 || Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && count > 8) { val summaryList = mutableListOf() nList.forEach { - summaryCounts[it.type]++ + summaryCounts[it.type] = summaryCounts.getOrDefault(it.type, 0) + 1 summaryList += listOf( it.profileName.asBoldSpannable(), it.text @@ -141,14 +127,14 @@ class PostNotifications(val app: App, nList: List) { else { // Less than 8 notifications val notifications = nList.map { - summaryCounts[it.type]++ + summaryCounts[it.type] = summaryCounts.getOrDefault(it.type, 0) + 1 NotificationCompat.Builder(app, app.notificationChannelsManager.data.key) .setContentTitle(it.profileName ?: app.getString(R.string.app_name)) .setContentText(it.text) - .setSubText(if (it.type == TYPE_SERVER_MESSAGE) null else it.title) + .setSubText(if (it.type == NotificationType.SERVER_MESSAGE) null else it.title) .setTicker("${it.profileName}: ${it.title}") .setSmallIcon(R.drawable.ic_notification) - .setLargeIcon(IconicsDrawable(app, it.getLargeIcon()).apply { + .setLargeIcon(IconicsDrawable(app, it.type.icon).apply { colorRes = R.color.colorPrimary }.toBitmap()) .setStyle(NotificationCompat.BigTextStyle() diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/task/SzkolnyTask.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/task/SzkolnyTask.kt index 39dd7e15..64f647e9 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/task/SzkolnyTask.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/task/SzkolnyTask.kt @@ -55,7 +55,7 @@ class SzkolnyTask(val app: App, val syncingProfiles: List) : IApiTask(- notificationList .mapNotNull { it.profileId } .distinct() - .map { app.config.getFor(it).sync.notificationFilter } + .map { app.config[it].sync.notificationFilter } .forEach { filter -> filter.forEach { type -> notificationList.removeAll { it.type == type } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/AppDb.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/AppDb.kt index 93758366..def90a37 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/AppDb.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/AppDb.kt @@ -44,14 +44,15 @@ import pl.szczodrzynski.edziennik.data.db.migration.* TimetableManual::class, Note::class, Metadata::class -], version = 97) +], version = 100) @TypeConverters( ConverterTime::class, ConverterDate::class, ConverterJsonObject::class, ConverterListLong::class, ConverterListString::class, - ConverterDateInt::class + ConverterDateInt::class, + ConverterEnums::class ) abstract class AppDb : RoomDatabase() { abstract fun gradeDao(): GradeDao @@ -185,6 +186,9 @@ abstract class AppDb : RoomDatabase() { Migration95(), Migration96(), Migration97(), + Migration98(), + Migration99(), + Migration100(), ).allowMainThreadQueries().build() } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/converter/ConverterEnums.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/converter/ConverterEnums.kt new file mode 100644 index 00000000..d9fc893b --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/converter/ConverterEnums.kt @@ -0,0 +1,60 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2022-10-17. + */ + +package pl.szczodrzynski.edziennik.data.db.converter + +import androidx.room.TypeConverter +import pl.szczodrzynski.edziennik.data.db.enums.FeatureType +import pl.szczodrzynski.edziennik.data.db.enums.LoginMethod +import pl.szczodrzynski.edziennik.data.db.enums.LoginMode +import pl.szczodrzynski.edziennik.data.db.enums.LoginType +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType +import pl.szczodrzynski.edziennik.data.db.enums.NotificationType +import pl.szczodrzynski.edziennik.ext.* +import pl.szczodrzynski.edziennik.ui.base.enums.NavTarget + +class ConverterEnums { + + @TypeConverter + fun fromFeatureType(value: FeatureType?) = value?.id + + @TypeConverter + fun fromLoginMethod(value: LoginMethod?) = value?.id + + @TypeConverter + fun fromLoginMode(value: LoginMode?) = value?.id + + @TypeConverter + fun fromLoginType(value: LoginType?) = value?.id + + @TypeConverter + fun fromMetadataType(value: MetadataType?) = value?.id + + @TypeConverter + fun fromNotificationType(value: NotificationType?) = value?.id + + @TypeConverter + fun fromNavTarget(value: NavTarget?) = value?.id + + @TypeConverter + fun toFeatureType(value: Int?) = value.asFeatureTypeOrNull() + + @TypeConverter + fun toLoginMethod(value: Int?) = value.asLoginMethodOrNull() + + @TypeConverter + fun toLoginMode(value: Int?) = value.asLoginModeOrNull() + + @TypeConverter + fun toLoginType(value: Int?) = value.asLoginTypeOrNull() + + @TypeConverter + fun toMetadataType(value: Int?) = value.asMetadataTypeOrNull() + + @TypeConverter + fun toNotificationType(value: Int?) = value.asNotificationTypeOrNull() + + @TypeConverter + fun toNavTarget(value: Int?) = value.asNavTargetOrNull() ?: NavTarget.HOME +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/AnnouncementDao.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/AnnouncementDao.kt index 88b3ccd1..aa35c810 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/AnnouncementDao.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/AnnouncementDao.kt @@ -14,6 +14,7 @@ import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.data.db.AppDb import pl.szczodrzynski.edziennik.data.db.entity.Announcement import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull @Dao @@ -26,7 +27,7 @@ abstract class AnnouncementDao : BaseDao { teachers.teacherName ||" "|| teachers.teacherSurname AS teacherName FROM announcements LEFT JOIN teachers USING(profileId, teacherId) - LEFT JOIN metadata ON announcementId = thingId AND thingType = ${Metadata.TYPE_ANNOUNCEMENT} AND metadata.profileId = announcements.profileId + LEFT JOIN metadata ON announcementId = thingId AND thingType = 7 AND metadata.profileId = announcements.profileId """ private const val ORDER_BY = """ORDER BY addedDate DESC""" diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/AttendanceDao.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/AttendanceDao.kt index 94be0c34..a420c5b5 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/AttendanceDao.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/AttendanceDao.kt @@ -28,7 +28,7 @@ abstract class AttendanceDao : BaseDao { FROM attendances LEFT JOIN teachers USING(profileId, teacherId) LEFT JOIN subjects USING(profileId, subjectId) - LEFT JOIN metadata ON attendanceId = thingId AND thingType = ${Metadata.TYPE_ATTENDANCE} AND metadata.profileId = attendances.profileId + LEFT JOIN metadata ON attendanceId = thingId AND thingType = 3 AND metadata.profileId = attendances.profileId """ private const val ORDER_BY = """ORDER BY attendanceDate DESC, attendanceTime DESC""" diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/EventDao.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/EventDao.kt index 24e58d02..09605429 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/EventDao.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/EventDao.kt @@ -16,6 +16,7 @@ import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.data.db.AppDb import pl.szczodrzynski.edziennik.data.db.entity.Event import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType import pl.szczodrzynski.edziennik.data.db.full.EventFull import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Time @@ -35,7 +36,7 @@ abstract class EventDao : BaseDao { LEFT JOIN subjects USING(profileId, subjectId) LEFT JOIN teams USING(profileId, teamId) LEFT JOIN eventTypes USING(profileId, eventType) - LEFT JOIN metadata ON eventId = thingId AND (thingType = ${Metadata.TYPE_EVENT} OR thingType = ${Metadata.TYPE_HOMEWORK}) AND metadata.profileId = events.profileId + LEFT JOIN metadata ON eventId = thingId AND (thingType = 4 OR thingType = 5) AND metadata.profileId = events.profileId """ private const val ORDER_BY = """GROUP BY eventId ORDER BY eventDate, eventTime, addedDate ASC""" @@ -132,7 +133,7 @@ abstract class EventDao : BaseDao { dontKeepFuture(profileId, todayDate, "eventType NOT IN " + exceptTypes.toString().replace('[', '(').replace(']', ')')) } - @Query("UPDATE metadata SET seen = :seen WHERE profileId = :profileId AND (thingType = " + Metadata.TYPE_EVENT + " OR thingType = " + Metadata.TYPE_LESSON_CHANGE + " OR thingType = " + Metadata.TYPE_HOMEWORK + ") AND thingId IN (SELECT eventId FROM events WHERE profileId = :profileId AND eventDate = :date)") + @Query("UPDATE metadata SET seen = :seen WHERE profileId = :profileId AND (thingType = 4 OR thingType = 6 OR thingType = 5) AND thingId IN (SELECT eventId FROM events WHERE profileId = :profileId AND eventDate = :date)") abstract fun setSeenByDate(profileId: Int, date: Date, seen: Boolean) @Query("UPDATE events SET eventBlacklisted = :blacklisted WHERE profileId = :profileId AND eventId = :eventId") @@ -142,12 +143,12 @@ abstract class EventDao : BaseDao { abstract fun remove(profileId: Int, id: Long) @Query("DELETE FROM metadata WHERE profileId = :profileId AND thingType = :thingType AND thingId = :thingId") - abstract fun removeMetadata(profileId: Int, thingType: Int, thingId: Long) + abstract fun removeMetadata(profileId: Int, thingType: MetadataType, thingId: Long) @Transaction open fun remove(profileId: Int, type: Long, id: Long) { remove(profileId, id) - removeMetadata(profileId, if (type == Event.TYPE_HOMEWORK) Metadata.TYPE_HOMEWORK else Metadata.TYPE_EVENT, id) + removeMetadata(profileId, if (type == Event.TYPE_HOMEWORK) MetadataType.HOMEWORK else MetadataType.EVENT, id) } @Transaction diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/EventTypeDao.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/EventTypeDao.kt index d5006970..2f3f5c94 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/EventTypeDao.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/EventTypeDao.kt @@ -3,15 +3,16 @@ */ package pl.szczodrzynski.edziennik.data.db.dao -import android.content.Context +import android.graphics.Color import androidx.lifecycle.LiveData import androidx.room.Dao import androidx.room.Insert import androidx.room.OnConflictStrategy import androidx.room.Query -import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_DEFAULT +import pl.szczodrzynski.edziennik.config.AppData import pl.szczodrzynski.edziennik.data.db.entity.EventType import pl.szczodrzynski.edziennik.data.db.entity.EventType.Companion.SOURCE_DEFAULT +import pl.szczodrzynski.edziennik.data.db.entity.Profile @Dao abstract class EventTypeDao { @@ -24,6 +25,9 @@ abstract class EventTypeDao { @Query("DELETE FROM eventTypes WHERE profileId = :profileId") abstract fun clear(profileId: Int) + @Query("DELETE FROM eventTypes WHERE profileId = :profileId AND eventTypeSource = :source") + abstract fun clearBySource(profileId: Int, source: Int) + @Query("SELECT * FROM eventTypes WHERE profileId = :profileId AND eventType = :typeId") abstract fun getByIdNow(profileId: Int, typeId: Long): EventType? @@ -36,20 +40,37 @@ abstract class EventTypeDao { @get:Query("SELECT * FROM eventTypes") abstract val allNow: List - fun addDefaultTypes(context: Context, profileId: Int): List { + fun addDefaultTypes(profile: Profile): List { + val data = AppData.get(profile.loginStoreType) var order = 100 - val colorMap = EventType.getTypeColorMap() - val typeList = EventType.getTypeNameMap().map { (id, name) -> + val typeList = data.eventTypes.map { EventType( - profileId = profileId, - id = id, - name = context.getString(name), - color = colorMap[id] ?: COLOR_DEFAULT, + profileId = profile.id, + id = it.id, + name = it.name, + color = Color.parseColor(it.color), order = order++, - source = SOURCE_DEFAULT + source = SOURCE_DEFAULT, ) } addAll(typeList) return typeList } + + fun getAllWithDefaults(profile: Profile): List { + val eventTypes = getAllNow(profile.id) + + val defaultIdsExpected = AppData.get(profile.loginStoreType).eventTypes + .map { it.id } + val defaultIdsFound = eventTypes.filter { it.source == SOURCE_DEFAULT } + .sortedBy { it.order } + .map { it.id } + + if (defaultIdsExpected == defaultIdsFound) + return eventTypes + + clearBySource(profile.id, SOURCE_DEFAULT) + addDefaultTypes(profile) + return eventTypes + } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/GradeDao.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/GradeDao.kt index 6254f80b..4703eca9 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/GradeDao.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/GradeDao.kt @@ -33,7 +33,7 @@ abstract class GradeDao : BaseDao { FROM grades LEFT JOIN teachers USING(profileId, teacherId) LEFT JOIN subjects USING(profileId, subjectId) - LEFT JOIN metadata ON gradeId = thingId AND thingType = ${Metadata.TYPE_GRADE} AND metadata.profileId = grades.profileId + LEFT JOIN metadata ON gradeId = thingId AND thingType = 1 AND metadata.profileId = grades.profileId """ private const val ORDER_BY = """ORDER BY addedDate DESC""" diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/LuckyNumberDao.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/LuckyNumberDao.kt index 3c752124..b4d788c5 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/LuckyNumberDao.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/LuckyNumberDao.kt @@ -25,7 +25,7 @@ abstract class LuckyNumberDao : BaseDao { SELECT * FROM luckyNumbers - LEFT JOIN metadata ON luckyNumberDate = thingId AND thingType = ${Metadata.TYPE_LUCKY_NUMBER} AND metadata.profileId = luckyNumbers.profileId + LEFT JOIN metadata ON luckyNumberDate = thingId AND thingType = 10 AND metadata.profileId = luckyNumbers.profileId """ private const val ORDER_BY = """ORDER BY luckyNumberDate DESC""" diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/MessageDao.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/MessageDao.kt index af4e608e..59e9ca82 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/MessageDao.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/MessageDao.kt @@ -26,7 +26,7 @@ abstract class MessageDao : BaseDao { teachers.teacherName ||" "|| teachers.teacherSurname AS senderName FROM messages LEFT JOIN teachers ON teachers.profileId = messages.profileId AND teacherId = senderId - LEFT JOIN metadata ON messageId = thingId AND thingType = ${Metadata.TYPE_MESSAGE} AND metadata.profileId = messages.profileId + LEFT JOIN metadata ON messageId = thingId AND thingType = 8 AND metadata.profileId = messages.profileId """ private const val ORDER_BY = """ORDER BY messageIsPinned DESC, addedDate DESC""" diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/MetadataDao.java b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/MetadataDao.java index ab694702..c8cb295b 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/MetadataDao.java +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/MetadataDao.java @@ -4,15 +4,6 @@ package pl.szczodrzynski.edziennik.data.db.dao; -import static pl.szczodrzynski.edziennik.data.db.entity.Metadata.TYPE_ANNOUNCEMENT; -import static pl.szczodrzynski.edziennik.data.db.entity.Metadata.TYPE_ATTENDANCE; -import static pl.szczodrzynski.edziennik.data.db.entity.Metadata.TYPE_EVENT; -import static pl.szczodrzynski.edziennik.data.db.entity.Metadata.TYPE_GRADE; -import static pl.szczodrzynski.edziennik.data.db.entity.Metadata.TYPE_HOMEWORK; -import static pl.szczodrzynski.edziennik.data.db.entity.Metadata.TYPE_LESSON_CHANGE; -import static pl.szczodrzynski.edziennik.data.db.entity.Metadata.TYPE_MESSAGE; -import static pl.szczodrzynski.edziennik.data.db.entity.Metadata.TYPE_NOTICE; - import androidx.lifecycle.LiveData; import androidx.room.Dao; import androidx.room.Insert; @@ -29,6 +20,7 @@ import pl.szczodrzynski.edziennik.data.db.entity.Grade; import pl.szczodrzynski.edziennik.data.db.entity.Message; import pl.szczodrzynski.edziennik.data.db.entity.Metadata; import pl.szczodrzynski.edziennik.data.db.entity.Notice; +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType; import pl.szczodrzynski.edziennik.data.db.full.LessonFull; import pl.szczodrzynski.edziennik.utils.models.UnreadCounter; @@ -44,10 +36,10 @@ public abstract class MetadataDao { public abstract void addAllReplace(List metadataList); @Query("UPDATE metadata SET seen = :seen WHERE thingId = :thingId AND thingType = :thingType AND profileId = :profileId") - abstract void updateSeen(int profileId, int thingType, long thingId, boolean seen); + abstract void updateSeen(int profileId, MetadataType thingType, long thingId, boolean seen); @Query("UPDATE metadata SET notified = :notified WHERE thingId = :thingId AND thingType = :thingType AND profileId = :profileId") - abstract void updateNotified(int profileId, int thingType, long thingId, boolean notified); + abstract void updateNotified(int profileId, MetadataType thingType, long thingId, boolean notified); @@ -63,38 +55,38 @@ public abstract class MetadataDao { @Transaction public void setSeen(int profileId, Object o, boolean seen) { if (o instanceof Grade) { - if (add(new Metadata(profileId, TYPE_GRADE, ((Grade) o).getId(), seen, false)) == -1) { - updateSeen(profileId, TYPE_GRADE, ((Grade) o).getId(), seen); + if (add(new Metadata(profileId, MetadataType.GRADE, ((Grade) o).getId(), seen, false)) == -1) { + updateSeen(profileId, MetadataType.GRADE, ((Grade) o).getId(), seen); } } if (o instanceof Attendance) { - if (add(new Metadata(profileId, TYPE_ATTENDANCE, ((Attendance) o).getId(), seen, false)) == -1) { - updateSeen(profileId, TYPE_ATTENDANCE, ((Attendance) o).getId(), seen); + if (add(new Metadata(profileId, MetadataType.ATTENDANCE, ((Attendance) o).getId(), seen, false)) == -1) { + updateSeen(profileId, MetadataType.ATTENDANCE, ((Attendance) o).getId(), seen); } } if (o instanceof Notice) { - if (add(new Metadata(profileId, TYPE_NOTICE, ((Notice) o).getId(), seen, false)) == -1) { - updateSeen(profileId, TYPE_NOTICE, ((Notice) o).getId(), seen); + if (add(new Metadata(profileId, MetadataType.NOTICE, ((Notice) o).getId(), seen, false)) == -1) { + updateSeen(profileId, MetadataType.NOTICE, ((Notice) o).getId(), seen); } } if (o instanceof Event) { - if (add(new Metadata(profileId, ((Event) o).isHomework() ? TYPE_HOMEWORK : TYPE_EVENT, ((Event) o).getId(), seen, false)) == -1) { - updateSeen(profileId, ((Event) o).isHomework() ? TYPE_HOMEWORK : TYPE_EVENT, ((Event) o).getId(), seen); + if (add(new Metadata(profileId, ((Event) o).isHomework() ? MetadataType.HOMEWORK : MetadataType.EVENT, ((Event) o).getId(), seen, false)) == -1) { + updateSeen(profileId, ((Event) o).isHomework() ? MetadataType.HOMEWORK : MetadataType.EVENT, ((Event) o).getId(), seen); } } if (o instanceof LessonFull) { - if (add(new Metadata(profileId, TYPE_LESSON_CHANGE, ((LessonFull) o).getId(), seen, false)) == -1) { - updateSeen(profileId, TYPE_LESSON_CHANGE, ((LessonFull) o).getId(), seen); + if (add(new Metadata(profileId, MetadataType.LESSON_CHANGE, ((LessonFull) o).getId(), seen, false)) == -1) { + updateSeen(profileId, MetadataType.LESSON_CHANGE, ((LessonFull) o).getId(), seen); } } if (o instanceof Announcement) { - if (add(new Metadata(profileId, TYPE_ANNOUNCEMENT, ((Announcement) o).getId(), seen, false)) == -1) { - updateSeen(profileId, TYPE_ANNOUNCEMENT, ((Announcement) o).getId(), seen); + if (add(new Metadata(profileId, MetadataType.ANNOUNCEMENT, ((Announcement) o).getId(), seen, false)) == -1) { + updateSeen(profileId, MetadataType.ANNOUNCEMENT, ((Announcement) o).getId(), seen); } } if (o instanceof Message) { - if (add(new Metadata(profileId, TYPE_MESSAGE, ((Message) o).getId(), seen, false)) == -1) { - updateSeen(profileId, TYPE_MESSAGE, ((Message) o).getId(), seen); + if (add(new Metadata(profileId, MetadataType.MESSAGE, ((Message) o).getId(), seen, false)) == -1) { + updateSeen(profileId, MetadataType.MESSAGE, ((Message) o).getId(), seen); } } } @@ -102,38 +94,38 @@ public abstract class MetadataDao { @Transaction public void setNotified(int profileId, Object o, boolean notified) { if (o instanceof Grade) { - if (add(new Metadata(profileId, TYPE_GRADE, ((Grade) o).getId(), false, notified)) == -1) { - updateNotified(profileId, TYPE_GRADE, ((Grade) o).getId(), notified); + if (add(new Metadata(profileId, MetadataType.GRADE, ((Grade) o).getId(), false, notified)) == -1) { + updateNotified(profileId, MetadataType.GRADE, ((Grade) o).getId(), notified); } } if (o instanceof Attendance) { - if (add(new Metadata(profileId, TYPE_ATTENDANCE, ((Attendance) o).getId(), false, notified)) == -1) { - updateNotified(profileId, TYPE_ATTENDANCE, ((Attendance) o).getId(), notified); + if (add(new Metadata(profileId, MetadataType.ATTENDANCE, ((Attendance) o).getId(), false, notified)) == -1) { + updateNotified(profileId, MetadataType.ATTENDANCE, ((Attendance) o).getId(), notified); } } if (o instanceof Notice) { - if (add(new Metadata(profileId, TYPE_NOTICE, ((Notice) o).getId(), false, notified)) == -1) { - updateNotified(profileId, TYPE_NOTICE, ((Notice) o).getId(), notified); + if (add(new Metadata(profileId, MetadataType.NOTICE, ((Notice) o).getId(), false, notified)) == -1) { + updateNotified(profileId, MetadataType.NOTICE, ((Notice) o).getId(), notified); } } if (o instanceof Event) { - if (add(new Metadata(profileId, ((Event) o).isHomework() ? TYPE_HOMEWORK : TYPE_EVENT, ((Event) o).getId(), false, notified)) == -1) { - updateNotified(profileId, ((Event) o).isHomework() ? TYPE_HOMEWORK : TYPE_EVENT, ((Event) o).getId(), notified); + if (add(new Metadata(profileId, ((Event) o).isHomework() ? MetadataType.HOMEWORK : MetadataType.EVENT, ((Event) o).getId(), false, notified)) == -1) { + updateNotified(profileId, ((Event) o).isHomework() ? MetadataType.HOMEWORK : MetadataType.EVENT, ((Event) o).getId(), notified); } } if (o instanceof LessonFull) { - if (add(new Metadata(profileId, TYPE_LESSON_CHANGE, ((LessonFull) o).getId(), false, notified)) == -1) { - updateNotified(profileId, TYPE_LESSON_CHANGE, ((LessonFull) o).getId(), notified); + if (add(new Metadata(profileId, MetadataType.LESSON_CHANGE, ((LessonFull) o).getId(), false, notified)) == -1) { + updateNotified(profileId, MetadataType.LESSON_CHANGE, ((LessonFull) o).getId(), notified); } } if (o instanceof Announcement) { - if (add(new Metadata(profileId, TYPE_ANNOUNCEMENT, ((Announcement) o).getId(), false, notified)) == -1) { - updateNotified(profileId, TYPE_ANNOUNCEMENT, ((Announcement) o).getId(), notified); + if (add(new Metadata(profileId, MetadataType.ANNOUNCEMENT, ((Announcement) o).getId(), false, notified)) == -1) { + updateNotified(profileId, MetadataType.ANNOUNCEMENT, ((Announcement) o).getId(), notified); } } if (o instanceof Message) { - if (add(new Metadata(profileId, TYPE_MESSAGE, ((Message) o).getId(), false, notified)) == -1) { - updateNotified(profileId, TYPE_MESSAGE, ((Message) o).getId(), notified); + if (add(new Metadata(profileId, MetadataType.MESSAGE, ((Message) o).getId(), false, notified)) == -1) { + updateNotified(profileId, MetadataType.MESSAGE, ((Message) o).getId(), notified); } } } @@ -141,9 +133,9 @@ public abstract class MetadataDao { @Transaction public void setBoth(int profileId, Event o, boolean seen, boolean notified, long addedDate) { if (o != null) { - if (add(new Metadata(profileId, o.isHomework() ? TYPE_HOMEWORK : TYPE_EVENT, o.getId(), seen, notified)) == -1) { - updateSeen(profileId, o.isHomework() ? TYPE_HOMEWORK : TYPE_EVENT, o.getId(), seen); - updateNotified(profileId, o.isHomework() ? TYPE_HOMEWORK : TYPE_EVENT, o.getId(), notified); + if (add(new Metadata(profileId, o.isHomework() ? MetadataType.HOMEWORK : MetadataType.EVENT, o.getId(), seen, notified)) == -1) { + updateSeen(profileId, o.isHomework() ? MetadataType.HOMEWORK : MetadataType.EVENT, o.getId(), seen); + updateNotified(profileId, o.isHomework() ? MetadataType.HOMEWORK : MetadataType.EVENT, o.getId(), notified); } } } @@ -151,18 +143,18 @@ public abstract class MetadataDao { @Query("UPDATE metadata SET seen = :seen WHERE profileId = :profileId AND thingType = :thingType") - public abstract void setAllSeen(int profileId, int thingType, boolean seen); + public abstract void setAllSeen(int profileId, MetadataType thingType, boolean seen); @Query("UPDATE metadata SET notified = :notified WHERE profileId = :profileId AND thingType = :thingType") - public abstract void setAllNotified(int profileId, int thingType, boolean notified); + public abstract void setAllNotified(int profileId, MetadataType thingType, boolean notified); @Query("UPDATE metadata SET seen = :seen WHERE profileId = :profileId") public abstract void setAllSeen(int profileId, boolean seen); - @Query("UPDATE metadata SET seen = :seen WHERE profileId = :profileId AND thingType != " + TYPE_MESSAGE) + @Query("UPDATE metadata SET seen = :seen WHERE profileId = :profileId AND thingType != 8") public abstract void setAllSeenExceptMessages(int profileId, boolean seen); - @Query("UPDATE metadata SET seen = :seen WHERE profileId = :profileId AND thingType != " + TYPE_MESSAGE + " AND thingType != " + TYPE_ANNOUNCEMENT) + @Query("UPDATE metadata SET seen = :seen WHERE profileId = :profileId AND thingType != 8 AND thingType != 7") public abstract void setAllSeenExceptMessagesAndAnnouncements(int profileId, boolean seen); @Query("UPDATE metadata SET notified = :notified WHERE profileId = :profileId") @@ -174,10 +166,10 @@ public abstract class MetadataDao { @Query("SELECT count() FROM metadata WHERE profileId = :profileId AND thingType = :thingType AND seen = 0") - public abstract LiveData countUnseen(int profileId, int thingType); + public abstract LiveData countUnseen(int profileId, MetadataType thingType); @Query("SELECT count() FROM metadata WHERE profileId = :profileId AND thingType = :thingType AND seen = 0") - public abstract Integer countUnseenNow(int profileId, int thingType); + public abstract Integer countUnseenNow(int profileId, MetadataType thingType); @Query("SELECT count() FROM metadata WHERE profileId = :profileId AND seen = 0") public abstract LiveData countUnseen(int profileId); @@ -191,7 +183,7 @@ public abstract class MetadataDao { @Query("DELETE FROM metadata WHERE profileId = :profileId AND thingType = :thingType AND thingId = :thingId") - public abstract void delete(int profileId, int thingType, long thingId); + public abstract void delete(int profileId, MetadataType thingType, long thingId); @Query("DELETE FROM metadata WHERE profileId = :profileId") public abstract void deleteAll(int profileId); @@ -203,25 +195,25 @@ public abstract class MetadataDao { - @Query("DELETE FROM metadata WHERE profileId = :profileId AND thingType = "+TYPE_GRADE+" AND thingId NOT IN (SELECT gradeId FROM grades WHERE profileId = :profileId);") + @Query("DELETE FROM metadata WHERE profileId = :profileId AND thingType = 1 AND thingId NOT IN (SELECT gradeId FROM grades WHERE profileId = :profileId);") public abstract void deleteUnusedGrades(int profileId); - @Query("DELETE FROM metadata WHERE profileId = :profileId AND thingType = "+TYPE_NOTICE+" AND thingId NOT IN (SELECT noticeId FROM notices WHERE profileId = :profileId);") + @Query("DELETE FROM metadata WHERE profileId = :profileId AND thingType = 2 AND thingId NOT IN (SELECT noticeId FROM notices WHERE profileId = :profileId);") public abstract void deleteUnusedNotices(int profileId); - @Query("DELETE FROM metadata WHERE profileId = :profileId AND thingType = "+TYPE_ATTENDANCE+" AND thingId NOT IN (SELECT attendanceId FROM attendances WHERE profileId = :profileId);") + @Query("DELETE FROM metadata WHERE profileId = :profileId AND thingType = 3 AND thingId NOT IN (SELECT attendanceId FROM attendances WHERE profileId = :profileId);") public abstract void deleteUnusedAttendance(int profileId); - @Query("DELETE FROM metadata WHERE profileId = :profileId AND thingType = "+TYPE_EVENT+" AND thingId NOT IN (SELECT eventId FROM events WHERE profileId = :profileId AND eventType != -1);") + @Query("DELETE FROM metadata WHERE profileId = :profileId AND thingType = 4 AND thingId NOT IN (SELECT eventId FROM events WHERE profileId = :profileId AND eventType != -1);") public abstract void deleteUnusedEvents(int profileId); - @Query("DELETE FROM metadata WHERE profileId = :profileId AND thingType = "+TYPE_HOMEWORK+" AND thingId NOT IN (SELECT eventId FROM events WHERE profileId = :profileId AND eventType = -1);") + @Query("DELETE FROM metadata WHERE profileId = :profileId AND thingType = 5 AND thingId NOT IN (SELECT eventId FROM events WHERE profileId = :profileId AND eventType = -1);") public abstract void deleteUnusedHomework(int profileId); - @Query("DELETE FROM metadata WHERE profileId = :profileId AND thingType = "+TYPE_ANNOUNCEMENT+" AND thingId NOT IN (SELECT announcementId FROM announcements WHERE profileId = :profileId);") + @Query("DELETE FROM metadata WHERE profileId = :profileId AND thingType = 7 AND thingId NOT IN (SELECT announcementId FROM announcements WHERE profileId = :profileId);") public abstract void deleteUnusedAnnouncements(int profileId); - @Query("DELETE FROM metadata WHERE profileId = :profileId AND thingType = "+TYPE_MESSAGE+" AND thingId NOT IN (SELECT messageId FROM messages WHERE profileId = :profileId);") + @Query("DELETE FROM metadata WHERE profileId = :profileId AND thingType = 8 AND thingId NOT IN (SELECT messageId FROM messages WHERE profileId = :profileId);") public abstract void deleteUnusedMessages(int profileId); @Transaction diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/NoticeDao.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/NoticeDao.kt index 51f3e839..20443c22 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/NoticeDao.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/NoticeDao.kt @@ -26,7 +26,7 @@ abstract class NoticeDao : BaseDao { teachers.teacherName ||" "|| teachers.teacherSurname AS teacherName FROM notices LEFT JOIN teachers USING(profileId, teacherId) - LEFT JOIN metadata ON noticeId = thingId AND thingType = ${Metadata.TYPE_NOTICE} AND metadata.profileId = notices.profileId + LEFT JOIN metadata ON noticeId = thingId AND thingType = 2 AND metadata.profileId = notices.profileId """ private const val ORDER_BY = """ORDER BY addedDate DESC""" diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/ProfileDao.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/ProfileDao.kt index 6d1e799d..f83a8e76 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/ProfileDao.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/ProfileDao.kt @@ -28,6 +28,9 @@ interface ProfileDao { @Query("SELECT * FROM profiles WHERE profileId = :profileId") fun getByIdNow(profileId: Int): Profile? + @Query("SELECT * FROM profiles WHERE profileId = :profileId") + suspend fun getByIdSuspend(profileId: Int): Profile? + @get:Query("SELECT * FROM profiles WHERE profileId >= 0 ORDER BY profileId") val all: LiveData> @@ -49,7 +52,7 @@ interface ProfileDao { @get:Query("SELECT profileId FROM profiles WHERE profileId >= 0 ORDER BY profileId") val idsNow: List - @Query("SELECT profiles.* FROM teams JOIN profiles USING(profileId) WHERE teamCode = :teamCode AND registration = " + Profile.REGISTRATION_ENABLED + " AND enableSharedEvents = 1") + @Query("SELECT profiles.* FROM teams JOIN profiles USING(profileId) WHERE teamCode = :teamCode AND registration = " + Profile.REGISTRATION_ENABLED) fun getByTeamCodeNowWithRegistration(teamCode: String?): List @get:Query("SELECT profileId FROM profiles WHERE profileId > 0 ORDER BY profileId ASC LIMIT 1") diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/TeacherAbsenceDao.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/TeacherAbsenceDao.kt index f12248b8..de65f730 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/TeacherAbsenceDao.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/TeacherAbsenceDao.kt @@ -27,7 +27,7 @@ abstract class TeacherAbsenceDao : BaseDao { teachers.teacherName ||" "|| teachers.teacherSurname AS teacherName FROM teacherAbsence LEFT JOIN teachers USING(profileId, teacherId) - LEFT JOIN metadata ON teacherAbsenceId = thingId AND thingType = ${Metadata.TYPE_TEACHER_ABSENCE} AND metadata.profileId = teacherAbsence.profileId + LEFT JOIN metadata ON teacherAbsenceId = thingId AND thingType = 9 AND metadata.profileId = teacherAbsence.profileId """ private const val ORDER_BY = """ORDER BY teacherAbsenceDateFrom ASC""" diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/TimetableDao.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/TimetableDao.kt index 4201a51d..701dabf6 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/TimetableDao.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/TimetableDao.kt @@ -38,7 +38,7 @@ abstract class TimetableDao : BaseDao { LEFT JOIN subjects AS oldS ON timetable.profileId = oldS.profileId AND timetable.oldSubjectId = oldS.subjectId LEFT JOIN teachers AS oldT ON timetable.profileId = oldT.profileId AND timetable.oldTeacherId = oldT.teacherId LEFT JOIN teams AS oldG ON timetable.profileId = oldG.profileId AND timetable.oldTeamId = oldG.teamId - LEFT JOIN metadata ON id = thingId AND thingType = ${Metadata.TYPE_LESSON_CHANGE} AND metadata.profileId = timetable.profileId + LEFT JOIN metadata ON id = thingId AND thingType = 6 AND metadata.profileId = timetable.profileId """ private const val ORDER_BY = """ORDER BY profileId, id, type""" @@ -107,6 +107,9 @@ abstract class TimetableDao : BaseDao { fun getByIdNow(profileId: Int, id: Long) = getOneNow("$QUERY WHERE timetable.profileId = $profileId AND timetable.id = $id") + fun getByOwnerIdNow(profileId: Int, ownerId: Long) = + getOneNow("$QUERY WHERE timetable.profileId = $profileId AND timetable.ownerId = $ownerId") + @Query("UPDATE timetable SET keep = 0 WHERE profileId = :profileId AND isExtra = :isExtra AND type != -1 AND ((type != 3 AND date >= :dateFrom) OR ((type = 3 OR type = 1) AND oldDate >= :dateFrom))") abstract fun dontKeepFromDate(profileId: Int, dateFrom: Date, isExtra: Boolean) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/EndpointTimer.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/EndpointTimer.kt index 05676131..a40df7da 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/EndpointTimer.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/EndpointTimer.kt @@ -6,6 +6,7 @@ package pl.szczodrzynski.edziennik.data.db.entity import androidx.room.ColumnInfo import androidx.room.Entity +import pl.szczodrzynski.edziennik.data.db.enums.FeatureType const val SYNC_NEVER = 0L const val SYNC_ALWAYS = 1L @@ -26,7 +27,7 @@ data class EndpointTimer ( var nextSync: Long = SYNC_ALWAYS, @ColumnInfo(name = "endpointViewId") - var viewId: Int? = null + var featureType: FeatureType? = null ) { @@ -49,17 +50,15 @@ data class EndpointTimer ( } /** - * Set this timer to sync only if [viewId] is the only + * Set this timer to sync only if [featureType] is the only * selected feature during the current process. - * - * [viewId] may be [DRAWER_ITEM_HOME] to sync only if all features are selected. */ - fun syncWhenView(viewId: Int): EndpointTimer { + fun syncWithFeature(featureType: FeatureType): EndpointTimer { // set to never sync if nextSync is not already a timestamp if (nextSync < 10) { this.nextSync = SYNC_NEVER } - this.viewId = viewId + this.featureType = featureType return this } @@ -68,7 +67,7 @@ data class EndpointTimer ( */ fun syncAlways(): EndpointTimer { nextSync = SYNC_ALWAYS - viewId = null + featureType = null return this } @@ -77,7 +76,7 @@ data class EndpointTimer ( */ fun syncNever(): EndpointTimer { nextSync = SYNC_NEVER - viewId = null + featureType = null return this } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Event.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Event.kt index 795bad85..505ed086 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Event.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Event.kt @@ -73,13 +73,22 @@ open class Event( const val COLOR_INFORMATION = 0xff039be5.toInt() } + /** + * Added manually - added by self, shared by self, or shared by someone else. + */ @ColumnInfo(name = "eventAddedManually") var addedManually: Boolean = false - get() = field || sharedBy == "self" + get() = field || isShared + + /** + * Shared by - user code who shared the event. Null if not shared. + * "Self" if shared by this app user. + */ @ColumnInfo(name = "eventSharedBy") var sharedBy: String? = null @ColumnInfo(name = "eventSharedByName") var sharedByName: String? = null + @ColumnInfo(name = "eventBlacklisted") var blacklisted: Boolean = false @ColumnInfo(name = "eventIsDone") @@ -104,6 +113,27 @@ open class Event( var attachmentIds: MutableList? = null var attachmentNames: MutableList? = null + val isHomework + get() = type == TYPE_HOMEWORK + + /** + * Whether the event is shared by anyone. Note that this implies [addedManually]. + */ + val isShared + get() = sharedBy != null + + /** + * Whether the event is shared by "self" (this app user). + */ + val isSharedSent + get() = sharedBy == "self" + + /** + * Whether the event is shared by someone else from the class group. + */ + val isSharedReceived + get() = sharedBy != null && sharedBy != "self" + /** * Add an attachment * @param id attachment ID @@ -134,9 +164,6 @@ open class Event( it.timeInMillis += 45 * MINUTE * 1000 } - val isHomework - get() = type == TYPE_HOMEWORK - @Ignore fun withMetadata(metadata: Metadata) = EventFull(this, metadata) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/EventType.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/EventType.kt index 7753b38a..00ba4c04 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/EventType.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/EventType.kt @@ -5,31 +5,6 @@ package pl.szczodrzynski.edziennik.data.db.entity import androidx.room.ColumnInfo import androidx.room.Entity -import pl.szczodrzynski.edziennik.R -import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_CLASS_EVENT -import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_DEFAULT -import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_ELEARNING -import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_ESSAY -import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_EXAM -import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_EXCURSION -import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_HOMEWORK -import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_INFORMATION -import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_PROJECT -import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_PT_MEETING -import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_READING -import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_SHORT_QUIZ -import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_CLASS_EVENT -import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_DEFAULT -import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_ELEARNING -import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_ESSAY -import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_EXAM -import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_EXCURSION -import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_HOMEWORK -import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_INFORMATION -import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_PROJECT -import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_PT_MEETING -import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_READING -import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_SHORT_QUIZ @Entity( tableName = "eventTypes", @@ -55,35 +30,5 @@ class EventType( const val SOURCE_REGISTER = 1 const val SOURCE_CUSTOM = 2 const val SOURCE_SHARED = 3 - - fun getTypeColorMap() = mapOf( - TYPE_ELEARNING to COLOR_ELEARNING, - TYPE_HOMEWORK to COLOR_HOMEWORK, - TYPE_DEFAULT to COLOR_DEFAULT, - TYPE_EXAM to COLOR_EXAM, - TYPE_SHORT_QUIZ to COLOR_SHORT_QUIZ, - TYPE_ESSAY to COLOR_ESSAY, - TYPE_PROJECT to COLOR_PROJECT, - TYPE_PT_MEETING to COLOR_PT_MEETING, - TYPE_EXCURSION to COLOR_EXCURSION, - TYPE_READING to COLOR_READING, - TYPE_CLASS_EVENT to COLOR_CLASS_EVENT, - TYPE_INFORMATION to COLOR_INFORMATION - ) - - fun getTypeNameMap() = mapOf( - TYPE_ELEARNING to R.string.event_type_elearning, - TYPE_HOMEWORK to R.string.event_type_homework, - TYPE_DEFAULT to R.string.event_other, - TYPE_EXAM to R.string.event_exam, - TYPE_SHORT_QUIZ to R.string.event_short_quiz, - TYPE_ESSAY to R.string.event_essay, - TYPE_PROJECT to R.string.event_project, - TYPE_PT_MEETING to R.string.event_pt_meeting, - TYPE_EXCURSION to R.string.event_excursion, - TYPE_READING to R.string.event_reading, - TYPE_CLASS_EVENT to R.string.event_class_event, - TYPE_INFORMATION to R.string.event_information - ) } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Lesson.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Lesson.kt index 5123a2f5..8827993b 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Lesson.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Lesson.kt @@ -50,6 +50,13 @@ open class Lesson( var isExtra: Boolean = false + /** + * Stable ID denoting this lesson, used for note sharing (i.e. [profileId]-independent). + * + * This is simply the Unix timestamp of the lesson (in seconds). + */ + var ownerId: Long = id + val displayDate: Date? get() { if (type == TYPE_SHIFTED_SOURCE) @@ -69,11 +76,18 @@ open class Lesson( val isChange get() = type == TYPE_CHANGE || type == TYPE_SHIFTED_TARGET - fun buildId(): Long = (displayDate?.combineWith(displayStartTime) ?: 0L) / 6L * 10L + (hashCode() and 0xFFFF) + fun buildId(): Long = + (displayDate?.combineWith(displayStartTime) ?: 0L) / 6L * 10L + + (hashCode() and 0xFFFF) + + fun buildOwnerId(): Long = + (displayDate?.combineWith(displayStartTime) ?: 0L) @Ignore var showAsUnseen = false + var color: Int? = null + override fun toString(): String { return "Lesson(profileId=$profileId, " + "id=$id, " + diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/LoginStore.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/LoginStore.kt index 32c0d7e7..f9ba31da 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/LoginStore.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/LoginStore.kt @@ -8,6 +8,8 @@ import android.os.Bundle import androidx.room.ColumnInfo import androidx.room.Entity import com.google.gson.JsonObject +import pl.szczodrzynski.edziennik.data.db.enums.LoginMode +import pl.szczodrzynski.edziennik.data.db.enums.LoginType import pl.szczodrzynski.edziennik.ext.* @Entity(tableName = "loginStores", primaryKeys = ["loginStoreId"]) @@ -16,25 +18,14 @@ class LoginStore( val id: Int, @ColumnInfo(name = "loginStoreType") - val type: Int, + val type: LoginType, @ColumnInfo(name = "loginStoreMode") - val mode: Int, + val mode: LoginMode, @ColumnInfo(name = "loginStoreData") val data: JsonObject = JsonObject() ) { - companion object { - const val LOGIN_TYPE_MOBIDZIENNIK = 1 - const val LOGIN_TYPE_LIBRUS = 2 - const val LOGIN_TYPE_VULCAN = 4 - const val LOGIN_TYPE_IDZIENNIK = 3 - const val LOGIN_TYPE_EDUDZIENNIK = 5 - const val LOGIN_TYPE_DEMO = 20 - const val LOGIN_MODE_LIBRUS_EMAIL = 0 - const val LOGIN_MODE_LIBRUS_SYNERGIA = 1 - const val LOGIN_MODE_LIBRUS_JST = 2 - } fun hasLoginData(key: String) = data.has(key) fun getLoginData(key: String, defaultValue: Boolean) = data.getBoolean(key) ?: defaultValue @@ -64,31 +55,11 @@ class LoginStore( } } - fun type(): String { - return when (type) { - LOGIN_TYPE_MOBIDZIENNIK -> "LOGIN_TYPE_MOBIDZIENNIK" - LOGIN_TYPE_LIBRUS -> "LOGIN_TYPE_LIBRUS" - LOGIN_TYPE_IDZIENNIK -> "LOGIN_TYPE_IDZIENNIK" - LOGIN_TYPE_VULCAN -> "LOGIN_TYPE_VULCAN" - LOGIN_TYPE_DEMO -> "LOGIN_TYPE_DEMO" - else -> "unknown" - } - } - - fun mode(): String { - return when (mode) { - LOGIN_MODE_LIBRUS_EMAIL -> "LOGIN_MODE_LIBRUS_EMAIL" - LOGIN_MODE_LIBRUS_SYNERGIA -> "LOGIN_MODE_LIBRUS_SYNERGIA" - LOGIN_MODE_LIBRUS_JST -> "LOGIN_MODE_LIBRUS_JST" - else -> "unknown" - } - } - override fun toString(): String { return "LoginStore{" + "id=" + id + - ", type=" + type() + - ", mode=" + mode() + + ", type=" + type + + ", mode=" + mode + ", data=" + data + '}' } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Metadata.java b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Metadata.java index 7374d915..7b3544df 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Metadata.java +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Metadata.java @@ -4,47 +4,32 @@ package pl.szczodrzynski.edziennik.data.db.entity; +import androidx.annotation.NonNull; import androidx.room.ColumnInfo; import androidx.room.Entity; import androidx.room.Ignore; import androidx.room.Index; import androidx.room.PrimaryKey; +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType; @Entity(tableName = "metadata", indices = {@Index(value = {"profileId", "thingType", "thingId"}, unique = true)} ) public class Metadata { - public static final int TYPE_GRADE = 1; - public static final int TYPE_NOTICE = 2; - public static final int TYPE_ATTENDANCE = 3; - public static final int TYPE_EVENT = 4; - public static final int TYPE_HOMEWORK = 5; - public static final int TYPE_LESSON_CHANGE = 6; - public static final int TYPE_ANNOUNCEMENT = 7; - public static final int TYPE_MESSAGE = 8; - public static final int TYPE_TEACHER_ABSENCE = 9; - public static final int TYPE_LUCKY_NUMBER = 10; - public int profileId; @PrimaryKey(autoGenerate = true) @ColumnInfo(name = "metadataId") public int id; - public int thingType; + @NonNull + public MetadataType thingType; public long thingId; public boolean seen; public boolean notified; - @Ignore - public Metadata() { - this.profileId = -1; - this.seen = false; - this.notified = false; - } - - public Metadata(int profileId, int thingType, long thingId, boolean seen, boolean notified) { + public Metadata(int profileId, @NonNull MetadataType thingType, long thingId, boolean seen, boolean notified) { this.profileId = profileId; this.thingType = thingType; this.thingId = thingId; @@ -52,35 +37,12 @@ public class Metadata { this.notified = notified; } - public String thingType() { - switch (thingType) { - case TYPE_GRADE: - return "TYPE_GRADE"; - case TYPE_NOTICE: - return "TYPE_NOTICE"; - case TYPE_ATTENDANCE: - return "TYPE_ATTENDANCE"; - case TYPE_EVENT: - return "TYPE_EVENT"; - case TYPE_HOMEWORK: - return "TYPE_HOMEWORK"; - case TYPE_LESSON_CHANGE: - return "TYPE_LESSON_CHANGE"; - case TYPE_ANNOUNCEMENT: - return "TYPE_ANNOUNCEMENT"; - case TYPE_MESSAGE: - return "TYPE_MESSAGE"; - default: - return "TYPE_UNKNOWN"; - } - } - @Override public String toString() { return "Metadata{" + "profileId=" + profileId + ", id=" + id + - ", thingType=" + thingType() + + ", thingType=" + thingType + ", thingId=" + thingId + ", seen=" + seen + ", notified=" + notified + diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Noteable.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Noteable.kt index 052596b3..8881c1b4 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Noteable.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Noteable.kt @@ -9,6 +9,7 @@ interface Noteable { fun getNoteType(): Note.OwnerType fun getNoteOwnerProfileId(): Int fun getNoteOwnerId(): Long + fun getNoteShareTeamId(): Long? = null var notes: MutableList diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Notification.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Notification.kt index 35208927..6c3097a4 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Notification.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Notification.kt @@ -7,13 +7,17 @@ package pl.szczodrzynski.edziennik.data.db.entity import android.app.PendingIntent import android.content.Context import android.content.Intent +import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey import com.google.gson.JsonObject import com.mikepenz.iconics.typeface.IIcon import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial import pl.szczodrzynski.edziennik.MainActivity +import pl.szczodrzynski.edziennik.data.db.enums.NotificationType import pl.szczodrzynski.edziennik.ext.pendingIntentFlag +import pl.szczodrzynski.edziennik.ext.putExtras +import pl.szczodrzynski.edziennik.ui.base.enums.NavTarget @Entity(tableName = "notifications") data class Notification( @@ -24,43 +28,22 @@ data class Notification( val text: String, val textLong: String? = null, - val type: Int, + val type: NotificationType, val profileId: Int?, val profileName: String?, var posted: Boolean = true, - var viewId: Int? = null, + @ColumnInfo(name = "viewId") + var navTarget: NavTarget? = null, var extras: JsonObject? = null, val addedDate: Long = System.currentTimeMillis() ) { companion object { - const val TYPE_GENERAL = 0 - const val TYPE_UPDATE = 1 - const val TYPE_ERROR = 2 - const val TYPE_TIMETABLE_CHANGED = 3 - const val TYPE_TIMETABLE_LESSON_CHANGE = 4 - const val TYPE_NEW_GRADE = 5 - const val TYPE_NEW_EVENT = 6 - const val TYPE_NEW_HOMEWORK = 10 - const val TYPE_NEW_SHARED_EVENT = 7 - const val TYPE_NEW_SHARED_HOMEWORK = 12 - const val TYPE_REMOVED_SHARED_EVENT = 18 - const val TYPE_NEW_MESSAGE = 8 - const val TYPE_NEW_NOTICE = 9 - const val TYPE_NEW_ATTENDANCE = 13 - const val TYPE_SERVER_MESSAGE = 11 - const val TYPE_LUCKY_NUMBER = 14 - const val TYPE_NEW_ANNOUNCEMENT = 15 - const val TYPE_FEEDBACK_MESSAGE = 16 - const val TYPE_AUTO_ARCHIVING = 17 - const val TYPE_TEACHER_ABSENCE = 19 - const val TYPE_NEW_SHARED_NOTE = 20 - - fun buildId(profileId: Int, type: Int, itemId: Long): Long { - return 1000000000000 + profileId*10000000000 + type*100000000 + itemId; + fun buildId(profileId: Int, type: NotificationType, itemId: Long): Long { + return 1000000000000 + profileId*10000000000 + type.id*100000000 + itemId; } } @@ -78,8 +61,8 @@ data class Notification( fun fillIntent(intent: Intent) { if (profileId != -1) intent.putExtra("profileId", profileId) - if (viewId != -1) - intent.putExtra("fragmentId", viewId) + if (navTarget != null) + intent.putExtras("fragmentId" to navTarget) try { extras?.entrySet()?.forEach { (key, value) -> if (!value.isJsonPrimitive) @@ -101,20 +84,4 @@ data class Notification( fillIntent(intent) return PendingIntent.getActivity(context, id.toInt(), intent, PendingIntent.FLAG_ONE_SHOT or pendingIntentFlag()) } - - fun getLargeIcon(): IIcon = when (type) { - TYPE_TIMETABLE_LESSON_CHANGE -> CommunityMaterial.Icon3.cmd_timetable - TYPE_NEW_GRADE -> CommunityMaterial.Icon3.cmd_numeric_5_box_outline - TYPE_NEW_EVENT -> CommunityMaterial.Icon.cmd_calendar_outline - TYPE_NEW_HOMEWORK -> CommunityMaterial.Icon3.cmd_notebook_outline - TYPE_NEW_SHARED_EVENT -> CommunityMaterial.Icon.cmd_calendar_outline - TYPE_NEW_SHARED_HOMEWORK -> CommunityMaterial.Icon3.cmd_notebook_outline - TYPE_NEW_MESSAGE -> CommunityMaterial.Icon.cmd_email_outline - TYPE_NEW_NOTICE -> CommunityMaterial.Icon.cmd_emoticon_outline - TYPE_NEW_ATTENDANCE -> CommunityMaterial.Icon.cmd_calendar_remove_outline - TYPE_LUCKY_NUMBER -> CommunityMaterial.Icon.cmd_emoticon_excited_outline - TYPE_NEW_ANNOUNCEMENT -> CommunityMaterial.Icon.cmd_bullhorn_outline - TYPE_NEW_SHARED_NOTE -> CommunityMaterial.Icon3.cmd_playlist_edit - else -> CommunityMaterial.Icon.cmd_bell_ring_outline - } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Profile.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Profile.kt index 9fc0521a..8e689883 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Profile.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Profile.kt @@ -5,33 +5,25 @@ package pl.szczodrzynski.edziennik.data.db.entity import android.content.Context -import android.graphics.PorterDuff -import android.graphics.PorterDuffColorFilter -import android.graphics.drawable.Drawable import android.widget.ImageView -import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.Ignore import com.google.gson.JsonObject -import pl.droidsonroids.gif.GifDrawable import pl.szczodrzynski.edziennik.App -import pl.szczodrzynski.edziennik.MainActivity -import pl.szczodrzynski.edziennik.data.api.* -import pl.szczodrzynski.edziennik.ext.* -import pl.szczodrzynski.edziennik.utils.ProfileImageHolder +import pl.szczodrzynski.edziennik.data.db.enums.LoginType +import pl.szczodrzynski.edziennik.ext.dateToSemester +import pl.szczodrzynski.edziennik.ext.getDrawable +import pl.szczodrzynski.edziennik.ext.getHolder import pl.szczodrzynski.edziennik.utils.models.Date -import pl.szczodrzynski.navlib.ImageHolder -import pl.szczodrzynski.navlib.R import pl.szczodrzynski.navlib.drawer.IDrawerProfile -import pl.szczodrzynski.navlib.getDrawableFromRes @Entity(tableName = "profiles", primaryKeys = ["profileId"]) open class Profile( @ColumnInfo(name = "profileId") override var id: Int, /* needs to be var for ProfileArchiver */ val loginStoreId: Int, - val loginStoreType: Int, + val loginStoreType: LoginType, override var name: String = "", override var subname: String? = null, @@ -61,9 +53,13 @@ open class Profile( } override var image: String? = null - var empty = true var archived = false + var syncEnabled = true + @ColumnInfo(name = "enableSharedEvents") + var unused1 = true + var registration = REGISTRATION_UNSPECIFIED + var userCode = "" /** * A unique ID matching [archived] profiles with current ones @@ -71,172 +67,36 @@ open class Profile( */ var archiveId: Int? = null - var syncEnabled = true - var enableSharedEvents = true - var registration = REGISTRATION_UNSPECIFIED - var userCode = "" - /** * The student's number in the class register. */ var studentNumber = -1 var studentClassName: String? = null var studentSchoolYearStart = Date.getToday().let { if (it.month < 9) it.year - 1 else it.year } - var dateSemester1Start = Date(studentSchoolYearStart, 9, 1) var dateSemester2Start = Date(studentSchoolYearStart + 1, 2, 1) var dateYearEnd = Date(studentSchoolYearStart + 1, 6, 30) - fun getSemesterStart(semester: Int) = if (semester == 1) dateSemester1Start else dateSemester2Start - fun getSemesterEnd(semester: Int) = if (semester == 1) dateSemester2Start.clone().stepForward(0, 0, -1) else dateYearEnd - fun dateToSemester(date: Date) = if (date >= dateSemester2Start) 2 else 1 - @delegate:Ignore - val currentSemester by lazy { dateToSemester(Date.getToday()) } - - fun shouldArchive(): Boolean { - // vulcan hotfix - if (dateYearEnd.month > 6) { - dateYearEnd.month = 6 - dateYearEnd.day = 30 - } - // fix for when versions <4.3 synced 2020/2021 year dates to older profiles during 2020 Jun-Aug - if (dateSemester1Start.year > studentSchoolYearStart) { - val diff = dateSemester1Start.year - studentSchoolYearStart - dateSemester1Start.year -= diff - dateSemester2Start.year -= diff - dateYearEnd.year -= diff - } - return App.config.archiverEnabled - && Date.getToday() >= dateYearEnd - && Date.getToday().year > studentSchoolYearStart - } - fun isBeforeYear() = false && Date.getToday() < dateSemester1Start - var disabledNotifications: List? = null - var lastReceiversSync: Long = 0 - fun hasStudentData(key: String) = studentData.has(key) - fun getStudentData(key: String, defaultValue: Boolean) = studentData.getBoolean(key) ?: defaultValue - fun getStudentData(key: String, defaultValue: String?) = studentData.getString(key) ?: defaultValue - fun getStudentData(key: String, defaultValue: Int) = studentData.getInt(key) ?: defaultValue - fun getStudentData(key: String, defaultValue: Long) = studentData.getLong(key) ?: defaultValue - fun getStudentData(key: String, defaultValue: Float) = studentData.getFloat(key) ?: defaultValue - fun getStudentData(key: String, defaultValue: Char) = studentData.getChar(key) ?: defaultValue - fun putStudentData(key: String, value: Boolean) { studentData[key] = value } - fun putStudentData(key: String, value: String?) { studentData[key] = value } - fun putStudentData(key: String, value: Number) { studentData[key] = value } - fun putStudentData(key: String, value: Char) { studentData[key] = value } - fun removeStudentData(key: String) { studentData.remove(key) } - + val currentSemester + get() = dateToSemester(Date.getToday()) val isParent get() = accountName != null - val accountOwnerName get() = accountName ?: studentNameLong - val registerName - get() = when (loginStoreType) { - LOGIN_TYPE_LIBRUS -> "librus" - LOGIN_TYPE_VULCAN -> "vulcan" - LOGIN_TYPE_IDZIENNIK -> "idziennik" - LOGIN_TYPE_MOBIDZIENNIK -> "mobidziennik" - LOGIN_TYPE_PODLASIE -> "podlasie" - LOGIN_TYPE_EDUDZIENNIK -> "edudziennik" - else -> "unknown" - } - + get() = loginStoreType.name.lowercase() val canShare get() = registration == REGISTRATION_ENABLED && !archived - override fun getImageDrawable(context: Context): Drawable { - if (archived) { - return context.getDrawableFromRes(pl.szczodrzynski.edziennik.R.drawable.profile_archived).also { - it.colorFilter = PorterDuffColorFilter(colorFromName(name), PorterDuff.Mode.DST_OVER) - } - } + @delegate:Ignore + @delegate:Transient + val config by lazy { App.config[this.id] } - if (!image.isNullOrEmpty()) { - try { - return if (image?.endsWith(".gif", true) == true) { - GifDrawable(image ?: "") - } else { - RoundedBitmapDrawableFactory.create(context.resources, image ?: "") - //return Drawable.createFromPath(image ?: "") ?: throw Exception() - } - } - catch (e: Exception) { - e.printStackTrace() - } - } - - return context.getDrawableFromRes(R.drawable.profile).also { - it.colorFilter = PorterDuffColorFilter(colorFromName(name), PorterDuff.Mode.DST_OVER) - } - } - - override fun getImageHolder(context: Context): ImageHolder { - if (archived) { - return ImageHolder(pl.szczodrzynski.edziennik.R.drawable.profile_archived, colorFromName(name)) - } - - return if (!image.isNullOrEmpty()) { - try { - ProfileImageHolder(image ?: "") - } catch (_: Exception) { - ImageHolder(R.drawable.profile, colorFromName(name)) - } - } - else { - ImageHolder(R.drawable.profile, colorFromName(name)) - } - } + override fun getImageDrawable(context: Context) = this.getDrawable(context) + override fun getImageHolder(context: Context) = this.getHolder() override fun applyImageTo(imageView: ImageView) { getImageHolder(imageView.context).applyTo(imageView) } - - val supportedFragments: List - get() = when (loginStoreType) { - LoginStore.LOGIN_TYPE_MOBIDZIENNIK, - LoginStore.LOGIN_TYPE_DEMO, - LoginStore.LOGIN_TYPE_VULCAN -> listOf( - MainActivity.DRAWER_ITEM_TIMETABLE, - MainActivity.DRAWER_ITEM_AGENDA, - MainActivity.DRAWER_ITEM_GRADES, - MainActivity.DRAWER_ITEM_MESSAGES, - MainActivity.DRAWER_ITEM_HOMEWORK, - MainActivity.DRAWER_ITEM_BEHAVIOUR, - MainActivity.DRAWER_ITEM_ATTENDANCE - ) - LoginStore.LOGIN_TYPE_LIBRUS, - LoginStore.LOGIN_TYPE_IDZIENNIK -> listOf( - MainActivity.DRAWER_ITEM_TIMETABLE, - MainActivity.DRAWER_ITEM_AGENDA, - MainActivity.DRAWER_ITEM_GRADES, - MainActivity.DRAWER_ITEM_MESSAGES, - MainActivity.DRAWER_ITEM_HOMEWORK, - MainActivity.DRAWER_ITEM_BEHAVIOUR, - MainActivity.DRAWER_ITEM_ATTENDANCE, - MainActivity.DRAWER_ITEM_ANNOUNCEMENTS - ) - LOGIN_TYPE_EDUDZIENNIK -> listOf( - MainActivity.DRAWER_ITEM_TIMETABLE, - MainActivity.DRAWER_ITEM_AGENDA, - MainActivity.DRAWER_ITEM_GRADES, - MainActivity.DRAWER_ITEM_HOMEWORK, - MainActivity.DRAWER_ITEM_BEHAVIOUR, - MainActivity.DRAWER_ITEM_ATTENDANCE, - MainActivity.DRAWER_ITEM_ANNOUNCEMENTS - ) - LOGIN_TYPE_PODLASIE -> listOf( - MainActivity.DRAWER_ITEM_TIMETABLE, - MainActivity.DRAWER_ITEM_AGENDA, - MainActivity.DRAWER_ITEM_GRADES, - MainActivity.DRAWER_ITEM_HOMEWORK - ) - else -> listOf( - MainActivity.DRAWER_ITEM_TIMETABLE, - MainActivity.DRAWER_ITEM_AGENDA, - MainActivity.DRAWER_ITEM_GRADES - ) - } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/enums/FeatureType.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/enums/FeatureType.kt new file mode 100644 index 00000000..90209435 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/enums/FeatureType.kt @@ -0,0 +1,36 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2022-10-17. + */ + +package pl.szczodrzynski.edziennik.data.db.enums + +import pl.szczodrzynski.edziennik.R + +enum class FeatureType( + val id: Int, + val isAlwaysNeeded: Boolean, + val nameRes: Int? = null, + val isUIAlwaysAvailable: Boolean = false, +) { + TIMETABLE(id = 1, isAlwaysNeeded = false, nameRes = R.string.menu_timetable), + AGENDA(id = 2, isAlwaysNeeded = false, nameRes = R.string.menu_agenda, isUIAlwaysAvailable = true), + GRADES(id = 3, isAlwaysNeeded = false, nameRes = R.string.menu_grades), + HOMEWORK(id = 4, isAlwaysNeeded = false, nameRes = R.string.menu_homework, isUIAlwaysAvailable = true), + BEHAVIOUR(id = 5, isAlwaysNeeded = false, nameRes = R.string.menu_notices), + ATTENDANCE(id = 6, isAlwaysNeeded = false, nameRes = R.string.menu_attendance), + MESSAGES_INBOX(id = 7, isAlwaysNeeded = false, nameRes = R.string.title_messages_inbox_single), + MESSAGES_SENT(id = 8, isAlwaysNeeded = false, nameRes = R.string.title_messages_sent_single), + ANNOUNCEMENTS(id = 9, isAlwaysNeeded = false, nameRes = R.string.menu_announcements), + + ALWAYS_NEEDED(id = 100, isAlwaysNeeded = true), + STUDENT_INFO(id = 101, isAlwaysNeeded = true), + STUDENT_NUMBER(id = 109, isAlwaysNeeded = true), + SCHOOL_INFO(id = 102, isAlwaysNeeded = true), + CLASS_INFO(id = 103, isAlwaysNeeded = true), + TEAM_INFO(id = 104, isAlwaysNeeded = true), + LUCKY_NUMBER(id = 105, isAlwaysNeeded = true), + TEACHERS(id = 106, isAlwaysNeeded = true), + SUBJECTS(id = 107, isAlwaysNeeded = true), + CLASSROOMS(id = 108, isAlwaysNeeded = true), + PUSH_CONFIG(id = 120, isAlwaysNeeded = true), +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/enums/LoginMethod.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/enums/LoginMethod.kt new file mode 100644 index 00000000..64b0b7ca --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/enums/LoginMethod.kt @@ -0,0 +1,86 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2022-10-17. + */ + +package pl.szczodrzynski.edziennik.data.db.enums + +import pl.szczodrzynski.edziennik.data.db.entity.LoginStore +import pl.szczodrzynski.edziennik.data.db.entity.Profile +import pl.szczodrzynski.edziennik.ext.getString +import pl.szczodrzynski.edziennik.ext.isNotNullNorBlank + +enum class LoginMethod( + val loginType: LoginType, + val id: Int, + val isPossible: (( + profile: Profile?, + loginStore: LoginStore, + ) -> Boolean)? = null, + val requiredLoginMethod: (( + profile: Profile?, + loginStore: LoginStore, + ) -> LoginMethod?)? = null, +) { + MOBIDZIENNIK_WEB( + loginType = LoginType.MOBIDZIENNIK, + id = 1100, + ), + MOBIDZIENNIK_API2( + loginType = LoginType.MOBIDZIENNIK, + id = 1300, + isPossible = { profile, _ -> profile?.studentData?.getString("email").isNotNullNorBlank() }, + ), + LIBRUS_PORTAL( + loginType = LoginType.LIBRUS, + id = 2100, + isPossible = { _, loginStore -> loginStore.mode == LoginMode.LIBRUS_EMAIL }, + ), + LIBRUS_API( + loginType = LoginType.LIBRUS, + id = 2200, + isPossible = { _, loginStore -> loginStore.mode != LoginMode.LIBRUS_SYNERGIA }, + requiredLoginMethod = { _, loginStore -> + if (loginStore.mode == LoginMode.LIBRUS_EMAIL) LIBRUS_PORTAL + else null + }, + ), + LIBRUS_SYNERGIA( + loginType = LoginType.LIBRUS, + id = 2300, + isPossible = { _, loginStore -> !loginStore.hasLoginData("fakeLogin") }, + requiredLoginMethod = { _, _ -> LIBRUS_API }, + ), + LIBRUS_MESSAGES( + loginType = LoginType.LIBRUS, + id = 2400, + isPossible = { _, loginStore -> !loginStore.hasLoginData("fakeLogin") }, + requiredLoginMethod = { _, _ -> LIBRUS_SYNERGIA }, + ), + VULCAN_WEB_MAIN( + loginType = LoginType.VULCAN, + id = 4100, + isPossible = { _, loginStore -> loginStore.getLoginData("webHost", null).isNotNullNorBlank() }, + ), + VULCAN_HEBE( + loginType = LoginType.VULCAN, + id = 4600, + isPossible = { _, loginStore -> loginStore.mode != LoginMode.VULCAN_API }, + ), + PODLASIE_API( + loginType = LoginType.PODLASIE, + id = 6100, + ), + USOS_API( + loginType = LoginType.USOS, + id = 7100, + ), + TEMPLATE_WEB( + loginType = LoginType.TEMPLATE, + id = 21100, + ), + TEMPLATE_API( + loginType = LoginType.TEMPLATE, + id = 21200, + requiredLoginMethod = { _, _ -> TEMPLATE_WEB }, + ), +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/enums/LoginMode.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/enums/LoginMode.kt new file mode 100644 index 00000000..9deb5310 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/enums/LoginMode.kt @@ -0,0 +1,20 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2022-10-17. + */ + +package pl.szczodrzynski.edziennik.data.db.enums + +enum class LoginMode( + val loginType: LoginType, + val id: Int, +) { + MOBIDZIENNIK_WEB(LoginType.MOBIDZIENNIK, id = 100), + LIBRUS_EMAIL(LoginType.LIBRUS, id = 200), + LIBRUS_SYNERGIA(LoginType.LIBRUS, id = 201), + LIBRUS_JST(LoginType.LIBRUS, id = 202), + VULCAN_API(LoginType.VULCAN, id = 400), + VULCAN_WEB(LoginType.VULCAN, id = 401), + VULCAN_HEBE(LoginType.VULCAN, id = 402), + PODLASIE_API(LoginType.PODLASIE, id = 600), + USOS_OAUTH(LoginType.USOS, id = 700), +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/enums/LoginType.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/enums/LoginType.kt new file mode 100644 index 00000000..de0ca2f8 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/enums/LoginType.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2022-10-17. + */ + +package pl.szczodrzynski.edziennik.data.db.enums + +enum class LoginType( + val id: Int, + val features: Set, + val schoolType: SchoolType = SchoolType.STANDARD, +) { + MOBIDZIENNIK(id = 1, features = FEATURES_MOBIDZIENNIK), + LIBRUS(id = 2, features = FEATURES_LIBRUS), + VULCAN(id = 4, features = FEATURES_VULCAN), + PODLASIE(id = 6, features = FEATURES_PODLASIE), + USOS(id = 7, features = FEATURES_USOS, schoolType = SchoolType.UNIVERSITY), + DEMO(id = 20, features = setOf()), + TEMPLATE(id = 21, features = setOf()), + + // the graveyard + EDUDZIENNIK(id = 5, features = FEATURES_EDUDZIENNIK), + IDZIENNIK(id = 3, features = FEATURES_IDZIENNIK), +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/enums/LoginTypeFeatures.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/enums/LoginTypeFeatures.kt new file mode 100644 index 00000000..b0d16002 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/enums/LoginTypeFeatures.kt @@ -0,0 +1,145 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2022-10-20. + */ + +package pl.szczodrzynski.edziennik.data.db.enums + +import pl.szczodrzynski.edziennik.data.db.enums.FeatureType.* + +internal val FEATURES_MOBIDZIENNIK = setOf( + TIMETABLE, + AGENDA, + GRADES, + HOMEWORK, + BEHAVIOUR, + ATTENDANCE, + MESSAGES_INBOX, + MESSAGES_SENT, + + STUDENT_INFO, + STUDENT_NUMBER, + SCHOOL_INFO, + CLASS_INFO, + TEAM_INFO, + LUCKY_NUMBER, + TEACHERS, + SUBJECTS, + CLASSROOMS, + PUSH_CONFIG, +) + +internal val FEATURES_LIBRUS = setOf( + TIMETABLE, + AGENDA, + GRADES, + HOMEWORK, + BEHAVIOUR, + ATTENDANCE, + MESSAGES_INBOX, + MESSAGES_SENT, + ANNOUNCEMENTS, + + STUDENT_INFO, + STUDENT_NUMBER, + SCHOOL_INFO, + CLASS_INFO, + TEAM_INFO, + LUCKY_NUMBER, + TEACHERS, + SUBJECTS, + CLASSROOMS, + PUSH_CONFIG, +) + +internal val FEATURES_VULCAN = setOf( + TIMETABLE, + AGENDA, + GRADES, + HOMEWORK, + BEHAVIOUR, + ATTENDANCE, + MESSAGES_INBOX, + MESSAGES_SENT, + + STUDENT_INFO, + STUDENT_NUMBER, + SCHOOL_INFO, + CLASS_INFO, + TEAM_INFO, + LUCKY_NUMBER, + TEACHERS, + SUBJECTS, + CLASSROOMS, + PUSH_CONFIG, +) + +internal val FEATURES_PODLASIE = setOf( + TIMETABLE, + AGENDA, + GRADES, + HOMEWORK, + + STUDENT_INFO, + STUDENT_NUMBER, + SCHOOL_INFO, + CLASS_INFO, + TEAM_INFO, + LUCKY_NUMBER, + TEACHERS, + SUBJECTS, + CLASSROOMS, +) + +internal val FEATURES_USOS = setOf( + TIMETABLE, + AGENDA, + + STUDENT_INFO, + STUDENT_NUMBER, + CLASS_INFO, + TEAM_INFO, + TEACHERS, + SUBJECTS, + CLASSROOMS, +) + +internal val FEATURES_EDUDZIENNIK = setOf( + TIMETABLE, + AGENDA, + GRADES, + HOMEWORK, + BEHAVIOUR, + ATTENDANCE, + + STUDENT_INFO, + STUDENT_NUMBER, + SCHOOL_INFO, + CLASS_INFO, + TEAM_INFO, + LUCKY_NUMBER, + TEACHERS, + SUBJECTS, + CLASSROOMS, +) + +internal val FEATURES_IDZIENNIK = setOf( + TIMETABLE, + AGENDA, + GRADES, + HOMEWORK, + BEHAVIOUR, + ATTENDANCE, + MESSAGES_INBOX, + MESSAGES_SENT, + ANNOUNCEMENTS, + + STUDENT_INFO, + STUDENT_NUMBER, + SCHOOL_INFO, + CLASS_INFO, + TEAM_INFO, + LUCKY_NUMBER, + TEACHERS, + SUBJECTS, + CLASSROOMS, +) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/enums/MetadataType.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/enums/MetadataType.kt new file mode 100644 index 00000000..a993820f --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/enums/MetadataType.kt @@ -0,0 +1,20 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2022-10-17. + */ + +package pl.szczodrzynski.edziennik.data.db.enums + +enum class MetadataType( + val id: Int, +) { + GRADE(id = 1), + NOTICE(id = 2), + ATTENDANCE(id = 3), + EVENT(id = 4), + HOMEWORK(id = 5), + LESSON_CHANGE(id = 6), + ANNOUNCEMENT(id = 7), + MESSAGE(id = 8), + TEACHER_ABSENCE(id = 9), + LUCKY_NUMBER(id = 10), +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/enums/NotificationType.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/enums/NotificationType.kt new file mode 100644 index 00000000..4b6842d9 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/enums/NotificationType.kt @@ -0,0 +1,134 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2022-10-20. + */ + +package pl.szczodrzynski.edziennik.data.db.enums + +import com.mikepenz.iconics.typeface.IIcon +import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial +import pl.szczodrzynski.edziennik.R + +enum class NotificationType( + val id: Int, + val icon: IIcon = CommunityMaterial.Icon.cmd_bell_ring_outline, + val titleRes: Int, + val pluralRes: Int = R.plurals.notification_other_format, + val enabledByDefault: Boolean? = true, +) { + TIMETABLE_LESSON_CHANGE( + id = 4, + icon = CommunityMaterial.Icon3.cmd_timetable, + titleRes = R.string.notification_type_timetable_lesson_change, + pluralRes = R.plurals.notification_new_timetable_change_format, + ), + GRADE( + id = 5, + icon = CommunityMaterial.Icon3.cmd_numeric_5_box_outline, + titleRes = R.string.notification_type_new_grade, + pluralRes = R.plurals.notification_new_grades_format, + ), + EVENT( + id = 6, + icon = CommunityMaterial.Icon.cmd_calendar_outline, + titleRes = R.string.notification_type_new_event, + pluralRes = R.plurals.notification_new_events_format, + ), + HOMEWORK( + id = 10, + icon = CommunityMaterial.Icon3.cmd_notebook_outline, + titleRes = R.string.notification_type_new_homework, + pluralRes = R.plurals.notification_new_homework_format, + ), + MESSAGE( + id = 8, + icon = CommunityMaterial.Icon.cmd_email_outline, + titleRes = R.string.notification_type_new_message, + pluralRes = R.plurals.notification_new_messages_format, + ), + LUCKY_NUMBER( + id = 14, + icon = CommunityMaterial.Icon.cmd_emoticon_excited_outline, + titleRes = R.string.notification_type_lucky_number, + pluralRes = R.plurals.notification_new_lucky_number_format, + ), + NOTICE( + id = 9, + icon = CommunityMaterial.Icon.cmd_emoticon_outline, + titleRes = R.string.notification_type_notice, + pluralRes = R.plurals.notification_new_notices_format, + ), + ATTENDANCE( + id = 13, + icon = CommunityMaterial.Icon.cmd_calendar_remove_outline, + titleRes = R.string.notification_type_attendance, + pluralRes = R.plurals.notification_new_attendance_format, + ), + ANNOUNCEMENT( + id = 15, + icon = CommunityMaterial.Icon.cmd_bullhorn_outline, + titleRes = R.string.notification_type_feedback_message, + pluralRes = R.plurals.notification_new_announcements_format, + ), + SHARED_EVENT( + id = 7, + icon = CommunityMaterial.Icon.cmd_calendar_outline, + titleRes = R.string.notification_type_new_shared_event, + pluralRes = R.plurals.notification_new_shared_events_format, + ), + SHARED_HOMEWORK( + id = 12, + icon = CommunityMaterial.Icon3.cmd_notebook_outline, + titleRes = R.string.notification_type_new_shared_homework, + pluralRes = R.plurals.notification_new_shared_homework_format, + ), + SHARED_NOTE( + id = 20, + icon = CommunityMaterial.Icon3.cmd_playlist_edit, + titleRes = R.string.notification_type_new_shared_note, + ), + REMOVED_SHARED_EVENT( + id = 18, + titleRes = R.string.notification_type_removed_shared_event, + ), + TEACHER_ABSENCE( + id = 19, + titleRes = R.string.notification_type_new_teacher_absence, + enabledByDefault = false, + ), + + GENERAL( + id = 0, + titleRes = R.string.notification_type_general, + enabledByDefault = null, + ), + UPDATE( + id = 1, + titleRes = R.string.notification_type_update, + enabledByDefault = null, + ), + ERROR( + id = 2, + titleRes = R.string.notification_type_error, + enabledByDefault = null, + ), + TIMETABLE_CHANGED( + id = 3, + titleRes = R.string.notification_type_timetable_change, + enabledByDefault = null, + ), + SERVER_MESSAGE( + id = 11, + titleRes = R.string.notification_type_server_message, + enabledByDefault = null, + ), + FEEDBACK_MESSAGE( + id = 16, + titleRes = R.string.notification_type_feedback_message, + enabledByDefault = null, + ), + AUTO_ARCHIVING( + id = 17, + titleRes = R.string.notification_type_auto_archiving, + enabledByDefault = null, + ), +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/enums/SchoolType.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/enums/SchoolType.kt new file mode 100644 index 00000000..593851e2 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/enums/SchoolType.kt @@ -0,0 +1,10 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2022-10-21. + */ + +package pl.szczodrzynski.edziennik.data.db.enums + +enum class SchoolType { + STANDARD, + UNIVERSITY, +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/EventFull.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/EventFull.kt index 3eb681f2..332b7ce5 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/EventFull.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/EventFull.kt @@ -9,6 +9,7 @@ import pl.szczodrzynski.edziennik.data.db.entity.Event import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.data.db.entity.Note import pl.szczodrzynski.edziennik.data.db.entity.Noteable +import pl.szczodrzynski.edziennik.ext.takePositive import pl.szczodrzynski.edziennik.ui.search.Searchable import pl.szczodrzynski.edziennik.utils.html.BetterHtml import pl.szczodrzynski.edziennik.utils.models.Date @@ -118,4 +119,5 @@ class EventFull( override fun getNoteType() = Note.OwnerType.EVENT override fun getNoteOwnerProfileId() = profileId override fun getNoteOwnerId() = id + override fun getNoteShareTeamId() = teamId.takePositive() } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/LessonFull.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/LessonFull.kt index a2982a30..b61bba30 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/LessonFull.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/LessonFull.kt @@ -9,6 +9,7 @@ import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.data.db.entity.Lesson import pl.szczodrzynski.edziennik.data.db.entity.Note import pl.szczodrzynski.edziennik.data.db.entity.Noteable +import pl.szczodrzynski.edziennik.ext.takePositive import pl.szczodrzynski.edziennik.utils.models.Time class LessonFull( @@ -137,9 +138,10 @@ class LessonFull( var seen: Boolean = false var notified: Boolean = false - @Relation(parentColumn = "id", entityColumn = "noteOwnerId", entity = Note::class) + @Relation(parentColumn = "ownerId", entityColumn = "noteOwnerId", entity = Note::class) override lateinit var notes: MutableList override fun getNoteType() = Note.OwnerType.LESSON override fun getNoteOwnerProfileId() = profileId - override fun getNoteOwnerId() = id + override fun getNoteOwnerId() = ownerId + override fun getNoteShareTeamId() = teamId.takePositive() } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration100.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration100.kt new file mode 100644 index 00000000..b1d05b1b --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration100.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2022-10-25. + */ + +package pl.szczodrzynski.edziennik.data.db.migration + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase + +class Migration100 : Migration(99, 100) { + override fun migrate(database: SupportSQLiteDatabase) { + // add Note Owner ID to Lesson, to make it profileId-independent + // calculate the new owner ID based on the old ID + database.execSQL("ALTER TABLE timetable ADD COLUMN ownerId INT NOT NULL DEFAULT 0;") + // set new ID for actual lessons + database.execSQL("UPDATE timetable SET ownerId = ROUND((id & ~65535) / 500000.0) * 300000;") + // copy the old ID (date value) for NO_LESSONS + database.execSQL("UPDATE timetable SET ownerId = id WHERE type = -1;") + // update ID for notes as well + database.execSQL("UPDATE notes SET noteOwnerId = ROUND((noteOwnerId & ~65535) / 500000.0) * 300000 WHERE noteOwnerType = 'LESSON' AND noteOwnerId > 2000000000000;") + // force full app sync to download notes with new IDs + database.execSQL("DELETE FROM config WHERE `key` = 'hash';") + database.execSQL("DELETE FROM config WHERE `key` = 'lastAppSync';") + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration58.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration58.kt index d0ebf548..ba1f26b7 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration58.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration58.kt @@ -6,14 +6,13 @@ package pl.szczodrzynski.edziennik.data.db.migration import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase -import pl.szczodrzynski.edziennik.data.db.entity.Metadata.TYPE_EVENT -import pl.szczodrzynski.edziennik.data.db.entity.Metadata.TYPE_HOMEWORK +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType class Migration58 : Migration(57, 58) { override fun migrate(database: SupportSQLiteDatabase) { database.execSQL("ALTER TABLE metadata RENAME TO _metadata_old;") database.execSQL("DROP INDEX index_metadata_profileId_thingType_thingId;") - database.execSQL("UPDATE _metadata_old SET thingType = $TYPE_HOMEWORK WHERE thingType = $TYPE_EVENT AND thingId IN (SELECT eventId FROM events WHERE eventType = -1)") + database.execSQL("UPDATE _metadata_old SET thingType = ${MetadataType.HOMEWORK.id} WHERE thingType = ${MetadataType.EVENT.id} AND thingId IN (SELECT eventId FROM events WHERE eventType = -1)") database.execSQL("""CREATE TABLE metadata ( profileId INTEGER NOT NULL, metadataId INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration68.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration68.kt index f7a079d0..6bbc8e86 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration68.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration68.kt @@ -6,12 +6,12 @@ package pl.szczodrzynski.edziennik.data.db.migration import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase -import pl.szczodrzynski.edziennik.data.db.entity.Metadata.TYPE_ANNOUNCEMENT +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType class Migration68 : Migration(67, 68) { override fun migrate(database: SupportSQLiteDatabase) { /* Migration from crc16 to crc32 id */ database.execSQL("DELETE FROM announcements") - database.execSQL("DELETE FROM metadata WHERE thingType=$TYPE_ANNOUNCEMENT") + database.execSQL("DELETE FROM metadata WHERE thingType=${MetadataType.ANNOUNCEMENT.id}") } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration71.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration71.kt index dabb2303..5166a0ce 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration71.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration71.kt @@ -6,7 +6,7 @@ package pl.szczodrzynski.edziennik.data.db.migration import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase -import pl.szczodrzynski.edziennik.data.db.entity.Metadata.TYPE_MESSAGE +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType class Migration71 : Migration(70, 71) { override fun migrate(database: SupportSQLiteDatabase) { @@ -14,7 +14,7 @@ class Migration71 : Migration(70, 71) { database.execSQL("DELETE FROM messageRecipients WHERE profileId IN (SELECT profileId FROM profiles WHERE archived = 0)") database.execSQL("DELETE FROM teachers WHERE profileId IN (SELECT profileId FROM profiles WHERE archived = 0)") database.execSQL("DELETE FROM endpointTimers WHERE profileId IN (SELECT profileId FROM profiles WHERE archived = 0)") - database.execSQL("DELETE FROM metadata WHERE profileId IN (SELECT profileId FROM profiles WHERE archived = 0) AND thingType = $TYPE_MESSAGE") + database.execSQL("DELETE FROM metadata WHERE profileId IN (SELECT profileId FROM profiles WHERE archived = 0) AND thingType = ${MetadataType.MESSAGE.id}") database.execSQL("UPDATE profiles SET empty = 1 WHERE archived = 0") database.execSQL("UPDATE profiles SET lastReceiversSync = 0 WHERE archived = 0") database.execSQL("INSERT INTO config (profileId, `key`, value) VALUES (-1, 'runSync', 'true')") diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration81.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration81.kt index bf547244..5af06357 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration81.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration81.kt @@ -2,10 +2,10 @@ package pl.szczodrzynski.edziennik.data.db.migration import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase -import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType class Migration81 : Migration(80, 81) { override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL("UPDATE metadata SET seen = 1, notified = 1 WHERE thingType = ${Metadata.TYPE_TEACHER_ABSENCE}") + database.execSQL("UPDATE metadata SET seen = 1, notified = 1 WHERE thingType = ${MetadataType.TEACHER_ABSENCE.id}") } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration85.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration85.kt index 6f1c79c0..b53c08e2 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration85.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration85.kt @@ -2,11 +2,11 @@ package pl.szczodrzynski.edziennik.data.db.migration import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase -import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_EDUDZIENNIK import pl.szczodrzynski.edziennik.data.db.entity.Event +import pl.szczodrzynski.edziennik.data.db.enums.LoginType class Migration85 : Migration(84, 85) { override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL("DELETE FROM events WHERE eventAddedManually = 0 AND eventType = ${Event.TYPE_HOMEWORK} AND profileId IN (SELECT profileId FROM (SELECT profileId FROM profiles WHERE loginStoreType = $LOGIN_TYPE_EDUDZIENNIK) x)") + database.execSQL("DELETE FROM events WHERE eventAddedManually = 0 AND eventType = ${Event.TYPE_HOMEWORK} AND profileId IN (SELECT profileId FROM (SELECT profileId FROM profiles WHERE loginStoreType = ${LoginType.EDUDZIENNIK.id}) x)") } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration94.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration94.kt index 84df9c59..1facd564 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration94.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration94.kt @@ -7,7 +7,7 @@ package pl.szczodrzynski.edziennik.data.db.migration import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase import pl.szczodrzynski.edziennik.data.db.entity.Event -import pl.szczodrzynski.edziennik.data.db.entity.LoginStore +import pl.szczodrzynski.edziennik.data.db.enums.LoginType class Migration94 : Migration(93, 94) { override fun migrate(database: SupportSQLiteDatabase) { @@ -15,7 +15,7 @@ class Migration94 : Migration(93, 94) { // get all profiles using Mobidziennik database.execSQL("CREATE TABLE _94_ids (id INTEGER NOT NULL);") - database.execSQL("INSERT INTO _94_ids SELECT profileId FROM profiles JOIN loginStores USING(loginStoreId) WHERE loginStores.loginStoreType = ${LoginStore.LOGIN_TYPE_MOBIDZIENNIK};") + database.execSQL("INSERT INTO _94_ids SELECT profileId FROM profiles JOIN loginStores USING(loginStoreId) WHERE loginStores.loginStoreType = ${LoginType.MOBIDZIENNIK};") database.execSQL("ALTER TABLE events ADD COLUMN eventIsDownloaded INT NOT NULL DEFAULT 1;") // set isDownloaded = 0 for information events in Mobidziennik diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration98.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration98.kt new file mode 100644 index 00000000..91003fd9 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration98.kt @@ -0,0 +1,15 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2022-10-16. + */ + +package pl.szczodrzynski.edziennik.data.db.migration + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase + +class Migration98 : Migration(97, 98) { + override fun migrate(database: SupportSQLiteDatabase) { + // timetable colors - override color in lesson object + database.execSQL("ALTER TABLE timetable ADD COLUMN color INT DEFAULT NULL;") + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration99.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration99.kt new file mode 100644 index 00000000..d321765c --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration99.kt @@ -0,0 +1,15 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2022-10-19. + */ + +package pl.szczodrzynski.edziennik.data.db.migration + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase + +class Migration99 : Migration(98, 99) { + override fun migrate(database: SupportSQLiteDatabase) { + // enum refactor, part 1 - make LoginStore modes unique, even without type + database.execSQL("UPDATE loginStores SET loginStoreMode = loginStoreType * 100 + loginStoreMode;") + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/firebase/SzkolnyAppFirebase.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/firebase/SzkolnyAppFirebase.kt index 4f90ac67..b92a38b5 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/firebase/SzkolnyAppFirebase.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/firebase/SzkolnyAppFirebase.kt @@ -6,20 +6,31 @@ package pl.szczodrzynski.edziennik.data.firebase import com.google.gson.JsonParser import com.google.gson.reflect.TypeToken -import kotlinx.coroutines.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import org.greenrobot.eventbus.EventBus -import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.data.api.events.FeedbackMessageEvent import pl.szczodrzynski.edziennik.data.api.events.RegisterAvailabilityEvent import pl.szczodrzynski.edziennik.data.api.szkolny.response.RegisterAvailabilityStatus import pl.szczodrzynski.edziennik.data.api.szkolny.response.Update import pl.szczodrzynski.edziennik.data.api.task.PostNotifications -import pl.szczodrzynski.edziennik.data.db.entity.* +import pl.szczodrzynski.edziennik.data.db.entity.Event +import pl.szczodrzynski.edziennik.data.db.entity.FeedbackMessage +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.Note +import pl.szczodrzynski.edziennik.data.db.entity.Notification +import pl.szczodrzynski.edziennik.data.db.entity.Profile +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType +import pl.szczodrzynski.edziennik.data.db.enums.NotificationType import pl.szczodrzynski.edziennik.ext.getInt import pl.szczodrzynski.edziennik.ext.getLong -import pl.szczodrzynski.edziennik.ext.getNotificationTitle import pl.szczodrzynski.edziennik.ext.getString -import pl.szczodrzynski.edziennik.sync.UpdateWorker +import pl.szczodrzynski.edziennik.ext.resolveString +import pl.szczodrzynski.edziennik.ui.base.enums.NavTarget import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Time import kotlin.coroutines.CoroutineContext @@ -61,7 +72,10 @@ class SzkolnyAppFirebase(val app: App, val profiles: List, val message: message.data.getString("title") ?: "", message.data.getString("message") ?: "" ) - "appUpdate" -> launch { UpdateWorker.runNow(app, app.gson.fromJson(message.data.getString("update"), Update::class.java)) } + "appUpdate" -> { + val update = app.gson.fromJson(message.data.getString("update"), Update::class.java) + app.updateManager.process(update, notify = true) + } "feedbackMessage" -> launch { val message = app.gson.fromJson(message.data.getString("message"), FeedbackMessage::class.java) ?: return@launch feedbackMessage(message) @@ -85,7 +99,7 @@ class SzkolnyAppFirebase(val app: App, val profiles: List, val message: id = System.currentTimeMillis(), title = title, text = message, - type = Notification.TYPE_SERVER_MESSAGE, + type = NotificationType.SERVER_MESSAGE, profileId = null, profileName = title ).addExtra("action", "serverMessage").addExtra("serverMessageTitle", title).addExtra("serverMessageText", message) @@ -105,7 +119,7 @@ class SzkolnyAppFirebase(val app: App, val profiles: List, val message: id = System.currentTimeMillis(), title = "Wiadomość od ${message.senderName}", text = message.text, - type = Notification.TYPE_FEEDBACK_MESSAGE, + type = NotificationType.FEEDBACK_MESSAGE, profileId = null, profileName = "Wiadomość od ${message.senderName}" ).addExtra("action", "feedbackMessage").addExtra("feedbackMessageDeviceId", message.deviceId) @@ -146,33 +160,33 @@ class SzkolnyAppFirebase(val app: App, val profiles: List, val message: if (event.color == -1) event.color = null + event.addedManually = true event.sharedBy = json.getString("sharedBy") event.sharedByName = json.getString("sharedByName") if (profile.userCode == event.sharedBy) { event.sharedBy = "self" - event.addedManually = true } val metadata = Metadata( event.profileId, - if (event.isHomework) Metadata.TYPE_HOMEWORK else Metadata.TYPE_EVENT, + if (event.isHomework) MetadataType.HOMEWORK else MetadataType.EVENT, event.id, false, true ) - val type = if (event.isHomework) Notification.TYPE_NEW_SHARED_HOMEWORK else Notification.TYPE_NEW_SHARED_EVENT - val notificationFilter = app.config.getFor(event.profileId).sync.notificationFilter + val type = if (event.isHomework) NotificationType.SHARED_HOMEWORK else NotificationType.SHARED_EVENT + val notificationFilter = app.config[event.profileId].sync.notificationFilter if (!notificationFilter.contains(type) && event.sharedBy != "self" && event.date >= Date.getToday()) { val notification = Notification( id = Notification.buildId(event.profileId, type, event.id), - title = app.getNotificationTitle(type), + title = type.titleRes.resolveString(app), text = message, type = type, profileId = profile.id, profileName = profile.name, - viewId = if (event.isHomework) MainActivity.DRAWER_ITEM_HOMEWORK else MainActivity.DRAWER_ITEM_AGENDA, + navTarget = if (event.isHomework) NavTarget.HOMEWORK else NavTarget.AGENDA, addedDate = event.addedDate ).addExtra("eventId", event.id).addExtra("eventDate", event.date.value.toLong()) notificationList += notification @@ -197,17 +211,17 @@ class SzkolnyAppFirebase(val app: App, val profiles: List, val message: val profile = profiles.firstOrNull { it.id == team.profileId } ?: return@forEach if (!profile.canShare) return@forEach - val notificationFilter = app.config.getFor(team.profileId).sync.notificationFilter + val notificationFilter = app.config[team.profileId].sync.notificationFilter - if (!notificationFilter.contains(Notification.TYPE_REMOVED_SHARED_EVENT)) { + if (!notificationFilter.contains(NotificationType.REMOVED_SHARED_EVENT)) { val notification = Notification( - id = Notification.buildId(profile.id, Notification.TYPE_REMOVED_SHARED_EVENT, eventId), - title = app.getNotificationTitle(Notification.TYPE_REMOVED_SHARED_EVENT), + id = Notification.buildId(profile.id, NotificationType.REMOVED_SHARED_EVENT, eventId), + title = NotificationType.REMOVED_SHARED_EVENT.titleRes.resolveString(app), text = message, - type = Notification.TYPE_REMOVED_SHARED_EVENT, + type = NotificationType.REMOVED_SHARED_EVENT, profileId = profile.id, profileName = profile.name, - viewId = MainActivity.DRAWER_ITEM_AGENDA + navTarget = NavTarget.AGENDA, ) notificationList += notification } @@ -250,18 +264,18 @@ class SzkolnyAppFirebase(val app: App, val profiles: List, val message: if (hadNote) return@forEach - val type = Notification.TYPE_NEW_SHARED_NOTE - val notificationFilter = app.config.getFor(note.profileId).sync.notificationFilter + val type = NotificationType.SHARED_NOTE + val notificationFilter = app.config[note.profileId].sync.notificationFilter if (!notificationFilter.contains(type) && note.sharedBy != "self") { val notification = Notification( id = Notification.buildId(note.profileId, type, note.id), - title = app.getNotificationTitle(type), + title = type.titleRes.resolveString(app), text = message, type = type, profileId = profile.id, profileName = profile.name, - viewId = MainActivity.DRAWER_ITEM_HOME, + navTarget = NavTarget.HOME, addedDate = note.addedDate ).addExtra("noteId", note.id) notificationList += notification diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/firebase/SzkolnyLibrusFirebase.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/firebase/SzkolnyLibrusFirebase.kt index a9a183e5..14c3f998 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/firebase/SzkolnyLibrusFirebase.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/firebase/SzkolnyLibrusFirebase.kt @@ -5,13 +5,38 @@ package pl.szczodrzynski.edziennik.data.firebase import pl.szczodrzynski.edziennik.App -import pl.szczodrzynski.edziennik.MainActivity -import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_LIBRUS import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask -import pl.szczodrzynski.edziennik.data.api.edziennik.librus.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_ANNOUNCEMENTS +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_ATTENDANCES +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_BEHAVIOUR_GRADES +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_BEHAVIOUR_GRADE_CATEGORIES +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_BEHAVIOUR_GRADE_COMMENTS +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_CLASS_FREE_DAYS +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_DESCRIPTIVE_GRADES +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_DESCRIPTIVE_GRADE_CATEGORIES +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_DESCRIPTIVE_TEXT_GRADES +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_EVENTS +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_HOMEWORK +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_LUCKY_NUMBER +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_NORMAL_GRADES +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_NORMAL_GRADE_CATEGORIES +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_NORMAL_GRADE_COMMENTS +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_NOTICES +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_POINT_GRADES +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_POINT_GRADE_CATEGORIES +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_PT_MEETINGS +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_SCHOOL_FREE_DAYS +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_TEACHER_FREE_DAYS +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_TEXT_GRADES +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_TIMETABLES +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_MESSAGES_RECEIVED +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_SYNERGIA_HOMEWORK import pl.szczodrzynski.edziennik.data.api.task.IApiTask import pl.szczodrzynski.edziennik.data.db.entity.Profile +import pl.szczodrzynski.edziennik.data.db.enums.FeatureType +import pl.szczodrzynski.edziennik.data.db.enums.LoginType import pl.szczodrzynski.edziennik.ext.getString +import pl.szczodrzynski.edziennik.ext.getStudentData class SzkolnyLibrusFirebase(val app: App, val profiles: List, val message: FirebaseService.Message) { /*{ @@ -31,27 +56,27 @@ class SzkolnyLibrusFirebase(val app: App, val profiles: List, val messa /* ./src/store/modules/helpers/change-processor.js */ val endpoints = when (type) { - "Notes" -> listOf(ENDPOINT_LIBRUS_API_NOTICES) - "Grades" -> listOf(ENDPOINT_LIBRUS_API_NORMAL_GRADES, ENDPOINT_LIBRUS_API_NORMAL_GRADE_CATEGORIES, ENDPOINT_LIBRUS_API_NORMAL_GRADE_COMMENTS) - "PointGrades" -> listOf(ENDPOINT_LIBRUS_API_POINT_GRADES, ENDPOINT_LIBRUS_API_POINT_GRADE_CATEGORIES) - "DescriptiveGrades" -> listOf(ENDPOINT_LIBRUS_API_DESCRIPTIVE_GRADES) - "DescriptiveGrades/Text/Categories" -> listOf(ENDPOINT_LIBRUS_API_DESCRIPTIVE_GRADE_CATEGORIES) - "DescriptiveTextGrades" -> listOf(ENDPOINT_LIBRUS_API_DESCRIPTIVE_TEXT_GRADES) - "TextGrades" -> listOf(ENDPOINT_LIBRUS_API_TEXT_GRADES) - "BehaviourGrades/Points" -> listOf(ENDPOINT_LIBRUS_API_BEHAVIOUR_GRADES, ENDPOINT_LIBRUS_API_BEHAVIOUR_GRADE_CATEGORIES, ENDPOINT_LIBRUS_API_BEHAVIOUR_GRADE_COMMENTS) - "BehaviourGrades" -> listOf() - "Attendances" -> listOf(ENDPOINT_LIBRUS_API_ATTENDANCES) - "HomeWorks" -> listOf(ENDPOINT_LIBRUS_API_EVENTS) - "ParentTeacherConferences" -> listOf(ENDPOINT_LIBRUS_API_PT_MEETINGS) - "Calendars/ClassFreeDays" -> listOf(ENDPOINT_LIBRUS_API_CLASS_FREE_DAYS) - "Calendars/TeacherFreeDays" -> listOf(ENDPOINT_LIBRUS_API_TEACHER_FREE_DAYS) - "Calendars/SchoolFreeDays" -> listOf(ENDPOINT_LIBRUS_API_SCHOOL_FREE_DAYS) - "Calendars/Substitutions" -> listOf(ENDPOINT_LIBRUS_API_TIMETABLES) - "HomeWorkAssignments" -> listOf(ENDPOINT_LIBRUS_API_HOMEWORK, ENDPOINT_LIBRUS_SYNERGIA_HOMEWORK) - "SchoolNotices" -> listOf(ENDPOINT_LIBRUS_API_ANNOUNCEMENTS) - "Messages" -> listOf(ENDPOINT_LIBRUS_MESSAGES_RECEIVED) - "LuckyNumbers" -> listOf(ENDPOINT_LIBRUS_API_LUCKY_NUMBER) - "Timetables" -> listOf(ENDPOINT_LIBRUS_API_TIMETABLES) + "Notes" -> setOf(ENDPOINT_LIBRUS_API_NOTICES) + "Grades" -> setOf(ENDPOINT_LIBRUS_API_NORMAL_GRADES, ENDPOINT_LIBRUS_API_NORMAL_GRADE_CATEGORIES, ENDPOINT_LIBRUS_API_NORMAL_GRADE_COMMENTS) + "PointGrades" -> setOf(ENDPOINT_LIBRUS_API_POINT_GRADES, ENDPOINT_LIBRUS_API_POINT_GRADE_CATEGORIES) + "DescriptiveGrades" -> setOf(ENDPOINT_LIBRUS_API_DESCRIPTIVE_GRADES) + "DescriptiveGrades/Text/Categories" -> setOf(ENDPOINT_LIBRUS_API_DESCRIPTIVE_GRADE_CATEGORIES) + "DescriptiveTextGrades" -> setOf(ENDPOINT_LIBRUS_API_DESCRIPTIVE_TEXT_GRADES) + "TextGrades" -> setOf(ENDPOINT_LIBRUS_API_TEXT_GRADES) + "BehaviourGrades/Points" -> setOf(ENDPOINT_LIBRUS_API_BEHAVIOUR_GRADES, ENDPOINT_LIBRUS_API_BEHAVIOUR_GRADE_CATEGORIES, ENDPOINT_LIBRUS_API_BEHAVIOUR_GRADE_COMMENTS) + "BehaviourGrades" -> setOf() + "Attendances" -> setOf(ENDPOINT_LIBRUS_API_ATTENDANCES) + "HomeWorks" -> setOf(ENDPOINT_LIBRUS_API_EVENTS) + "ParentTeacherConferences" -> setOf(ENDPOINT_LIBRUS_API_PT_MEETINGS) + "Calendars/ClassFreeDays" -> setOf(ENDPOINT_LIBRUS_API_CLASS_FREE_DAYS) + "Calendars/TeacherFreeDays" -> setOf(ENDPOINT_LIBRUS_API_TEACHER_FREE_DAYS) + "Calendars/SchoolFreeDays" -> setOf(ENDPOINT_LIBRUS_API_SCHOOL_FREE_DAYS) + "Calendars/Substitutions" -> setOf(ENDPOINT_LIBRUS_API_TIMETABLES) + "HomeWorkAssignments" -> setOf(ENDPOINT_LIBRUS_API_HOMEWORK, ENDPOINT_LIBRUS_SYNERGIA_HOMEWORK) + "SchoolNotices" -> setOf(ENDPOINT_LIBRUS_API_ANNOUNCEMENTS) + "Messages" -> setOf(ENDPOINT_LIBRUS_MESSAGES_RECEIVED) + "LuckyNumbers" -> setOf(ENDPOINT_LIBRUS_API_LUCKY_NUMBER) + "Timetables" -> setOf(ENDPOINT_LIBRUS_API_TIMETABLES) else -> return@run } @@ -59,10 +84,10 @@ class SzkolnyLibrusFirebase(val app: App, val profiles: List, val messa return@run val tasks = profiles.filter { - it.loginStoreType == LOGIN_TYPE_LIBRUS && + it.loginStoreType == LoginType.LIBRUS && it.getStudentData("accountLogin", "")?.replace("u", "") == accountLogin }.map { - EdziennikTask.syncProfile(it.id, listOf(MainActivity.DRAWER_ITEM_HOME to 0), onlyEndpoints = endpoints) + EdziennikTask.syncProfile(it.id, setOf(FeatureType.ALWAYS_NEEDED), onlyEndpoints = endpoints) } IApiTask.enqueueAll(app, tasks) }} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/firebase/SzkolnyMobidziennikFirebase.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/firebase/SzkolnyMobidziennikFirebase.kt index d5b6c077..c1397ab2 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/firebase/SzkolnyMobidziennikFirebase.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/firebase/SzkolnyMobidziennikFirebase.kt @@ -5,17 +5,14 @@ package pl.szczodrzynski.edziennik.data.firebase import pl.szczodrzynski.edziennik.App -import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_ATTENDANCE -import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_BEHAVIOUR -import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_GRADES -import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_MESSAGES -import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_MOBIDZIENNIK import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask import pl.szczodrzynski.edziennik.data.api.task.IApiTask -import pl.szczodrzynski.edziennik.data.db.entity.Message.Companion.TYPE_RECEIVED import pl.szczodrzynski.edziennik.data.db.entity.Profile +import pl.szczodrzynski.edziennik.data.db.enums.FeatureType +import pl.szczodrzynski.edziennik.data.db.enums.LoginType import pl.szczodrzynski.edziennik.ext.getLong import pl.szczodrzynski.edziennik.ext.getString +import pl.szczodrzynski.edziennik.ext.getStudentData class SzkolnyMobidziennikFirebase(val app: App, val profiles: List, val message: FirebaseService.Message) { /*{ @@ -52,19 +49,19 @@ class SzkolnyMobidziennikFirebase(val app: App, val profiles: List, val val globalId = message.data.getLong("global_id") /* assets/www/js/push.js */ - val viewIdPair = when (type) { - "wiadOdebrana" -> DRAWER_ITEM_MESSAGES to TYPE_RECEIVED - "oceny", "ocenyKoncowe", "zachowanie" -> DRAWER_ITEM_GRADES to 0 - "uwagi" -> DRAWER_ITEM_BEHAVIOUR to 0 - "nieobecnoscPierwszaLekcja", "nieobecnosciDzisiaj" -> DRAWER_ITEM_ATTENDANCE to 0 + val featureType = when (type) { + "wiadOdebrana" -> FeatureType.MESSAGES_INBOX + "oceny", "ocenyKoncowe", "zachowanie" -> FeatureType.GRADES + "uwagi" -> FeatureType.BEHAVIOUR + "nieobecnoscPierwszaLekcja", "nieobecnosciDzisiaj" -> FeatureType.ATTENDANCE else -> return@run } val tasks = profiles.filter { - it.loginStoreType == LOGIN_TYPE_MOBIDZIENNIK && + it.loginStoreType == LoginType.MOBIDZIENNIK && it.getStudentData("globalId", 0L) == globalId }.map { - EdziennikTask.syncProfile(it.id, listOf(viewIdPair)) + EdziennikTask.syncProfile(it.id, setOf(featureType)) } IApiTask.enqueueAll(app, tasks) }} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/firebase/SzkolnyVulcanFirebase.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/firebase/SzkolnyVulcanFirebase.kt index 74ccdd85..ec8f8011 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/firebase/SzkolnyVulcanFirebase.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/firebase/SzkolnyVulcanFirebase.kt @@ -4,16 +4,16 @@ package pl.szczodrzynski.edziennik.data.firebase -import pl.szczodrzynski.edziennik.* -import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_VULCAN +import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask import pl.szczodrzynski.edziennik.data.api.task.IApiTask -import pl.szczodrzynski.edziennik.data.db.entity.Message import pl.szczodrzynski.edziennik.data.db.entity.Profile +import pl.szczodrzynski.edziennik.data.db.enums.FeatureType +import pl.szczodrzynski.edziennik.data.db.enums.LoginType import pl.szczodrzynski.edziennik.ext.getInt import pl.szczodrzynski.edziennik.ext.getString +import pl.szczodrzynski.edziennik.ext.getStudentData import pl.szczodrzynski.edziennik.ext.toJsonObject -import java.util.* class SzkolnyVulcanFirebase(val app: App, val profiles: List, val message: FirebaseService.Message) { /*{ @@ -35,22 +35,22 @@ class SzkolnyVulcanFirebase(val app: App, val profiles: List, val messa val loginId = data.getInt("loginid") /* pl.vulcan.uonetmobile.auxilary.enums.CDCPushEnum */ - val viewIdPair = when (type.lowercase()) { - "wiadomosc" -> MainActivity.DRAWER_ITEM_MESSAGES to Message.TYPE_RECEIVED - "ocena" -> MainActivity.DRAWER_ITEM_GRADES to 0 - "uwaga" -> MainActivity.DRAWER_ITEM_BEHAVIOUR to 0 - "frekwencja" -> MainActivity.DRAWER_ITEM_ATTENDANCE to 0 + val featureType = when (type.lowercase()) { + "wiadomosc" -> FeatureType.MESSAGES_INBOX + "ocena" -> FeatureType.GRADES + "uwaga" -> FeatureType.BEHAVIOUR + "frekwencja" -> FeatureType.ATTENDANCE // this type is not even implemented in Dzienniczek+ - "sprawdzian" -> MainActivity.DRAWER_ITEM_AGENDA to 0 + "sprawdzian" -> FeatureType.AGENDA else -> return@run } val tasks = profiles.filter { - it.loginStoreType == LOGIN_TYPE_VULCAN + it.loginStoreType == LoginType.VULCAN && (it.getStudentData("studentId", 0) == studentId || it.getStudentData("studentLoginId", 0) == loginId) }.map { - EdziennikTask.syncProfile(it.id, listOf(viewIdPair)) + EdziennikTask.syncProfile(it.id, setOf(featureType)) } IApiTask.enqueueAll(app, tasks) }} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ext/BundleExtensions.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ext/BundleExtensions.kt index cc6488bd..eddcf322 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ext/BundleExtensions.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ext/BundleExtensions.kt @@ -9,19 +9,24 @@ import android.content.Intent import android.os.Bundle import android.os.Parcelable import com.google.gson.JsonObject +import pl.szczodrzynski.edziennik.data.db.enums.* +import pl.szczodrzynski.edziennik.ui.base.enums.NavTarget +import java.io.Serializable -fun Bundle?.getInt(key: String, defaultValue: Int): Int { - return this?.getInt(key, defaultValue) ?: defaultValue -} -fun Bundle?.getLong(key: String, defaultValue: Long): Long { - return this?.getLong(key, defaultValue) ?: defaultValue -} -fun Bundle?.getFloat(key: String, defaultValue: Float): Float { - return this?.getFloat(key, defaultValue) ?: defaultValue -} -fun Bundle?.getString(key: String, defaultValue: String): String { - return this?.getString(key, defaultValue) ?: defaultValue -} +fun Bundle?.getInt(key: String, defaultValue: Int) = + this?.getInt(key, defaultValue) ?: defaultValue + +fun Bundle?.getLong(key: String, defaultValue: Long) = + this?.getLong(key, defaultValue) ?: defaultValue + +fun Bundle?.getFloat(key: String, defaultValue: Float) = + this?.getFloat(key, defaultValue) ?: defaultValue + +fun Bundle?.getString(key: String, defaultValue: String) = + this?.getString(key, defaultValue) ?: defaultValue + +inline fun > Bundle?.getEnum(key: String) = this?.getInt(key)?.toEnum() +fun Bundle.putEnum(key: String, value: Enum<*>) = putInt(key, value.toInt()) fun Bundle?.getIntOrNull(key: String): Int? { return this?.get(key) as? Int @@ -36,29 +41,37 @@ fun Bundle?.get(key: String): T? { fun Bundle(vararg properties: Pair): Bundle { return Bundle().apply { for (property in properties) { - when (property.second) { - is String -> putString(property.first, property.second as String?) - is Char -> putChar(property.first, property.second as Char) - is Int -> putInt(property.first, property.second as Int) - is Long -> putLong(property.first, property.second as Long) - is Float -> putFloat(property.first, property.second as Float) - is Short -> putShort(property.first, property.second as Short) - is Double -> putDouble(property.first, property.second as Double) - is Boolean -> putBoolean(property.first, property.second as Boolean) - is Bundle -> putBundle(property.first, property.second as Bundle) - is Parcelable -> putParcelable(property.first, property.second as Parcelable) - is Array<*> -> putParcelableArray(property.first, property.second as Array) + val (key, value) = property + when (value) { + is String -> putString(key, value as String?) + is Char -> putChar(key, value) + is Int -> putInt(key, value) + is Long -> putLong(key, value) + is Float -> putFloat(key, value) + is Short -> putShort(key, value) + is Double -> putDouble(key, value) + is Boolean -> putBoolean(key, value) + is Bundle -> putBundle(key, value) + is Enum<*> -> putEnum(key, value) + is Array<*> -> putParcelableArray(key, value as Array) + is Parcelable -> putParcelable(key, value) + is Serializable -> putSerializable(key, value) } } } } + fun Intent(action: String? = null, vararg properties: Pair): Intent { return Intent(action).putExtras(Bundle(*properties)) } + fun Intent(packageContext: Context, cls: Class<*>, vararg properties: Pair): Intent { return Intent(packageContext, cls).putExtras(Bundle(*properties)) } +fun Intent.putExtras(vararg properties: Pair) = putExtras(Bundle(*properties)) +fun Bundle.putExtras(vararg properties: Pair) = putAll(Bundle(*properties)) + fun Bundle.toJsonObject(): JsonObject { val json = JsonObject() keySet()?.forEach { key -> @@ -79,4 +92,5 @@ fun Bundle.toJsonObject(): JsonObject { } return json } + fun Intent.toJsonObject() = extras?.toJsonObject() diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ext/DataExtensions.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ext/DataExtensions.kt index b1e99c43..977952e5 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ext/DataExtensions.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ext/DataExtensions.kt @@ -4,13 +4,8 @@ package pl.szczodrzynski.edziennik.ext -import android.content.Context import android.util.LongSparseArray import androidx.core.util.forEach -import com.google.android.material.datepicker.CalendarConstraints -import com.google.gson.JsonElement -import pl.szczodrzynski.edziennik.R -import pl.szczodrzynski.edziennik.data.db.entity.Notification import pl.szczodrzynski.edziennik.data.db.entity.Profile import pl.szczodrzynski.edziennik.data.db.entity.Teacher import pl.szczodrzynski.edziennik.data.db.entity.Team @@ -34,43 +29,3 @@ fun LongSparseArray.getById(id: Long): Team? { } return null } - -operator fun Profile.set(key: String, value: JsonElement) = this.studentData.add(key, value) -operator fun Profile.set(key: String, value: Boolean) = this.studentData.addProperty(key, value) -operator fun Profile.set(key: String, value: String?) = this.studentData.addProperty(key, value) -operator fun Profile.set(key: String, value: Number) = this.studentData.addProperty(key, value) -operator fun Profile.set(key: String, value: Char) = this.studentData.addProperty(key, value) - -fun Context.getNotificationTitle(type: Int): String { - return getString(when (type) { - Notification.TYPE_UPDATE -> R.string.notification_type_update - Notification.TYPE_ERROR -> R.string.notification_type_error - Notification.TYPE_TIMETABLE_CHANGED -> R.string.notification_type_timetable_change - Notification.TYPE_TIMETABLE_LESSON_CHANGE -> R.string.notification_type_timetable_lesson_change - Notification.TYPE_NEW_GRADE -> R.string.notification_type_new_grade - Notification.TYPE_NEW_EVENT -> R.string.notification_type_new_event - Notification.TYPE_NEW_HOMEWORK -> R.string.notification_type_new_homework - Notification.TYPE_NEW_SHARED_EVENT -> R.string.notification_type_new_shared_event - Notification.TYPE_NEW_SHARED_HOMEWORK -> R.string.notification_type_new_shared_homework - Notification.TYPE_REMOVED_SHARED_EVENT -> R.string.notification_type_removed_shared_event - Notification.TYPE_NEW_MESSAGE -> R.string.notification_type_new_message - Notification.TYPE_NEW_NOTICE -> R.string.notification_type_notice - Notification.TYPE_NEW_ATTENDANCE -> R.string.notification_type_attendance - Notification.TYPE_SERVER_MESSAGE -> R.string.notification_type_server_message - Notification.TYPE_LUCKY_NUMBER -> R.string.notification_type_lucky_number - Notification.TYPE_FEEDBACK_MESSAGE -> R.string.notification_type_feedback_message - Notification.TYPE_NEW_ANNOUNCEMENT -> R.string.notification_type_new_announcement - Notification.TYPE_AUTO_ARCHIVING -> R.string.notification_type_auto_archiving - Notification.TYPE_TEACHER_ABSENCE -> R.string.notification_type_new_teacher_absence - Notification.TYPE_GENERAL -> R.string.notification_type_general - Notification.TYPE_NEW_SHARED_NOTE -> R.string.notification_type_new_shared_note - else -> R.string.notification_type_general - }) -} - -fun Profile.getSchoolYearConstrains(): CalendarConstraints { - return CalendarConstraints.Builder() - .setStart(dateSemester1Start.inMillisUtc) - .setEnd(dateYearEnd.inMillisUtc) - .build() -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ext/EnumExtensions.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ext/EnumExtensions.kt new file mode 100644 index 00000000..1a55edb6 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ext/EnumExtensions.kt @@ -0,0 +1,75 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2022-10-17. + */ + +package pl.szczodrzynski.edziennik.ext + +import android.view.View +import pl.szczodrzynski.edziennik.MainActivity +import pl.szczodrzynski.edziennik.data.db.enums.* +import pl.szczodrzynski.edziennik.ui.base.enums.NavTarget +import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem + +fun Int.asFeatureType() = FeatureType.values().first { it.id == this } +fun Int.asLoginMethod() = LoginMethod.values().first { it.id == this } +fun Int.asLoginMode() = LoginMode.values().first { it.id == this } +fun Int.asLoginType() = LoginType.values().first { it.id == this } +fun Int.asMetadataType() = MetadataType.values().first { it.id == this } +fun Int.asNotificationType() = NotificationType.values().first { it.id == this } +fun Int.asNavTarget() = NavTarget.values().first { it.id == this } + +fun Int?.asFeatureTypeOrNull() = FeatureType.values().firstOrNull { it.id == this } +fun Int?.asLoginMethodOrNull() = LoginMethod.values().firstOrNull { it.id == this } +fun Int?.asLoginModeOrNull() = LoginMode.values().firstOrNull { it.id == this } +fun Int?.asLoginTypeOrNull() = LoginType.values().firstOrNull { it.id == this } +fun Int?.asMetadataTypeOrNull() = MetadataType.values().firstOrNull { it.id == this } +fun Int?.asNotificationTypeOrNull() = NotificationType.values().firstOrNull { it.id == this } +fun Int?.asNavTargetOrNull() = NavTarget.values().firstOrNull { it.id == this } + +fun Enum<*>.toInt() = when (this) { + is FeatureType -> this.id + is LoginMethod -> this.id + is LoginMode -> this.id + is LoginType -> this.id + is MetadataType -> this.id + is NotificationType -> this.id + is NavTarget -> this.id + else -> this.ordinal +} + +inline fun > Int.toEnum() = when (E::class.java) { + // enums commented out are not really used in Bundles + FeatureType::class.java -> this.asFeatureType() + // LoginMethod::class.java -> this.asLoginMethod() + LoginMode::class.java -> this.asLoginMode() + LoginType::class.java -> this.asLoginType() + // MetadataType::class.java -> this.asMetadataType() + // NotificationType::class.java -> this.asNotificationType() + NavTarget::class.java -> this.asNavTarget() + else -> enumValues()[this] +} as E + +fun > Int.toEnum(type: Class<*>) = when (type) { + // this is used for Config so all enums are here + FeatureType::class.java -> this.asFeatureType() + LoginMethod::class.java -> this.asLoginMethod() + LoginMode::class.java -> this.asLoginMode() + LoginType::class.java -> this.asLoginType() + MetadataType::class.java -> this.asMetadataType() + NotificationType::class.java -> this.asNotificationType() + NavTarget::class.java -> this.asNavTarget() + else -> throw IllegalArgumentException("Unknown type $type") +} as E + +fun getFeatureTypesNecessary() = FeatureType.values().filter { it.isAlwaysNeeded }.toSet() +fun getFeatureTypesUnnecessary() = FeatureType.values().filter { !it.isAlwaysNeeded }.toSet() + +fun NavTarget.toBottomSheetItem(activity: MainActivity) = + BottomSheetPrimaryItem(isContextual = false).also { + it.titleRes = this.nameRes + if (this.icon != null) + it.iconicsIcon = this.icon + it.onClickListener = View.OnClickListener { + activity.navigate(navTarget = this) + } + } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ext/JsonExtensions.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ext/JsonExtensions.kt index 9f0db8ad..fb261e0e 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ext/JsonExtensions.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ext/JsonExtensions.kt @@ -5,11 +5,14 @@ package pl.szczodrzynski.edziennik.ext import android.os.Bundle +import com.google.gson.Gson import com.google.gson.JsonArray import com.google.gson.JsonElement import com.google.gson.JsonObject import com.google.gson.JsonParser import com.google.gson.JsonPrimitive +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.contract fun JsonObject?.get(key: String): JsonElement? = this?.get(key) @@ -40,7 +43,16 @@ fun JsonArray.getChar(key: Int): Char? = if (key >= size()) null else get(key)?. fun JsonArray.getJsonObject(key: Int): JsonObject? = if (key >= size()) null else get(key)?.let { if (it.isJsonObject) it.asJsonObject else null } fun JsonArray.getJsonArray(key: Int): JsonArray? = if (key >= size()) null else get(key)?.let { if (it.isJsonArray) it.asJsonArray else null } +inline fun > JsonObject?.getEnum(key: String) = this?.getInt(key)?.toEnum() +fun JsonObject.putEnum(key: String, value: Enum<*>) = addProperty(key, value.toInt()) + fun String.toJsonObject(): JsonObject? = try { JsonParser.parseString(this).asJsonObject } catch (ignore: Exception) { null } +fun String.toJsonArray(): JsonArray? = try { JsonParser.parseString(this).asJsonArray } catch (ignore: Exception) { null } + +fun Any?.toJsonElement(): JsonElement = when (this) { + is Collection<*> -> JsonArray(this) + else -> Gson().toJsonTree(this) +} operator fun JsonObject.set(key: String, value: JsonElement) = this.add(key, value) operator fun JsonObject.set(key: String, value: Boolean) = this.addProperty(key, value) @@ -53,12 +65,16 @@ fun JsonArray.asJsonObjectList() = this.mapNotNull { it.asJsonObject } fun JsonObject(vararg properties: Pair): JsonObject { return JsonObject().apply { for (property in properties) { - when (property.second) { - is JsonElement -> add(property.first, property.second as JsonElement?) - is String -> addProperty(property.first, property.second as String?) - is Char -> addProperty(property.first, property.second as Char?) - is Number -> addProperty(property.first, property.second as Number?) - is Boolean -> addProperty(property.first, property.second as Boolean?) + val (key, value) = property + when (value) { + is JsonElement -> add(key, value) + is String -> addProperty(key, value) + is Char -> addProperty(key, value) + is Number -> addProperty(key, value) + is Boolean -> addProperty(key, value) + is Enum<*> -> addProperty(key, value.toInt()) + null -> add(key, null) + else -> add(key, value.toJsonElement()) } } } @@ -79,7 +95,9 @@ fun JsonObject.toBundle(): Bundle { } } -fun JsonArray(vararg properties: Any?): JsonArray { +fun JsonArray(vararg properties: Any?) = JsonArray(properties.toList()) + +fun JsonArray(properties: Collection): JsonArray { return JsonArray().apply { for (property in properties) { when (property) { @@ -88,14 +106,39 @@ fun JsonArray(vararg properties: Any?): JsonArray { is Char -> add(property as Char?) is Number -> add(property as Number?) is Boolean -> add(property as Boolean?) + is Enum<*> -> add(property.toInt()) + else -> add(property.toJsonElement()) } } } } -fun JsonArray?.isNullOrEmpty(): Boolean = (this?.size() ?: 0) == 0 +@OptIn(ExperimentalContracts::class) +fun JsonArray?.isNullOrEmpty(): Boolean { + contract { + returns(false) implies (this@isNullOrEmpty != null) + } + return this == null || this.isEmpty +} operator fun JsonArray.plusAssign(o: JsonElement) = this.add(o) operator fun JsonArray.plusAssign(o: String) = this.add(o) operator fun JsonArray.plusAssign(o: Char) = this.add(o) operator fun JsonArray.plusAssign(o: Number) = this.add(o) operator fun JsonArray.plusAssign(o: Boolean) = this.add(o) + +fun JsonObject.mergeWith(other: JsonObject): JsonObject { + for ((key, value) in other.entrySet()) { + when (value) { + is JsonObject -> when { + this.has(key) -> this.getJsonObject(key)?.mergeWith(value) + else -> this.add(key, value) + } + is JsonArray -> when { + this.has(key) -> this.getJsonArray(key)?.addAll(value) + else -> this.add(key, value) + } + else -> this.add(key, value) + } + } + return this +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ext/MiscExtensions.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ext/MiscExtensions.kt index c7d01093..b617e0bf 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ext/MiscExtensions.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ext/MiscExtensions.kt @@ -72,3 +72,19 @@ fun pendingIntentFlag(): Int { return PendingIntent.FLAG_IMMUTABLE return 0 } + +fun pendingIntentMutable(): Int { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) + return PendingIntent.FLAG_MUTABLE + return 0 +} + +fun Int?.takeValue() = if (this == -1) null else this +fun Int?.takePositive() = if (this == -1 || this == 0) null else this + +fun Long?.takeValue() = if (this == -1L) null else this +fun Long?.takePositive() = if (this == -1L || this == 0L) null else this + +fun String?.takeValue() = if (this.isNullOrBlank()) null else this + +fun Any?.ignore() = Unit diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ext/ProfileExtensions.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ext/ProfileExtensions.kt new file mode 100644 index 00000000..a2ef0289 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ext/ProfileExtensions.kt @@ -0,0 +1,127 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2022-10-25. + */ + +package pl.szczodrzynski.edziennik.ext + +import android.content.Context +import android.graphics.PorterDuff +import android.graphics.PorterDuffColorFilter +import android.graphics.drawable.Drawable +import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory +import com.google.android.material.datepicker.CalendarConstraints +import com.google.gson.JsonElement +import pl.droidsonroids.gif.GifDrawable +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.config.AppData +import pl.szczodrzynski.edziennik.data.db.entity.Profile +import pl.szczodrzynski.edziennik.data.db.enums.FeatureType +import pl.szczodrzynski.edziennik.utils.ProfileImageHolder +import pl.szczodrzynski.edziennik.utils.models.Date +import pl.szczodrzynski.navlib.ImageHolder +import pl.szczodrzynski.navlib.getDrawableFromRes + +// TODO refactor Data* fields and make the receiver non-nullable +operator fun Profile?.set(key: String, value: JsonElement) = this?.studentData?.add(key, value) +operator fun Profile?.set(key: String, value: Boolean) = this?.studentData?.addProperty(key, value) +operator fun Profile?.set(key: String, value: String?) = this?.studentData?.addProperty(key, value) +operator fun Profile?.set(key: String, value: Number) = this?.studentData?.addProperty(key, value) +operator fun Profile?.set(key: String, value: Char) = this?.studentData?.addProperty(key, value) + +fun Profile.getStudentData(key: String, defaultValue: Boolean) = + studentData.getBoolean(key) ?: defaultValue + +fun Profile.getStudentData(key: String, defaultValue: String?) = + studentData.getString(key) ?: defaultValue + +fun Profile.getStudentData(key: String, defaultValue: Int) = + studentData.getInt(key) ?: defaultValue + +fun Profile.getStudentData(key: String, defaultValue: Long) = + studentData.getLong(key) ?: defaultValue + +fun Profile.getStudentData(key: String, defaultValue: Float) = + studentData.getFloat(key) ?: defaultValue + +fun Profile.getStudentData(key: String, defaultValue: Char) = + studentData.getChar(key) ?: defaultValue + +fun Profile.getSemesterStart(semester: Int) = + if (semester == 1) dateSemester1Start else dateSemester2Start + +fun Profile.getSemesterEnd(semester: Int) = + if (semester == 1) dateSemester2Start.clone().stepForward(0, 0, -1) else dateYearEnd + +fun Profile.dateToSemester(date: Date) = if (date >= dateSemester2Start) 2 else 1 +fun Profile.isBeforeYear() = false && Date.getToday() < dateSemester1Start + +fun Profile.getSchoolYearConstrains(): CalendarConstraints { + return CalendarConstraints.Builder() + .setStart(dateSemester1Start.inMillisUtc) + .setEnd(dateYearEnd.inMillisUtc) + .build() +} + +fun Profile.hasFeature(featureType: FeatureType) = featureType in this.loginStoreType.features +fun Profile.hasUIFeature(featureType: FeatureType) = + featureType.isUIAlwaysAvailable || hasFeature(featureType) + +fun Profile.getAppData() = + if (App.profileId == this.id) App.data else AppData.get(this.loginStoreType) + +fun Profile.shouldArchive(): Boolean { + // vulcan hotfix + if (dateYearEnd.month > 6) { + dateYearEnd.month = 6 + dateYearEnd.day = 30 + } + // fix for when versions <4.3 synced 2020/2021 year dates to older profiles during 2020 Jun-Aug + if (dateSemester1Start.year > studentSchoolYearStart) { + val diff = dateSemester1Start.year - studentSchoolYearStart + dateSemester1Start.year -= diff + dateSemester2Start.year -= diff + dateYearEnd.year -= diff + } + return App.config.archiverEnabled && Date.getToday() >= dateYearEnd && Date.getToday().year > studentSchoolYearStart +} + +fun Profile.getDrawable(context: Context): Drawable { + if (archived) { + return context.getDrawableFromRes(R.drawable.profile_archived).also { + it.colorFilter = PorterDuffColorFilter(colorFromName(name), PorterDuff.Mode.DST_OVER) + } + } + + if (!image.isNullOrEmpty()) { + try { + return if (image?.endsWith(".gif", true) == true) { + GifDrawable(image ?: "") + } else { + RoundedBitmapDrawableFactory.create(context.resources, image ?: "") + } + } catch (e: Exception) { + e.printStackTrace() + } + } + + return context.getDrawableFromRes(R.drawable.profile).also { + it.colorFilter = PorterDuffColorFilter(colorFromName(name), PorterDuff.Mode.DST_OVER) + } +} + +fun Profile.getHolder(): ImageHolder { + if (archived) { + return ImageHolder(R.drawable.profile_archived, colorFromName(name)) + } + + return if (!image.isNullOrEmpty()) { + try { + ProfileImageHolder(image ?: "") + } catch (_: Exception) { + ImageHolder(R.drawable.profile, colorFromName(name)) + } + } else { + ImageHolder(R.drawable.profile, colorFromName(name)) + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ext/TextExtensions.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ext/TextExtensions.kt index ebfb1410..d4149320 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ext/TextExtensions.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ext/TextExtensions.kt @@ -15,9 +15,12 @@ import android.text.style.CharacterStyle import android.text.style.ForegroundColorSpan import android.text.style.StrikethroughSpan import android.text.style.StyleSpan +import android.text.style.UnderlineSpan import androidx.annotation.PluralsRes import androidx.annotation.StringRes import com.mikepenz.materialdrawer.holder.StringHolder +import java.net.URLDecoder +import java.net.URLEncoder fun CharSequence?.isNotNullNorEmpty(): Boolean { return this != null && this.isNotEmpty() @@ -158,6 +161,11 @@ fun CharSequence?.asBoldSpannable(): Spannable { spannable.setSpan(StyleSpan(Typeface.BOLD), 0, spannable.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) return spannable } +fun CharSequence?.asUnderlineSpannable(): Spannable { + val spannable = SpannableString(this) + spannable.setSpan(UnderlineSpan(), 0, spannable.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + return spannable +} fun CharSequence.asSpannable( vararg spans: CharacterStyle, substring: CharSequence? = null, @@ -343,3 +351,17 @@ fun Int.toStringHolder() = StringHolder(this) fun CharSequence.toStringHolder() = StringHolder(this) fun @receiver:StringRes Int.resolveString(context: Context) = context.getString(this) + +fun String.urlEncode(): String = URLEncoder.encode(this, "UTF-8").replace("+", "%20") +fun String.urlDecode(): String = URLDecoder.decode(this, "UTF-8") + +fun Map.toQueryString() = this + .map { it.key.urlEncode() to it.value.urlEncode() } + .sortedBy { it.first } + .joinToString("&") { "${it.first}=${it.second}" } + +fun String.fromQueryString() = this + .substringAfter('?') + .split("&") + .map { it.split("=") } + .associate { it[0].urlDecode() to it[1].urlDecode() } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ext/TimeExtensions.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ext/TimeExtensions.kt index 114a5a40..31bd952c 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ext/TimeExtensions.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ext/TimeExtensions.kt @@ -7,9 +7,10 @@ package pl.szczodrzynski.edziennik.ext import android.content.Context import im.wangchao.mhttp.Response import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Time import java.text.SimpleDateFormat -import java.util.* +import java.util.Locale const val MINUTE = 60L const val HOUR = 60L*MINUTE @@ -115,3 +116,11 @@ fun Context.getSyncInterval(interval: Int): String { "" return hoursText?.plus(" $minutesText") ?: minutesText } + +fun ClosedRange.asSequence(): Sequence = sequence { + val date = this@asSequence.start.clone() + while (date in this@asSequence) { + yield(date.clone()) + date.stepForward(0, 0, 1) + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ext/ViewExtensions.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ext/ViewExtensions.kt index 3a0468ef..ddf49975 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ext/ViewExtensions.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ext/ViewExtensions.kt @@ -8,6 +8,7 @@ import android.content.Context import android.content.res.Resources import android.graphics.Rect import android.view.View +import android.view.ViewGroup import android.view.WindowManager import android.widget.* import androidx.annotation.StringRes @@ -161,3 +162,12 @@ val SwipeRefreshLayout.onScrollListener: RecyclerView.OnScrollListener } } +fun View.removeFromParent() { + (parent as? ViewGroup)?.removeView(this) +} + +fun View.appendView(child: View) { + val parent = parent as? ViewGroup ?: return + val index = parent.indexOfChild(this) + parent.addView(child, index + 1) +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/network/cookie/DumbCookie.kt b/app/src/main/java/pl/szczodrzynski/edziennik/network/cookie/DumbCookie.kt index 75b60309..ecd513b0 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/network/cookie/DumbCookie.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/network/cookie/DumbCookie.kt @@ -5,8 +5,20 @@ package pl.szczodrzynski.edziennik.network.cookie import okhttp3.Cookie +import okhttp3.HttpUrl class DumbCookie(var cookie: Cookie) { + companion object { + fun deserialize(key: String, value: String): DumbCookie? { + val (domain, _) = key.split('|', limit = 2) + val url = HttpUrl.Builder() + .scheme("https") + .host(domain) + .build() + val cookie = Cookie.parse(url, value) ?: return null + return DumbCookie(cookie) + } + } constructor(domain: String, name: String, value: String, expiresAt: Long? = null) : this( Cookie.Builder() @@ -21,7 +33,10 @@ class DumbCookie(var cookie: Cookie) { cookie = Cookie.Builder() .name(cookie.name()) .value(cookie.value()) - .expiresAt(cookie.expiresAt()) + .also { + if (cookie.persistent()) + it.expiresAt(cookie.expiresAt()) + } .domain(cookie.domain()) .build() } @@ -45,4 +60,7 @@ class DumbCookie(var cookie: Cookie) { hash = 31 * hash + cookie.domain().hashCode() return hash } + + fun serializeKey() = cookie.domain() + "|" + cookie.name() + fun serialize() = serializeKey() to cookie.toString() } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/network/cookie/DumbCookieJar.kt b/app/src/main/java/pl/szczodrzynski/edziennik/network/cookie/DumbCookieJar.kt index 39cec6eb..945e16ea 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/network/cookie/DumbCookieJar.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/network/cookie/DumbCookieJar.kt @@ -5,6 +5,7 @@ package pl.szczodrzynski.edziennik.network.cookie import android.content.Context +import androidx.core.content.edit import okhttp3.Cookie import okhttp3.CookieJar import okhttp3.HttpUrl @@ -26,22 +27,48 @@ class DumbCookieJar( ) : CookieJar { private val prefs = context.getSharedPreferences("cookies", Context.MODE_PRIVATE) - val sessionCookies = mutableSetOf() - private val savedCookies = mutableSetOf() + private val sessionCookies = mutableSetOf() + + init { + val toRemove = mutableListOf() + prefs.all.forEach { (key, value) -> + if (value !is String) + return@forEach + val dc = DumbCookie.deserialize(key, value) ?: return@forEach + if (dc.cookie.expiresAt() > System.currentTimeMillis()) + sessionCookies.add(dc) + else + toRemove.add(key) + } + prefs.edit { + for (key in toRemove) { + remove(key) + } + } + } + private fun save(dc: DumbCookie) { sessionCookies.remove(dc) sessionCookies.add(dc) if (dc.cookie.persistent() || persistAll) { - savedCookies.remove(dc) - savedCookies.add(dc) + prefs.edit { + val (key, value) = dc.serialize() + putString(key, value) + } } } private fun delete(vararg toRemove: DumbCookie) { - sessionCookies.removeAll(toRemove) - savedCookies.removeAll(toRemove) + sessionCookies.removeAll(toRemove.toSet()) + prefs.edit { + for (dc in toRemove) { + val key = dc.serializeKey() + if (prefs.contains(key)) + remove(key) + } + } } - override fun saveFromResponse(url: HttpUrl?, cookies: List) { + override fun saveFromResponse(url: HttpUrl, cookies: MutableList) { for (cookie in cookies) { val dc = DumbCookie(cookie) save(dc) @@ -54,6 +81,10 @@ class DumbCookieJar( }.map { it.cookie } } + fun getAllDomains(): List { + return sessionCookies.map { it.cookie } + } + fun get(domain: String, name: String): String? { return sessionCookies.firstOrNull { it.domainMatches(domain) && it.cookie.name() == name @@ -84,7 +115,7 @@ class DumbCookieJar( fun getAll(domain: String): Map { return sessionCookies.filter { it.domainMatches(domain) - }.map { it.cookie.name() to it.cookie.value() }.toMap() + }.associate { it.cookie.name() to it.cookie.value() } } fun remove(domain: String, name: String) { @@ -100,4 +131,11 @@ class DumbCookieJar( } delete(*toRemove.toTypedArray()) } + + fun clearAllDomains() { + sessionCookies.clear() + prefs.edit { + clear() + } + } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/sync/UpdateStateEvent.kt b/app/src/main/java/pl/szczodrzynski/edziennik/sync/UpdateStateEvent.kt new file mode 100644 index 00000000..878efdb8 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/sync/UpdateStateEvent.kt @@ -0,0 +1,9 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2022-10-22. + */ + +package pl.szczodrzynski.edziennik.sync + +import pl.szczodrzynski.edziennik.data.api.szkolny.response.Update + +class UpdateStateEvent(val running: Boolean, val update: Update?, val downloadId: Long) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/sync/UpdateWorker.kt b/app/src/main/java/pl/szczodrzynski/edziennik/sync/UpdateWorker.kt index 0df0bc83..3d22e044 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/sync/UpdateWorker.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/sync/UpdateWorker.kt @@ -5,25 +5,14 @@ package pl.szczodrzynski.edziennik.sync import android.annotation.SuppressLint -import android.app.NotificationManager -import android.app.PendingIntent import android.content.Context -import android.content.Intent -import android.widget.Toast -import androidx.core.app.NotificationCompat import androidx.work.* import kotlinx.coroutines.* -import org.greenrobot.eventbus.EventBus import pl.szczodrzynski.edziennik.* -import pl.szczodrzynski.edziennik.R -import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi import pl.szczodrzynski.edziennik.data.api.szkolny.response.Update import pl.szczodrzynski.edziennik.ext.DAY -import pl.szczodrzynski.edziennik.ext.concat import pl.szczodrzynski.edziennik.ext.formatDate -import pl.szczodrzynski.edziennik.ext.pendingIntentFlag import pl.szczodrzynski.edziennik.utils.Utils -import pl.szczodrzynski.edziennik.utils.html.BetterHtml import java.util.concurrent.TimeUnit import kotlin.coroutines.CoroutineContext @@ -76,63 +65,6 @@ class UpdateWorker(val context: Context, val params: WorkerParameters) : Worker( Utils.d(TAG, "Cancelling work by tag $TAG") WorkManager.getInstance(app).cancelAllWorkByTag(TAG) } - - suspend fun runNow(app: App, overrideUpdate: Update? = null) { - try { - val update = overrideUpdate - ?: run { - withContext(Dispatchers.Default) { - SzkolnyApi(app).runCatching({ - getUpdate("beta") - }, { - Toast.makeText(app, app.getString(R.string.notification_cant_check_update), Toast.LENGTH_SHORT).show() - }) - } ?: return@run null - - if (app.config.update == null - || app.config.update?.versionCode ?: BuildConfig.VERSION_CODE <= BuildConfig.VERSION_CODE) { - app.config.update = null - Toast.makeText(app, app.getString(R.string.notification_no_update), Toast.LENGTH_SHORT).show() - return@run null - } - app.config.update - } ?: return - - if (update.versionCode <= BuildConfig.VERSION_CODE) { - app.config.update = null - return - } - - if (EventBus.getDefault().hasSubscriberForEvent(update::class.java)) { - if (!update.updateMandatory) // mandatory updates are posted by the SzkolnyApi - EventBus.getDefault().postSticky(update) - return - } - - val notificationIntent = Intent(app, UpdateDownloaderService::class.java) - val pendingIntent = PendingIntent.getService(app, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT or pendingIntentFlag()) - val notification = NotificationCompat.Builder(app, app.notificationChannelsManager.updates.key) - .setContentTitle(app.getString(R.string.notification_updates_title)) - .setContentText(app.getString(R.string.notification_updates_text, update.versionName)) - .setTicker(app.getString(R.string.notification_updates_summary)) - .setSmallIcon(R.drawable.ic_notification) - .setStyle(NotificationCompat.BigTextStyle() - .bigText(listOf( - app.getString(R.string.notification_updates_text, update.versionName), - update.releaseNotes?.let { BetterHtml.fromHtml(context = null, it) } - ).concat("\n"))) - .setColor(0xff2196f3.toInt()) - .setLights(0xFF00FFFF.toInt(), 2000, 2000) - .setPriority(NotificationCompat.PRIORITY_HIGH) - .setDefaults(NotificationCompat.DEFAULT_ALL) - .setGroup(app.notificationChannelsManager.updates.key) - .setContentIntent(pendingIntent) - .setAutoCancel(false) - .build() - (app.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager).notify(app.notificationChannelsManager.updates.id, notification) - - } catch (ignore: Exception) { } - } } private val job = Job() @@ -146,22 +78,13 @@ class UpdateWorker(val context: Context, val params: WorkerParameters) : Worker( return Result.success() } - launch { - runNow(app) - } + val channel = if (App.devMode) + Update.Type.BETA + else + Update.Type.RELEASE + app.updateManager.checkNowSync(channel, notify = true) rescheduleNext(this.context) return Result.success() } - - class JavaWrapper(app: App) : CoroutineScope { - private val job = Job() - override val coroutineContext: CoroutineContext - get() = job + Dispatchers.Main - init { - launch { - runNow(app) - } - } - } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/agenda/AgendaFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/agenda/AgendaFragment.kt index 83d89e67..cbdbce7b 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/agenda/AgendaFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/agenda/AgendaFragment.kt @@ -17,13 +17,18 @@ import com.mikepenz.iconics.typeface.library.community.material.CommunityMateria import com.mikepenz.iconics.utils.colorInt import com.mikepenz.iconics.utils.sizeDp import eu.szkolny.font.SzkolnyFont -import kotlinx.coroutines.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.MainActivity import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.data.db.entity.EventType -import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.data.db.entity.Profile +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType import pl.szczodrzynski.edziennik.databinding.FragmentAgendaCalendarBinding import pl.szczodrzynski.edziennik.databinding.FragmentAgendaDefaultBinding import pl.szczodrzynski.edziennik.ui.dialogs.settings.AgendaConfigDialog @@ -32,7 +37,6 @@ import pl.szczodrzynski.edziennik.utils.Themes import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetSeparatorItem -import java.util.* import kotlin.coroutines.CoroutineContext class AgendaFragment : Fragment(), CoroutineScope { @@ -54,7 +58,7 @@ class AgendaFragment : Fragment(), CoroutineScope { if (getActivity() == null || context == null) return null activity = getActivity() as MainActivity context?.theme?.applyStyle(Themes.appTheme, true) - type = app.config.forProfile().ui.agendaViewType + type = app.profile.config.ui.agendaViewType b = when (type) { Profile.AGENDA_DEFAULT -> FragmentAgendaDefaultBinding.inflate(inflater, container, false) Profile.AGENDA_CALENDAR -> FragmentAgendaCalendarBinding.inflate(inflater, container, false) @@ -93,7 +97,7 @@ class AgendaFragment : Fragment(), CoroutineScope { activity.bottomSheet.close() type = if (type == Profile.AGENDA_DEFAULT) Profile.AGENDA_CALENDAR else Profile.AGENDA_DEFAULT - app.config.forProfile().ui.agendaViewType = type + app.profile.config.ui.agendaViewType = type activity.reloadTarget() }, BottomSheetSeparatorItem(true), @@ -105,7 +109,7 @@ class AgendaFragment : Fragment(), CoroutineScope { activity.bottomSheet.close() withContext(Dispatchers.Default) { App.db.metadataDao() - .setAllSeen(app.profileId, Metadata.TYPE_EVENT, true) + .setAllSeen(app.profileId, MetadataType.EVENT, true) } Toast.makeText( activity, @@ -138,13 +142,7 @@ class AgendaFragment : Fragment(), CoroutineScope { private suspend fun checkEventTypes() { withContext(Dispatchers.Default) { - val eventTypes = app.db.eventTypeDao().getAllNow(app.profileId).map { - it.id - } - val defaultEventTypes = EventType.getTypeColorMap().keys - if (!eventTypes.containsAll(defaultEventTypes)) { - app.db.eventTypeDao().addDefaultTypes(activity, app.profileId) - } + app.db.eventTypeDao().getAllWithDefaults(app.profile) } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/agenda/AgendaFragmentDefault.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/agenda/AgendaFragmentDefault.kt index d51e2e70..f25dfacc 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/agenda/AgendaFragmentDefault.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/agenda/AgendaFragmentDefault.kt @@ -16,7 +16,12 @@ import com.github.tibolte.agendacalendarview.agenda.AgendaAdapter import com.github.tibolte.agendacalendarview.models.BaseCalendarEvent import com.github.tibolte.agendacalendarview.models.CalendarEvent import com.github.tibolte.agendacalendarview.models.IDayItem -import kotlinx.coroutines.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.MainActivity import pl.szczodrzynski.edziennik.data.db.full.EventFull @@ -49,7 +54,6 @@ class AgendaFragmentDefault( private val unreadDates = mutableSetOf() private val events = mutableListOf() private var isInitialized = false - private val profileConfig by lazy { app.config.forProfile().ui } private val listView get() = b.agendaDefaultView.agendaView.agendaListView @@ -107,10 +111,10 @@ class AgendaFragmentDefault( isInitialized = false withContext(Dispatchers.Default) { - if (profileConfig.agendaLessonChanges) + if (app.profile.config.ui.agendaLessonChanges) addLessonChanges(events) - if (profileConfig.agendaTeacherAbsence) + if (app.profile.config.ui.agendaTeacherAbsence) addTeacherAbsence(events) } @@ -127,7 +131,7 @@ class AgendaFragmentDefault( val dateStart = app.profile.dateSemester1Start.asCalendar val dateEnd = app.profile.dateYearEnd.asCalendar - val isCompactMode = profileConfig.agendaCompactMode + val isCompactMode = app.profile.config.ui.agendaCompactMode b.agendaDefaultView.init( events, @@ -247,7 +251,7 @@ class AgendaFragmentDefault( ) { events.removeAll { it is AgendaEvent || it is AgendaEventGroup } - if (!profileConfig.agendaGroupByType) { + if (!app.profile.config.ui.agendaGroupByType) { events += eventList.map { if (!it.seen) unreadDates.add(it.date.value) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/announcements/AnnouncementsFragment.java b/app/src/main/java/pl/szczodrzynski/edziennik/ui/announcements/AnnouncementsFragment.java index 3eadad95..3bc0e13e 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/announcements/AnnouncementsFragment.java +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/announcements/AnnouncementsFragment.java @@ -1,8 +1,6 @@ package pl.szczodrzynski.edziennik.ui.announcements; import static androidx.recyclerview.widget.RecyclerView.SCROLL_STATE_IDLE; -import static pl.szczodrzynski.edziennik.data.db.entity.LoginStore.LOGIN_TYPE_LIBRUS; -import static pl.szczodrzynski.edziennik.data.db.entity.Metadata.TYPE_ANNOUNCEMENT; import android.os.AsyncTask; import android.os.Bundle; @@ -30,6 +28,8 @@ import pl.szczodrzynski.edziennik.MainActivity; import pl.szczodrzynski.edziennik.R; import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask; import pl.szczodrzynski.edziennik.data.api.events.AnnouncementGetEvent; +import pl.szczodrzynski.edziennik.data.db.enums.LoginType; +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType; import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull; import pl.szczodrzynski.edziennik.databinding.DialogAnnouncementBinding; import pl.szczodrzynski.edziennik.databinding.FragmentAnnouncementsBinding; @@ -69,10 +69,10 @@ public class AnnouncementsFragment extends Fragment { .withIcon(CommunityMaterial.Icon.cmd_eye_check_outline) .withOnClickListener(v3 -> { activity.getBottomSheet().close(); - if (app.getProfile().getLoginStoreType() == LOGIN_TYPE_LIBRUS) { + if (app.getProfile().getLoginStoreType() == LoginType.LIBRUS) { EdziennikTask.Companion.announcementsRead(App.Companion.getProfileId()).enqueue(requireContext()); } else { - AsyncTask.execute(() -> App.db.metadataDao().setAllSeen(App.Companion.getProfileId(), TYPE_ANNOUNCEMENT, true)); + AsyncTask.execute(() -> App.Companion.getDb().metadataDao().setAllSeen(App.Companion.getProfileId(), MetadataType.ANNOUNCEMENT, true)); Toast.makeText(activity, R.string.main_menu_mark_as_read_success, Toast.LENGTH_SHORT).show(); } }) @@ -103,7 +103,7 @@ public class AnnouncementsFragment extends Fragment { } }); - app.db.announcementDao().getAll(App.Companion.getProfileId()).observe(this, announcements -> { + app.getDb().announcementDao().getAll(App.Companion.getProfileId()).observe(getViewLifecycleOwner(), announcements -> { if (app == null || activity == null || b == null || !isAdded()) return; @@ -124,7 +124,7 @@ public class AnnouncementsFragment extends Fragment { return; }*/ AnnouncementsAdapter announcementsAdapter = new AnnouncementsAdapter(activity, announcements, (v, announcement) -> { - if (announcement.getText() == null || (app.getProfile().getLoginStoreType() == LOGIN_TYPE_LIBRUS && !announcement.getSeen())) { + if (announcement.getText() == null || (app.getProfile().getLoginStoreType() == LoginType.LIBRUS && !announcement.getSeen())) { EdziennikTask.Companion.announcementGet(App.Companion.getProfileId(), announcement).enqueue(requireContext()); } else { showAnnouncementDetailsDialog(announcement); @@ -172,9 +172,9 @@ public class AnnouncementsFragment extends Fragment { .setPositiveButton(R.string.ok, null) .show(); b.text.setText(announcement.getTeacherName() +"\n\n"+ (announcement.getStartDate() != null ? announcement.getStartDate().getFormattedString() : "-") + (announcement.getEndDate() != null ? " do " + announcement.getEndDate().getFormattedString() : "")+"\n\n" +announcement.getText()); - if (!announcement.getSeen() && app.getProfile().getLoginStoreType() != LOGIN_TYPE_LIBRUS) { + if (!announcement.getSeen() && app.getProfile().getLoginStoreType() != LoginType.LIBRUS) { announcement.setSeen(true); - AsyncTask.execute(() -> App.db.metadataDao().setSeen(App.Companion.getProfileId(), announcement, true)); + AsyncTask.execute(() -> App.Companion.getDb().metadataDao().setSeen(App.Companion.getProfileId(), announcement, true)); if (recyclerView.getAdapter() != null) recyclerView.getAdapter().notifyDataSetChanged(); } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/attendance/AttendanceFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/attendance/AttendanceFragment.kt index e5687a3c..f3dbc2f7 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/attendance/AttendanceFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/attendance/AttendanceFragment.kt @@ -18,7 +18,7 @@ import kotlinx.coroutines.Job import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.MainActivity import pl.szczodrzynski.edziennik.R -import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType import pl.szczodrzynski.edziennik.databinding.AttendanceFragmentBinding import pl.szczodrzynski.edziennik.ext.Bundle import pl.szczodrzynski.edziennik.ext.addOnPageSelectedListener @@ -75,14 +75,14 @@ class AttendanceFragment : Fragment(), CoroutineScope { .withIcon(CommunityMaterial.Icon.cmd_eye_check_outline) .withOnClickListener(View.OnClickListener { activity.bottomSheet.close() - AsyncTask.execute { App.db.metadataDao().setAllSeen(App.profileId, Metadata.TYPE_ATTENDANCE, true) } + AsyncTask.execute { App.db.metadataDao().setAllSeen(App.profileId, MetadataType.ATTENDANCE, true) } Toast.makeText(activity, R.string.main_menu_mark_as_read_success, Toast.LENGTH_SHORT).show() }) ) activity.gainAttention() if (pageSelection == 1) - pageSelection = app.config.forProfile().attendance.attendancePageSelection + pageSelection = app.profile.config.attendance.attendancePageSelection val pagerAdapter = FragmentLazyPagerAdapter( parentFragmentManager, @@ -113,7 +113,7 @@ class AttendanceFragment : Fragment(), CoroutineScope { currentItem = pageSelection addOnPageSelectedListener { pageSelection = it - app.config.forProfile().attendance.attendancePageSelection = it + app.profile.config.attendance.attendancePageSelection = it } b.tabLayout.setupWithViewPager(this) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/attendance/AttendanceListFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/attendance/AttendanceListFragment.kt index acc19715..02eae698 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/attendance/AttendanceListFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/attendance/AttendanceListFragment.kt @@ -129,8 +129,8 @@ class AttendanceListFragment : LazyFragment(), CoroutineScope { if (attendance.isEmpty()) return mutableListOf() - val groupConsecutiveDays = app.config.forProfile().attendance.groupConsecutiveDays - val showPresenceInMonth = app.config.forProfile().attendance.showPresenceInMonth + val groupConsecutiveDays = app.profile.config.attendance.groupConsecutiveDays + val showPresenceInMonth = app.profile.config.attendance.showPresenceInMonth if (viewType == AttendanceFragment.VIEW_DAYS) { val items = attendance diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/base/enums/NavTarget.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/base/enums/NavTarget.kt new file mode 100644 index 00000000..749c1343 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/base/enums/NavTarget.kt @@ -0,0 +1,243 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2022-10-17. + */ + +package pl.szczodrzynski.edziennik.ui.base.enums + +import androidx.annotation.StringRes +import androidx.fragment.app.Fragment +import com.mikepenz.iconics.typeface.IIcon +import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial +import eu.szkolny.font.SzkolnyFont +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.data.db.enums.FeatureType +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType +import pl.szczodrzynski.edziennik.ui.agenda.AgendaFragment +import pl.szczodrzynski.edziennik.ui.announcements.AnnouncementsFragment +import pl.szczodrzynski.edziennik.ui.attendance.AttendanceFragment +import pl.szczodrzynski.edziennik.ui.behaviour.BehaviourFragment +import pl.szczodrzynski.edziennik.ui.debug.DebugFragment +import pl.szczodrzynski.edziennik.ui.debug.LabFragment +import pl.szczodrzynski.edziennik.ui.feedback.FeedbackFragment +import pl.szczodrzynski.edziennik.ui.grades.GradesListFragment +import pl.szczodrzynski.edziennik.ui.grades.editor.GradesEditorFragment +import pl.szczodrzynski.edziennik.ui.home.HomeFragment +import pl.szczodrzynski.edziennik.ui.homework.HomeworkFragment +import pl.szczodrzynski.edziennik.ui.messages.compose.MessagesComposeFragment +import pl.szczodrzynski.edziennik.ui.messages.list.MessagesFragment +import pl.szczodrzynski.edziennik.ui.messages.single.MessageFragment +import pl.szczodrzynski.edziennik.ui.notes.NotesFragment +import pl.szczodrzynski.edziennik.ui.notifications.NotificationsListFragment +import pl.szczodrzynski.edziennik.ui.settings.ProfileManagerFragment +import pl.szczodrzynski.edziennik.ui.settings.SettingsFragment +import pl.szczodrzynski.edziennik.ui.teachers.TeachersListFragment +import pl.szczodrzynski.edziennik.ui.timetable.TimetableFragment +import pl.szczodrzynski.edziennik.ui.webpush.WebPushFragment + +enum class NavTarget( + val id: Int, + val fragmentClass: Class?, + val location: NavTargetLocation = NavTargetLocation.NOWHERE, + @StringRes val nameRes: Int, + @StringRes val descriptionRes: Int? = null, + @StringRes val titleRes: Int? = null, + val icon: IIcon? = null, + val popTo: NavTarget? = null, + val badgeType: MetadataType? = null, + val featureType: FeatureType? = null, + val devModeOnly: Boolean = false, +) { + HOME( + id = 1, + fragmentClass = HomeFragment::class.java, + location = NavTargetLocation.DRAWER, + nameRes = R.string.menu_home_page, + titleRes = R.string.app_name, + icon = CommunityMaterial.Icon2.cmd_home_outline, + ), + TIMETABLE( + id = 11, + fragmentClass = TimetableFragment::class.java, + location = NavTargetLocation.DRAWER, + nameRes = R.string.menu_timetable, + icon = CommunityMaterial.Icon3.cmd_timetable, + popTo = HOME, + badgeType = MetadataType.LESSON_CHANGE, + featureType = FeatureType.TIMETABLE, + ), + AGENDA( + id = 12, + fragmentClass = AgendaFragment::class.java, + location = NavTargetLocation.DRAWER, + nameRes = R.string.menu_agenda, + icon = CommunityMaterial.Icon.cmd_calendar_outline, + popTo = HOME, + badgeType = MetadataType.EVENT, + featureType = FeatureType.AGENDA, + ), + GRADES( + id = 13, + fragmentClass = GradesListFragment::class.java, + location = NavTargetLocation.DRAWER, + nameRes = R.string.menu_grades, + icon = CommunityMaterial.Icon3.cmd_numeric_5_box_outline, + popTo = HOME, + badgeType = MetadataType.GRADE, + featureType = FeatureType.GRADES, + ), + MESSAGES( + id = 17, + fragmentClass = MessagesFragment::class.java, + location = NavTargetLocation.DRAWER, + nameRes = R.string.menu_messages, + icon = CommunityMaterial.Icon.cmd_email_outline, + popTo = HOME, + badgeType = MetadataType.MESSAGE, + featureType = FeatureType.MESSAGES_INBOX, + ), + HOMEWORK( + id = 14, + fragmentClass = HomeworkFragment::class.java, + location = NavTargetLocation.DRAWER, + nameRes = R.string.menu_homework, + icon = SzkolnyFont.Icon.szf_notebook_outline, + popTo = HOME, + badgeType = MetadataType.HOMEWORK, + featureType = FeatureType.HOMEWORK, + ), + BEHAVIOUR( + id = 15, + fragmentClass = BehaviourFragment::class.java, + location = NavTargetLocation.DRAWER, + nameRes = R.string.menu_notices, + icon = CommunityMaterial.Icon.cmd_emoticon_outline, + popTo = HOME, + badgeType = MetadataType.NOTICE, + featureType = FeatureType.BEHAVIOUR, + ), + ATTENDANCE( + id = 16, + fragmentClass = AttendanceFragment::class.java, + location = NavTargetLocation.DRAWER, + nameRes = R.string.menu_attendance, + icon = CommunityMaterial.Icon.cmd_calendar_remove_outline, + popTo = HOME, + badgeType = MetadataType.ATTENDANCE, + featureType = FeatureType.ATTENDANCE, + ), + ANNOUNCEMENTS( + id = 18, + fragmentClass = AnnouncementsFragment::class.java, + location = NavTargetLocation.DRAWER, + nameRes = R.string.menu_announcements, + icon = CommunityMaterial.Icon.cmd_bullhorn_outline, + popTo = HOME, + badgeType = MetadataType.ANNOUNCEMENT, + featureType = FeatureType.ANNOUNCEMENTS, + ), + NOTES( + id = 23, + fragmentClass = NotesFragment::class.java, + location = NavTargetLocation.DRAWER_MORE, + nameRes = R.string.menu_notes, + icon = CommunityMaterial.Icon3.cmd_text_box_multiple_outline, + ), + TEACHERS( + id = 22, + fragmentClass = TeachersListFragment::class.java, + location = NavTargetLocation.DRAWER_MORE, + nameRes = R.string.menu_teachers, + icon = CommunityMaterial.Icon3.cmd_shield_account_outline, + ), + NOTIFICATIONS( + id = 20, + fragmentClass = NotificationsListFragment::class.java, + location = NavTargetLocation.DRAWER_BOTTOM, + nameRes = R.string.menu_notifications, + icon = CommunityMaterial.Icon.cmd_bell_ring_outline, + popTo = HOME, + ), + SETTINGS( + id = 101, + fragmentClass = SettingsFragment::class.java, + location = NavTargetLocation.DRAWER_BOTTOM, + nameRes = R.string.menu_settings, + icon = CommunityMaterial.Icon.cmd_cog_outline, + ), + LAB( + id = 1000, + fragmentClass = LabFragment::class.java, + location = NavTargetLocation.DRAWER_BOTTOM, + nameRes = R.string.menu_lab, + icon = CommunityMaterial.Icon2.cmd_flask_outline, + popTo = HOME, + devModeOnly = true, + ), + PROFILE_ADD( + id = 200, + fragmentClass = null, + location = NavTargetLocation.PROFILE_LIST, + nameRes = R.string.menu_add_new_profile, + descriptionRes = R.string.drawer_add_new_profile_desc, + icon = CommunityMaterial.Icon3.cmd_plus, + ), + PROFILE_MANAGER( + id = 203, + fragmentClass = ProfileManagerFragment::class.java, + location = NavTargetLocation.NOWHERE, + nameRes = R.string.menu_manage_profiles, + titleRes = R.string.title_profile_manager, + descriptionRes = R.string.drawer_manage_profiles_desc, + icon = CommunityMaterial.Icon.cmd_account_group, + ), + PROFILE_MARK_AS_READ( + id = 204, + fragmentClass = null, + location = NavTargetLocation.PROFILE_LIST, + nameRes = R.string.menu_mark_everything_as_read, + icon = CommunityMaterial.Icon.cmd_eye_check_outline, + ), + PROFILE_SYNC_ALL( + id = 201, + fragmentClass = null, + location = NavTargetLocation.PROFILE_LIST, + nameRes = R.string.menu_sync_all, + icon = CommunityMaterial.Icon.cmd_download_outline, + ), + FEEDBACK( + id = 120, + fragmentClass = FeedbackFragment::class.java, + location = NavTargetLocation.BOTTOM_SHEET, + nameRes = R.string.menu_feedback, + icon = CommunityMaterial.Icon2.cmd_help_circle_outline, + ), + DEBUG( + id = 102, + fragmentClass = DebugFragment::class.java, + location = NavTargetLocation.BOTTOM_SHEET, + nameRes = R.string.menu_debug, + icon = CommunityMaterial.Icon.cmd_android_debug_bridge, + devModeOnly = true, + ), + GRADES_EDITOR( + id = 501, + fragmentClass = GradesEditorFragment::class.java, + nameRes = R.string.menu_grades_editor, + ), + MESSAGE( + id = 503, + fragmentClass = MessageFragment::class.java, + nameRes = R.string.menu_message, + popTo = MESSAGES, + ), + MESSAGE_COMPOSE( + id = 504, + fragmentClass = MessagesComposeFragment::class.java, + nameRes = R.string.menu_message_compose, + ), + WEB_PUSH( + id = 140, + fragmentClass = WebPushFragment::class.java, + nameRes = R.string.menu_web_push, + ) +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/base/enums/NavTargetLocation.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/base/enums/NavTargetLocation.kt new file mode 100644 index 00000000..24ab9134 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/base/enums/NavTargetLocation.kt @@ -0,0 +1,14 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2022-10-17. + */ + +package pl.szczodrzynski.edziennik.ui.base.enums + +enum class NavTargetLocation { + NOWHERE, + DRAWER, + DRAWER_MORE, + DRAWER_BOTTOM, + PROFILE_LIST, + BOTTOM_SHEET, +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/behaviour/BehaviourFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/behaviour/BehaviourFragment.kt index 3df0276c..9b093d34 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/behaviour/BehaviourFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/behaviour/BehaviourFragment.kt @@ -5,28 +5,22 @@ import android.os.AsyncTask import android.os.Bundle import android.view.* import android.widget.Toast -import androidx.appcompat.widget.PopupMenu import androidx.databinding.DataBindingUtil import androidx.fragment.app.Fragment import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.App.Companion.profileId import pl.szczodrzynski.edziennik.MainActivity import pl.szczodrzynski.edziennik.R -import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.data.db.entity.Notice +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType import pl.szczodrzynski.edziennik.data.db.full.NoticeFull import pl.szczodrzynski.edziennik.databinding.FragmentBehaviourBinding -import pl.szczodrzynski.edziennik.ui.behaviour.NoticesAdapter import pl.szczodrzynski.edziennik.utils.Themes.getPrimaryTextColor import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem import java.util.* -import kotlin.coroutines.CoroutineContext class BehaviourFragment : Fragment() { @@ -55,7 +49,7 @@ class BehaviourFragment : Fragment() { .withOnClickListener { v3: View? -> activity.bottomSheet.close() AsyncTask.execute { - App.db.metadataDao().setAllSeen(profileId, Metadata.TYPE_NOTICE, true) + App.db.metadataDao().setAllSeen(profileId, MetadataType.NOTICE, true) } Toast.makeText( activity, @@ -97,7 +91,7 @@ class BehaviourFragment : Fragment() { } } }) - App.db.noticeDao().getAll(profileId).observe(this) { notices: List? -> + App.db.noticeDao().getAll(profileId).observe(viewLifecycleOwner) { notices: List? -> if (app == null || activity == null || b == null || !isAdded) return@observe if (notices == null) { b.noticesView.visibility = View.GONE diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/behaviour/NoticesAdapter.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/behaviour/NoticesAdapter.kt index a00c7a72..3e64dd3b 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/behaviour/NoticesAdapter.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/behaviour/NoticesAdapter.kt @@ -21,7 +21,6 @@ import com.mikepenz.materialize.color.Material import eu.szkolny.font.SzkolnyFont import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.R -import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_MOBIDZIENNIK import pl.szczodrzynski.edziennik.data.db.entity.Notice import pl.szczodrzynski.edziennik.data.db.full.NoticeFull import pl.szczodrzynski.edziennik.ext.resolveColor @@ -45,7 +44,7 @@ class NoticesAdapter//getting the context and product list with constructor val notice = noticeList[position] - if (app.profile.loginStoreType == LOGIN_TYPE_MOBIDZIENNIK && false) { + if (app.data.uiConfig.enableNoticePoints && false) { holder.noticesItemReason.text = bs(null, notice.category, "\n") + notice.text if (notice.teacherName != null || notice.points != null) { holder.noticesItemTeacherName.visibility = View.VISIBLE diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/captcha/RecaptchaDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/captcha/RecaptchaDialog.kt index cfecde91..56d3976a 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/captcha/RecaptchaDialog.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/captcha/RecaptchaDialog.kt @@ -25,6 +25,7 @@ class RecaptchaDialog( private val autoRetry: Boolean = true, private val onSuccess: (recaptchaCode: String) -> Unit, private val onFailure: (() -> Unit)? = null, + private val onServerError: (() -> Unit)? = null, onShowListener: ((tag: String) -> Unit)? = null, onDismissListener: ((tag: String) -> Unit)? = null, ) : BindingDialog(activity, onShowListener, onDismissListener) { @@ -44,7 +45,11 @@ class RecaptchaDialog( override suspend fun onBeforeShow(): Boolean { val (title, text, bitmap) = withContext(Dispatchers.Default) { - val html = loadCaptchaHtml() ?: return@withContext null + val html = loadCaptchaHtml() + if (html == null) { + onServerError?.invoke() + return@withContext null + } return@withContext loadCaptchaData(html) } ?: run { onFailure?.invoke() diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/captcha/LibrusCaptchaDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/captcha/RecaptchaPromptDialog.kt similarity index 84% rename from app/src/main/java/pl/szczodrzynski/edziennik/ui/captcha/LibrusCaptchaDialog.kt rename to app/src/main/java/pl/szczodrzynski/edziennik/ui/captcha/RecaptchaPromptDialog.kt index 12ad0e37..ca876ddb 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/captcha/LibrusCaptchaDialog.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/captcha/RecaptchaPromptDialog.kt @@ -13,14 +13,17 @@ import pl.szczodrzynski.edziennik.databinding.RecaptchaViewBinding import pl.szczodrzynski.edziennik.ext.onClick import pl.szczodrzynski.edziennik.ui.dialogs.base.BindingDialog -class LibrusCaptchaDialog( +class RecaptchaPromptDialog( activity: AppCompatActivity, + private val siteKey: String, + private val referer: String, private val onSuccess: (recaptchaCode: String) -> Unit, - private val onFailure: (() -> Unit)?, + private val onCancel: (() -> Unit)?, + private val onServerError: (() -> Unit)? = null, onShowListener: ((tag: String) -> Unit)? = null, onDismissListener: ((tag: String) -> Unit)? = null, ) : BindingDialog(activity, onShowListener, onDismissListener) { - override val TAG = "LibrusCaptchaDialog" + override val TAG = "RecaptchaPromptDialog" override fun getTitleRes(): Int? = null override fun inflate(layoutInflater: LayoutInflater) = @@ -46,8 +49,8 @@ class LibrusCaptchaDialog( b.progress.visibility = View.VISIBLE RecaptchaDialog( activity, - siteKey = "6Lf48moUAAAAAB9ClhdvHr46gRWR-CN31CXQPG2U", - referer = "https://portal.librus.pl/rodzina/login", + siteKey = siteKey, + referer = referer, onSuccess = { recaptchaCode -> b.checkbox.background = checkboxBackground b.checkbox.foreground = checkboxForeground @@ -60,13 +63,14 @@ class LibrusCaptchaDialog( b.checkbox.background = checkboxBackground b.checkbox.foreground = checkboxForeground b.progress.visibility = View.GONE - } + }, + onServerError = onServerError, ).show() } } override fun onDismiss() { if (!success) - onFailure?.invoke() + onCancel?.invoke() } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/debug/LabPageFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/debug/LabPageFragment.kt index aecbecb1..2e40eefc 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/debug/LabPageFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/debug/LabPageFragment.kt @@ -11,6 +11,7 @@ import android.view.View import android.view.ViewGroup import androidx.appcompat.app.AppCompatActivity import androidx.core.view.isVisible +import androidx.core.widget.doAfterTextChanged import androidx.sqlite.db.SimpleSQLiteQuery import com.chuckerteam.chucker.api.Chucker import com.chuckerteam.chucker.api.Chucker.SCREEN_HTTP @@ -21,6 +22,8 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.launch import pl.szczodrzynski.edziennik.* import pl.szczodrzynski.edziennik.config.Config +import pl.szczodrzynski.edziennik.data.api.szkolny.interceptor.SignatureInterceptor +import pl.szczodrzynski.edziennik.data.db.entity.EventType.Companion.SOURCE_DEFAULT import pl.szczodrzynski.edziennik.databinding.LabFragmentBinding import pl.szczodrzynski.edziennik.ext.* import pl.szczodrzynski.edziennik.ui.base.lazypager.LazyFragment @@ -60,8 +63,10 @@ class LabPageFragment : LazyFragment(), CoroutineScope { b.last10unseen.isVisible = false b.fullSync.isVisible = false b.clearProfile.isVisible = false + b.clearEndpointTimers.isVisible = false b.rodo.isVisible = false b.removeHomework.isVisible = false + b.resetEventTypes.isVisible = false b.unarchive.isVisible = false b.profile.isVisible = false } @@ -80,19 +85,28 @@ class LabPageFragment : LazyFragment(), CoroutineScope { app.db.teacherDao().query(SimpleSQLiteQuery("UPDATE teachers SET teacherSurname = \"\" WHERE profileId = ${App.profileId}")) } - b.fullSync.onClick { - app.db.query(SimpleSQLiteQuery("UPDATE profiles SET empty = 1 WHERE profileId = ${App.profileId}")) - app.db.query(SimpleSQLiteQuery("DELETE FROM endpointTimers WHERE profileId = ${App.profileId}")) + b.fullSync.onClick { + app.profile.empty = true + app.profileSave() } b.clearProfile.onClick { ProfileRemoveDialog(activity, App.profileId, "FAKE", noProfileRemoval = true).show() } + b.clearEndpointTimers.onClick { + app.db.endpointTimerDao().clear(app.profileId) + } + b.removeHomework.onClick { app.db.eventDao().getRawNow("UPDATE events SET homeworkBody = NULL WHERE profileId = ${App.profileId}") } + b.resetEventTypes.onClick { + app.db.eventTypeDao().clearBySource(App.profileId, SOURCE_DEFAULT) + app.db.eventTypeDao().getAllWithDefaults(App.profile) + } + b.chucker.isChecked = App.enableChucker b.chucker.onChange { _, isChecked -> app.config.enableChucker = isChecked @@ -141,6 +155,16 @@ class LabPageFragment : LazyFragment(), CoroutineScope { app.config.apiInvalidCert = null } + b.apiKey.setText(app.config.apiKeyCustom ?: SignatureInterceptor.API_KEY) + b.apiKey.doAfterTextChanged { + it?.toString()?.let { key -> + if (key == SignatureInterceptor.API_KEY) + app.config.apiKeyCustom = null + else + app.config.apiKeyCustom = key.takeValue()?.trim() + } + } + b.rebuildConfig.onClick { App.config = Config(App.db) } @@ -151,32 +175,43 @@ class LabPageFragment : LazyFragment(), CoroutineScope { b.profile.select(app.profileId.toLong()) b.profile.setOnChangeListener { if (activity is MainActivity) - (activity as MainActivity).loadProfile(it.id.toInt()) + (activity as MainActivity).navigate(profileId = it.id.toInt()) return@setOnChangeListener true } + b.clearCookies.onClick { + app.cookieJar.clearAllDomains() + } + val colorSecondary = android.R.attr.textColorSecondary.resolveAttr(activity) startCoroutineTimer(500L, 300L) { - val text = app.cookieJar.sessionCookies - .map { it.cookie } - .sortedBy { it.domain() } - .groupBy { it.domain() } - .map { - listOf( - it.key.asBoldSpannable(), - ":\n", - it.value - .sortedBy { it.name() } - .map { - listOf( - " ", - it.name(), - "=", - it.value().decode().take(40).asItalicSpannable().asColoredSpannable(colorSecondary) - ).concat("") - }.concat("\n") - ).concat("") - }.concat("\n\n") + val text = app.cookieJar.getAllDomains() + .sortedBy { it.domain() } + .groupBy { it.domain() } + .map { pair -> + listOf( + pair.key.asBoldSpannable(), + ":\n", + pair.value + .sortedBy { it.name() } + .map { cookie -> + listOf( + " ", + if (cookie.persistent()) + cookie.name() + .asUnderlineSpannable() + else + cookie.name(), + "=", + cookie.value() + .decode() + .take(40) + .asItalicSpannable() + .asColoredSpannable(colorSecondary), + ).concat("") + }.concat("\n") + ).concat("") + }.concat("\n\n") b.cookies.text = text } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/debug/LabProfileFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/debug/LabProfileFragment.kt index 5b289ea4..f4267a25 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/debug/LabProfileFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/debug/LabProfileFragment.kt @@ -12,16 +12,19 @@ import androidx.appcompat.app.AppCompatActivity import androidx.core.view.isVisible import androidx.recyclerview.widget.LinearLayoutManager import com.google.android.material.dialog.MaterialAlertDialogBuilder -import com.google.gson.* +import com.google.gson.JsonArray +import com.google.gson.JsonObject +import com.google.gson.JsonParser +import com.google.gson.JsonPrimitive import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job -import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.MainActivity +import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.data.api.models.ApiError import pl.szczodrzynski.edziennik.databinding.TemplateListPageFragmentBinding -import pl.szczodrzynski.edziennik.ext.input -import pl.szczodrzynski.edziennik.ext.set -import pl.szczodrzynski.edziennik.ext.startCoroutineTimer +import pl.szczodrzynski.edziennik.ext.* import pl.szczodrzynski.edziennik.ui.base.lazypager.LazyFragment import pl.szczodrzynski.edziennik.ui.login.LoginActivity import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration @@ -59,14 +62,16 @@ class LabProfileFragment : LazyFragment(), CoroutineScope { try { var parent: Any = Unit var obj: Any = Unit - var objName: String = "" + var objName = "" item.key.split(":").forEach { el -> parent = obj obj = when (el) { - "App.profile" -> app.profile - "App.profile.studentData" -> app.profile.studentData - "App.profile.loginStore" -> loginStore?.data ?: JsonObject() - "App.config" -> app.config.values + "Profile" -> app.profile + "Profile / studentData" -> app.profile.studentData + "LoginStore" -> loginStore + "LoginStore / data" -> loginStore?.data ?: JsonObject() + "Config" -> app.config.values + "Config (profile)" -> app.profile.config.values else -> when (obj) { is JsonObject -> (obj as JsonObject).get(el) is JsonArray -> (obj as JsonArray).get(el.toInt()) @@ -89,6 +94,7 @@ class LabProfileFragment : LazyFragment(), CoroutineScope { objVal.isBoolean -> objVal.asBoolean.toString() else -> objVal.asString } + is Enum<*> -> objVal.toInt().toString() else -> objVal.toString() } @@ -123,6 +129,7 @@ class LabProfileFragment : LazyFragment(), CoroutineScope { is String -> input is Long -> input.toLong() is Double -> input.toDouble() + is Enum<*> -> input.toInt().toEnum(objVal::class.java) else -> input } field.set(parent, newVal) @@ -130,9 +137,8 @@ class LabProfileFragment : LazyFragment(), CoroutineScope { } when (item.key.substringBefore(":")) { - "App.profile" -> app.profileSave() - "App.profile.studentData" -> app.profileSave() - "App.profile.loginStore" -> app.db.loginStoreDao().add(loginStore) + "Profile", "Profile / studentData" -> app.profileSave() + "LoginStore", "LoginStore / data" -> app.db.loginStoreDao().add(loginStore) } showJson() @@ -170,10 +176,12 @@ class LabProfileFragment : LazyFragment(), CoroutineScope { private fun showJson() { val json = JsonObject().also { json -> - json.add("App.profile", app.gson.toJsonTree(app.profile)) - json.add("App.profile.studentData", app.profile.studentData) - json.add("App.profile.loginStore", loginStore?.data ?: JsonObject()) - json.add("App.config", JsonParser.parseString(app.gson.toJson(app.config.values.toSortedMap()))) + json.add("Profile", app.gson.toJsonTree(app.profile)) + json.add("Profile / studentData", app.profile.studentData) + json.add("LoginStore", app.gson.toJsonTree(loginStore)) + json.add("LoginStore / data", loginStore?.data ?: JsonObject()) + json.add("Config", JsonParser.parseString(app.gson.toJson(app.config.values.toSortedMap()))) + json.add("Config (profile)", JsonParser.parseString(app.gson.toJson(app.profile.config.values.toSortedMap()))) } adapter.items = LabJsonAdapter.expand(json, 0) adapter.notifyDataSetChanged() diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/ProfileRemoveDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/ProfileRemoveDialog.kt index 99d907e0..6deb0b06 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/ProfileRemoveDialog.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/ProfileRemoveDialog.kt @@ -20,7 +20,7 @@ class ProfileRemoveDialog( val onRemove: (() -> Unit)? = null, onShowListener: ((tag: String) -> Unit)? = null, onDismissListener: ((tag: String) -> Unit)? = null, -) : BaseDialog(activity, onShowListener, onDismissListener) { +) : BaseDialog(activity, onShowListener, onDismissListener) { override val TAG = "ProfileRemoveDialog" diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/base/BaseDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/base/BaseDialog.kt index ba91a3f5..bfdd1687 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/base/BaseDialog.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/base/BaseDialog.kt @@ -18,7 +18,7 @@ import pl.szczodrzynski.edziennik.ext.onClick import pl.szczodrzynski.edziennik.ext.setMessage import kotlin.coroutines.CoroutineContext -abstract class BaseDialog( +abstract class BaseDialog( protected val activity: AppCompatActivity, protected val onShowListener: ((tag: String) -> Unit)? = null, protected val onDismissListener: ((tag: String) -> Unit)? = null, @@ -38,8 +38,8 @@ abstract class BaseDialog( override val coroutineContext: CoroutineContext get() = job + Dispatchers.Main - private var items = emptyList() - private var itemSelected: Any? = null + private var items = emptyList() + private var itemSelected: I? = null private var itemStates = BooleanArray(0) protected open fun getTitle(): CharSequence? = null @@ -53,16 +53,16 @@ abstract class BaseDialog( open fun getNeutralButtonText(): Int? = null open fun getNegativeButtonText(): Int? = null - protected open fun getSingleChoiceItems(): Map? = null - protected open fun getMultiChoiceItems(): Map? = null - protected open fun getDefaultSelectedItem(): Any? = null - protected open fun getDefaultSelectedItems(): Set = emptySet() + protected open fun getSingleChoiceItems(): Map? = null + protected open fun getMultiChoiceItems(): Map? = null + protected open fun getDefaultSelectedItem(): I? = null + protected open fun getDefaultSelectedItems(): Set = emptySet() open suspend fun onPositiveClick() = true open suspend fun onNeutralClick() = true open suspend fun onNegativeClick() = true - open suspend fun onSingleSelectionChanged(item: Any?) = Unit - open suspend fun onMultiSelectionChanged(items: Set) = Unit + open suspend fun onSingleSelectionChanged(item: I?) = Unit + open suspend fun onMultiSelectionChanged(items: Set) = Unit protected open suspend fun onBeforeShow() = true protected abstract suspend fun onShow() @@ -184,7 +184,7 @@ abstract class BaseDialog( } protected fun getSingleSelection() = itemSelected - protected fun getMultiSelection(): Set { + protected fun getMultiSelection(): Set { return itemStates.mapIndexed { position, isChecked -> if (isChecked) items[position] diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/base/ViewDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/base/ViewDialog.kt index 17893a74..20d81b95 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/base/ViewDialog.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/base/ViewDialog.kt @@ -11,7 +11,7 @@ abstract class ViewDialog( activity: AppCompatActivity, onShowListener: ((tag: String) -> Unit)? = null, onDismissListener: ((tag: String) -> Unit)? = null -) : BaseDialog(activity, onShowListener, onDismissListener) { +) : BaseDialog(activity, onShowListener, onDismissListener) { protected lateinit var root: V protected abstract fun getRootView(): V diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/settings/AgendaConfigDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/settings/AgendaConfigDialog.kt index 05012c32..5cc4ff3f 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/settings/AgendaConfigDialog.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/settings/AgendaConfigDialog.kt @@ -6,13 +6,11 @@ package pl.szczodrzynski.edziennik.ui.dialogs.settings import android.view.LayoutInflater import androidx.appcompat.app.AppCompatActivity -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.data.db.entity.Profile import pl.szczodrzynski.edziennik.databinding.DialogConfigAgendaBinding import pl.szczodrzynski.edziennik.ext.onChange import pl.szczodrzynski.edziennik.ui.dialogs.base.ConfigDialog -import java.util.* class AgendaConfigDialog( activity: AppCompatActivity, @@ -32,48 +30,35 @@ class AgendaConfigDialog( override fun inflate(layoutInflater: LayoutInflater) = DialogConfigAgendaBinding.inflate(layoutInflater) - private val profileConfig by lazy { app.config.forProfile().ui } - override suspend fun loadConfig() { - b.config = profileConfig - b.isAgendaMode = profileConfig.agendaViewType == Profile.AGENDA_DEFAULT + b.config = app.profile.config + b.isAgendaMode = app.profile.config.ui.agendaViewType == Profile.AGENDA_DEFAULT - b.eventSharingEnabled.isChecked = - app.profile.enableSharedEvents && app.profile.canShare + var calledFromListener = false + b.eventSharingEnabled.isChecked = app.profile.canShare + b.shareByDefault.isEnabled = app.profile.canShare b.eventSharingEnabled.onChange { _, isChecked -> - if (isChecked && !app.profile.canShare) { - b.eventSharingEnabled.isChecked = false - val dialog = RegistrationConfigDialog( - activity, - app.profile, - onChangeListener = { enabled -> - b.eventSharingEnabled.isChecked = enabled - setEventSharingEnabled(enabled) - }, - onShowListener, - onDismissListener, - ) - dialog.showEnableDialog() + if (calledFromListener) { + calledFromListener = false return@onChange } - setEventSharingEnabled(isChecked) + b.eventSharingEnabled.isChecked = !isChecked + val dialog = RegistrationConfigDialog( + activity, + app.profile, + onChangeListener = { enabled -> + calledFromListener = true + b.eventSharingEnabled.isChecked = enabled + b.shareByDefault.isEnabled = enabled + }, + onShowListener, + onDismissListener, + ) + if (isChecked) + dialog.showEnableDialog() + else + dialog.showDisableDialog() + return@onChange } } - - private fun setEventSharingEnabled(enabled: Boolean) { - if (enabled == app.profile.enableSharedEvents) - return - app.profile.enableSharedEvents = enabled - app.profileSave() - MaterialAlertDialogBuilder(activity) - .setTitle(R.string.event_sharing) - .setMessage( - if (enabled) - R.string.settings_register_shared_events_dialog_enabled_text - else - R.string.settings_register_shared_events_dialog_disabled_text - ) - .setPositiveButton(R.string.ok, null) - .show() - } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/settings/AppLanguageDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/settings/AppLanguageDialog.kt index d9ec1631..751c91de 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/settings/AppLanguageDialog.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/settings/AppLanguageDialog.kt @@ -12,7 +12,7 @@ class AppLanguageDialog( activity: AppCompatActivity, onShowListener: ((tag: String) -> Unit)? = null, onDismissListener: ((tag: String) -> Unit)? = null, -) : BaseDialog(activity, onShowListener, onDismissListener) { +) : BaseDialog(activity, onShowListener, onDismissListener) { override val TAG = "AppLanguageDialog" override fun getTitleRes() = R.string.app_language_dialog_title diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/settings/AttendanceConfigDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/settings/AttendanceConfigDialog.kt index d29c806f..b4734141 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/settings/AttendanceConfigDialog.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/settings/AttendanceConfigDialog.kt @@ -29,23 +29,21 @@ class AttendanceConfigDialog( override fun inflate(layoutInflater: LayoutInflater) = AttendanceConfigDialogBinding.inflate(layoutInflater) - private val profileConfig by lazy { app.config.getFor(app.profileId).attendance } - override suspend fun loadConfig() { - b.useSymbols.isChecked = profileConfig.useSymbols - b.groupConsecutiveDays.isChecked = profileConfig.groupConsecutiveDays - b.showPresenceInMonth.isChecked = profileConfig.showPresenceInMonth + b.useSymbols.isChecked = app.profile.config.attendance.useSymbols + b.groupConsecutiveDays.isChecked = app.profile.config.attendance.groupConsecutiveDays + b.showPresenceInMonth.isChecked = app.profile.config.attendance.showPresenceInMonth } override fun initView() { b.useSymbols.onChange { _, isChecked -> - profileConfig.useSymbols = isChecked + app.profile.config.attendance.useSymbols = isChecked } b.groupConsecutiveDays.onChange { _, isChecked -> - profileConfig.groupConsecutiveDays = isChecked + app.profile.config.attendance.groupConsecutiveDays = isChecked } b.showPresenceInMonth.onChange { _, isChecked -> - profileConfig.showPresenceInMonth = isChecked + app.profile.config.attendance.showPresenceInMonth = isChecked } } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/settings/GradesConfigDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/settings/GradesConfigDialog.kt index 6f5072e5..b1c0c86f 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/settings/GradesConfigDialog.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/settings/GradesConfigDialog.kt @@ -45,17 +45,15 @@ class GradesConfigDialog( override fun inflate(layoutInflater: LayoutInflater) = DialogConfigGradesBinding.inflate(layoutInflater) - private val profileConfig by lazy { app.config.getFor(app.profileId).grades } - @SuppressLint("SetTextI18n") override suspend fun loadConfig() { - b.customPlusCheckBox.isChecked = profileConfig.plusValue != null + b.customPlusCheckBox.isChecked = app.profile.config.grades.plusValue != null b.customPlusValue.isVisible = b.customPlusCheckBox.isChecked - b.customMinusCheckBox.isChecked = profileConfig.minusValue != null + b.customMinusCheckBox.isChecked = app.profile.config.grades.minusValue != null b.customMinusValue.isVisible = b.customMinusCheckBox.isChecked - b.customPlusValue.progress = profileConfig.plusValue ?: 0.5f - b.customMinusValue.progress = profileConfig.minusValue ?: 0.25f + b.customPlusValue.progress = app.profile.config.grades.plusValue ?: 0.5f + b.customMinusValue.progress = app.profile.config.grades.minusValue ?: 0.25f when (config.orderBy) { ORDER_BY_DATE_DESC -> b.sortGradesByDateRadio @@ -63,13 +61,13 @@ class GradesConfigDialog( else -> null }?.isChecked = true - when (profileConfig.colorMode) { + when (app.profile.config.grades.colorMode) { COLOR_MODE_DEFAULT -> b.gradeColorFromERegister COLOR_MODE_WEIGHTED -> b.gradeColorByValue else -> null }?.isChecked = true - when (profileConfig.yearAverageMode) { + when (app.profile.config.grades.yearAverageMode) { YEAR_ALL_GRADES -> b.gradeAverageMode4 YEAR_1_AVG_2_AVG -> b.gradeAverageMode0 YEAR_1_SEM_2_AVG -> b.gradeAverageMode1 @@ -79,21 +77,21 @@ class GradesConfigDialog( }?.isChecked = true b.dontCountGrades.isChecked = - profileConfig.dontCountEnabled && profileConfig.dontCountGrades.isNotEmpty() - b.hideImproved.isChecked = profileConfig.hideImproved - b.averageWithoutWeight.isChecked = profileConfig.averageWithoutWeight + app.profile.config.grades.dontCountEnabled && app.profile.config.grades.dontCountGrades.isNotEmpty() + b.hideImproved.isChecked = app.profile.config.grades.hideImproved + b.averageWithoutWeight.isChecked = app.profile.config.grades.averageWithoutWeight - if (profileConfig.dontCountGrades.isEmpty()) { + if (app.profile.config.grades.dontCountGrades.isEmpty()) { b.dontCountGradesText.setText("nb, 0, bz, bd") } else { - b.dontCountGradesText.setText(profileConfig.dontCountGrades.join(", ")) + b.dontCountGradesText.setText(app.profile.config.grades.dontCountGrades.join(", ")) } } override suspend fun saveConfig() { - profileConfig.plusValue = + app.profile.config.grades.plusValue = if (b.customPlusCheckBox.isChecked) b.customPlusValue.progress else null - profileConfig.minusValue = + app.profile.config.grades.minusValue = if (b.customMinusCheckBox.isChecked) b.customMinusValue.progress else null b.dontCountGradesText.setText( @@ -103,8 +101,8 @@ class GradesConfigDialog( ?.lowercase() ?.replace(", ", ",") ) - profileConfig.dontCountEnabled = b.dontCountGrades.isChecked - profileConfig.dontCountGrades = b.dontCountGradesText.text + app.profile.config.grades.dontCountEnabled = b.dontCountGrades.isChecked + app.profile.config.grades.dontCountGrades = b.dontCountGradesText.text ?.split(",") ?.map { it.trim() } ?: listOf() @@ -121,39 +119,39 @@ class GradesConfigDialog( // who the hell named those methods // THIS SHIT DOES NOT EVEN WORK b.customPlusValue.doOnStopTrackingTouch { - profileConfig.plusValue = it.progress + app.profile.config.grades.plusValue = it.progress } b.customMinusValue.doOnStopTrackingTouch { - profileConfig.minusValue = it.progress + app.profile.config.grades.minusValue = it.progress } b.sortGradesByDateRadio.setOnSelectedListener { config.orderBy = ORDER_BY_DATE_DESC } b.sortGradesBySubjectRadio.setOnSelectedListener { config.orderBy = ORDER_BY_SUBJECT_ASC } b.gradeColorFromERegister.setOnSelectedListener { - profileConfig.colorMode = COLOR_MODE_DEFAULT + app.profile.config.grades.colorMode = COLOR_MODE_DEFAULT } - b.gradeColorByValue.setOnSelectedListener { profileConfig.colorMode = COLOR_MODE_WEIGHTED } + b.gradeColorByValue.setOnSelectedListener { app.profile.config.grades.colorMode = COLOR_MODE_WEIGHTED } b.gradeAverageMode4.setOnSelectedListener { - profileConfig.yearAverageMode = YEAR_ALL_GRADES + app.profile.config.grades.yearAverageMode = YEAR_ALL_GRADES } b.gradeAverageMode0.setOnSelectedListener { - profileConfig.yearAverageMode = YEAR_1_AVG_2_AVG + app.profile.config.grades.yearAverageMode = YEAR_1_AVG_2_AVG } b.gradeAverageMode1.setOnSelectedListener { - profileConfig.yearAverageMode = YEAR_1_SEM_2_AVG + app.profile.config.grades.yearAverageMode = YEAR_1_SEM_2_AVG } b.gradeAverageMode2.setOnSelectedListener { - profileConfig.yearAverageMode = YEAR_1_AVG_2_SEM + app.profile.config.grades.yearAverageMode = YEAR_1_AVG_2_SEM } b.gradeAverageMode3.setOnSelectedListener { - profileConfig.yearAverageMode = YEAR_1_SEM_2_SEM + app.profile.config.grades.yearAverageMode = YEAR_1_SEM_2_SEM } - b.hideImproved.onChange { _, isChecked -> profileConfig.hideImproved = isChecked } + b.hideImproved.onChange { _, isChecked -> app.profile.config.grades.hideImproved = isChecked } b.averageWithoutWeight.onChange { _, isChecked -> - profileConfig.averageWithoutWeight = isChecked + app.profile.config.grades.averageWithoutWeight = isChecked } b.averageWithoutWeightHelp.onClick { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/settings/MessagesConfigDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/settings/MessagesConfigDialog.kt index 35770ba0..0e5696db 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/settings/MessagesConfigDialog.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/settings/MessagesConfigDialog.kt @@ -28,13 +28,11 @@ class MessagesConfigDialog( override fun inflate(layoutInflater: LayoutInflater) = MessagesConfigDialogBinding.inflate(layoutInflater) - private val profileConfig by lazy { app.config.getFor(app.profileId).ui } - override suspend fun loadConfig() { - b.config = profileConfig + b.config = app.profile.config.ui b.greetingText.setText( - profileConfig.messagesGreetingText + app.profile.config.ui.messagesGreetingText ?: "\n\nZ poważaniem\n${app.profile.accountOwnerName}" ) } @@ -42,8 +40,8 @@ class MessagesConfigDialog( override suspend fun saveConfig() { val greetingText = b.greetingText.text?.toString()?.trim() if (greetingText.isNullOrEmpty()) - profileConfig.messagesGreetingText = null + app.profile.config.ui.messagesGreetingText = null else - profileConfig.messagesGreetingText = "\n\n$greetingText" + app.profile.config.ui.messagesGreetingText = "\n\n$greetingText" } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/settings/MiniMenuConfigDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/settings/MiniMenuConfigDialog.kt index 956a6b03..5a87f1ce 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/settings/MiniMenuConfigDialog.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/settings/MiniMenuConfigDialog.kt @@ -5,26 +5,19 @@ package pl.szczodrzynski.edziennik.ui.dialogs.settings import androidx.appcompat.app.AppCompatActivity +import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.MainActivity -import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_AGENDA -import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_ANNOUNCEMENTS -import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_ATTENDANCE -import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_BEHAVIOUR -import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_GRADES -import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_HOME -import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_HOMEWORK -import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_MESSAGES -import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_NOTIFICATIONS -import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_SETTINGS -import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_TIMETABLE import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.ext.resolveString +import pl.szczodrzynski.edziennik.ui.base.enums.NavTarget +import pl.szczodrzynski.edziennik.ui.base.enums.NavTargetLocation import pl.szczodrzynski.edziennik.ui.dialogs.base.BaseDialog class MiniMenuConfigDialog( activity: AppCompatActivity, onShowListener: ((tag: String) -> Unit)? = null, onDismissListener: ((tag: String) -> Unit)? = null, -) : BaseDialog(activity, onShowListener, onDismissListener) { +) : BaseDialog(activity, onShowListener, onDismissListener) { override val TAG = "BellSyncTimeChooseDialog" @@ -33,26 +26,23 @@ class MiniMenuConfigDialog( override fun getPositiveButtonText() = R.string.ok override fun getNegativeButtonText() = R.string.cancel - override fun getMultiChoiceItems(): Map = mapOf( - R.string.menu_home_page to DRAWER_ITEM_HOME, - R.string.menu_timetable to DRAWER_ITEM_TIMETABLE, - R.string.menu_agenda to DRAWER_ITEM_AGENDA, - R.string.menu_grades to DRAWER_ITEM_GRADES, - R.string.menu_messages to DRAWER_ITEM_MESSAGES, - R.string.menu_homework to DRAWER_ITEM_HOMEWORK, - R.string.menu_notices to DRAWER_ITEM_BEHAVIOUR, - R.string.menu_attendance to DRAWER_ITEM_ATTENDANCE, - R.string.menu_announcements to DRAWER_ITEM_ANNOUNCEMENTS, - R.string.menu_notifications to DRAWER_ITEM_NOTIFICATIONS, - R.string.menu_settings to DRAWER_ITEM_SETTINGS, - ).mapKeys { (resId, _) -> activity.getString(resId) } + @Suppress("USELESS_CAST") + override fun getMultiChoiceItems() = NavTarget.values() + .filter { + (!it.devModeOnly || App.devMode) && it.location in listOf( + NavTargetLocation.DRAWER, + // NavTargetLocation.DRAWER_MORE, + NavTargetLocation.DRAWER_BOTTOM, + ) + } + .associateBy { it.nameRes.resolveString(activity) as CharSequence } - override fun getDefaultSelectedItems() = app.config.ui.miniMenuButtons.toSet() + override fun getDefaultSelectedItems() = app.config.ui.miniMenuButtons override suspend fun onShow() = Unit override suspend fun onPositiveClick(): Boolean { - app.config.ui.miniMenuButtons = getMultiSelection().filterIsInstance() + app.config.ui.miniMenuButtons = getMultiSelection() if (activity is MainActivity) { activity.setDrawerItems() activity.drawer.updateBadges() diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/settings/NotificationFilterDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/settings/NotificationFilterDialog.kt index 8487f8be..5c157eb7 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/settings/NotificationFilterDialog.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/settings/NotificationFilterDialog.kt @@ -7,14 +7,15 @@ package pl.szczodrzynski.edziennik.ui.dialogs.settings import androidx.appcompat.app.AppCompatActivity import com.google.android.material.dialog.MaterialAlertDialogBuilder import pl.szczodrzynski.edziennik.R -import pl.szczodrzynski.edziennik.data.db.entity.Notification +import pl.szczodrzynski.edziennik.data.db.enums.NotificationType +import pl.szczodrzynski.edziennik.ext.resolveString import pl.szczodrzynski.edziennik.ui.dialogs.base.BaseDialog class NotificationFilterDialog( activity: AppCompatActivity, onShowListener: ((tag: String) -> Unit)? = null, onDismissListener: ((tag: String) -> Unit)? = null, -) : BaseDialog(activity, onShowListener, onDismissListener) { +) : BaseDialog(activity, onShowListener, onDismissListener) { override val TAG = "NotificationFilterDialog" @@ -23,43 +24,30 @@ class NotificationFilterDialog( override fun getPositiveButtonText() = R.string.ok override fun getNegativeButtonText() = R.string.cancel - override fun getMultiChoiceItems(): Map { - notificationTypes = mapOf( - R.string.notification_type_timetable_lesson_change to Notification.TYPE_TIMETABLE_LESSON_CHANGE, - R.string.notification_type_new_grade to Notification.TYPE_NEW_GRADE, - R.string.notification_type_new_event to Notification.TYPE_NEW_EVENT, - R.string.notification_type_new_homework to Notification.TYPE_NEW_HOMEWORK, - R.string.notification_type_new_message to Notification.TYPE_NEW_MESSAGE, - R.string.notification_type_lucky_number to Notification.TYPE_LUCKY_NUMBER, - R.string.notification_type_notice to Notification.TYPE_NEW_NOTICE, - R.string.notification_type_attendance to Notification.TYPE_NEW_ATTENDANCE, - R.string.notification_type_new_announcement to Notification.TYPE_NEW_ANNOUNCEMENT, - R.string.notification_type_new_shared_event to Notification.TYPE_NEW_SHARED_EVENT, - R.string.notification_type_new_shared_homework to Notification.TYPE_NEW_SHARED_HOMEWORK, - R.string.notification_type_removed_shared_event to Notification.TYPE_REMOVED_SHARED_EVENT, - R.string.notification_type_new_teacher_absence to Notification.TYPE_TEACHER_ABSENCE, - ).mapKeys { (resId, _) -> activity.getString(resId) } - return notificationTypes - } + @Suppress("USELESS_CAST") + override fun getMultiChoiceItems() = NotificationType.values() + .filter { it.enabledByDefault != null } + .associateBy { it.titleRes.resolveString(activity) as CharSequence } - override fun getDefaultSelectedItems() = - notificationTypes.values.subtract(app.config.forProfile().sync.notificationFilter) + override fun getDefaultSelectedItems() = NotificationType.values() + .filter { it.enabledByDefault != null && it !in app.profile.config.sync.notificationFilter } + .toSet() override suspend fun onShow() = Unit - private lateinit var notificationTypes: Map - override suspend fun onPositiveClick(): Boolean { - val enabledTypes = getMultiSelection().filterIsInstance() - val disabledTypes = notificationTypes.values.subtract(enabledTypes).toList() + val enabledTypes = getMultiSelection() + val disabledTypes = NotificationType.values() + .filter { it.enabledByDefault != null && it !in enabledTypes } + .toSet() - if (disabledTypes.isNotEmpty()) { + if (disabledTypes.any { it.enabledByDefault == true }) { // warn user when he tries to disable some notifications MaterialAlertDialogBuilder(activity) .setTitle(R.string.are_you_sure) .setMessage(R.string.notification_filter_warning) .setPositiveButton(R.string.ok) { _, _ -> - app.config.forProfile().sync.notificationFilter = disabledTypes + app.profile.config.sync.notificationFilter = disabledTypes dismiss() } .setNegativeButton(R.string.cancel, null) @@ -67,7 +55,7 @@ class NotificationFilterDialog( return NO_DISMISS } - app.config.forProfile().sync.notificationFilter = disabledTypes + app.profile.config.sync.notificationFilter = disabledTypes return DISMISS } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/settings/RegistrationConfigDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/settings/RegistrationConfigDialog.kt index 51b2a0e1..dbe25d98 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/settings/RegistrationConfigDialog.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/settings/RegistrationConfigDialog.kt @@ -7,7 +7,11 @@ package pl.szczodrzynski.edziennik.ui.dialogs.settings import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import com.google.android.material.dialog.MaterialAlertDialogBuilder -import kotlinx.coroutines.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi @@ -117,7 +121,7 @@ class RegistrationConfigDialog( profile.registration = Profile.REGISTRATION_ENABLED // force full registration of the user - App.config.getFor(profile.id).hash = "" + profile.config.hash = "" SzkolnyApi(app).runCatching(activity) { AppSync(app, mutableListOf(), listOf(profile), this).run( diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/settings/SyncIntervalDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/settings/SyncIntervalDialog.kt index 433538a5..b64ce774 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/settings/SyncIntervalDialog.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/settings/SyncIntervalDialog.kt @@ -16,7 +16,7 @@ class SyncIntervalDialog( private val onChangeListener: (() -> Unit)? = null, onShowListener: ((tag: String) -> Unit)? = null, onDismissListener: ((tag: String) -> Unit)? = null, -) : BaseDialog(activity, onShowListener, onDismissListener) { +) : BaseDialog(activity, onShowListener, onDismissListener) { override val TAG = "SyncIntervalDialog" diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/settings/ThemeChooserDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/settings/ThemeChooserDialog.kt index 3a52dae5..34d86324 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/settings/ThemeChooserDialog.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/settings/ThemeChooserDialog.kt @@ -13,7 +13,7 @@ class ThemeChooserDialog( activity: AppCompatActivity, onShowListener: ((tag: String) -> Unit)? = null, onDismissListener: ((tag: String) -> Unit)? = null, -) : BaseDialog(activity, onShowListener, onDismissListener) { +) : BaseDialog(activity, onShowListener, onDismissListener) { override val TAG = "ThemeChooserDialog" diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/settings/TimetableConfigDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/settings/TimetableConfigDialog.kt new file mode 100644 index 00000000..09893c6a --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/settings/TimetableConfigDialog.kt @@ -0,0 +1,44 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2022-10-7. + */ + +package pl.szczodrzynski.edziennik.ui.dialogs.settings + +import android.view.LayoutInflater +import androidx.appcompat.app.AppCompatActivity +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.databinding.TimetableConfigDialogBinding +import pl.szczodrzynski.edziennik.ext.Intent +import pl.szczodrzynski.edziennik.ui.dialogs.base.ConfigDialog +import pl.szczodrzynski.edziennik.ui.timetable.TimetableFragment + +class TimetableConfigDialog( + activity: AppCompatActivity, + reloadOnDismiss: Boolean = true, + onShowListener: ((tag: String) -> Unit)? = null, + onDismissListener: ((tag: String) -> Unit)? = null, +) : ConfigDialog( + activity, + reloadOnDismiss, + onShowListener, + onDismissListener, +) { + + override val TAG = "TimetableConfigDialog" + + override fun getTitleRes() = R.string.menu_timetable_config + override fun inflate(layoutInflater: LayoutInflater) = + TimetableConfigDialogBinding.inflate(layoutInflater) + + override fun initView() { + b.features = app.profile.loginStoreType.features + } + + override suspend fun loadConfig() { + b.config = app.profile.config.ui + } + + override suspend fun saveConfig() { + activity.sendBroadcast(Intent(TimetableFragment.ACTION_RELOAD_PAGES)) + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/sync/ServerMessageDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/sync/ServerMessageDialog.kt index 7ea7452e..b0f4f3fa 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/sync/ServerMessageDialog.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/sync/ServerMessageDialog.kt @@ -14,7 +14,7 @@ class ServerMessageDialog( private val messageText: CharSequence, onShowListener: ((tag: String) -> Unit)? = null, onDismissListener: ((tag: String) -> Unit)? = null, -) : BaseDialog(activity, onShowListener, onDismissListener) { +) : BaseDialog(activity, onShowListener, onDismissListener) { override val TAG = "ServerMessageDialog" diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/sync/SyncViewListDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/sync/SyncViewListDialog.kt index adc1a91a..50f918d7 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/sync/SyncViewListDialog.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/sync/SyncViewListDialog.kt @@ -6,26 +6,22 @@ package pl.szczodrzynski.edziennik.ui.dialogs.sync import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.MainActivity -import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_AGENDA -import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_ANNOUNCEMENTS -import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_ATTENDANCE -import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_BEHAVIOUR -import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_GRADES -import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_HOME -import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_HOMEWORK -import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_MESSAGES -import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_TIMETABLE import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask +import pl.szczodrzynski.edziennik.data.db.entity.Message +import pl.szczodrzynski.edziennik.data.db.enums.FeatureType +import pl.szczodrzynski.edziennik.ext.hasFeature +import pl.szczodrzynski.edziennik.ext.resolveString +import pl.szczodrzynski.edziennik.ui.base.enums.NavTarget import pl.szczodrzynski.edziennik.ui.dialogs.base.BaseDialog import pl.szczodrzynski.edziennik.ui.messages.list.MessagesFragment class SyncViewListDialog( activity: MainActivity, - private val currentViewId: Int, + private val currentNavTarget: NavTarget, onShowListener: ((tag: String) -> Unit)? = null, onDismissListener: ((tag: String) -> Unit)? = null, -) : BaseDialog(activity, onShowListener, onDismissListener) { +) : BaseDialog(activity, onShowListener, onDismissListener) { override val TAG = "SyncViewListDialog" @@ -34,47 +30,25 @@ class SyncViewListDialog( override fun getNeutralButtonText() = R.string.sync_feature_all override fun getNegativeButtonText() = R.string.cancel - override fun getMultiChoiceItems(): Map { - items = mapOf( - R.string.menu_timetable to DRAWER_ITEM_TIMETABLE, - R.string.menu_agenda to DRAWER_ITEM_AGENDA, - R.string.menu_grades to DRAWER_ITEM_GRADES, - R.string.menu_homework to DRAWER_ITEM_HOMEWORK, - R.string.menu_notices to DRAWER_ITEM_BEHAVIOUR, - R.string.menu_attendance to DRAWER_ITEM_ATTENDANCE, - R.string.title_messages_inbox_single to (DRAWER_ITEM_MESSAGES to 0), - R.string.title_messages_sent_single to (DRAWER_ITEM_MESSAGES to 1), - R.string.menu_announcements to DRAWER_ITEM_ANNOUNCEMENTS, - ).mapKeys { (resId, _) -> activity.getString(resId) } - return items - } + @Suppress("USELESS_CAST") + override fun getMultiChoiceItems() = FeatureType.values() + .filter { it.nameRes != null && app.profile.hasFeature(it) } + .associateBy { it.nameRes!!.resolveString(activity) as CharSequence } - override fun getDefaultSelectedItems(): Set { - val everything = currentViewId == DRAWER_ITEM_HOME - return when { - everything -> items.values.toSet() - currentViewId == DRAWER_ITEM_MESSAGES -> when (MessagesFragment.pageSelection) { - 1 -> setOf(DRAWER_ITEM_MESSAGES to 1) - else -> setOf(DRAWER_ITEM_MESSAGES to 0) - } - else -> setOf(currentViewId) + override fun getDefaultSelectedItems() = when (currentNavTarget) { + NavTarget.HOME -> getMultiChoiceItems().values.toSet() + NavTarget.MESSAGES -> when (MessagesFragment.pageSelection) { + Message.TYPE_SENT -> setOf(FeatureType.MESSAGES_SENT) + else -> setOf(FeatureType.MESSAGES_INBOX) } + else -> currentNavTarget.featureType?.let { setOf(it) } ?: getMultiChoiceItems().values.toSet() } override suspend fun onShow() = Unit - private lateinit var items: Map - @Suppress("UNCHECKED_CAST") override suspend fun onPositiveClick(): Boolean { - val selected = getMultiSelection().mapNotNull { - when (it) { - is Int -> it to 0 - is Pair<*, *> -> it as Pair - else -> null - } - } - + val selected = getMultiSelection() if (selected.isEmpty()) return DISMISS diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/sync/UpdateAvailableDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/sync/UpdateAvailableDialog.kt index c0335999..c6766ece 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/sync/UpdateAvailableDialog.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/sync/UpdateAvailableDialog.kt @@ -20,7 +20,7 @@ class UpdateAvailableDialog( private val mandatory: Boolean = update?.updateMandatory ?: false, onShowListener: ((tag: String) -> Unit)? = null, onDismissListener: ((tag: String) -> Unit)? = null, -) : BaseDialog(activity, onShowListener, onDismissListener) { +) : BaseDialog(activity, onShowListener, onDismissListener) { override val TAG = "UpdateAvailableDialog" @@ -43,11 +43,11 @@ class UpdateAvailableDialog( override suspend fun onShow() = Unit override suspend fun onPositiveClick(): Boolean { - if (update == null) + if (update == null || update.isOnGooglePlay) Utils.openGooglePlay(activity) else activity.startService(Intent(app, UpdateDownloaderService::class.java)) - return NO_DISMISS + return DISMISS } override suspend fun onBeforeShow(): Boolean { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/sync/UpdateProgressDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/sync/UpdateProgressDialog.kt new file mode 100644 index 00000000..68bd8d54 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/sync/UpdateProgressDialog.kt @@ -0,0 +1,87 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2022-10-22. + */ + +package pl.szczodrzynski.edziennik.ui.dialogs.sync + +import android.app.DownloadManager +import android.database.CursorIndexOutOfBoundsException +import android.view.LayoutInflater +import androidx.appcompat.app.AppCompatActivity +import androidx.core.content.getSystemService +import kotlinx.coroutines.Job +import org.greenrobot.eventbus.EventBus +import org.greenrobot.eventbus.Subscribe +import org.greenrobot.eventbus.ThreadMode +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.data.api.szkolny.response.Update +import pl.szczodrzynski.edziennik.databinding.UpdateProgressDialogBinding +import pl.szczodrzynski.edziennik.ext.getInt +import pl.szczodrzynski.edziennik.ext.startCoroutineTimer +import pl.szczodrzynski.edziennik.sync.UpdateStateEvent +import pl.szczodrzynski.edziennik.ui.dialogs.base.BindingDialog +import pl.szczodrzynski.edziennik.utils.Utils + +class UpdateProgressDialog( + activity: AppCompatActivity, + private val update: Update, + private val downloadId: Long, + onShowListener: ((tag: String) -> Unit)? = null, + onDismissListener: ((tag: String) -> Unit)? = null, +) : BindingDialog(activity, onShowListener, onDismissListener) { + + override val TAG = "UpdateProgressDialog" + + override fun getTitleRes() = R.string.notification_downloading_update + override fun inflate(layoutInflater: LayoutInflater) = + UpdateProgressDialogBinding.inflate(layoutInflater) + + override fun isCancelable() = false + override fun getNegativeButtonText() = R.string.cancel + + private var timerJob: Job? = null + + override suspend fun onShow() { + EventBus.getDefault().register(this) + b.update = update + b.progress.progress = 0 + + val downloadManager = app.getSystemService() ?: return + val query = DownloadManager.Query().setFilterById(downloadId) + + timerJob?.cancel() + timerJob = startCoroutineTimer(repeatMillis = 100L) { + try { + val cursor = downloadManager.query(query) + cursor.moveToFirst() + val progress = cursor.getInt(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR) + ?.toFloat() ?: return@startCoroutineTimer + b.downloadedSize.text = Utils.readableFileSize(progress.toLong()) + val total = cursor.getInt(DownloadManager.COLUMN_TOTAL_SIZE_BYTES) + ?.toFloat() ?: return@startCoroutineTimer + b.totalSize.text = Utils.readableFileSize(total.toLong()) + b.progress.progress = (progress / total * 100.0f).toInt() + } catch (_: CursorIndexOutOfBoundsException) {} + } + } + + override fun onDismiss() { + EventBus.getDefault().unregister(this) + timerJob?.cancel() + } + + override suspend fun onNegativeClick(): Boolean { + val downloadManager = app.getSystemService() ?: return NO_DISMISS + downloadManager.remove(downloadId) + return DISMISS + } + + @Subscribe(threadMode = ThreadMode.MAIN, sticky = true) + fun onUpdateStateEvent(event: UpdateStateEvent) { + if (event.downloadId != downloadId) + return + EventBus.getDefault().removeStickyEvent(event) + if (!event.running) + dismiss() + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/error/ErrorDetailsDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/error/ErrorDetailsDialog.kt index b25cb2be..10822787 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/error/ErrorDetailsDialog.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/error/ErrorDetailsDialog.kt @@ -21,7 +21,7 @@ class ErrorDetailsDialog( private val titleRes: Int = R.string.dialog_error_details_title, onShowListener: ((tag: String) -> Unit)? = null, onDismissListener: ((tag: String) -> Unit)? = null, -) : BaseDialog(activity, onShowListener, onDismissListener) { +) : BaseDialog(activity, onShowListener, onDismissListener) { override val TAG = "ErrorDetailsDialog" diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/event/EventDetailsDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/event/EventDetailsDialog.kt index 0a174472..42137b0c 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/event/EventDetailsDialog.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/event/EventDetailsDialog.kt @@ -27,6 +27,7 @@ import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi import pl.szczodrzynski.edziennik.data.db.full.EventFull import pl.szczodrzynski.edziennik.databinding.DialogEventDetailsBinding import pl.szczodrzynski.edziennik.ext.* +import pl.szczodrzynski.edziennik.ui.base.enums.NavTarget import pl.szczodrzynski.edziennik.ui.dialogs.base.BindingDialog import pl.szczodrzynski.edziennik.ui.notes.setupNotesButton import pl.szczodrzynski.edziennik.ui.timetable.TimetableFragment @@ -113,9 +114,20 @@ class EventDetailsDialog( b.typeColor.background?.setTintColor(MaterialColors.harmonizeWithPrimary(b.root.context, event.eventColor)) - b.details = mutableListOf( + val agendaSubjectImportant = event.subjectLongName != null + && App.config[event.profileId].ui.agendaSubjectImportant + + b.name = if (agendaSubjectImportant) + event.subjectLongName + else + event.typeName + + b.details = listOfNotNull( + if (agendaSubjectImportant) + event.typeName + else event.subjectLongName, - event.teamName?.asColoredSpannable(colorSecondary) + event.teamName?.asColoredSpannable(colorSecondary) ).concat(bullet) b.addedBy.setText( @@ -196,7 +208,7 @@ class EventDetailsDialog( val dateStr = event.date.stringY_m_d val intent = - if (activity is MainActivity && activity.navTargetId == MainActivity.DRAWER_ITEM_TIMETABLE) + if (activity is MainActivity && activity.navTarget == NavTarget.TIMETABLE) Intent(TimetableFragment.ACTION_SCROLL_TO_DATE) else if (activity is MainActivity) Intent("android.intent.action.MAIN") @@ -204,8 +216,10 @@ class EventDetailsDialog( Intent(activity, MainActivity::class.java) intent.apply { - putExtra("fragmentId", MainActivity.DRAWER_ITEM_TIMETABLE) - putExtra("timetableDate", dateStr) + putExtras( + "fragmentId" to NavTarget.TIMETABLE, + "timetableDate" to dateStr, + ) } if (activity is MainActivity) activity.sendBroadcast(intent) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/event/EventListAdapter.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/event/EventListAdapter.kt index f991eeef..5c2f4eef 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/event/EventListAdapter.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/event/EventListAdapter.kt @@ -24,6 +24,7 @@ class EventListAdapter( val showDate: Boolean = false, val showColor: Boolean = true, val showType: Boolean = true, + val showTypeColor: Boolean = showType, val showTime: Boolean = true, val showSubject: Boolean = true, val markAsSeen: Boolean = true, diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/event/EventManualDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/event/EventManualDialog.kt index 5f032d75..ba60f490 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/event/EventManualDialog.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/event/EventManualDialog.kt @@ -14,21 +14,37 @@ import com.google.android.material.color.MaterialColors import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.jaredrummler.android.colorpicker.ColorPickerDialog import com.jaredrummler.android.colorpicker.ColorPickerDialogListener -import kotlinx.coroutines.* +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode -import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.config.AppData import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask import pl.szczodrzynski.edziennik.data.api.events.ApiTaskAllFinishedEvent import pl.szczodrzynski.edziennik.data.api.events.ApiTaskErrorEvent import pl.szczodrzynski.edziennik.data.api.events.ApiTaskFinishedEvent import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi -import pl.szczodrzynski.edziennik.data.db.entity.* +import pl.szczodrzynski.edziennik.data.db.entity.Event +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.Profile +import pl.szczodrzynski.edziennik.data.db.entity.Subject +import pl.szczodrzynski.edziennik.data.db.enums.FeatureType +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType import pl.szczodrzynski.edziennik.data.db.full.EventFull import pl.szczodrzynski.edziennik.data.db.full.LessonFull import pl.szczodrzynski.edziennik.databinding.DialogEventManualV2Binding -import pl.szczodrzynski.edziennik.ext.* +import pl.szczodrzynski.edziennik.ext.JsonObject +import pl.szczodrzynski.edziennik.ext.appendView +import pl.szczodrzynski.edziennik.ext.getStudentData +import pl.szczodrzynski.edziennik.ext.onChange +import pl.szczodrzynski.edziennik.ext.onClick +import pl.szczodrzynski.edziennik.ext.removeFromParent +import pl.szczodrzynski.edziennik.ext.setText +import pl.szczodrzynski.edziennik.ext.setTintColor import pl.szczodrzynski.edziennik.ui.dialogs.base.BindingDialog import pl.szczodrzynski.edziennik.ui.dialogs.settings.RegistrationConfigDialog import pl.szczodrzynski.edziennik.ui.views.TimeDropdown.Companion.DISPLAY_LESSONS @@ -106,8 +122,14 @@ class EventManualDialog( } override suspend fun onShow() { - b.shareSwitch.isChecked = editingShared - b.shareSwitch.isEnabled = !editingShared || (editingShared && editingOwn) + val data = withContext(Dispatchers.IO) { + val profile = app.db.profileDao().getByIdSuspend(profileId) ?: return@withContext null + AppData.get(profile.loginStoreType) + } + if (data?.uiConfig?.eventManualShowSubjectDropdown == true) { + b.subjectDropdownLayout.removeFromParent() + b.timeDropdownLayout.appendView(b.subjectDropdownLayout) + } b.showMore.onClick { // TODO iconics is broken it.apply { @@ -136,6 +158,11 @@ class EventManualDialog( } loadLists() + + val shareByDefault = app.profile.config.shareByDefault && profile.canShare + + b.shareSwitch.isChecked = editingShared || editingEvent == null && shareByDefault + b.shareSwitch.isEnabled = !editingShared || editingOwn } private fun updateShareText(checked: Boolean = b.shareSwitch.isChecked) { @@ -171,9 +198,7 @@ class EventManualDialog( EdziennikTask.syncProfile( profileId = profileId, - viewIds = listOf( - MainActivity.DRAWER_ITEM_TIMETABLE to 0 - ), + featureTypes = setOf(FeatureType.TIMETABLE), arguments = JsonObject( "weekStart" to weekStart.stringY_m_d ) @@ -480,8 +505,8 @@ class EventManualDialog( val metadataObject = Metadata( profileId, when (type?.id) { - Event.TYPE_HOMEWORK -> Metadata.TYPE_HOMEWORK - else -> Metadata.TYPE_EVENT + Event.TYPE_HOMEWORK -> MetadataType.HOMEWORK + else -> MetadataType.EVENT }, eventObject.id, true, diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/event/EventViewHolder.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/event/EventViewHolder.kt index ee03b46f..72c9cbb8 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/event/EventViewHolder.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/event/EventViewHolder.kt @@ -114,7 +114,7 @@ class EventViewHolder( b.attachmentIcon.isVisible = item.hasAttachments b.typeColor.background?.setTintColor(MaterialColors.harmonizeWithPrimary(b.root.context, item.eventColor)) - b.typeColor.isVisible = adapter.showType && adapter.showColor + b.typeColor.isVisible = adapter.showTypeColor b.editButton.isVisible = !adapter.simpleMode && item.addedManually diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/grades/GradesListFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/grades/GradesListFragment.kt index 83ecb671..905abefd 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/grades/GradesListFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/grades/GradesListFragment.kt @@ -17,12 +17,12 @@ import androidx.recyclerview.widget.LinearLayoutManager import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial import kotlinx.coroutines.* import pl.szczodrzynski.edziennik.* -import pl.szczodrzynski.edziennik.MainActivity.Companion.TARGET_GRADES_EDITOR import pl.szczodrzynski.edziennik.data.db.entity.Grade -import pl.szczodrzynski.edziennik.data.db.entity.Metadata.TYPE_GRADE +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType import pl.szczodrzynski.edziennik.data.db.full.GradeFull import pl.szczodrzynski.edziennik.databinding.GradesListFragmentBinding import pl.szczodrzynski.edziennik.ext.* +import pl.szczodrzynski.edziennik.ui.base.enums.NavTarget import pl.szczodrzynski.edziennik.ui.dialogs.settings.GradesConfigDialog import pl.szczodrzynski.edziennik.ui.grades.models.GradesAverages import pl.szczodrzynski.edziennik.ui.grades.models.GradesSemester @@ -81,7 +81,7 @@ class GradesListFragment : Fragment(), CoroutineScope { } val items = when { - app.config.forProfile().grades.hideSticksFromOld && App.devMode -> grades.filter { it.value != 1.0f } + app.profile.config.grades.hideSticksFromOld && App.devMode -> grades.filter { it.value != 1.0f } else -> grades } @@ -126,7 +126,7 @@ class GradesListFragment : Fragment(), CoroutineScope { gradeCountOtherSemester = otherSemester?.averages?.normalCount?.toFloat() } - activity.loadTarget(TARGET_GRADES_EDITOR, Bundle( + activity.navigate(navTarget = NavTarget.GRADES_EDITOR, args = Bundle( "subjectId" to subject.subjectId, "semester" to semester.number, "averageMode" to manager.yearAverageMode, @@ -152,7 +152,7 @@ class GradesListFragment : Fragment(), CoroutineScope { .withIcon(CommunityMaterial.Icon.cmd_eye_check_outline) .withOnClickListener(View.OnClickListener { activity.bottomSheet.close() - AsyncTask.execute { App.db.metadataDao().setAllSeen(App.profileId, TYPE_GRADE, true) } + AsyncTask.execute { App.db.metadataDao().setAllSeen(App.profileId, MetadataType.GRADE, true) } Toast.makeText(activity, R.string.main_menu_mark_as_read_success, Toast.LENGTH_SHORT).show() }) ) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/grades/editor/GradesEditorFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/grades/editor/GradesEditorFragment.kt index ac235249..154a42bf 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/grades/editor/GradesEditorFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/grades/editor/GradesEditorFragment.kt @@ -12,7 +12,9 @@ import androidx.fragment.app.Fragment import androidx.lifecycle.Observer import androidx.recyclerview.widget.LinearLayoutManager import com.google.android.material.dialog.MaterialAlertDialogBuilder -import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.MainActivity +import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.data.db.entity.Grade import pl.szczodrzynski.edziennik.databinding.FragmentGradesEditorBinding import pl.szczodrzynski.edziennik.ext.getFloat @@ -37,7 +39,7 @@ class GradesEditorFragment : Fragment() { private val navController: NavController by lazy { Navigation.findNavController(b.root) } */ - private val config by lazy { app.config.getFor(App.profileId).grades } + private val config by lazy { app.profile.config.grades } private var subjectId: Long = -1 private var semester: Int = 1 diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/home/CounterActivity.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/home/CounterActivity.kt index c63311e1..b1f10d3b 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/home/CounterActivity.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/home/CounterActivity.kt @@ -4,8 +4,10 @@ package pl.szczodrzynski.edziennik.ui.home +import android.graphics.BitmapFactory import android.os.Bundle import androidx.appcompat.app.AppCompatActivity +import com.jetradarmobile.snowfall.SnowfallView import com.mikepenz.iconics.IconicsDrawable import com.mikepenz.iconics.utils.colorInt import com.mikepenz.iconics.utils.sizeDp @@ -20,6 +22,7 @@ import pl.szczodrzynski.edziennik.ext.startCoroutineTimer import pl.szczodrzynski.edziennik.ext.timeLeft import pl.szczodrzynski.edziennik.ext.timeTill import pl.szczodrzynski.edziennik.ui.dialogs.BellSyncTimeChooseDialog +import pl.szczodrzynski.edziennik.utils.BigNightUtil import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Time import kotlin.coroutines.CoroutineContext @@ -61,6 +64,7 @@ class CounterActivity : AppCompatActivity(), CoroutineScope { it.type != Lesson.TYPE_SHIFTED_SOURCE }) } + lessonList.onEach { it.filterNotes() } } b.bellSync.setImageDrawable( @@ -81,6 +85,27 @@ class CounterActivity : AppCompatActivity(), CoroutineScope { counterJob = startCoroutineTimer(repeatMillis = 500) { update() } + + // IT'S WINTER MY DUDES + val today = Date.getToday() + if ((today.month / 3 % 4 == 0) && app.config.ui.snowfall) { + b.rootFrame.addView(layoutInflater.inflate(R.layout.snowfall, b.rootFrame, false)) + } else if (app.config.ui.eggfall && BigNightUtil().isDataWielkanocyNearDzisiaj()) { + val eggfall = layoutInflater.inflate( + R.layout.eggfall, + b.rootFrame, + false + ) as SnowfallView + eggfall.setSnowflakeBitmaps(listOf( + BitmapFactory.decodeResource(resources, R.drawable.egg1), + BitmapFactory.decodeResource(resources, R.drawable.egg2), + BitmapFactory.decodeResource(resources, R.drawable.egg3), + BitmapFactory.decodeResource(resources, R.drawable.egg4), + BitmapFactory.decodeResource(resources, R.drawable.egg5), + BitmapFactory.decodeResource(resources, R.drawable.egg6) + )) + b.rootFrame.addView(eggfall) + } }} private fun update() { @@ -101,13 +126,15 @@ class CounterActivity : AppCompatActivity(), CoroutineScope { when { actual != null -> { - b.lessonName.text = actual.displaySubjectName + b.lessonName.text = actual.getNoteSubstituteText(showNotes = true) + ?: actual.displaySubjectName val left = actual.displayEndTime!! - now b.timeLeft.text = timeLeft(left.toInt(), "\n", countInSeconds) } next != null -> { - b.lessonName.text = next.displaySubjectName + b.lessonName.text = next.getNoteSubstituteText(showNotes = true) + ?: next.displaySubjectName val till = next.displayStartTime!! - now b.timeLeft.text = timeTill(till.toInt(), "\n", countInSeconds) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/home/HomeConfigDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/home/HomeConfigDialog.kt index 73367c99..256daf6e 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/home/HomeConfigDialog.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/home/HomeConfigDialog.kt @@ -20,7 +20,7 @@ class HomeConfigDialog( private val reloadOnDismiss: Boolean = true, onShowListener: ((tag: String) -> Unit)? = null, onDismissListener: ((tag: String) -> Unit)? = null, -) : BaseDialog(activity, onShowListener, onDismissListener) { +) : BaseDialog(activity, onShowListener, onDismissListener) { override val TAG = "HomeConfigDialog" @@ -37,18 +37,17 @@ class HomeConfigDialog( ).mapKeys { (resId, _) -> activity.getString(resId) } override fun getDefaultSelectedItems() = - profileConfig.homeCards + app.profile.config.ui.homeCards .filter { it.profileId == App.profileId } .map { it.cardId } .toSet() override suspend fun onShow() = Unit - private val profileConfig by lazy { app.config.getFor(app.profileId).ui } private var configChanged = false override suspend fun onPositiveClick(): Boolean { - val homeCards = profileConfig.homeCards.toMutableList() + val homeCards = app.profile.config.ui.homeCards.toMutableList() homeCards.removeAll { it.profileId == App.profileId } homeCards += getMultiSelection().mapNotNull { HomeCardModel( @@ -56,7 +55,7 @@ class HomeConfigDialog( cardId = it as? Int ?: return@mapNotNull null ) } - profileConfig.homeCards = homeCards + app.profile.config.ui.homeCards = homeCards return DISMISS } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/home/HomeFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/home/HomeFragment.kt index 54f94433..ed8e7519 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/home/HomeFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/home/HomeFragment.kt @@ -25,8 +25,9 @@ import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.BuildConfig import pl.szczodrzynski.edziennik.MainActivity import pl.szczodrzynski.edziennik.R -import pl.szczodrzynski.edziennik.data.db.entity.LoginStore +import pl.szczodrzynski.edziennik.data.db.enums.FeatureType import pl.szczodrzynski.edziennik.databinding.FragmentHomeBinding +import pl.szczodrzynski.edziennik.ext.hasUIFeature import pl.szczodrzynski.edziennik.ext.onClick import pl.szczodrzynski.edziennik.ui.dialogs.settings.StudentNumberDialog import pl.szczodrzynski.edziennik.ui.home.cards.* @@ -49,18 +50,18 @@ class HomeFragment : Fragment(), CoroutineScope { cardAdapter.items[toPosition] = fromCard cardAdapter.notifyItemMoved(fromPosition, toPosition) - val homeCards = App.config.forProfile().ui.homeCards.toMutableList() + val homeCards = App.profile.config.ui.homeCards.toMutableList() val fromIndex = homeCards.indexOfFirst { it.cardId == fromCard.id } val toIndex = homeCards.indexOfFirst { it.cardId == toCard.id } val fromPair = homeCards[fromIndex] homeCards[fromIndex] = homeCards[toIndex] homeCards[toIndex] = fromPair - App.config.forProfile().ui.homeCards = homeCards + App.profile.config.ui.homeCards = homeCards return true } fun removeCard(position: Int, cardAdapter: HomeCardAdapter) { - val homeCards = App.config.forProfile().ui.homeCards.toMutableList() + val homeCards = App.profile.config.ui.homeCards.toMutableList() if (position >= homeCards.size) return val card = cardAdapter.items[position] @@ -70,7 +71,7 @@ class HomeFragment : Fragment(), CoroutineScope { return } homeCards.removeAll { it.cardId == card.id } - App.config.forProfile().ui.homeCards = homeCards + App.profile.config.ui.homeCards = homeCards } } @@ -81,7 +82,8 @@ class HomeFragment : Fragment(), CoroutineScope { private val job: Job = Job() override val coroutineContext: CoroutineContext get() = job + Dispatchers.Main - + private val manager + get() = app.permissionManager override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { activity = (getActivity() as MainActivity?) ?: return null context ?: return null @@ -96,6 +98,10 @@ class HomeFragment : Fragment(), CoroutineScope { if (!isAdded) return + if (!manager.isNotificationPermissionGranted) { + manager.requestNotificationsPermission(activity, 0, false){} + } + activity.bottomSheet.prependItems( BottomSheetPrimaryItem(true) .withTitle(R.string.menu_add_remove_cards) @@ -120,7 +126,7 @@ class HomeFragment : Fragment(), CoroutineScope { .withOnClickListener(OnClickListener { activity.bottomSheet.close() launch { withContext(Dispatchers.Default) { - if (app.profile.loginStoreType != LoginStore.LOGIN_TYPE_LIBRUS) { + if (!app.data.uiConfig.enableMarkAsReadAnnouncements) { app.db.metadataDao().setAllSeenExceptMessagesAndAnnouncements(App.profileId, true) } else { app.db.metadataDao().setAllSeenExceptMessages(App.profileId, true) @@ -138,16 +144,16 @@ class HomeFragment : Fragment(), CoroutineScope { b.refreshLayout.isEnabled = scrollY == 0 } - val cards = app.config.forProfile().ui.homeCards.filter { it.profileId == app.profile.id }.toMutableList() + val cards = app.profile.config.ui.homeCards.filter { it.profileId == app.profile.id }.toMutableList() if (cards.isEmpty()) { - cards += listOf( - HomeCardModel(app.profile.id, HomeCard.CARD_LUCKY_NUMBER), - HomeCardModel(app.profile.id, HomeCard.CARD_TIMETABLE), - HomeCardModel(app.profile.id, HomeCard.CARD_EVENTS), - HomeCardModel(app.profile.id, HomeCard.CARD_GRADES), + cards += listOfNotNull( + HomeCardModel(app.profile.id, HomeCard.CARD_LUCKY_NUMBER).takeIf { app.profile.hasUIFeature(FeatureType.LUCKY_NUMBER) }, + HomeCardModel(app.profile.id, HomeCard.CARD_TIMETABLE).takeIf { app.profile.hasUIFeature(FeatureType.TIMETABLE) }, + HomeCardModel(app.profile.id, HomeCard.CARD_EVENTS).takeIf { app.profile.hasUIFeature(FeatureType.AGENDA) }, + HomeCardModel(app.profile.id, HomeCard.CARD_GRADES).takeIf { app.profile.hasUIFeature(FeatureType.GRADES) }, HomeCardModel(app.profile.id, HomeCard.CARD_NOTES), ) - app.config.forProfile().ui.homeCards = app.config.forProfile().ui.homeCards.toMutableList().also { it.addAll(cards) } + app.profile.config.ui.homeCards = app.profile.config.ui.homeCards.toMutableList().also { it.addAll(cards) } } val items = mutableListOf() diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/home/cards/HomeArchiveCard.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/home/cards/HomeArchiveCard.kt index 73fc5610..2404d0f1 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/home/cards/HomeArchiveCard.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/home/cards/HomeArchiveCard.kt @@ -18,6 +18,7 @@ import pl.szczodrzynski.edziennik.ext.dp import pl.szczodrzynski.edziennik.ext.onClick import pl.szczodrzynski.edziennik.ext.setMessage import pl.szczodrzynski.edziennik.ext.setText +import pl.szczodrzynski.edziennik.ui.base.enums.NavTarget import pl.szczodrzynski.edziennik.ui.home.HomeCard import pl.szczodrzynski.edziennik.ui.home.HomeCardAdapter import pl.szczodrzynski.edziennik.ui.home.HomeFragment @@ -70,12 +71,12 @@ class HomeArchiveCard( .show() return@launch } - activity.loadProfile(profile) + activity.navigate(profile = profile) } } holder.root.onClick { - activity.loadTarget(MainActivity.DRAWER_ITEM_AGENDA) + activity.navigate(navTarget = NavTarget.AGENDA) } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/home/cards/HomeAvailabilityCard.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/home/cards/HomeAvailabilityCard.kt index 9960b305..4bb264d0 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/home/cards/HomeAvailabilityCard.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/home/cards/HomeAvailabilityCard.kt @@ -15,7 +15,10 @@ import coil.load import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job -import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.BuildConfig +import pl.szczodrzynski.edziennik.MainActivity +import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.data.db.entity.Profile import pl.szczodrzynski.edziennik.databinding.CardHomeAvailabilityBinding import pl.szczodrzynski.edziennik.ext.Intent @@ -28,6 +31,7 @@ import pl.szczodrzynski.edziennik.ui.dialogs.sync.UpdateAvailableDialog import pl.szczodrzynski.edziennik.ui.home.HomeCard import pl.szczodrzynski.edziennik.ui.home.HomeCardAdapter import pl.szczodrzynski.edziennik.ui.home.HomeFragment +import pl.szczodrzynski.edziennik.utils.Utils import pl.szczodrzynski.edziennik.utils.html.BetterHtml import kotlin.coroutines.CoroutineContext @@ -79,7 +83,7 @@ class HomeAvailabilityCard( else if (update != null && update.versionCode > BuildConfig.VERSION_CODE) { b.homeAvailabilityTitle.setText(R.string.home_availability_title) b.homeAvailabilityText.setText(R.string.home_availability_text, update.versionName) - b.homeAvailabilityUpdate.isVisible = true + b.homeAvailabilityUpdate.isVisible = !app.buildManager.isPlayRelease b.homeAvailabilityIcon.setImageResource(R.drawable.ic_update) onInfoClick = { UpdateAvailableDialog(activity, update).show() @@ -92,7 +96,10 @@ class HomeAvailabilityCard( b.homeAvailabilityUpdate.onClick { if (update == null) return@onClick - activity.startService(Intent(app, UpdateDownloaderService::class.java)) + if (update.isOnGooglePlay) + Utils.openGooglePlay(activity) + else + activity.startService(Intent(app, UpdateDownloaderService::class.java)) } b.homeAvailabilityInfo.onClick(onInfoClick) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/home/cards/HomeDebugCard.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/home/cards/HomeDebugCard.kt index 329a677a..6947b74b 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/home/cards/HomeDebugCard.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/home/cards/HomeDebugCard.kt @@ -27,7 +27,7 @@ import pl.szczodrzynski.edziennik.data.db.entity.Profile import pl.szczodrzynski.edziennik.databinding.CardHomeDebugBinding import pl.szczodrzynski.edziennik.ext.dp import pl.szczodrzynski.edziennik.ext.onClick -import pl.szczodrzynski.edziennik.ui.captcha.LibrusCaptchaDialog +import pl.szczodrzynski.edziennik.ui.captcha.RecaptchaPromptDialog import pl.szczodrzynski.edziennik.ui.home.HomeCard import pl.szczodrzynski.edziennik.ui.home.HomeCardAdapter import pl.szczodrzynski.edziennik.ui.home.HomeFragment @@ -85,11 +85,6 @@ class HomeDebugCard( app.startActivity(Chucker.getLaunchIntent(activity, 1)); } - b.librusCaptchaButton.onClick { - //app.startActivity(Intent(activity, LoginLibrusCaptchaActivity::class.java)) - LibrusCaptchaDialog(activity, onSuccess = {}, onFailure = {}).show() - } - b.getLogs.onClick { val logs = HyperLog.getDeviceLogsInFile(activity, true) val intent = Intent(Intent.ACTION_SEND) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/home/cards/HomeEventsCard.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/home/cards/HomeEventsCard.kt index 96534719..acf99b2c 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/home/cards/HomeEventsCard.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/home/cards/HomeEventsCard.kt @@ -22,6 +22,7 @@ import pl.szczodrzynski.edziennik.data.db.entity.Profile import pl.szczodrzynski.edziennik.databinding.CardHomeEventsBinding import pl.szczodrzynski.edziennik.ext.dp import pl.szczodrzynski.edziennik.ext.onClick +import pl.szczodrzynski.edziennik.ui.base.enums.NavTarget import pl.szczodrzynski.edziennik.ui.event.EventDetailsDialog import pl.szczodrzynski.edziennik.ui.event.EventListAdapter import pl.szczodrzynski.edziennik.ui.event.EventManualDialog @@ -62,9 +63,10 @@ class HomeEventsCard( simpleMode = true, showWeekDay = true, showDate = true, - showType = true, + showType = !profile.config.ui.agendaSubjectImportant, + showTypeColor = true, showTime = false, - showSubject = false, + showSubject = profile.config.ui.agendaSubjectImportant, markAsSeen = false, onEventClick = { EventDetailsDialog( @@ -108,7 +110,7 @@ class HomeEventsCard( }) holder.root.onClick { - activity.loadTarget(MainActivity.DRAWER_ITEM_AGENDA) + activity.navigate(navTarget = NavTarget.AGENDA) } }} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/home/cards/HomeGradesCard.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/home/cards/HomeGradesCard.kt index c990b2b8..c64f2cc3 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/home/cards/HomeGradesCard.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/home/cards/HomeGradesCard.kt @@ -30,6 +30,7 @@ import pl.szczodrzynski.edziennik.data.db.entity.Subject import pl.szczodrzynski.edziennik.data.db.full.GradeFull import pl.szczodrzynski.edziennik.databinding.CardHomeGradesBinding import pl.szczodrzynski.edziennik.ext.dp +import pl.szczodrzynski.edziennik.ui.base.enums.NavTarget import pl.szczodrzynski.edziennik.ui.grades.GradeView import pl.szczodrzynski.edziennik.ui.home.HomeCard import pl.szczodrzynski.edziennik.ui.home.HomeCardAdapter @@ -75,7 +76,7 @@ class HomeGradesCard( }) b.root.setOnClickListener { - activity.loadTarget(MainActivity.DRAWER_ITEM_GRADES) + activity.navigate(navTarget = NavTarget.GRADES) } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/home/cards/HomeNotesCard.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/home/cards/HomeNotesCard.kt index f3758a2e..be829285 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/home/cards/HomeNotesCard.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/home/cards/HomeNotesCard.kt @@ -21,6 +21,7 @@ import pl.szczodrzynski.edziennik.data.db.entity.Profile import pl.szczodrzynski.edziennik.databinding.CardHomeNotesBinding import pl.szczodrzynski.edziennik.ext.dp import pl.szczodrzynski.edziennik.ext.onClick +import pl.szczodrzynski.edziennik.ui.base.enums.NavTarget import pl.szczodrzynski.edziennik.ui.home.HomeCard import pl.szczodrzynski.edziennik.ui.home.HomeCardAdapter import pl.szczodrzynski.edziennik.ui.home.HomeFragment @@ -114,7 +115,7 @@ class HomeNotesCard( b.addNote.onClick(this@HomeNotesCard::onNoteAddClick) holder.root.onClick { - activity.loadTarget(MainActivity.DRAWER_ITEM_NOTES) + activity.navigate(navTarget = NavTarget.NOTES) } }} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/home/cards/HomeTimetableCard.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/home/cards/HomeTimetableCard.kt index 4b38486f..fa2d0381 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/home/cards/HomeTimetableCard.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/home/cards/HomeTimetableCard.kt @@ -27,9 +27,11 @@ import pl.szczodrzynski.edziennik.data.api.events.ApiTaskAllFinishedEvent import pl.szczodrzynski.edziennik.data.db.entity.Event import pl.szczodrzynski.edziennik.data.db.entity.Lesson import pl.szczodrzynski.edziennik.data.db.entity.Profile +import pl.szczodrzynski.edziennik.data.db.enums.FeatureType import pl.szczodrzynski.edziennik.data.db.full.LessonFull import pl.szczodrzynski.edziennik.databinding.CardHomeTimetableBinding import pl.szczodrzynski.edziennik.ext.* +import pl.szczodrzynski.edziennik.ui.base.enums.NavTarget import pl.szczodrzynski.edziennik.ui.dialogs.BellSyncTimeChooseDialog import pl.szczodrzynski.edziennik.ui.home.CounterActivity import pl.szczodrzynski.edziennik.ui.home.HomeCard @@ -73,6 +75,7 @@ class HomeTimetableCard( private var counterJob: Job? = null private var counterStart: Time? = null private var counterEnd: Time? = null + private var showAllLessons: Boolean = false private var subjectSpannable: CharSequence? = null private val ignoreCancelled = false @@ -114,9 +117,9 @@ class HomeTimetableCard( } b.root.onClick { - activity.loadTarget(MainActivity.DRAWER_ITEM_TIMETABLE, Bundle().apply { - putString("timetableDate", timetableDate.stringY_m_d) - }) + activity.navigate(navTarget = NavTarget.TIMETABLE, args = Bundle( + "timetableDate" to timetableDate.stringY_m_d, + )) } if (app.profile.getStudentData("timetableNotPublic", false)) { @@ -201,9 +204,7 @@ class HomeTimetableCard( it.isEnabled = false EdziennikTask.syncProfile( profileId = profile.id, - viewIds = listOf( - MainActivity.DRAWER_ITEM_TIMETABLE to 0 - ), + featureTypes = setOf(FeatureType.TIMETABLE), arguments = JsonObject( "weekStart" to weekStart.stringY_m_d ) @@ -225,6 +226,7 @@ class HomeTimetableCard( } lessons = lessons.filter { it.type != Lesson.TYPE_NO_LESSONS } + lessons.onEach { it.filterNotes() } b.timetableLayout.visibility = View.VISIBLE b.noTimetableLayout.visibility = View.GONE @@ -270,6 +272,8 @@ class HomeTimetableCard( counterJob = startCoroutineTimer(repeatMillis = 500) { count() } + + showAllLessons = !isOngoing } else { val isTomorrow = today.clone().stepForward(0, 0, 1) == timetableDate @@ -306,12 +310,22 @@ class HomeTimetableCard( } ?: run { b.classroom.visibility = View.GONE } + + showAllLessons = true } val text = mutableListOf( + if (showAllLessons) + activity.getString(R.string.home_timetable_all_lessons) + else activity.getString(R.string.home_timetable_later) ) - val nextLessons = lessons.drop(skipFirst + 1) + + val nextLessons = if (showAllLessons) + lessons.drop(skipFirst) + else + lessons.drop(skipFirst + 1) + for (lesson in nextLessons) { text += listOf( lesson.displayStartTime?.stringHM, @@ -325,6 +339,7 @@ class HomeTimetableCard( private val LessonFull?.subjectSpannable: CharSequence get() = if (this == null) "?" else when { + hasReplacingNotes() -> getNoteSubstituteText(showNotes = true) ?: "?" isCancelled -> displaySubjectName?.asStrikethroughSpannable() ?: "?" isChange -> displaySubjectName?.asItalicSpannable() ?: "?" else -> displaySubjectName ?: "?" @@ -342,6 +357,14 @@ class HomeTimetableCard( } val now = syncedNow + if (now >= counterStart && showAllLessons) { + // update "next lessons" view to remove current lesson + this.counterJob?.cancel() + this.counterStart = null + this.counterEnd = null + update() + return + } if (now > counterEnd) { // the lesson is already over b.progress.visibility = View.GONE diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/homework/HomeworkFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/homework/HomeworkFragment.kt index d041d298..f47fa63e 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/homework/HomeworkFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/homework/HomeworkFragment.kt @@ -21,6 +21,7 @@ import pl.szczodrzynski.edziennik.MainActivity import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.data.db.entity.Event import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType import pl.szczodrzynski.edziennik.databinding.HomeworkFragmentBinding import pl.szczodrzynski.edziennik.ext.Bundle import pl.szczodrzynski.edziennik.ext.addOnPageSelectedListener @@ -73,7 +74,7 @@ class HomeworkFragment : Fragment(), CoroutineScope { .withIcon(CommunityMaterial.Icon.cmd_eye_check_outline) .withOnClickListener(View.OnClickListener { activity.bottomSheet.close() - AsyncTask.execute { app.db.metadataDao().setAllSeen(App.profileId, Metadata.TYPE_HOMEWORK, true) } + AsyncTask.execute { app.db.metadataDao().setAllSeen(App.profileId, MetadataType.HOMEWORK, true) } Toast.makeText(activity, R.string.main_menu_mark_as_read_success, Toast.LENGTH_SHORT).show() })) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/login/LoginActivity.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/login/LoginActivity.kt index 3b49dde7..d91b3c9c 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/login/LoginActivity.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/login/LoginActivity.kt @@ -31,6 +31,7 @@ class LoginActivity : AppCompatActivity(), CoroutineScope { private val app: App by lazy { applicationContext as App } private lateinit var b: LoginActivityBinding lateinit var navOptions: NavOptions + lateinit var navOptionsBuilder: NavOptions.Builder val nav by lazy { Navigation.findNavController(this, R.id.nav_host_fragment) } val errorSnackbar: ErrorSnackbar by lazy { ErrorSnackbar(this) } val swipeRefreshLayout: SwipeRefreshLayoutNoTouch by lazy { b.swipeRefreshLayout } @@ -88,12 +89,12 @@ class LoginActivity : AppCompatActivity(), CoroutineScope { super.onCreate(savedInstanceState) setTheme(R.style.AppTheme_Dark) - navOptions = NavOptions.Builder() + navOptionsBuilder = NavOptions.Builder() .setEnterAnim(R.anim.slide_in_right) .setExitAnim(R.anim.slide_out_left) .setPopEnterAnim(R.anim.slide_in_left) .setPopExitAnim(R.anim.slide_out_right) - .build() + navOptions = navOptionsBuilder.build() b = LoginActivityBinding.inflate(layoutInflater) setContentView(b.root) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/login/LoginChooserFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/login/LoginChooserFragment.kt index 1bfe0abd..f48258a9 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/login/LoginChooserFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/login/LoginChooserFragment.kt @@ -23,8 +23,10 @@ import androidx.fragment.app.Fragment import androidx.recyclerview.widget.LinearLayoutManager import com.google.android.material.dialog.MaterialAlertDialogBuilder import kotlinx.coroutines.* -import pl.szczodrzynski.edziennik.* -import pl.szczodrzynski.edziennik.data.api.* +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.data.db.enums.LoginMode +import pl.szczodrzynski.edziennik.data.db.enums.LoginType import pl.szczodrzynski.edziennik.databinding.LoginChooserFragmentBinding import pl.szczodrzynski.edziennik.ext.* import pl.szczodrzynski.edziennik.ui.dialogs.sync.RegisterUnavailableDialog @@ -51,7 +53,8 @@ class LoginChooserFragment : Fragment(), CoroutineScope { private val job: Job = Job() override val coroutineContext: CoroutineContext get() = job + Dispatchers.Main - + private val manager + get() = app.permissionManager // local/private variables go here override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { @@ -67,6 +70,9 @@ class LoginChooserFragment : Fragment(), CoroutineScope { if (!isAdded) return val adapter = LoginChooserAdapter(activity, this::onLoginModeClicked) + if (!manager.isNotificationPermissionGranted) { + manager.requestNotificationsPermission(activity, 0, false){} + } b.versionText.setText( R.string.login_chooser_version_format, @@ -77,18 +83,17 @@ class LoginChooserFragment : Fragment(), CoroutineScope { app.buildManager.showVersionDialog(activity) if (!App.devMode) return@onClick - if (adapter.items.firstOrNull { it is LoginInfo.Register && it.internalName == "lab" } != null) + if (adapter.items.firstOrNull { it is LoginInfo.Register && it.loginType == LoginType.TEMPLATE } != null) return@onClick adapter.items.add( index = 0, element = LoginInfo.Register( - loginType = 999999, - internalName = "lab", + loginType = LoginType.TEMPLATE, registerName = R.string.menu_lab, registerLogo = R.drawable.face_2, loginModes = listOf( LoginInfo.Mode( - loginMode = 0, + loginMode = LoginMode.PODLASIE_API, name = 0, icon = 0, guideText = 0, @@ -192,13 +197,12 @@ class LoginChooserFragment : Fragment(), CoroutineScope { adapter.items.removeAll { it !is LoginInfo.Register } adapter.items.add( LoginInfo.Register( - loginType = 74, - internalName = "eggs", + loginType = LoginType.DEMO, registerName = R.string.eggs, registerLogo = R.drawable.face_1, loginModes = listOf( LoginInfo.Mode( - loginMode = 0, + loginMode = LoginMode.PODLASIE_API, name = 0, icon = 0, guideText = 0, @@ -237,12 +241,12 @@ class LoginChooserFragment : Fragment(), CoroutineScope { loginType: LoginInfo.Register, loginMode: LoginInfo.Mode ) { - if (loginType.internalName == "eggs") { + if (loginType.loginType == LoginType.DEMO) { nav.navigate(R.id.loginEggsFragment, null, activity.navOptions) return } - if (loginType.internalName == "lab") { + if (loginType.loginType == LoginType.TEMPLATE) { nav.navigate(R.id.labFragment, null, activity.navOptions) return } @@ -299,7 +303,7 @@ class LoginChooserFragment : Fragment(), CoroutineScope { ), activity.navOptions) } - private suspend fun checkAvailability(loginType: Int): Boolean { + private suspend fun checkAvailability(loginType: LoginType): Boolean { val error = withContext(Dispatchers.IO) { app.availabilityManager.check(loginType) } ?: return true diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/login/LoginFinishFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/login/LoginFinishFragment.kt index e7703dab..ee9e34af 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/login/LoginFinishFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/login/LoginFinishFragment.kt @@ -17,6 +17,7 @@ import pl.szczodrzynski.edziennik.* import pl.szczodrzynski.edziennik.databinding.LoginFinishFragmentBinding import pl.szczodrzynski.edziennik.ext.Intent import pl.szczodrzynski.edziennik.ext.onClick +import pl.szczodrzynski.edziennik.ui.base.enums.NavTarget import kotlin.coroutines.CoroutineContext class LoginFinishFragment : Fragment(), CoroutineScope { @@ -64,14 +65,14 @@ class LoginFinishFragment : Fragment(), CoroutineScope { activity, MainActivity::class.java, "profileId" to firstProfileId, - "fragmentId" to MainActivity.DRAWER_ITEM_HOME + "fragmentId" to NavTarget.HOME )) } else { activity.setResult(Activity.RESULT_OK, Intent( null, "profileId" to firstProfileId, - "fragmentId" to MainActivity.DRAWER_ITEM_HOME + "fragmentId" to NavTarget.HOME )) } activity.finish() diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/login/LoginFormFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/login/LoginFormFragment.kt index 3c5a26ca..c4485aba 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/login/LoginFormFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/login/LoginFormFragment.kt @@ -14,6 +14,8 @@ import android.widget.Toast import androidx.core.view.isVisible import androidx.core.widget.addTextChangedListener import androidx.fragment.app.Fragment +import androidx.navigation.NavOptions +import androidx.navigation.navOptions import androidx.viewbinding.ViewBinding import com.google.android.material.textfield.TextInputLayout import com.mikepenz.iconics.IconicsDrawable @@ -24,6 +26,8 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.data.db.enums.LoginMode +import pl.szczodrzynski.edziennik.data.db.enums.LoginType import pl.szczodrzynski.edziennik.databinding.LoginFormCheckboxItemBinding import pl.szczodrzynski.edziennik.databinding.LoginFormFieldItemBinding import pl.szczodrzynski.edziennik.databinding.LoginFormFragmentBinding @@ -33,7 +37,6 @@ import pl.szczodrzynski.edziennik.ui.login.LoginInfo.BaseCredential import pl.szczodrzynski.edziennik.ui.login.LoginInfo.FormCheckbox import pl.szczodrzynski.edziennik.ui.login.LoginInfo.FormField import pl.szczodrzynski.navlib.colorAttr -import java.util.* import kotlin.coroutines.CoroutineContext class LoginFormFragment : Fragment(), CoroutineScope { @@ -63,8 +66,10 @@ class LoginFormFragment : Fragment(), CoroutineScope { get() = arguments?.getString("platformDescription") private val platformFormFields get() = arguments?.getString("platformFormFields")?.split(";") - private val platformRealmData - get() = arguments?.getString("platformRealmData")?.toJsonObject() + private val platformData + get() = arguments?.getString("platformData")?.toJsonObject() + private val platformStoreKey + get() = arguments?.getString("platformStoreKey") override fun onCreateView( inflater: LayoutInflater, @@ -85,17 +90,22 @@ class LoginFormFragment : Fragment(), CoroutineScope { b.errorLayout.isVisible = false b.errorLayout.background?.setTintColor(R.attr.colorError.resolveAttr(activity)) - val loginType = arguments?.getInt("loginType") ?: return + val loginType = arguments?.getEnum("loginType") ?: return val register = LoginInfo.list.firstOrNull { it.loginType == loginType } ?: return - val loginMode = arguments?.getInt("loginMode") ?: return + val loginMode = arguments?.getEnum("loginMode") ?: return val mode = register.loginModes.firstOrNull { it.loginMode == loginMode } ?: return + if (mode.credentials.isEmpty()) { + login(loginType, loginMode) + return + } + b.title.setText(R.string.login_form_title_format, app.getString(register.registerName)) b.subTitle.text = platformName ?: app.getString(mode.name) b.text.text = platformGuideText ?: app.getString(mode.guideText) // eggs - isEggs = register.internalName == "podlasie" + isEggs = register.loginType == LoginType.PODLASIE for (credential in mode.credentials) { if (platformFormFields?.contains(credential.keyName) == false) @@ -240,7 +250,7 @@ class LoginFormFragment : Fragment(), CoroutineScope { } } - private fun login(loginType: Int, loginMode: Int) { + private fun login(loginType: LoginType, loginMode: LoginMode) { val payload = Bundle( "loginType" to loginType, "loginMode" to loginMode @@ -250,7 +260,10 @@ class LoginFormFragment : Fragment(), CoroutineScope { payload.putBoolean("fakeLogin", true) } - payload.putBundle("webRealmData", platformRealmData?.toBundle()) + if (platformStoreKey == null) + payload.putAll(platformData?.toBundle() ?: Bundle()) + else + payload.putBundle(platformStoreKey, platformData?.toBundle()) var hasErrors = false credentials.forEach { (credential, b) -> @@ -295,6 +308,14 @@ class LoginFormFragment : Fragment(), CoroutineScope { if (hasErrors) return - nav.navigate(R.id.loginProgressFragment, payload, activity.navOptions) + val navOptions = + if (credentials.isEmpty()) + activity.navOptionsBuilder + .setPopUpTo(R.id.loginPlatformListFragment, inclusive = false) + .build() + else + activity.navOptions + + nav.navigate(R.id.loginProgressFragment, payload, navOptions) } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/login/LoginInfo.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/login/LoginInfo.kt index 0448b514..33e191d7 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/login/LoginInfo.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/login/LoginInfo.kt @@ -6,10 +6,13 @@ package pl.szczodrzynski.edziennik.ui.login import androidx.annotation.DrawableRes import androidx.annotation.StringRes +import com.google.gson.JsonObject import com.mikepenz.iconics.typeface.IIcon import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.data.api.* +import pl.szczodrzynski.edziennik.data.db.enums.LoginMode +import pl.szczodrzynski.edziennik.data.db.enums.LoginType import pl.szczodrzynski.edziennik.ui.grades.models.ExpandableItemModel import pl.szczodrzynski.edziennik.ui.login.qr.LoginLibrusQrDecoder import pl.szczodrzynski.edziennik.ui.login.qr.LoginQrDecoder @@ -45,13 +48,12 @@ object LoginInfo { val list by lazy { listOf( Register( - loginType = LOGIN_TYPE_LIBRUS, - internalName = "librus", + loginType = LoginType.LIBRUS, registerName = R.string.login_register_librus, registerLogo = R.drawable.login_logo_librus, loginModes = listOf( Mode( - loginMode = LOGIN_MODE_LIBRUS_EMAIL, + loginMode = LoginMode.LIBRUS_EMAIL, name = R.string.login_mode_librus_email, icon = R.drawable.login_mode_librus_email, hintText = R.string.login_mode_librus_email_hint, @@ -64,11 +66,10 @@ object LoginInfo { errorCodes = mapOf( ERROR_LOGIN_LIBRUS_PORTAL_NOT_ACTIVATED to R.string.login_error_account_not_activated, ERROR_LOGIN_LIBRUS_PORTAL_INVALID_LOGIN to R.string.login_error_incorrect_login_or_password, - ERROR_CAPTCHA_LIBRUS_PORTAL to R.string.error_3001_reason ) ), /*Mode( - loginMode = LOGIN_MODE_LIBRUS_SYNERGIA, + loginMode = LoginMode.LIBRUS_SYNERGIA, name = R.string.login_mode_librus_synergia, icon = R.drawable.login_mode_librus_synergia, hintText = R.string.login_mode_librus_synergia_hint, @@ -93,7 +94,7 @@ object LoginInfo { ) ),*/ Mode( - loginMode = LOGIN_MODE_LIBRUS_JST, + loginMode = LoginMode.LIBRUS_JST, name = R.string.login_mode_librus_jst, icon = R.drawable.login_mode_librus_jst, hintText = R.string.login_mode_librus_jst_hint, @@ -131,13 +132,12 @@ object LoginInfo { ) ), Register( - loginType = LOGIN_TYPE_VULCAN, - internalName = "vulcan", + loginType = LoginType.VULCAN, registerName = R.string.login_type_vulcan, registerLogo = R.drawable.login_logo_vulcan, loginModes = listOf( Mode( - loginMode = LOGIN_MODE_VULCAN_HEBE, + loginMode = LoginMode.VULCAN_HEBE, name = R.string.login_mode_vulcan_api, icon = R.drawable.login_mode_vulcan_hebe, hintText = R.string.login_mode_vulcan_api_hint, @@ -192,7 +192,7 @@ object LoginInfo { errorCodes = mapOf() ), Mode( - loginMode = LOGIN_MODE_VULCAN_WEB, + loginMode = LoginMode.VULCAN_WEB, name = R.string.login_mode_vulcan_web, icon = R.drawable.login_mode_vulcan_web, hintText = R.string.login_mode_vulcan_web_hint, @@ -219,13 +219,12 @@ object LoginInfo { ) ), Register( - loginType = LOGIN_TYPE_MOBIDZIENNIK, - internalName = "mobidziennik", + loginType = LoginType.MOBIDZIENNIK, registerName = R.string.login_type_mobidziennik, registerLogo = R.drawable.login_logo_mobidziennik, loginModes = listOf( Mode( - loginMode = LOGIN_MODE_MOBIDZIENNIK_WEB, + loginMode = LoginMode.MOBIDZIENNIK_WEB, name = R.string.login_mode_mobidziennik_web, icon = R.drawable.login_mode_mobidziennik_web, hintText = R.string.login_mode_mobidziennik_web_hint, @@ -279,13 +278,12 @@ object LoginInfo { ) ), Register( - loginType = LOGIN_TYPE_PODLASIE, - internalName = "podlasie", + loginType = LoginType.PODLASIE, registerName = R.string.login_type_podlasie, registerLogo = R.drawable.login_logo_podlasie, loginModes = listOf( Mode( - loginMode = LOGIN_MODE_PODLASIE_API, + loginMode = LoginMode.PODLASIE_API, name = R.string.login_mode_podlasie_api, icon = R.drawable.login_mode_podlasie_api, guideText = R.string.login_mode_podlasie_api_guide, @@ -313,13 +311,28 @@ object LoginInfo { errorCodes = mapOf() ) ) - ) + ), + Register( + loginType = LoginType.USOS, + registerName = R.string.login_type_usos, + registerLogo = R.drawable.login_logo_usos, + loginModes = listOf( + Mode( + loginMode = LoginMode.USOS_OAUTH, + name = R.string.login_mode_usos_oauth, + icon = R.drawable.login_mode_usos_api, + guideText = R.string.login_mode_usos_oauth_guide, + isPlatformSelection = true, + credentials = listOf(), + errorCodes = mapOf(), + ), + ), + ), ) } data class Register( - val loginType: Int, - val internalName: String, + val loginType: LoginType, val registerName: Int, @DrawableRes val registerLogo: Int, @@ -330,7 +343,7 @@ object LoginInfo { } data class Mode( - val loginMode: Int, + val loginMode: LoginMode, @StringRes val name: Int, @@ -357,7 +370,8 @@ object LoginInfo { val icon: String, val screenshot: String?, val formFields: List, - val realmData: RealmData + val data: JsonObject, + val storeKey: String?, ) open class BaseCredential( diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/login/LoginPlatformListFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/login/LoginPlatformListFragment.kt index 8a2bb25d..5055e228 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/login/LoginPlatformListFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/login/LoginPlatformListFragment.kt @@ -17,8 +17,11 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.launch import pl.szczodrzynski.edziennik.* import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi +import pl.szczodrzynski.edziennik.data.db.enums.LoginMode +import pl.szczodrzynski.edziennik.data.db.enums.LoginType import pl.szczodrzynski.edziennik.databinding.LoginPlatformListFragmentBinding import pl.szczodrzynski.edziennik.ext.Bundle +import pl.szczodrzynski.edziennik.ext.getEnum import pl.szczodrzynski.edziennik.ext.onClick import pl.szczodrzynski.edziennik.ext.startCoroutineTimer import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration @@ -56,9 +59,9 @@ class LoginPlatformListFragment : Fragment(), CoroutineScope { if (!isAdded) return b.backButton.onClick { nav.navigateUp() } - val loginType = arguments?.getInt("loginType") ?: return + val loginType = arguments?.getEnum("loginType") ?: return val register = LoginInfo.list.firstOrNull { it.loginType == loginType } ?: return - val loginMode = arguments?.getInt("loginMode") ?: return + val loginMode = arguments?.getEnum("loginMode") ?: return val mode = register.loginModes.firstOrNull { it.loginMode == loginMode } ?: return adapter = LoginPlatformAdapter(activity) { platform -> @@ -68,7 +71,8 @@ class LoginPlatformListFragment : Fragment(), CoroutineScope { "platformName" to platform.name, "platformDescription" to platform.description, "platformFormFields" to platform.formFields.joinToString(";"), - "platformRealmData" to app.gson.toJson(platform.realmData) + "platformData" to platform.data.toString(), + "platformStoreKey" to platform.storeKey, ), activity.navOptions) } @@ -99,7 +103,7 @@ class LoginPlatformListFragment : Fragment(), CoroutineScope { val platforms = LoginInfo.platformList[mode.name] ?: run { api.runCatching(activity) { - getRealms(register.internalName) + getRealms(register.loginType.name.lowercase()) } ?: run { nav.navigateUp() return@launch diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/login/LoginProgressFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/login/LoginProgressFragment.kt index 03abbc2b..a0ad6e78 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/login/LoginProgressFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/login/LoginProgressFragment.kt @@ -19,7 +19,7 @@ import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.R -import pl.szczodrzynski.edziennik.data.api.ERROR_CAPTCHA_NEEDED +import pl.szczodrzynski.edziennik.data.api.ERROR_REQUIRES_USER_ACTION import pl.szczodrzynski.edziennik.data.api.LOGIN_NO_ARGUMENTS import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask import pl.szczodrzynski.edziennik.data.api.events.ApiTaskErrorEvent @@ -27,8 +27,12 @@ import pl.szczodrzynski.edziennik.data.api.events.FirstLoginFinishedEvent import pl.szczodrzynski.edziennik.data.api.events.UserActionRequiredEvent import pl.szczodrzynski.edziennik.data.api.models.ApiError import pl.szczodrzynski.edziennik.data.db.entity.LoginStore +import pl.szczodrzynski.edziennik.data.db.enums.LoginMode +import pl.szczodrzynski.edziennik.data.db.enums.LoginType import pl.szczodrzynski.edziennik.databinding.LoginProgressFragmentBinding +import pl.szczodrzynski.edziennik.ext.getEnum import pl.szczodrzynski.edziennik.ext.joinNotNullStrings +import pl.szczodrzynski.edziennik.utils.managers.UserActionManager import kotlin.coroutines.CoroutineContext import kotlin.math.max @@ -78,8 +82,8 @@ class LoginProgressFragment : Fragment(), CoroutineScope { app.db.profileDao().lastId ?: 0, activity.profiles.maxByOrNull { it.profile.id }?.profile?.id ?: 0 ) - val loginType = args.getInt("loginType", -1) - val loginMode = args.getInt("loginMode", 0) + val loginType = args.getEnum("loginType") ?: return@launch + val loginMode = args.getEnum("loginMode") ?: return@launch val loginStore = LoginStore( id = maxProfileId + 1, @@ -137,14 +141,21 @@ class LoginProgressFragment : Fragment(), CoroutineScope { return } - app.userActionManager.execute(activity, event.profileId, event.type, onSuccess = { code -> - args.putString("recaptchaCode", code) - args.putLong("recaptchaTime", System.currentTimeMillis()) - doFirstLogin(args) - }, onFailure = { - activity.error(ApiError(TAG, ERROR_CAPTCHA_NEEDED)) - nav.navigateUp() - }) + val callback = UserActionManager.UserActionCallback( + onSuccess = { data -> + args.putAll(data) + doFirstLogin(args) + }, + onFailure = { + activity.error(ApiError(TAG, ERROR_REQUIRES_USER_ACTION)) + nav.navigateUp() + }, + onCancel = { + nav.navigateUp() + }, + ) + + app.userActionManager.execute(activity, event, callback) } override fun onStart() { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/login/LoginSyncFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/login/LoginSyncFragment.kt index 64384bbc..c7f6d6a4 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/login/LoginSyncFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/login/LoginSyncFragment.kt @@ -14,11 +14,14 @@ import androidx.navigation.Navigation import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.config.AppData import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask import pl.szczodrzynski.edziennik.data.api.events.ApiTaskAllFinishedEvent import pl.szczodrzynski.edziennik.data.api.events.ApiTaskErrorEvent @@ -29,6 +32,7 @@ import pl.szczodrzynski.edziennik.databinding.LoginSyncFragmentBinding import pl.szczodrzynski.edziennik.ext.Bundle import pl.szczodrzynski.edziennik.ext.asBoldSpannable import pl.szczodrzynski.edziennik.ext.concat +import pl.szczodrzynski.edziennik.ext.ignore import kotlin.coroutines.CoroutineContext import kotlin.math.roundToInt @@ -56,7 +60,7 @@ class LoginSyncFragment : Fragment(), CoroutineScope { return b.root } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + override fun onViewCreated(view: View, savedInstanceState: Bundle?) = launch { EventBus.getDefault().removeStickyEvent(ApiTaskAllFinishedEvent::class.java) EventBus.getDefault().removeStickyEvent(ApiTaskErrorEvent::class.java) @@ -64,26 +68,33 @@ class LoginSyncFragment : Fragment(), CoroutineScope { val profiles = activity.profiles.filter { it.isSelected }.map { it.profile } val loginStores = activity.loginStores.filter { store -> profiles.any { it.loginStoreId == store.id } } - val registrationAllowed = arguments?.getBoolean("registrationAllowed") ?: false - profiles.forEach { - it.registration = if (registrationAllowed) - Profile.REGISTRATION_ENABLED - else - Profile.REGISTRATION_DISABLED + withContext(Dispatchers.IO) { + val registrationAllowed = arguments?.getBoolean("registrationAllowed") ?: false + profiles.forEach { + it.registration = if (registrationAllowed) + Profile.REGISTRATION_ENABLED + else + Profile.REGISTRATION_DISABLED - app.db.eventTypeDao().addDefaultTypes(activity, it.id) + val data = AppData.get(it.loginStoreType) + for ((key, value) in data.configOverrides) { + it.config.set(key, value) + } + + app.db.eventTypeDao().addDefaultTypes(it) + } + + app.db.profileDao().addAll(profiles) + app.db.loginStoreDao().addAll(loginStores) } - app.db.profileDao().addAll(profiles) - app.db.loginStoreDao().addAll(loginStores) - finishArguments = Bundle( "firstProfileId" to profiles.firstOrNull()?.id ) - val profileIds = profiles.map { it.id } + val profileIds = profiles.map { it.id }.toSet() EdziennikTask.syncProfileList(profileIds).enqueue(activity) - } + }.ignore() @Subscribe(threadMode = ThreadMode.MAIN) fun onSyncStartedEvent(event: ApiTaskStartedEvent) { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/login/oauth/OAuthLoginActivity.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/login/oauth/OAuthLoginActivity.kt new file mode 100644 index 00000000..c80c3d82 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/login/oauth/OAuthLoginActivity.kt @@ -0,0 +1,64 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2022-10-15. + */ + +package pl.szczodrzynski.edziennik.ui.login.oauth + +import android.annotation.SuppressLint +import android.graphics.Bitmap +import android.os.Bundle +import android.view.ViewGroup.LayoutParams.MATCH_PARENT +import android.webkit.WebView +import android.webkit.WebViewClient +import android.widget.FrameLayout +import androidx.appcompat.app.AppCompatActivity +import org.greenrobot.eventbus.EventBus +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.utils.Utils.d + +class OAuthLoginActivity : AppCompatActivity() { + companion object { + private const val TAG = "OAuthLoginActivity" + } + + private var isSuccessful = false + + @SuppressLint("SetJavaScriptEnabled") + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setTitle(R.string.oauth_dialog_title) + + val authorizeUrl = intent.getStringExtra("authorizeUrl") ?: return + val redirectUrl = intent.getStringExtra("redirectUrl") ?: return + + val webView = WebView(this) + webView.webViewClient = object : WebViewClient() { + override fun onPageStarted(view: WebView, url: String, favicon: Bitmap?) { + d(TAG, "Navigating to $url") + if (url.startsWith(redirectUrl)) { + isSuccessful = true + EventBus.getDefault().post(OAuthLoginResult( + isError = false, + responseUrl = url, + )) + finish() + } + } + } + webView.settings.javaScriptEnabled = true + webView.layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT) + setContentView(webView) + + webView.loadUrl(authorizeUrl) + } + + override fun onDestroy() { + super.onDestroy() + if (!isSuccessful) + EventBus.getDefault().post(OAuthLoginResult( + isError = false, + responseUrl = null, + )) + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/login/oauth/OAuthLoginResult.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/login/oauth/OAuthLoginResult.kt new file mode 100644 index 00000000..108c0c8e --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/login/oauth/OAuthLoginResult.kt @@ -0,0 +1,10 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2022-10-15. + */ + +package pl.szczodrzynski.edziennik.ui.login.oauth + +data class OAuthLoginResult( + val isError: Boolean, + val responseUrl: String?, +) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/login/recaptcha/RecaptchaActivity.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/login/recaptcha/RecaptchaActivity.kt new file mode 100644 index 00000000..2e58da07 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/login/recaptcha/RecaptchaActivity.kt @@ -0,0 +1,132 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2023-3-24. + */ + +package pl.szczodrzynski.edziennik.ui.login.recaptcha + +import android.annotation.SuppressLint +import android.graphics.Color +import android.os.Build +import android.os.Bundle +import android.util.Base64 +import android.view.ViewGroup.LayoutParams.MATCH_PARENT +import android.webkit.JavascriptInterface +import android.webkit.WebView +import android.widget.FrameLayout +import androidx.appcompat.app.AppCompatActivity +import org.greenrobot.eventbus.EventBus +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.data.api.SYSTEM_USER_AGENT +import pl.szczodrzynski.edziennik.utils.Themes +import java.nio.charset.Charset + +class RecaptchaActivity : AppCompatActivity() { + companion object { + private const val TAG = "RecaptchaActivity" + + private const val CODE = """ + PCFET0NUWVBFIGh0bWw+PGh0bWw+PGhlYWQ+PHNjcmlwdCBzcmM9Imh0dHBzOi8vd3d3Lmdvb2ds + ZS5jb20vcmVjYXB0Y2hhL2FwaS5qcz9vbmxvYWQ9cmVhZHkmcmVuZGVyPWV4cGxpY2l0Ij48L3Nj + cmlwdD48L2hlYWQ+PGJvZHk+PGJyPjxkaXYgaWQ9ImdyIiBzdHlsZT0icG9zaXRpb246YWJzb2x1 + dGU7dG9wOjUwJTt0cmFuc2Zvcm06dHJhbnNsYXRlKDAsLTUwJSk7Ij48L2Rpdj48YnI+PHNjcmlw + dD5mdW5jdGlvbiByZWFkeSgpe2dyZWNhcHRjaGEucmVuZGVyKCJnciIse3NpdGVrZXk6IlNJVEVL + RVkiLHRoZW1lOiJUSEVNRSIsY2FsbGJhY2s6ZnVuY3Rpb24oZSl7d2luZG93LmlmLmNhbGxiYWNr + KGUpO30sImV4cGlyZWQtY2FsbGJhY2siOndpbmRvdy5pZi5leHBpcmVkQ2FsbGJhY2ssImVycm9y + LWNhbGxiYWNrIjp3aW5kb3cuaWYuZXJyb3JDYWxsYmFja30pO308L3NjcmlwdD48L2JvZHk+PC9o + dG1sPg== + """ + } + + private var isSuccessful = false + private lateinit var jsInterface: CaptchaCallbackInterface + + interface CaptchaCallbackInterface { + @JavascriptInterface + fun callback(recaptchaResponse: String) + + @JavascriptInterface + fun expiredCallback() + + @JavascriptInterface + fun errorCallback() + } + + @SuppressLint("AddJavascriptInterface", "SetJavaScriptEnabled") + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setTitle(R.string.recaptcha_dialog_title) + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + WebView.setWebContentsDebuggingEnabled(true) + } + + val siteKey = intent.getStringExtra("siteKey") ?: return + val referer = intent.getStringExtra("referer") ?: return + val userAgent = intent.getStringExtra("userAgent") ?: SYSTEM_USER_AGENT + + val htmlContent = Base64.decode(CODE, Base64.DEFAULT) + .toString(Charset.defaultCharset()) + .replace("THEME", if (Themes.isDark) "dark" else "light") + .replace("SITEKEY", siteKey) + + jsInterface = object : CaptchaCallbackInterface { + @JavascriptInterface + override fun callback(recaptchaResponse: String) { + isSuccessful = true + EventBus.getDefault().post( + RecaptchaResult( + isError = false, + code = recaptchaResponse, + ) + ) + finish() + } + + @JavascriptInterface + override fun expiredCallback() { + isSuccessful = false + } + + @JavascriptInterface + override fun errorCallback() { + isSuccessful = false + EventBus.getDefault().post( + RecaptchaResult( + isError = true, + code = null, + ) + ) + finish() + } + } + + val webView = WebView(this).apply { + setBackgroundColor(Color.TRANSPARENT) + settings.javaScriptEnabled = true + settings.userAgentString = userAgent + addJavascriptInterface(jsInterface, "if") + loadDataWithBaseURL( + referer, + htmlContent, + "text/html", + "UTF-8", + null, + ) + // setLayerType(WebView.LAYER_TYPE_SOFTWARE, null) + layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT) + } + setContentView(webView) + } + + override fun onDestroy() { + super.onDestroy() + if (!isSuccessful) + EventBus.getDefault().post( + RecaptchaResult( + isError = false, + code = null, + ) + ) + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/login/recaptcha/RecaptchaResult.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/login/recaptcha/RecaptchaResult.kt new file mode 100644 index 00000000..ae5fae19 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/login/recaptcha/RecaptchaResult.kt @@ -0,0 +1,10 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2023-3-24. + */ + +package pl.szczodrzynski.edziennik.ui.login.recaptcha + +data class RecaptchaResult( + val isError: Boolean, + val code: String?, +) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/messages/compose/MessagesComposeFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/messages/compose/MessagesComposeFragment.kt index 52e7c23b..426c337f 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/messages/compose/MessagesComposeFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/messages/compose/MessagesComposeFragment.kt @@ -25,19 +25,18 @@ import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode import pl.szczodrzynski.edziennik.* -import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_MESSAGES import pl.szczodrzynski.edziennik.data.api.ERROR_MESSAGE_NOT_SENT -import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_MOBIDZIENNIK import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask import pl.szczodrzynski.edziennik.data.api.events.MessageSentEvent import pl.szczodrzynski.edziennik.data.api.events.RecipientListGetEvent import pl.szczodrzynski.edziennik.data.api.models.ApiError -import pl.szczodrzynski.edziennik.data.db.entity.LoginStore import pl.szczodrzynski.edziennik.data.db.entity.Message import pl.szczodrzynski.edziennik.data.db.entity.Teacher +import pl.szczodrzynski.edziennik.data.db.enums.LoginType import pl.szczodrzynski.edziennik.databinding.MessagesComposeFragmentBinding import pl.szczodrzynski.edziennik.ext.Bundle import pl.szczodrzynski.edziennik.ext.DAY +import pl.szczodrzynski.edziennik.ui.base.enums.NavTarget import pl.szczodrzynski.edziennik.ui.dialogs.settings.MessagesConfigDialog import pl.szczodrzynski.edziennik.ui.messages.list.MessagesFragment import pl.szczodrzynski.edziennik.utils.DefaultTextStyles @@ -68,16 +67,13 @@ class MessagesComposeFragment : Fragment(), CoroutineScope { get() = app.messageManager private val textStylingManager get() = app.textStylingManager - private val profileConfig by lazy { app.config.forProfile().ui } private val greetingText - get() = profileConfig.messagesGreetingText ?: "\n\nZ poważaniem\n${app.profile.accountOwnerName}" + get() = app.profile.config.ui.messagesGreetingText ?: "\n\nZ poważaniem\n${app.profile.accountOwnerName}" private val teachers = mutableListOf() private lateinit var stylingConfig: StylingConfig private lateinit var uiConfig: UIConfig - private val enableTextStyling - get() = app.profile.loginStoreType != LoginStore.LOGIN_TYPE_LIBRUS private var changedRecipients = false private var changedSubject = false private var changedBody = false @@ -154,14 +150,14 @@ class MessagesComposeFragment : Fragment(), CoroutineScope { } private fun getMessageBody(): String { - return if (enableTextStyling) + return if (app.data.messagesConfig.textStyling) textStylingManager.getHtmlText(stylingConfig) else b.text.text?.toString() ?: "" } private fun getRecipientList() { - if (System.currentTimeMillis() - app.profile.lastReceiversSync > 1 * DAY * 1000 && app.profile.loginStoreType != LoginStore.LOGIN_TYPE_VULCAN) { + if (app.data.messagesConfig.syncRecipientList && System.currentTimeMillis() - app.profile.lastReceiversSync > 1 * DAY * 1000) { activity.snackbar("Pobieranie listy odbiorców...") EdziennikTask.recipientListGet(App.profileId).enqueue(activity) } @@ -194,19 +190,19 @@ class MessagesComposeFragment : Fragment(), CoroutineScope { }) b.subjectLayout.counterMaxLength = when (app.profile.loginStoreType) { - LoginStore.LOGIN_TYPE_MOBIDZIENNIK -> 100 - LoginStore.LOGIN_TYPE_LIBRUS -> 150 - LoginStore.LOGIN_TYPE_VULCAN -> 200 - LoginStore.LOGIN_TYPE_IDZIENNIK -> 180 - LoginStore.LOGIN_TYPE_EDUDZIENNIK -> 0 + LoginType.MOBIDZIENNIK -> 100 + LoginType.LIBRUS -> 150 + LoginType.VULCAN -> 200 + LoginType.IDZIENNIK -> 180 + LoginType.EDUDZIENNIK -> 0 else -> -1 } b.textLayout.counterMaxLength = when (app.profile.loginStoreType) { - LoginStore.LOGIN_TYPE_MOBIDZIENNIK -> -1 - LoginStore.LOGIN_TYPE_LIBRUS -> 20000 - LoginStore.LOGIN_TYPE_VULCAN -> -1 - LoginStore.LOGIN_TYPE_IDZIENNIK -> 1983 - LoginStore.LOGIN_TYPE_EDUDZIENNIK -> 0 + LoginType.MOBIDZIENNIK -> -1 + LoginType.LIBRUS -> 20000 + LoginType.VULCAN -> -1 + LoginType.IDZIENNIK -> 1983 + LoginType.EDUDZIENNIK -> 0 else -> -1 } @@ -245,9 +241,9 @@ class MessagesComposeFragment : Fragment(), CoroutineScope { subject = b.subject, body = b.text, teachers = teachers, - greetingOnCompose = profileConfig.messagesGreetingOnCompose, - greetingOnReply = profileConfig.messagesGreetingOnReply, - greetingOnForward = profileConfig.messagesGreetingOnForward, + greetingOnCompose = app.profile.config.ui.messagesGreetingOnCompose, + greetingOnReply = app.profile.config.ui.messagesGreetingOnReply, + greetingOnForward = app.profile.config.ui.messagesGreetingOnForward, greetingText = greetingText, ) stylingConfig = StylingConfig( @@ -257,13 +253,13 @@ class MessagesComposeFragment : Fragment(), CoroutineScope { styles = styles, textHtml = if (App.devMode) b.textHtml else null, htmlMode = when (app.profile.loginStoreType) { - LOGIN_TYPE_MOBIDZIENNIK -> COMPATIBLE + LoginType.MOBIDZIENNIK -> COMPATIBLE else -> ORIGINAL }, ) - b.fontStyle.root.isVisible = enableTextStyling - if (enableTextStyling) { + b.fontStyle.root.isVisible = app.data.messagesConfig.textStyling + if (app.data.messagesConfig.textStyling) { textStylingManager.attach(stylingConfig) b.fontStyle.styles.addOnButtonCheckedListener { _, _, _ -> changedBody = true @@ -303,7 +299,7 @@ class MessagesComposeFragment : Fragment(), CoroutineScope { .setPositiveButton(R.string.save) { _, _ -> saveDraft() MessagesFragment.pageSelection = Message.TYPE_DRAFT - activity.loadTarget(DRAWER_ITEM_MESSAGES, skipBeforeNavigate = true) + activity.navigate(navTarget = NavTarget.MESSAGES, skipBeforeNavigate = true) } .setNegativeButton(R.string.discard) { _, _ -> activity.resumePausedNavigation() @@ -374,7 +370,7 @@ class MessagesComposeFragment : Fragment(), CoroutineScope { else -> b.text.requestFocus() } - if (!enableTextStyling) + if (!app.data.messagesConfig.textStyling) b.text.setText(b.text.text?.toString()) b.text.setSelection(0) (b.root as? ScrollView)?.smoothScrollTo(0, 0) @@ -393,7 +389,7 @@ class MessagesComposeFragment : Fragment(), CoroutineScope { b.recipientsLayout.error = getString(R.string.messages_compose_recipients_error) return } - val recipients = mutableListOf() + val recipients = mutableSetOf() b.recipients.allChips.forEach { chip -> if (chip.data !is Teacher) return@forEach @@ -487,7 +483,7 @@ class MessagesComposeFragment : Fragment(), CoroutineScope { } activity.snackbar(app.getString(R.string.messages_sent_success), app.getString(R.string.ok)) - activity.loadTarget(MainActivity.TARGET_MESSAGES_DETAILS, Bundle( + activity.navigate(navTarget = NavTarget.MESSAGE, args = Bundle( "messageId" to event.message.id, "message" to app.gson.toJson(event.message), "sentDate" to event.sentDate diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/messages/list/MessagesFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/messages/list/MessagesFragment.kt index 92cab017..823a3289 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/messages/list/MessagesFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/messages/list/MessagesFragment.kt @@ -14,6 +14,7 @@ import pl.szczodrzynski.edziennik.data.db.entity.Message import pl.szczodrzynski.edziennik.databinding.MessagesFragmentBinding import pl.szczodrzynski.edziennik.ext.Bundle import pl.szczodrzynski.edziennik.ext.addOnPageSelectedListener +import pl.szczodrzynski.edziennik.ui.base.enums.NavTarget import pl.szczodrzynski.edziennik.ui.base.lazypager.FragmentLazyPagerAdapter import pl.szczodrzynski.edziennik.ui.dialogs.settings.MessagesConfigDialog import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem @@ -52,7 +53,7 @@ class MessagesFragment : Fragment(), CoroutineScope { val args = Bundle() args.putLong("messageId", messageId) arguments?.remove("messageId") - activity.loadTarget(MainActivity.TARGET_MESSAGES_DETAILS, args) + activity.navigate(navTarget = NavTarget.MESSAGE, args = args) return } @@ -123,7 +124,7 @@ class MessagesFragment : Fragment(), CoroutineScope { ) setFabOnClickListener { - activity.loadTarget(MainActivity.TARGET_MESSAGES_COMPOSE) + activity.navigate(navTarget = NavTarget.MESSAGE_COMPOSE) } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/messages/list/MessagesListFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/messages/list/MessagesListFragment.kt index fb6edc56..fc19abfa 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/messages/list/MessagesListFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/messages/list/MessagesListFragment.kt @@ -13,14 +13,13 @@ import androidx.lifecycle.Observer import androidx.recyclerview.widget.LinearLayoutManager import kotlinx.coroutines.* import pl.szczodrzynski.edziennik.* -import pl.szczodrzynski.edziennik.MainActivity.Companion.TARGET_MESSAGES_COMPOSE -import pl.szczodrzynski.edziennik.MainActivity.Companion.TARGET_MESSAGES_DETAILS import pl.szczodrzynski.edziennik.data.db.entity.Message import pl.szczodrzynski.edziennik.data.db.entity.Teacher import pl.szczodrzynski.edziennik.databinding.MessagesListFragmentBinding import pl.szczodrzynski.edziennik.ext.Bundle import pl.szczodrzynski.edziennik.ext.getInt import pl.szczodrzynski.edziennik.ext.startCoroutineTimer +import pl.szczodrzynski.edziennik.ui.base.enums.NavTarget import pl.szczodrzynski.edziennik.ui.base.lazypager.LazyFragment import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration import kotlin.coroutines.CoroutineContext @@ -65,11 +64,11 @@ class MessagesListFragment : LazyFragment(), CoroutineScope { adapter = MessagesAdapter(activity, teachers, onMessageClick = { val (target, args) = if (it.isDraft) { - TARGET_MESSAGES_COMPOSE to Bundle("message" to app.gson.toJson(it)) + NavTarget.MESSAGE_COMPOSE to Bundle("message" to app.gson.toJson(it)) } else { - TARGET_MESSAGES_DETAILS to Bundle("messageId" to it.id) + NavTarget.MESSAGE to Bundle("messageId" to it.id) } - activity.loadTarget(target, args) + activity.navigate(navTarget = target, args = args) }, onStarClick = { this@MessagesListFragment.launch { manager.starMessage(it, !it.isStarred) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/messages/single/MessageFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/messages/single/MessageFragment.kt index 05c8fe0a..3c071fbe 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/messages/single/MessageFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/messages/single/MessageFragment.kt @@ -23,11 +23,11 @@ import org.greenrobot.eventbus.ThreadMode import pl.szczodrzynski.edziennik.* import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask import pl.szczodrzynski.edziennik.data.api.events.MessageGetEvent -import pl.szczodrzynski.edziennik.data.db.entity.LoginStore -import pl.szczodrzynski.edziennik.data.db.entity.LoginStore.Companion.LOGIN_TYPE_IDZIENNIK +import pl.szczodrzynski.edziennik.data.db.enums.LoginType import pl.szczodrzynski.edziennik.data.db.full.MessageFull import pl.szczodrzynski.edziennik.databinding.MessageFragmentBinding import pl.szczodrzynski.edziennik.ext.* +import pl.szczodrzynski.edziennik.ui.base.enums.NavTarget import pl.szczodrzynski.edziennik.ui.dialogs.settings.MessagesConfigDialog import pl.szczodrzynski.edziennik.ui.messages.MessagesUtils import pl.szczodrzynski.edziennik.ui.messages.list.MessagesFragment @@ -105,13 +105,13 @@ class MessageFragment : Fragment(), CoroutineScope { b.messageStar.attachToastHint(R.string.hint_message_star) b.replyButton.onClick { - activity.loadTarget(MainActivity.TARGET_MESSAGES_COMPOSE, Bundle( + activity.navigate(navTarget = NavTarget.MESSAGE_COMPOSE, args = Bundle( "message" to app.gson.toJson(message), "type" to "reply" )) } b.forwardButton.onClick { - activity.loadTarget(MainActivity.TARGET_MESSAGES_COMPOSE, Bundle( + activity.navigate(navTarget = NavTarget.MESSAGE_COMPOSE, args = Bundle( "message" to app.gson.toJson(message), "type" to "forward" )) @@ -158,7 +158,7 @@ class MessageFragment : Fragment(), CoroutineScope { return } - if (app.profile.loginStoreType == LOGIN_TYPE_IDZIENNIK) { + if (app.profile.loginStoreType == LoginType.IDZIENNIK) { val meta = "\\[META:([A-z0-9]+);([0-9-]+)]".toRegex().find(message.body!!) val messageIdBefore = meta?.get(2)?.toLong() ?: -1 @@ -168,7 +168,7 @@ class MessageFragment : Fragment(), CoroutineScope { } } - if (app.profile.loginStoreType == LoginStore.LOGIN_TYPE_VULCAN) { + if (app.data.messagesConfig.needsReadStatus) { // vulcan: change message status or download attachments if ((message.isReceived || message.isDeleted) && !message.seen || message.attachmentIds == null) { EdziennikTask.messageGet(App.profileId, message).enqueue(activity) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/notes/NoteEditorDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/notes/NoteEditorDialog.kt index ea77e2d3..8114a6db 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/notes/NoteEditorDialog.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/notes/NoteEditorDialog.kt @@ -87,7 +87,12 @@ class NoteEditorDialog( .show() } - val success = manager.saveNote(activity, note, wasShared = editingNote?.isShared ?: false) + val success = manager.saveNote( + activity = activity, + note = note, + teamId = owner?.getNoteShareTeamId(), + wasShared = editingNote?.isShared ?: false, + ) progressDialog?.dismiss() return success } @@ -127,8 +132,13 @@ class NoteEditorDialog( topicStylingConfig = StylingConfigBase(editText = b.topic, htmlMode = HtmlMode.SIMPLE) bodyStylingConfig = StylingConfigBase(editText = b.body, htmlMode = HtmlMode.SIMPLE) + val profile = withContext(Dispatchers.IO) { + app.db.profileDao().getByIdNow(profileId) + } + b.ownerType = owner?.getNoteType() ?: Note.OwnerType.NONE b.editingNote = editingNote + b.shareByDefault = app.profile.config.shareByDefault && profile?.canShare == true b.color.clear().append(Note.Color.values().map { color -> TextInputDropDown.Item( diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/notifications/NotificationsAdapter.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/notifications/NotificationsAdapter.kt index 38c9de5a..b1da86af 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/notifications/NotificationsAdapter.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/notifications/NotificationsAdapter.kt @@ -49,7 +49,7 @@ class NotificationsAdapter( val date = Date.fromMillis(item.addedDate).formattedString val colorSecondary = android.R.attr.textColorSecondary.resolveAttr(activity) - b.notificationIcon.background = IconicsDrawable(app, item.getLargeIcon()).apply { + b.notificationIcon.background = IconicsDrawable(app, item.type.icon).apply { colorInt = getColorFromAttr(b.root.context, R.attr.colorPrimary) } @@ -59,7 +59,7 @@ class NotificationsAdapter( " • ", date ).concat().asColoredSpannable(colorSecondary) - b.type.text = activity.getNotificationTitle(item.type) + b.type.text = item.type.titleRes.resolveString(activity) onItemClick?.let { listener -> b.root.onClick { listener(item) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/notifications/NotificationsListFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/notifications/NotificationsListFragment.kt index 95a1bd70..f2a98f11 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/notifications/NotificationsListFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/notifications/NotificationsListFragment.kt @@ -67,14 +67,14 @@ class NotificationsListFragment : Fragment(), CoroutineScope { val intent = Intent("android.intent.action.MAIN") notification.fillIntent(intent) - Utils.d(TAG, "notification with item " + notification.viewId + " extras " + if (intent.extras == null) "null" else intent.extras!!.toString()) + Utils.d(TAG, "notification with item " + notification.navTarget + " extras " + if (intent.extras == null) "null" else intent.extras!!.toString()) if (notification.profileId != null && notification.profileId != -1 && notification.profileId != app.profile.id && context is Activity) { Toast.makeText(app, app.getString(R.string.toast_changing_profile), Toast.LENGTH_LONG).show() } app.sendBroadcast(intent) } - app.db.notificationDao().getAll().observe(this@NotificationsListFragment, Observer { items -> + app.db.notificationDao().getAll().observe(viewLifecycleOwner, Observer { items -> if (!isAdded) return@Observer // load & configure the adapter diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/settings/SettingsCard.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/settings/SettingsCard.kt index b04ee4a8..104cfc54 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/settings/SettingsCard.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/settings/SettingsCard.kt @@ -16,13 +16,13 @@ abstract class SettingsCard( protected val activity: MainActivity = util.activity protected val configGlobal by lazy { app.config } - protected val configProfile by lazy { app.config.forProfile() } + protected val configProfile by lazy { app.profile.config } val card by lazy { buildCard() } protected abstract fun buildCard(): MaterialAboutCard - protected abstract fun getItems(): List - protected open fun getItemsMore(): List = listOf() + protected abstract fun getItems(card: MaterialAboutCard): List + protected open fun getItemsMore(card: MaterialAboutCard): List = listOf() } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/settings/SettingsUtil.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/settings/SettingsUtil.kt index 779d3d02..6f41ca20 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/settings/SettingsUtil.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/settings/SettingsUtil.kt @@ -35,8 +35,8 @@ class SettingsUtil( fun createCard( titleRes: Int?, - items: List, - itemsMore: List, + items: (card: MaterialAboutCard) -> List, + itemsMore: (card: MaterialAboutCard) -> List, backgroundColor: Int? = null, theme: Int? = null ): MaterialAboutCard { @@ -45,10 +45,11 @@ class SettingsUtil( .cardColor(backgroundColor ?: 0) .theme(theme ?: 0) .build() - card.items.addAll(items) + card.items.addAll(items(card)) - if (itemsMore.isNotEmpty()) { - card.items.add(createMoreItem(card, itemsMore)) + val more = itemsMore(card) + if (more.isNotEmpty()) { + card.items.add(createMoreItem(card, more)) } return card diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/settings/cards/SettingsAboutCard.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/settings/cards/SettingsAboutCard.kt index c2d03cc0..c398cc3d 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/settings/cards/SettingsAboutCard.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/settings/cards/SettingsAboutCard.kt @@ -18,6 +18,7 @@ import kotlinx.coroutines.launch import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.BuildConfig import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.data.api.szkolny.response.Update import pl.szczodrzynski.edziennik.ext.after import pl.szczodrzynski.edziennik.ext.resolveAttr import pl.szczodrzynski.edziennik.sync.UpdateWorker @@ -43,16 +44,13 @@ class SettingsAboutCard(util: SettingsUtil) : SettingsCard(util), CoroutineScope override fun buildCard(): MaterialAboutCard = util.createCard( null, - items = listOf(), - itemsMore = listOf(), + items = ::getItems, + itemsMore = ::getItemsMore, backgroundColor = R.attr.colorPrimaryContainer.resolveAttr(activity) ).also { it.items.addAll(getItems(it)) } - override fun getItems() = listOf() - override fun getItemsMore() = listOf() - private val versionDetailsItem by lazy { util.createActionItem( text = R.string.settings_about_version_details_text, @@ -64,7 +62,7 @@ class SettingsAboutCard(util: SettingsUtil) : SettingsCard(util), CoroutineScope ) } - private fun getItems(card: MaterialAboutCard) = listOf( + override fun getItems(card: MaterialAboutCard) = listOf( util.createTitleItem(), util.createActionItem( @@ -113,7 +111,17 @@ class SettingsAboutCard(util: SettingsUtil) : SettingsCard(util), CoroutineScope icon = CommunityMaterial.Icon3.cmd_update ) { launch { - UpdateWorker.runNow(app) + val channel = if (App.devMode) + Update.Type.BETA + else + Update.Type.RC + val result = app.updateManager.checkNow(channel, notify = false) + val update = result.getOrNull() + // the dialog is shown by MainActivity (EventBus) + when { + result.isFailure -> Toast.makeText(app, app.getString(R.string.notification_cant_check_update), Toast.LENGTH_SHORT).show() + update == null -> Toast.makeText(app, app.getString(R.string.notification_no_update), Toast.LENGTH_SHORT).show() + } } } )), diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/settings/cards/SettingsProfileCard.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/settings/cards/SettingsProfileCard.kt index 88c4dae7..a5ec33d8 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/settings/cards/SettingsProfileCard.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/settings/cards/SettingsProfileCard.kt @@ -5,6 +5,7 @@ package pl.szczodrzynski.edziennik.ui.settings.cards import android.content.Intent +import com.danielstone.materialaboutlibrary.model.MaterialAboutCard import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.ui.dialogs.settings.ProfileConfigDialog @@ -17,8 +18,8 @@ class SettingsProfileCard(util: SettingsUtil) : SettingsCard(util) { override fun buildCard() = util.createCard( null, - items = getItems(), - itemsMore = listOf() + items = ::getItems, + itemsMore = ::getItemsMore, ) private fun getProfileItem(): MaterialAboutProfileItem = util.createProfileItem( @@ -34,7 +35,7 @@ class SettingsProfileCard(util: SettingsUtil) : SettingsCard(util) { }).show() } - override fun getItems() = listOf( + override fun getItems(card: MaterialAboutCard) = listOf( getProfileItem(), util.createActionItem( diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/settings/cards/SettingsRegisterCard.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/settings/cards/SettingsRegisterCard.kt index 3b6b51f9..7b689b47 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/settings/cards/SettingsRegisterCard.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/settings/cards/SettingsRegisterCard.kt @@ -4,14 +4,24 @@ package pl.szczodrzynski.edziennik.ui.settings.cards -import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.danielstone.materialaboutlibrary.model.MaterialAboutCard import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial import eu.szkolny.font.SzkolnyFont import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.R -import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_LIBRUS +import pl.szczodrzynski.edziennik.data.db.enums.FeatureType +import pl.szczodrzynski.edziennik.data.db.enums.LoginType import pl.szczodrzynski.edziennik.ext.after -import pl.szczodrzynski.edziennik.ui.dialogs.settings.* +import pl.szczodrzynski.edziennik.ext.getStudentData +import pl.szczodrzynski.edziennik.ext.hasUIFeature +import pl.szczodrzynski.edziennik.ext.set +import pl.szczodrzynski.edziennik.ui.dialogs.settings.AgendaConfigDialog +import pl.szczodrzynski.edziennik.ui.dialogs.settings.AttendanceConfigDialog +import pl.szczodrzynski.edziennik.ui.dialogs.settings.BellSyncConfigDialog +import pl.szczodrzynski.edziennik.ui.dialogs.settings.GradesConfigDialog +import pl.szczodrzynski.edziennik.ui.dialogs.settings.MessagesConfigDialog +import pl.szczodrzynski.edziennik.ui.dialogs.settings.RegistrationConfigDialog +import pl.szczodrzynski.edziennik.ui.dialogs.settings.TimetableConfigDialog import pl.szczodrzynski.edziennik.ui.settings.SettingsCard import pl.szczodrzynski.edziennik.ui.settings.SettingsUtil @@ -19,8 +29,8 @@ class SettingsRegisterCard(util: SettingsUtil) : SettingsCard(util) { override fun buildCard() = util.createCard( R.string.settings_card_register_title, - items = getItems(), - itemsMore = getItemsMore() + items = ::getItems, + itemsMore = ::getItemsMore, ) private fun getBellSync() = @@ -31,48 +41,47 @@ class SettingsRegisterCard(util: SettingsUtil) : SettingsCard(util) { ) } ?: activity.getString(R.string.settings_register_bell_sync_subtext_disabled) - private val sharedEventsItem by lazy { + private val sharedEventsDefaultItem by lazy { util.createPropertyItem( - text = R.string.settings_register_shared_events_text, - subText = R.string.settings_register_shared_events_subtext, - icon = CommunityMaterial.Icon3.cmd_share_outline, - value = app.profile.enableSharedEvents + text = R.string.settings_register_share_by_default_text, + subText = R.string.settings_register_share_by_default_subtext, + icon = CommunityMaterial.Icon3.cmd_toggle_switch_outline, + value = configProfile.shareByDefault ) { _, value -> - app.profile.enableSharedEvents = value - app.profileSave() - MaterialAlertDialogBuilder(activity) - .setTitle(R.string.event_sharing) - .setMessage( - if (value) - R.string.settings_register_shared_events_dialog_enabled_text - else - R.string.settings_register_shared_events_dialog_disabled_text - ) - .setPositiveButton(R.string.ok, null) - .show() + configProfile.shareByDefault = value } } - override fun getItems() = listOfNotNull( + override fun getItems(card: MaterialAboutCard) = listOfNotNull( + util.createActionItem( + text = R.string.menu_timetable_config, + icon = CommunityMaterial.Icon3.cmd_timetable + ) { + TimetableConfigDialog(activity, reloadOnDismiss = false).show() + }.takeIf { app.profile.hasUIFeature(FeatureType.TIMETABLE) }, + util.createActionItem( text = R.string.menu_agenda_config, icon = CommunityMaterial.Icon.cmd_calendar_outline ) { AgendaConfigDialog(activity, reloadOnDismiss = false).show() - }, + }.takeIf { app.profile.hasUIFeature(FeatureType.AGENDA) }, util.createActionItem( text = R.string.menu_grades_config, icon = CommunityMaterial.Icon3.cmd_numeric_5_box_outline ) { GradesConfigDialog(activity, reloadOnDismiss = false).show() - }, + }.takeIf { app.profile.hasUIFeature(FeatureType.GRADES) }, util.createActionItem( text = R.string.menu_messages_config, - icon = CommunityMaterial.Icon.cmd_calendar_outline + icon = CommunityMaterial.Icon.cmd_email_outline ) { MessagesConfigDialog(activity, reloadOnDismiss = false).show() + }.takeIf { + app.profile.hasUIFeature(FeatureType.MESSAGES_INBOX) || app.profile.hasUIFeature( + FeatureType.MESSAGES_SENT) }, util.createActionItem( @@ -80,90 +89,92 @@ class SettingsRegisterCard(util: SettingsUtil) : SettingsCard(util) { icon = CommunityMaterial.Icon.cmd_calendar_remove_outline ) { AttendanceConfigDialog(activity, reloadOnDismiss = false).show() - }, + }.takeIf { app.profile.hasUIFeature(FeatureType.ATTENDANCE) }, - if (app.profile.archived) - null - else - util.createPropertyItem( - text = R.string.settings_register_allow_registration_text, - subText = R.string.settings_register_allow_registration_subtext, - icon = CommunityMaterial.Icon.cmd_account_circle_outline, - value = app.profile.canShare, - beforeChange = { item, value -> - if (app.profile.canShare == value) - // allow the switch to change - needed for util.refresh() to change the visual state - return@createPropertyItem true - val dialog = - RegistrationConfigDialog(activity, app.profile, onChangeListener = { enabled -> + util.createMoreItem( + card = card, + items = listOfNotNull( + util.createActionItem( + text = R.string.settings_register_bell_sync_text, + icon = SzkolnyFont.Icon.szf_alarm_bell_outline, + onClick = { + BellSyncConfigDialog(activity, onChangeListener = { + it.subText = getBellSync() + util.refresh() + }).show() + } + ).also { + it.subText = getBellSync() + }, + + util.createPropertyItem( + text = R.string.settings_register_count_in_seconds_text, + subText = R.string.settings_register_count_in_seconds_subtext, + icon = CommunityMaterial.Icon3.cmd_timer_outline, + value = configGlobal.timetable.countInSeconds + ) { _, it -> + configGlobal.timetable.countInSeconds = it + }, + + util.createPropertyItem( + text = R.string.settings_register_show_teacher_absences_text, + icon = CommunityMaterial.Icon.cmd_account_arrow_right_outline, + value = app.profile.getStudentData("showTeacherAbsences", true) + ) { _, it -> + app.profile["showTeacherAbsences"] = it + app.profileSave() + }.takeIf { app.profile.loginStoreType == LoginType.LIBRUS }, + + util.createPropertyItem( + text = R.string.settings_register_hide_sticks_from_old, + icon = CommunityMaterial.Icon3.cmd_numeric_1_box_outline, + value = configProfile.grades.hideSticksFromOld + ) { _, it -> + configProfile.grades.hideSticksFromOld = it + }.takeIf { App.devMode && app.profile.hasUIFeature(FeatureType.GRADES) }, + ), + ), + + *(getRegistrationItems().takeIf { !app.profile.archived } ?: arrayOf()), + ) + + private fun getRegistrationItems() = listOfNotNull( + util.createSectionItem( + text = R.string.settings_registration_section, + ), + + util.createPropertyItem( + text = R.string.settings_register_allow_registration_text, + subText = R.string.settings_register_allow_registration_subtext, + icon = CommunityMaterial.Icon.cmd_account_circle_outline, + value = app.profile.canShare, + beforeChange = + { item, value -> + if (app.profile.canShare == value) + // allow the switch to change - needed for util.refresh() to change the visual state + return@createPropertyItem true + val dialog = + RegistrationConfigDialog(activity, + app.profile, + onChangeListener = { enabled -> if (item.isChecked == enabled) return@RegistrationConfigDialog item.isChecked = enabled if (value) { - card.items.after(item, sharedEventsItem) + card.items.after(item, sharedEventsDefaultItem) } else { - card.items.remove(sharedEventsItem) + card.items.remove(sharedEventsDefaultItem) } util.refresh() }) - if (value) - dialog.showEnableDialog() - else - dialog.showDisableDialog() - false - } - ) { _, _ -> }, - - if (app.profile.canShare) - sharedEventsItem - else - null - ) - - override fun getItemsMore() = listOfNotNull( - util.createActionItem( - text = R.string.settings_register_bell_sync_text, - icon = SzkolnyFont.Icon.szf_alarm_bell_outline, - onClick = { - BellSyncConfigDialog(activity, onChangeListener = { - it.subText = getBellSync() - util.refresh() - }).show() + if (value) + dialog.showEnableDialog() + else + dialog.showDisableDialog() + false } - ).also { - it.subText = getBellSync() - }, + ) { _, _ -> }, - util.createPropertyItem( - text = R.string.settings_register_count_in_seconds_text, - subText = R.string.settings_register_count_in_seconds_subtext, - icon = CommunityMaterial.Icon3.cmd_timer_outline, - value = configGlobal.timetable.countInSeconds - ) { _, it -> - configGlobal.timetable.countInSeconds = it - }, - - if (app.profile.loginStoreType == LOGIN_TYPE_LIBRUS) - util.createPropertyItem( - text = R.string.settings_register_show_teacher_absences_text, - icon = CommunityMaterial.Icon.cmd_account_arrow_right_outline, - value = app.profile.getStudentData("showTeacherAbsences", true) - ) { _, it -> - app.profile.putStudentData("showTeacherAbsences", it) - app.profileSave() - } - else - null, - - if (App.devMode) - util.createPropertyItem( - text = R.string.settings_register_hide_sticks_from_old, - icon = CommunityMaterial.Icon3.cmd_numeric_1_box_outline, - value = configProfile.grades.hideSticksFromOld - ) { _, it -> - configProfile.grades.hideSticksFromOld = it - } - else - null - ) + sharedEventsDefaultItem.takeIf { app.profile.canShare }, + ).toTypedArray() } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/settings/cards/SettingsSyncCard.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/settings/cards/SettingsSyncCard.kt index 13384e75..a8a68da1 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/settings/cards/SettingsSyncCard.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/settings/cards/SettingsSyncCard.kt @@ -10,6 +10,7 @@ import android.net.Uri import android.os.Build.VERSION.SDK_INT import android.os.Build.VERSION_CODES import android.provider.Settings +import com.danielstone.materialaboutlibrary.model.MaterialAboutCard import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial import pl.szczodrzynski.edziennik.MainActivity import pl.szczodrzynski.edziennik.R @@ -17,6 +18,7 @@ import pl.szczodrzynski.edziennik.ext.after import pl.szczodrzynski.edziennik.ext.getSyncInterval import pl.szczodrzynski.edziennik.sync.SyncWorker import pl.szczodrzynski.edziennik.sync.UpdateWorker +import pl.szczodrzynski.edziennik.ui.base.enums.NavTarget import pl.szczodrzynski.edziennik.ui.dialogs.settings.NotificationFilterDialog import pl.szczodrzynski.edziennik.ui.dialogs.settings.QuietHoursConfigDialog import pl.szczodrzynski.edziennik.ui.dialogs.settings.SyncIntervalDialog @@ -28,8 +30,8 @@ class SettingsSyncCard(util: SettingsUtil) : SettingsCard(util) { override fun buildCard() = util.createCard( R.string.settings_card_sync_title, - items = getItems(), - itemsMore = getItemsMore() + items = ::getItems, + itemsMore = ::getItemsMore, ) private fun getQuietHours(): String { @@ -62,7 +64,7 @@ class SettingsSyncCard(util: SettingsUtil) : SettingsCard(util) { } } - override fun getItems() = listOfNotNull( + override fun getItems(card: MaterialAboutCard) = listOfNotNull( util.createPropertyActionItem( text = R.string.settings_sync_sync_interval_text, subText = R.string.settings_sync_sync_interval_subtext_disabled, @@ -151,11 +153,11 @@ class SettingsSyncCard(util: SettingsUtil) : SettingsCard(util) { subText = R.string.settings_sync_web_push_subtext, icon = CommunityMaterial.Icon2.cmd_laptop ) { - activity.loadTarget(MainActivity.TARGET_WEB_PUSH) + activity.navigate(navTarget = NavTarget.WEB_PUSH) } ) - override fun getItemsMore() = listOfNotNull( + override fun getItemsMore(card: MaterialAboutCard) = listOfNotNull( util.createPropertyItem( text = R.string.settings_sync_updates_text, icon = CommunityMaterial.Icon.cmd_cellphone_arrow_down, diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/settings/cards/SettingsThemeCard.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/settings/cards/SettingsThemeCard.kt index 59c2590f..ccf884fa 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/settings/cards/SettingsThemeCard.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/settings/cards/SettingsThemeCard.kt @@ -4,6 +4,7 @@ package pl.szczodrzynski.edziennik.ui.settings.cards +import com.danielstone.materialaboutlibrary.model.MaterialAboutCard import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial import pl.szczodrzynski.edziennik.R @@ -20,11 +21,11 @@ class SettingsThemeCard(util: SettingsUtil) : SettingsCard(util) { override fun buildCard() = util.createCard( R.string.settings_card_theme_title, - items = getItems(), - itemsMore = getItemsMore() + items = ::getItems, + itemsMore = ::getItemsMore, ) - override fun getItems() = listOfNotNull( + override fun getItems(card: MaterialAboutCard) = listOfNotNull( if (Date.getToday().month % 11 == 1) // cool math games util.createPropertyItem( text = R.string.settings_theme_snowfall_text, @@ -76,7 +77,7 @@ class SettingsThemeCard(util: SettingsUtil) : SettingsCard(util) { } ) - override fun getItemsMore() = listOf( + override fun getItemsMore(card: MaterialAboutCard) = listOf( util.createActionItem( text = R.string.settings_theme_mini_drawer_buttons_text, icon = CommunityMaterial.Icon2.cmd_format_list_checks diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/teachers/TeachersAdapter.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/teachers/TeachersAdapter.kt index 2edfa8cc..28a6541c 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/teachers/TeachersAdapter.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/teachers/TeachersAdapter.kt @@ -21,6 +21,7 @@ import pl.szczodrzynski.edziennik.data.db.entity.Subject import pl.szczodrzynski.edziennik.data.db.entity.Teacher import pl.szczodrzynski.edziennik.databinding.TeacherItemBinding import pl.szczodrzynski.edziennik.ext.* +import pl.szczodrzynski.edziennik.ui.base.enums.NavTarget import kotlin.coroutines.CoroutineContext class TeachersAdapter( @@ -67,7 +68,7 @@ class TeachersAdapter( b.sendMessage.onClick { val intent = Intent( Intent.ACTION_MAIN, - "fragmentId" to MainActivity.TARGET_MESSAGES_COMPOSE, + "fragmentId" to NavTarget.MESSAGE_COMPOSE, "messageRecipientId" to item.id ) activity.sendBroadcast(intent) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/template/TemplateAdapter.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/template/TemplateAdapter.kt index 7ff8b162..9e5300cb 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/template/TemplateAdapter.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/template/TemplateAdapter.kt @@ -54,7 +54,7 @@ class TemplateAdapter( " • ", date ).concat().asColoredSpannable(colorSecondary) - b.type.text = activity.getNotificationTitle(item.type) + b.type.text = item.type.titleRes.resolveString(app) onItemClick?.let { listener -> b.root.onClick { listener(item) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/timetable/GenerateBlockTimetableDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/timetable/GenerateBlockTimetableDialog.kt index e76180b6..d4b75462 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/timetable/GenerateBlockTimetableDialog.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/timetable/GenerateBlockTimetableDialog.kt @@ -31,6 +31,7 @@ import pl.szczodrzynski.edziennik.data.api.events.ApiTaskAllFinishedEvent import pl.szczodrzynski.edziennik.data.api.events.ApiTaskErrorEvent import pl.szczodrzynski.edziennik.data.api.events.ApiTaskFinishedEvent import pl.szczodrzynski.edziennik.data.db.entity.Lesson +import pl.szczodrzynski.edziennik.data.db.enums.FeatureType import pl.szczodrzynski.edziennik.data.db.full.LessonFull import pl.szczodrzynski.edziennik.databinding.DialogGenerateBlockTimetableBinding import pl.szczodrzynski.edziennik.ext.* @@ -108,12 +109,10 @@ class GenerateBlockTimetableDialog( .show() dialog.getButton(AlertDialog.BUTTON_POSITIVE)?.onClick { - app.permissionManager.requestStoragePermission(activity, permissionMessage = R.string.permissions_generate_timetable) { - when (b.weekSelectionRadioGroup.checkedRadioButtonId) { - R.id.withChangesCurrentWeekRadio -> generateBlockTimetable(weekCurrentStart, weekCurrentEnd) - R.id.withChangesNextWeekRadio -> generateBlockTimetable(weekNextStart, weekNextEnd) - R.id.forSelectedWeekRadio -> selectDate() - } + when (b.weekSelectionRadioGroup.checkedRadioButtonId) { + R.id.withChangesCurrentWeekRadio -> generateBlockTimetable(weekCurrentStart, weekCurrentEnd) + R.id.withChangesNextWeekRadio -> generateBlockTimetable(weekNextStart, weekNextEnd) + R.id.forSelectedWeekRadio -> selectDate() } } }} @@ -202,9 +201,7 @@ class GenerateBlockTimetableDialog( EdziennikTask.syncProfile( profileId = App.profileId, - viewIds = listOf( - MainActivity.DRAWER_ITEM_TIMETABLE to 0 - ), + featureTypes = setOf(FeatureType.TIMETABLE), arguments = JsonObject( "weekStart" to weekStart.stringY_m_d ) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/timetable/TimetableDayFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/timetable/TimetableDayFragment.kt index dd3f1329..8f5da9fa 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/timetable/TimetableDayFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/timetable/TimetableDayFragment.kt @@ -4,6 +4,7 @@ package pl.szczodrzynski.edziennik.ui.timetable +import android.graphics.drawable.ColorDrawable import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -12,32 +13,61 @@ import android.widget.FrameLayout import android.widget.LinearLayout import android.widget.TextView import androidx.asynclayoutinflater.view.AsyncLayoutInflater -import androidx.core.view.* +import androidx.core.graphics.ColorUtils +import androidx.core.view.isVisible +import androidx.core.view.marginTop +import androidx.core.view.setPadding +import androidx.core.view.updateLayoutParams +import androidx.core.view.updateMargins import com.linkedin.android.tachyon.DayView import com.linkedin.android.tachyon.DayViewConfig import com.mikepenz.iconics.IconicsDrawable import com.mikepenz.iconics.utils.colorInt import com.mikepenz.iconics.utils.sizeDp -import kotlinx.coroutines.* -import pl.szczodrzynski.edziennik.* -import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_TIMETABLE +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.MainActivity +import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask import pl.szczodrzynski.edziennik.data.db.entity.Lesson +import pl.szczodrzynski.edziennik.data.db.enums.FeatureType import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull import pl.szczodrzynski.edziennik.data.db.full.EventFull import pl.szczodrzynski.edziennik.data.db.full.LessonFull import pl.szczodrzynski.edziennik.databinding.TimetableDayFragmentBinding import pl.szczodrzynski.edziennik.databinding.TimetableLessonBinding +import pl.szczodrzynski.edziennik.databinding.TimetableNoLessonsBinding import pl.szczodrzynski.edziennik.databinding.TimetableNoTimetableBinding -import pl.szczodrzynski.edziennik.ext.* +import pl.szczodrzynski.edziennik.ext.Intent +import pl.szczodrzynski.edziennik.ext.JsonObject +import pl.szczodrzynski.edziennik.ext.asColoredSpannable +import pl.szczodrzynski.edziennik.ext.asStrikethroughSpannable +import pl.szczodrzynski.edziennik.ext.concat +import pl.szczodrzynski.edziennik.ext.dp +import pl.szczodrzynski.edziennik.ext.findParentById +import pl.szczodrzynski.edziennik.ext.getStudentData +import pl.szczodrzynski.edziennik.ext.listOfNotEmpty +import pl.szczodrzynski.edziennik.ext.onClick +import pl.szczodrzynski.edziennik.ext.resolveAttr +import pl.szczodrzynski.edziennik.ext.resolveDrawable +import pl.szczodrzynski.edziennik.ext.setText +import pl.szczodrzynski.edziennik.ext.setTintColor +import pl.szczodrzynski.edziennik.ext.startCoroutineTimer import pl.szczodrzynski.edziennik.ui.base.lazypager.LazyFragment import pl.szczodrzynski.edziennik.ui.timetable.TimetableFragment.Companion.DEFAULT_END_HOUR import pl.szczodrzynski.edziennik.ui.timetable.TimetableFragment.Companion.DEFAULT_START_HOUR +import pl.szczodrzynski.edziennik.utils.Colors import pl.szczodrzynski.edziennik.utils.managers.NoteManager import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Time -import java.util.* +import pl.szczodrzynski.edziennik.utils.models.Week +import pl.szczodrzynski.edziennik.utils.mutableLazy import kotlin.coroutines.CoroutineContext +import kotlin.math.max import kotlin.math.min class TimetableDayFragment : LazyFragment(), CoroutineScope { @@ -72,12 +102,12 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope { // find SwipeRefreshLayout in the hierarchy private val refreshLayout by lazy { view?.findParentById(R.id.refreshLayout) } - private val dayView by lazy { + private val dayViewDelegate = mutableLazy { val dayView = DayView(activity, DayViewConfig( startHour = startHour, endHour = endHour, dividerHeight = 1.dp, - halfHourHeight = 60.dp, + halfHourHeight = app.data.uiConfig.lessonHeight.dp, hourDividerColor = R.attr.hourDividerColor.resolveAttr(context), halfHourDividerColor = R.attr.halfHourDividerColor.resolveAttr(context), hourLabelWidth = 40.dp, @@ -85,8 +115,9 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope { eventMargin = 2.dp ), true) dayView.setPadding(10.dp) - return@lazy dayView + return@mutableLazy dayView } + private val dayView by dayViewDelegate override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { activity = (getActivity() as MainActivity?) ?: return null @@ -137,9 +168,7 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope { it.isEnabled = false EdziennikTask.syncProfile( profileId = App.profileId, - viewIds = listOf( - DRAWER_ITEM_TIMETABLE to 0 - ), + featureTypes = setOf(FeatureType.TIMETABLE), arguments = JsonObject( "weekStart" to weekStart ) @@ -155,6 +184,20 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope { b.root.removeAllViews() b.root.addView(view) viewsRemoved = true + + val b = TimetableNoLessonsBinding.bind(view) + val weekStart = date.weekStart.stringY_m_d + b.noLessonsSync.onClick { + it.isEnabled = false + EdziennikTask.syncProfile( + profileId = App.profileId, + featureTypes = setOf(FeatureType.TIMETABLE), + arguments = JsonObject( + "weekStart" to weekStart + ) + ).enqueue(activity) + } + b.noLessonsSync.isVisible = date.weekDay !in Week.SATURDAY..Week.SUNDAY } return } @@ -173,8 +216,26 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope { return } + if (dayViewDelegate.isInitialized()) + b.dayFrame.removeView(dayView) + + val lessonsActual = lessons.filter { it.type != Lesson.TYPE_NO_LESSONS } + + val minStartHour = lessonsActual.minOf { it.displayStartTime?.hour ?: DEFAULT_END_HOUR } + val maxEndHour = lessonsActual.maxOf { it.displayEndTime?.hour?.plus(1) ?: DEFAULT_START_HOUR } + + if (app.profile.config.ui.timetableTrimHourRange) { + dayViewDelegate.deinitialize() + // end/start defaults are swapped on purpose + startHour = minStartHour + endHour = maxEndHour + } else if (startHour > minStartHour || endHour < maxEndHour) { + dayViewDelegate.deinitialize() + startHour = min(startHour, minStartHour) + endHour = max(endHour, maxEndHour) + } + b.scrollView.isVisible = true - b.dayFrame.removeView(dayView) b.dayFrame.addView(dayView, 0) // Inflate a label view for each hour the day view will display @@ -195,7 +256,7 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope { lessons.forEach { it.showAsUnseen = !it.seen } - buildLessonViews(lessons.filter { it.type != Lesson.TYPE_NO_LESSONS }, events, attendanceList) + buildLessonViews(lessonsActual, events, attendanceList) } private fun buildLessonViews(lessons: List, events: List, attendanceList: List) { @@ -215,7 +276,10 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope { val colorSecondary = android.R.attr.textColorSecondary.resolveAttr(activity) for (lesson in lessons) { - val attendance = attendanceList.find { it.startTime == lesson.startTime } + val attendance = if (app.profile.config.ui.timetableShowAttendance) + attendanceList.find { it.startTime == lesson.startTime } + else + null val startTime = lesson.displayStartTime ?: continue val endTime = lesson.displayEndTime ?: continue @@ -245,23 +309,20 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope { } } - val eventList = events.filter { it.time != null && it.time == lesson.displayStartTime }.take(3) - eventList.getOrNull(0).let { - lb.event1.visibility = if (it == null) View.GONE else View.VISIBLE - lb.event1.background = it?.let { - R.drawable.bg_circle.resolveDrawable(activity).setTintColor(it.eventColor) + val eventIcons = listOf(lb.event1, lb.event2, lb.event3) + if (app.profile.config.ui.timetableShowEvents) { + val eventList = events.filter { it.time != null && it.time == lesson.displayStartTime }.take(3) + for ((i, eventIcon) in eventIcons.withIndex()) { + eventList.getOrNull(i).let { + eventIcon.isVisible = it != null + eventIcon.background = it?.let { + R.drawable.bg_circle.resolveDrawable(activity).setTintColor(it.eventColor) + } + } } - } - eventList.getOrNull(1).let { - lb.event2.visibility = if (it == null) View.GONE else View.VISIBLE - lb.event2.background = it?.let { - R.drawable.bg_circle.resolveDrawable(activity).setTintColor(it.eventColor) - } - } - eventList.getOrNull(2).let { - lb.event3.visibility = if (it == null) View.GONE else View.VISIBLE - lb.event3.background = it?.let { - R.drawable.bg_circle.resolveDrawable(activity).setTintColor(it.eventColor) + } else { + for (eventIcon in eventIcons) { + eventIcon.visibility = View.GONE } } @@ -292,13 +353,36 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope { lesson.classroom?.let { add(it) } }.concat() + lb.annotationVisible = manager.getAnnotation(activity, lesson, lb.annotation) - lb.lessonNumber = lesson.displayLessonNumber val lessonText = lesson.getNoteSubstituteText(showNotes = true) ?: lesson.displaySubjectName + + val (subjectTextPrimary, subjectTextSecondary) = if (app.profile.config.ui.timetableColorSubjectName) { + val subjectColor = lesson.color ?: Colors.stringToMaterialColorCRC(lessonText?.toString() ?: "") + if (lb.annotationVisible) { + lb.subjectContainer.background = ColorDrawable(subjectColor) + } else { + lb.subjectContainer.setBackgroundResource(R.drawable.timetable_subject_color_rounded) + lb.subjectContainer.background.setTintColor(subjectColor) + } + when (ColorUtils.calculateLuminance(subjectColor) > 0.5) { + true -> /* light */ 0xFF000000 to 0xFF666666 + false -> /* dark */ 0xFFFFFFFF to 0xFFAAAAAA + } + } else { + lb.subjectContainer.background = null + null to colorSecondary + } + + lb.lessonNumber = lesson.displayLessonNumber + if (subjectTextPrimary != null) + lb.lessonNumberText.setTextColor(subjectTextPrimary.toInt()) lb.subjectName.text = lessonText?.let { if (lesson.type == Lesson.TYPE_SHIFTED_SOURCE) it.asStrikethroughSpannable().asColoredSpannable(colorSecondary) + else if (subjectTextPrimary != null) + it.asColoredSpannable(subjectTextPrimary.toInt()) else it } @@ -329,9 +413,8 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope { // The day view needs the event time ranges in the start minute/end minute format, // so calculate those here - val startMinute = 60 * (lesson.displayStartTime?.hour - ?: 0) + (lesson.displayStartTime?.minute ?: 0) - val endMinute = startMinute + 45 + val startMinute = 60 * startTime.hour + startTime.minute + val endMinute = 60 * endTime.hour + endTime.minute eventTimeRanges.add(DayView.EventTimeRange(startMinute, endMinute)) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/timetable/TimetableFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/timetable/TimetableFragment.kt index b8a17871..d4e710ef 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/timetable/TimetableFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/timetable/TimetableFragment.kt @@ -19,13 +19,22 @@ import androidx.viewpager.widget.ViewPager import com.google.android.material.datepicker.MaterialDatePicker import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial import eu.szkolny.font.SzkolnyFont -import kotlinx.coroutines.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.async +import kotlinx.coroutines.launch import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.MainActivity import pl.szczodrzynski.edziennik.R -import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask +import pl.szczodrzynski.edziennik.data.db.enums.FeatureType +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType import pl.szczodrzynski.edziennik.databinding.FragmentTimetableV2Binding +import pl.szczodrzynski.edziennik.ext.JsonObject import pl.szczodrzynski.edziennik.ext.getSchoolYearConstrains +import pl.szczodrzynski.edziennik.ext.getStudentData +import pl.szczodrzynski.edziennik.ui.dialogs.settings.TimetableConfigDialog import pl.szczodrzynski.edziennik.ui.event.EventManualDialog import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem @@ -128,8 +137,8 @@ class TimetableFragment : Fragment(), CoroutineScope { } val lessonRanges = app.db.lessonRangeDao().getAllNow(App.profileId) - startHour = lessonRanges.map { it.startTime.hour }.minOrNull() ?: DEFAULT_START_HOUR - endHour = lessonRanges.map { it.endTime.hour }.maxOrNull()?.plus(1) ?: DEFAULT_END_HOUR + startHour = lessonRanges.minOfOrNull { it.startTime.hour } ?: DEFAULT_START_HOUR + endHour = lessonRanges.maxOfOrNull { it.endTime.hour }?.plus(1) ?: DEFAULT_END_HOUR } deferred.await() if (!isAdded) @@ -172,6 +181,21 @@ class TimetableFragment : Fragment(), CoroutineScope { b.viewPager.setCurrentItem(items.indexOfFirst { it.value == selectedDate?.value ?: today }, false) activity.navView.bottomSheet.prependItems( + BottomSheetPrimaryItem(true) + .withTitle(R.string.menu_timetable_sync) + .withIcon(CommunityMaterial.Icon.cmd_calendar_sync_outline) + .withOnClickListener { + activity.bottomSheet.close() + val date = pageSelection ?: Date.getToday() + val weekStart = date.weekStart.stringY_m_d + EdziennikTask.syncProfile( + profileId = App.profileId, + featureTypes = setOf(FeatureType.TIMETABLE), + arguments = JsonObject( + "weekStart" to weekStart + ) + ).enqueue(activity) + }, BottomSheetPrimaryItem(true) .withTitle(R.string.timetable_select_day) .withIcon(SzkolnyFont.Icon.szf_calendar_today_outline) @@ -208,13 +232,20 @@ class TimetableFragment : Fragment(), CoroutineScope { activity.bottomSheet.close() GenerateBlockTimetableDialog(activity) }), + BottomSheetPrimaryItem(true) + .withTitle(R.string.menu_timetable_config) + .withIcon(CommunityMaterial.Icon.cmd_cog_outline) + .withOnClickListener { + activity.bottomSheet.close() + TimetableConfigDialog(activity, false, null, null).show() + }, BottomSheetSeparatorItem(true), BottomSheetPrimaryItem(true) .withTitle(R.string.menu_mark_as_read) .withIcon(CommunityMaterial.Icon.cmd_eye_check_outline) .withOnClickListener(View.OnClickListener { activity.bottomSheet.close() - AsyncTask.execute { app.db.metadataDao().setAllSeen(App.profileId, Metadata.TYPE_LESSON_CHANGE, true) } + AsyncTask.execute { app.db.metadataDao().setAllSeen(App.profileId, MetadataType.LESSON_CHANGE, true) } Toast.makeText(activity, R.string.main_menu_mark_as_read_success, Toast.LENGTH_SHORT).show() }) ) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/views/AttachmentsView.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/views/AttachmentsView.kt index 7e2bc5af..81240d62 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/views/AttachmentsView.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/views/AttachmentsView.kt @@ -5,6 +5,7 @@ package pl.szczodrzynski.edziennik.ui.views import android.content.Context +import android.os.Build import android.os.Bundle import android.util.AttributeSet import androidx.appcompat.app.AppCompatActivity @@ -50,6 +51,10 @@ class AttachmentsView @JvmOverloads constructor( val attachmentSizes = arguments.getLongArray("attachmentSizes") val adapter = AttachmentAdapter(context, onAttachmentClick = { item -> + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + downloadAttachment(item) + return@AttachmentAdapter + } app.permissionManager.requestStoragePermission(activity, R.string.permissions_attachment) { downloadAttachment(item) } @@ -57,6 +62,10 @@ class AttachmentsView @JvmOverloads constructor( val popupMenu = PopupMenu(chip.context, chip) popupMenu.menu.add(0, 1, 0, R.string.messages_attachment_download_again) popupMenu.setOnMenuItemClickListener { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + downloadAttachment(item) + return@setOnMenuItemClickListener true + } app.permissionManager.requestStoragePermission(activity, R.string.permissions_attachment) { downloadAttachment(item, forceDownload = true) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/views/DateDropdown.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/views/DateDropdown.kt index a2a86d6b..69ccb26a 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/views/DateDropdown.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/views/DateDropdown.kt @@ -112,7 +112,7 @@ class DateDropdown : TextInputDropDown { date.stepForward(0, 0, -weekDay + 7) weekDay = 0 // ALL SCHOOL DAYS OF THE NEXT WEEK - while (weekDay < 4) { + while (weekDay < 5) { dates += Item( date.value.toLong(), context.getString(R.string.dialog_event_manual_date_next_week, Week.getFullDayName(weekDay), date.formattedString), diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/views/EventTypeDropdown.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/views/EventTypeDropdown.kt index f61ec9a4..48d327fc 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/views/EventTypeDropdown.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/views/EventTypeDropdown.kt @@ -33,12 +33,8 @@ class EventTypeDropdown : TextInputDropDown { suspend fun loadItems() { val types = withContext(Dispatchers.Default) { val list = mutableListOf() - - var types = db.eventTypeDao().getAllNow(profileId) - - if (types.none { it.id in -1L..10L }) { - types = db.eventTypeDao().addDefaultTypes(context, profileId) - } + val types = db.eventTypeDao().getAllNow(profileId) + .sortedBy { it.order } list += types.map { Item(it.id, it.name, tag = it, icon = IconicsDrawable(context).apply { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/widgets/LessonDialogActivity.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/widgets/LessonDialogActivity.kt index 32ee7d82..609657b5 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/widgets/LessonDialogActivity.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/widgets/LessonDialogActivity.kt @@ -13,6 +13,8 @@ import androidx.appcompat.app.AppCompatActivity import kotlinx.coroutines.* import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.MainActivity +import pl.szczodrzynski.edziennik.ext.Intent +import pl.szczodrzynski.edziennik.ui.base.enums.NavTarget import pl.szczodrzynski.edziennik.ui.timetable.LessonDetailsDialog import pl.szczodrzynski.edziennik.utils.Themes import kotlin.coroutines.CoroutineContext @@ -44,11 +46,12 @@ class LessonDialogActivity : AppCompatActivity(), CoroutineScope { val profileId = extras?.getInt("profileId") ?: return@async null if (extras.getBoolean("separatorItem", false)) { - val i = Intent(app, MainActivity::class.java) - .putExtra("fragmentId", MainActivity.DRAWER_ITEM_TIMETABLE) - .putExtra("profileId", profileId) - .putExtra("timetableDate", extras.getString("timetableDate", null)) - .addFlags(FLAG_ACTIVITY_REORDER_TO_FRONT or FLAG_ACTIVITY_NEW_TASK) + val i = Intent( + app, MainActivity::class.java, + "fragmentId" to NavTarget.TIMETABLE, + "profileId" to profileId, + "timetableDate" to extras.getString("timetableDate", null), + ).addFlags(FLAG_ACTIVITY_REORDER_TO_FRONT or FLAG_ACTIVITY_NEW_TASK) app.startActivity(i) finish() return@async null diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/widgets/WidgetConfigActivity.java b/app/src/main/java/pl/szczodrzynski/edziennik/ui/widgets/WidgetConfigActivity.java index 028bb482..d427e1b0 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/widgets/WidgetConfigActivity.java +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/widgets/WidgetConfigActivity.java @@ -29,6 +29,7 @@ import java.util.List; import pl.szczodrzynski.edziennik.App; import pl.szczodrzynski.edziennik.R; import pl.szczodrzynski.edziennik.data.db.entity.Profile; +import pl.szczodrzynski.edziennik.data.db.enums.LoginType; import pl.szczodrzynski.edziennik.databinding.DialogWidgetConfigBinding; import pl.szczodrzynski.edziennik.databinding.WidgetProfileDialogItemBinding; import pl.szczodrzynski.edziennik.ui.widgets.luckynumber.WidgetLuckyNumberProvider; @@ -84,7 +85,7 @@ public class WidgetConfigActivity extends Activity { opacity = 0.8f; AsyncTask.execute(() -> { - profileList = App.db.profileDao().getAllNow(); + profileList = App.Companion.getDb().profileDao().getAllNow(); profileList = filterOutArchived(profileList); if (widgetType == WIDGET_NOTIFICATIONS) @@ -102,7 +103,7 @@ public class WidgetConfigActivity extends Activity { profileList.add( new Profile(-1, 0, - 0, + LoginType.TEMPLATE, getString(R.string.widget_config_all_profiles), null, "", diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/widgets/luckynumber/WidgetLuckyNumberProvider.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/widgets/luckynumber/WidgetLuckyNumberProvider.kt index 40b40d5c..bde01d34 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/widgets/luckynumber/WidgetLuckyNumberProvider.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/widgets/luckynumber/WidgetLuckyNumberProvider.kt @@ -17,6 +17,8 @@ import pl.szczodrzynski.edziennik.MainActivity import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.ext.getJsonObject import pl.szczodrzynski.edziennik.ext.pendingIntentFlag +import pl.szczodrzynski.edziennik.ext.putExtras +import pl.szczodrzynski.edziennik.ui.base.enums.NavTarget import pl.szczodrzynski.edziennik.ui.widgets.WidgetConfig import pl.szczodrzynski.edziennik.utils.Utils import pl.szczodrzynski.edziennik.utils.models.Date @@ -81,7 +83,7 @@ class WidgetLuckyNumberProvider : AppWidgetProvider() { val openIntent = Intent(context, MainActivity::class.java) openIntent.action = Intent.ACTION_MAIN - openIntent.putExtra("fragmentId", MainActivity.DRAWER_ITEM_HOME) + openIntent.putExtras("fragmentId" to NavTarget.HOME) val openPendingIntent = PendingIntent.getActivity(context, 0, openIntent, pendingIntentFlag()) views.setOnClickPendingIntent(R.id.widgetLuckyNumberRoot, openPendingIntent) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/widgets/notifications/WidgetNotificationsFactory.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/widgets/notifications/WidgetNotificationsFactory.kt index 587837a8..11825724 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/widgets/notifications/WidgetNotificationsFactory.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/widgets/notifications/WidgetNotificationsFactory.kt @@ -14,10 +14,8 @@ import com.google.gson.JsonParser import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.data.db.entity.Notification -import pl.szczodrzynski.edziennik.ext.getInt -import pl.szczodrzynski.edziennik.ext.getLong -import pl.szczodrzynski.edziennik.ext.getNotificationTitle -import pl.szczodrzynski.edziennik.ext.getString +import pl.szczodrzynski.edziennik.data.db.enums.NotificationType +import pl.szczodrzynski.edziennik.ext.* import pl.szczodrzynski.edziennik.ui.widgets.WidgetConfig import pl.szczodrzynski.edziennik.utils.models.Date @@ -51,11 +49,11 @@ class WidgetNotificationsFactory(val app: App, val config: WidgetConfig) : Remot getString("title") ?: "", getString("text") ?: "", getString("textLong"), - getInt("type") ?: 0, + getInt("type")?.asNotificationTypeOrNull() ?: NotificationType.GENERAL, getInt("profileId"), getString("profileName"), getInt("posted") == 1, - getInt("viewId"), + getInt("viewId")?.asNavTargetOrNull(), getString("extras")?.let { JsonParser.parseString(it).asJsonObject }, getLong("addedDate") ?: System.currentTimeMillis() ) @@ -63,7 +61,7 @@ class WidgetNotificationsFactory(val app: App, val config: WidgetConfig) : Remot views.apply { setTextViewText(R.id.widgetNotificationsTitle, - app.getString(R.string.widget_notifications_title_format, notification.title, app.getNotificationTitle(notification.type))) + app.getString(R.string.widget_notifications_title_format, notification.title, notification.type.titleRes.resolveString(app))) setTextViewText(R.id.widgetNotificationsText, notification.text) setTextViewText(R.id.widgetNotificationsDate, Date.fromMillis(notification.addedDate).formattedString) setOnClickFillInIntent(R.id.widgetNotificationsRoot, Intent().also { notification.fillIntent(it) }) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/widgets/notifications/WidgetNotificationsProvider.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/widgets/notifications/WidgetNotificationsProvider.kt index dc4eb02e..a082c3da 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/widgets/notifications/WidgetNotificationsProvider.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/widgets/notifications/WidgetNotificationsProvider.kt @@ -23,7 +23,10 @@ import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.ext.Bundle import pl.szczodrzynski.edziennik.ext.getJsonObject import pl.szczodrzynski.edziennik.ext.pendingIntentFlag +import pl.szczodrzynski.edziennik.ext.pendingIntentMutable +import pl.szczodrzynski.edziennik.ext.putExtras import pl.szczodrzynski.edziennik.receivers.SzkolnyReceiver +import pl.szczodrzynski.edziennik.ui.base.enums.NavTarget import pl.szczodrzynski.edziennik.ui.widgets.WidgetConfig class WidgetNotificationsProvider : AppWidgetProvider() { @@ -48,7 +51,7 @@ class WidgetNotificationsProvider : AppWidgetProvider() { val syncIntent = SzkolnyReceiver.getIntent(context, Bundle( "task" to "SyncRequest" )) - val syncPendingIntent = PendingIntent.getBroadcast(context, 0, syncIntent, pendingIntentFlag()) + val syncPendingIntent = PendingIntent.getBroadcast(context, 0, syncIntent, pendingIntentMutable()) views.setOnClickPendingIntent(R.id.widgetNotificationsSync, syncPendingIntent) views.setImageViewBitmap( @@ -69,13 +72,13 @@ class WidgetNotificationsProvider : AppWidgetProvider() { val itemIntent = Intent(context, MainActivity::class.java) itemIntent.action = Intent.ACTION_MAIN - val itemPendingIntent = PendingIntent.getActivity(context, 0, itemIntent, pendingIntentFlag()) + val itemPendingIntent = PendingIntent.getActivity(context, appWidgetId, itemIntent, pendingIntentMutable()) views.setPendingIntentTemplate(R.id.widgetNotificationsListView, itemPendingIntent) val headerIntent = Intent(context, MainActivity::class.java) headerIntent.action = Intent.ACTION_MAIN - headerIntent.putExtra("fragmentId", MainActivity.DRAWER_ITEM_NOTIFICATIONS) - val headerPendingIntent = PendingIntent.getActivity(context, 0, headerIntent, pendingIntentFlag()) + headerIntent.putExtras("fragmentId" to NavTarget.NOTIFICATIONS) + val headerPendingIntent = PendingIntent.getActivity(context, appWidgetId, headerIntent, pendingIntentMutable()) views.setOnClickPendingIntent(R.id.widgetNotificationsHeader, headerPendingIntent) appWidgetManager.updateAppWidget(appWidgetId, views) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/widgets/timetable/WidgetTimetableProvider.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/widgets/timetable/WidgetTimetableProvider.kt index 60eeb6cc..a0be777c 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/widgets/timetable/WidgetTimetableProvider.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/widgets/timetable/WidgetTimetableProvider.kt @@ -25,13 +25,18 @@ import com.mikepenz.iconics.IconicsDrawable import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial import com.mikepenz.iconics.utils.colorInt import com.mikepenz.iconics.utils.sizeDp -import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.MainActivity +import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask import pl.szczodrzynski.edziennik.data.db.entity.Lesson import pl.szczodrzynski.edziennik.data.db.entity.Lesson.Companion.TYPE_NO_LESSONS import pl.szczodrzynski.edziennik.ext.filterOutArchived import pl.szczodrzynski.edziennik.ext.getJsonObject import pl.szczodrzynski.edziennik.ext.pendingIntentFlag +import pl.szczodrzynski.edziennik.ext.pendingIntentMutable +import pl.szczodrzynski.edziennik.ext.putExtras +import pl.szczodrzynski.edziennik.ui.base.enums.NavTarget import pl.szczodrzynski.edziennik.ui.widgets.LessonDialogActivity import pl.szczodrzynski.edziennik.ui.widgets.WidgetConfig import pl.szczodrzynski.edziennik.utils.models.Date @@ -115,7 +120,7 @@ class WidgetTimetableProvider : AppWidgetProvider() { 0, refreshIntent, PendingIntent.FLAG_UPDATE_CURRENT or pendingIntentFlag()) views.setOnClickPendingIntent(R.id.widgetTimetableRefresh, refreshPendingIntent) - views.setOnClickPendingIntent(R.id.widgetTimetableSync, getPendingSelfIntent(context, ACTION_SYNC_DATA)) + views.setViewVisibility(R.id.widgetTimetableSync, View.GONE) views.setImageViewBitmap( R.id.widgetTimetableRefresh, @@ -125,14 +130,6 @@ class WidgetTimetableProvider : AppWidgetProvider() { }.toBitmap() ) - views.setImageViewBitmap( - R.id.widgetTimetableSync, - IconicsDrawable(context, CommunityMaterial.Icon.cmd_download_outline).apply { - colorInt = Color.WHITE - sizeDp = if (config.bigStyle) 28 else 20 - }.toBitmap() - ) - prepareAppWidget(app, appWidgetId, views, config, bellSyncDiffMillis) appWidgetManager.updateAppWidget(appWidgetId, views) @@ -333,8 +330,11 @@ class WidgetTimetableProvider : AppWidgetProvider() { scrollPos = pos + 1 } + // remove notes from other profiles + lesson.filterNotes() // set the subject and classroom name - model.subjectName = lesson.displaySubjectName + model.subjectName = lesson.getNoteSubstituteText(showNotes = true) + ?: lesson.displaySubjectName model.classroomName = lesson.displayClassroom // set the bell sync to calculate progress in ListProvider @@ -391,11 +391,11 @@ class WidgetTimetableProvider : AppWidgetProvider() { headerIntent.putExtra("profileId", it) } displayingDate?.let { - headerIntent.putExtra("timetableDate", it.value) + headerIntent.putExtra("timetableDate", it.stringY_m_d) } } - headerIntent.putExtra("fragmentId", MainActivity.DRAWER_ITEM_TIMETABLE) - val headerPendingIntent = PendingIntent.getActivity(app, appWidgetId, headerIntent, pendingIntentFlag()) + headerIntent.putExtras("fragmentId" to NavTarget.TIMETABLE) + val headerPendingIntent = PendingIntent.getActivity(app, appWidgetId, headerIntent, pendingIntentMutable()) views.setOnClickPendingIntent(R.id.widgetTimetableHeader, headerPendingIntent) timetables!!.put(appWidgetId, models) @@ -409,7 +409,7 @@ class WidgetTimetableProvider : AppWidgetProvider() { // create an intent used to display the lesson details dialog val itemIntent = Intent(app, LessonDialogActivity::class.java) itemIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK/* or Intent.FLAG_ACTIVITY_CLEAR_TASK*/) - val itemPendingIntent = PendingIntent.getActivity(app, appWidgetId, itemIntent, PendingIntent.FLAG_MUTABLE) + val itemPendingIntent = PendingIntent.getActivity(app, appWidgetId, itemIntent, pendingIntentMutable()) views.setPendingIntentTemplate(R.id.widgetTimetableListView, itemPendingIntent) if (!unified) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/utils/BetterLink.kt b/app/src/main/java/pl/szczodrzynski/edziennik/utils/BetterLink.kt index 0665704d..ee6a3f65 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/utils/BetterLink.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/utils/BetterLink.kt @@ -24,10 +24,9 @@ import androidx.appcompat.view.menu.MenuPopupHelper import androidx.core.widget.addTextChangedListener import pl.szczodrzynski.edziennik.* import pl.szczodrzynski.edziennik.data.api.Regexes -import pl.szczodrzynski.edziennik.ext.Intent -import pl.szczodrzynski.edziennik.ext.copyToClipboard -import pl.szczodrzynski.edziennik.ext.get -import pl.szczodrzynski.edziennik.ext.getTextPosition +import pl.szczodrzynski.edziennik.data.db.enums.FeatureType +import pl.szczodrzynski.edziennik.ext.* +import pl.szczodrzynski.edziennik.ui.base.enums.NavTarget import pl.szczodrzynski.edziennik.utils.models.Date @SuppressLint("RestrictedApi") @@ -107,6 +106,8 @@ object BetterLink { } private fun createTeacherItems(menu: MenuBuilder, context: Context, teacherId: Long, fullName: String) { + if (!(context.applicationContext as App).profile.hasFeature(FeatureType.MESSAGES_INBOX)) + return menu.setTitle(fullName) menu.add( 1, @@ -116,7 +117,7 @@ object BetterLink { ).setOnMenuItemClickListener { val intent = Intent( Intent.ACTION_MAIN, - "fragmentId" to MainActivity.TARGET_MESSAGES_COMPOSE, + "fragmentId" to NavTarget.MESSAGE_COMPOSE, "messageRecipientId" to teacherId ) context.sendBroadcast(intent) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/utils/Colors.java b/app/src/main/java/pl/szczodrzynski/edziennik/utils/Colors.java index 1f6c7ad3..39371516 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/utils/Colors.java +++ b/app/src/main/java/pl/szczodrzynski/edziennik/utils/Colors.java @@ -15,6 +15,7 @@ import androidx.core.graphics.ColorUtils; import java.security.MessageDigest; import java.util.Arrays; import java.util.Random; +import java.util.zip.CRC32; import pl.szczodrzynski.edziennik.data.db.entity.Grade; @@ -84,6 +85,28 @@ public class Colors { 0xFF6D4C41 }; + public static final int[] materialColorsBasic = { + 0xFFE53935, 0xFFD81B60, 0xFF8E24AA, 0xFF5E35B1, + 0xFF3949AB, 0xFF1E88E5, 0xFF039BE5, 0xFF00ACC1, + 0xFF00897B, 0xFF43A047, 0xFF7CB342, 0xFFC0CA33, + 0xFFFDD835, 0xFFFFB300, 0xFFFB8C00, 0xFFF4511E, + 0xFF6D4C41, 0xFF757575, 0xFF546E7A, 0xFF00E676, + + 0xFFEF9A9A, 0xFFF48FB1, 0xFFCE93D8, 0xFFB39DDB, + 0xFF9FA8DA, 0xFF90CAF9, 0xFF81D4FA, 0xFF80DEEA, + 0xFF80CBC4, 0xFFA5D6A7, 0xFFC5E1A5, 0xFFE6EE9C, + 0xFFFFF59D, 0xFFFFE082, 0xFFFFCC80, 0xFFFFAB91, + 0xFFBCAAA4, 0xFFEEEEEE, 0xFFB0BEC5, 0xFFCCFF90, + }; + + public static final int[] metroColors = { + 0xFF76FF03, 0xFF60A917, 0xFF00C853, 0xFF00ABA9, + 0xFF1BA1E2, 0xFF0050EF, 0xFF6A00FF, 0xFFAA00FF, + 0xFFF472D0, 0xFFD80073, 0xFFA20025, 0xFFE51400, + 0xFFFA6800, 0xFFF0A30A, 0xFFE3C800, 0xFF795548, + 0xFF6D8764, 0xFF647687, 0xFF76608A, 0xFFA0522D, + }; + /** * Used for teacher's images (e.g. in messages or announcements). * @param s teacher's fullName @@ -115,6 +138,18 @@ public class Colors { return materialColors[getRandomNumberInRange(0, materialColors.length-1, seed)]; } + public static int stringToMaterialColorCRC(String s) { + long seed; + try { + CRC32 crc = new CRC32(); + crc.update(s.getBytes()); + seed = crc.getValue(); + } catch (Exception e) { + seed = 1234; + } + return metroColors[(int) (seed % metroColors.length)]; + } + public static int gradeToColor(Grade grade) { if (grade.getType() == Grade.TYPE_POINT_SUM) { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/utils/MutableLazy.kt b/app/src/main/java/pl/szczodrzynski/edziennik/utils/MutableLazy.kt index cf000e1f..f742c185 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/utils/MutableLazy.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/utils/MutableLazy.kt @@ -19,7 +19,6 @@ class MutableLazyImpl(initializer: () -> T, lock: Any? = null) { return synchronized(lock) { val typedValue = initializer!!() _value = typedValue - initializer = null typedValue } } @@ -29,7 +28,11 @@ class MutableLazyImpl(initializer: () -> T, lock: Any? = null) { fun isInitialized() = _value !== UNINITIALIZED_VALUE + fun deinitialize() { + _value = UNINITIALIZED_VALUE + } + override fun toString() = if (isInitialized()) _value.toString() else "ChangeableLazy value not initialized yet." } -fun mutableLazy(initializer: () -> T): MutableLazyImpl = MutableLazyImpl(initializer) \ No newline at end of file +fun mutableLazy(initializer: () -> T): MutableLazyImpl = MutableLazyImpl(initializer) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/utils/PausedNavigationData.kt b/app/src/main/java/pl/szczodrzynski/edziennik/utils/PausedNavigationData.kt index 83d7ae96..b5975089 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/utils/PausedNavigationData.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/utils/PausedNavigationData.kt @@ -5,17 +5,10 @@ package pl.szczodrzynski.edziennik.utils import android.os.Bundle +import pl.szczodrzynski.edziennik.ui.base.enums.NavTarget -open class PausedNavigationData { - - data class LoadProfile( - val id: Int, - val drawerSelection: Int, - val arguments: Bundle?, - ) : PausedNavigationData() - - data class LoadTarget( - val id: Int, - val arguments: Bundle?, - ) : PausedNavigationData() -} +data class PausedNavigationData( + val profileId: Int?, + val navTarget: NavTarget?, + val args: Bundle?, +) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/utils/Utils.java b/app/src/main/java/pl/szczodrzynski/edziennik/utils/Utils.java index d4ffae05..f039952d 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/utils/Utils.java +++ b/app/src/main/java/pl/szczodrzynski/edziennik/utils/Utils.java @@ -774,14 +774,21 @@ public class Utils { private static File storageDir = null; public static File getStorageDir() { - if (storageDir != null) - return storageDir; - storageDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); - storageDir = new File(storageDir, "Szkolny.eu"); - storageDir.mkdirs(); return storageDir; } + public static void initializeStorageDir(Context context) { + if (storageDir != null) + return; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + storageDir = context.getExternalFilesDir(null); + } else { + storageDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); + storageDir = new File(storageDir, "Szkolny.eu"); + } + storageDir.mkdirs(); + } + public static void writeStringToFile(File file, String data) throws IOException { OutputStreamWriter outputStreamWriter = new OutputStreamWriter(new FileOutputStream(file)); outputStreamWriter.write(data); diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/AttendanceManager.kt b/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/AttendanceManager.kt index fa62e695..5b112af9 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/AttendanceManager.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/AttendanceManager.kt @@ -24,7 +24,7 @@ class AttendanceManager(val app: App) : CoroutineScope { get() = job + Dispatchers.Default val useSymbols - get() = app.config.forProfile().attendance.useSymbols + get() = app.profile.config.attendance.useSymbols fun getTypeShort(baseType: Int): String { return when (baseType) { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/AvailabilityManager.kt b/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/AvailabilityManager.kt index 83d579d8..2c989a5a 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/AvailabilityManager.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/AvailabilityManager.kt @@ -11,6 +11,7 @@ import pl.szczodrzynski.edziennik.data.api.models.ApiError import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi import pl.szczodrzynski.edziennik.data.api.szkolny.response.RegisterAvailabilityStatus import pl.szczodrzynski.edziennik.data.db.entity.Profile +import pl.szczodrzynski.edziennik.data.db.enums.LoginType import pl.szczodrzynski.edziennik.ext.currentTimeUnix import pl.szczodrzynski.edziennik.ext.toApiError @@ -48,17 +49,8 @@ class AvailabilityManager(val app: App) { return check(profile.registerName, cacheOnly) } - fun check(loginType: Int, cacheOnly: Boolean = false): Error? { - val registerName = when (loginType) { - LOGIN_TYPE_LIBRUS -> "librus" - LOGIN_TYPE_VULCAN -> "vulcan" - LOGIN_TYPE_IDZIENNIK -> "idziennik" - LOGIN_TYPE_MOBIDZIENNIK -> "mobidziennik" - LOGIN_TYPE_PODLASIE -> "podlasie" - LOGIN_TYPE_EDUDZIENNIK -> "edudziennik" - else -> "unknown" - } - return check(registerName, cacheOnly) + fun check(loginType: LoginType, cacheOnly: Boolean = false): Error? { + return check(loginType.name.lowercase(), cacheOnly) } fun check(registerName: String, cacheOnly: Boolean = false): Error? { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/BuildManager.kt b/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/BuildManager.kt index 1c1e2dbe..2cbf4f23 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/BuildManager.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/BuildManager.kt @@ -9,11 +9,29 @@ import android.text.TextUtils import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import com.google.android.material.dialog.MaterialAlertDialogBuilder -import kotlinx.coroutines.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import okhttp3.Request -import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.BuildConfig +import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.data.api.szkolny.interceptor.Signing -import pl.szczodrzynski.edziennik.ext.* +import pl.szczodrzynski.edziennik.data.api.szkolny.response.Update +import pl.szczodrzynski.edziennik.ext.Intent +import pl.szczodrzynski.edziennik.ext.asBoldSpannable +import pl.szczodrzynski.edziennik.ext.asColoredSpannable +import pl.szczodrzynski.edziennik.ext.concat +import pl.szczodrzynski.edziennik.ext.getJsonObject +import pl.szczodrzynski.edziennik.ext.getString +import pl.szczodrzynski.edziennik.ext.isNotNullNorBlank +import pl.szczodrzynski.edziennik.ext.join +import pl.szczodrzynski.edziennik.ext.md5 +import pl.szczodrzynski.edziennik.ext.resolveAttr +import pl.szczodrzynski.edziennik.ext.resolveColor +import pl.szczodrzynski.edziennik.ext.toJsonObject import pl.szczodrzynski.edziennik.ui.base.BuildInvalidActivity import pl.szczodrzynski.edziennik.utils.Utils import pl.szczodrzynski.edziennik.utils.Utils.d @@ -75,6 +93,14 @@ class BuildManager(val app: App) : CoroutineScope { else -> null } + val releaseType = when { + isNightly || isDaily -> Update.Type.NIGHTLY + BuildConfig.VERSION_BASE.endsWith("-dev") -> Update.Type.DEV + BuildConfig.VERSION_BASE.contains("-beta.") -> Update.Type.BETA + BuildConfig.VERSION_BASE.contains("-rc.") -> Update.Type.RC + else -> Update.Type.RELEASE + } + fun showVersionDialog(activity: AppCompatActivity) { val yes = activity.getString(R.string.yes) val no = activity.getString(R.string.no) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/EventManager.kt b/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/EventManager.kt index 62ca88a1..38a8fee3 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/EventManager.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/EventManager.kt @@ -48,6 +48,7 @@ class EventManager(val app: App) : CoroutineScope { title: TextView, event: EventFull, showType: Boolean = true, + showSubject: Boolean = false, showNotes: Boolean = true, doneIconColor: Int? = null ) { @@ -55,10 +56,12 @@ class EventManager(val app: App) : CoroutineScope { val hasReplacingNotes = event.hasReplacingNotes() title.text = listOfNotNull( - if (event.addedManually) "{cmd-clipboard-edit-outline} " else null, + if (event.addedManually && !event.isSharedReceived) "{cmd-calendar-edit} " else null, + if (event.isSharedReceived) "{cmd-share-variant} " else null, if (event.hasNotes() && hasReplacingNotes && showNotes) "{cmd-swap-horizontal} " else null, if (event.hasNotes() && !hasReplacingNotes && showNotes) "{cmd-playlist-edit} " else null, if (showType) "${event.typeName ?: "wydarzenie"} - " else null, + if (showSubject) event.subjectLongName?.plus(" - ") else null, topicSpan, ).concat() @@ -77,6 +80,8 @@ class EventManager(val app: App) : CoroutineScope { fun setLegendText(legend: IconicsTextView, event: EventFull, showNotes: Boolean = true) { legend.text = listOfNotNull( if (event.addedManually) R.string.legend_event_added_manually else null, + if (event.isSharedSent) R.string.legend_event_shared_sent else null, + if (event.isSharedReceived) R.string.legend_event_shared_received else null, if (event.isDone) R.string.legend_event_is_done else null, if (showNotes) NoteManager.getLegendText(event) else null, ).map { legend.context.getString(it) }.join("\n") diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/GradesManager.kt b/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/GradesManager.kt index 9b92e6ba..6ae0cb31 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/GradesManager.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/GradesManager.kt @@ -17,7 +17,13 @@ import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_POINT_AVG import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_POINT_SUM import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_YEAR_FINAL import pl.szczodrzynski.edziennik.data.db.full.GradeFull -import pl.szczodrzynski.edziennik.ext.* +import pl.szczodrzynski.edziennik.ext.asColoredSpannable +import pl.szczodrzynski.edziennik.ext.get +import pl.szczodrzynski.edziennik.ext.ifNotNull +import pl.szczodrzynski.edziennik.ext.notEmptyOrNull +import pl.szczodrzynski.edziennik.ext.plural +import pl.szczodrzynski.edziennik.ext.resolveAttr +import pl.szczodrzynski.edziennik.ext.startCoroutineTimer import pl.szczodrzynski.edziennik.ui.grades.models.GradesAverages import pl.szczodrzynski.edziennik.ui.grades.models.GradesSemester import java.text.DecimalFormat @@ -49,21 +55,21 @@ class GradesManager(val app: App) : CoroutineScope { val orderBy get() = app.config.grades.orderBy val yearAverageMode - get() = app.config.forProfile().grades.yearAverageMode + get() = app.profile.config.grades.yearAverageMode val colorMode - get() = app.config.forProfile().grades.colorMode + get() = app.profile.config.grades.colorMode val plusValue - get() = app.config.forProfile().grades.plusValue + get() = app.profile.config.grades.plusValue val minusValue - get() = app.config.forProfile().grades.minusValue + get() = app.profile.config.grades.minusValue val dontCountEnabled - get() = app.config.forProfile().grades.dontCountEnabled + get() = app.profile.config.grades.dontCountEnabled val dontCountGrades - get() = app.config.forProfile().grades.dontCountGrades + get() = app.profile.config.grades.dontCountGrades val hideImproved - get() = app.config.forProfile().grades.hideImproved + get() = app.profile.config.grades.hideImproved val averageWithoutWeight - get() = app.config.forProfile().grades.averageWithoutWeight + get() = app.profile.config.grades.averageWithoutWeight fun getOrderByString() = when (orderBy) { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/MessageManager.kt b/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/MessageManager.kt index 996ac3bf..6b0b66fa 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/MessageManager.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/MessageManager.kt @@ -21,6 +21,7 @@ import pl.szczodrzynski.edziennik.data.db.entity.Message import pl.szczodrzynski.edziennik.data.db.entity.MessageRecipient import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.data.db.entity.Teacher +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType import pl.szczodrzynski.edziennik.data.db.full.MessageFull import pl.szczodrzynski.edziennik.ext.appendSpan import pl.szczodrzynski.edziennik.ext.appendText @@ -149,7 +150,7 @@ class MessageManager(private val app: App) { withContext(Dispatchers.Default) { app.db.messageRecipientDao().clearFor(profileId, messageId) app.db.messageDao().delete(profileId, messageId) - app.db.metadataDao().delete(profileId, Metadata.TYPE_MESSAGE, messageId) + app.db.metadataDao().delete(profileId, MetadataType.MESSAGE, messageId) } } @@ -172,7 +173,7 @@ class MessageManager(private val app: App) { senderId = -1L, addedDate = System.currentTimeMillis(), ) - val metadata = Metadata(profileId, Metadata.TYPE_MESSAGE, message.id, true, true) + val metadata = Metadata(profileId, MetadataType.MESSAGE, message.id, true, true) val recipients = teachers.map { MessageRecipient(profileId, it.id, message.id) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/NoteManager.kt b/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/NoteManager.kt index 7c25c129..50ac84cd 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/NoteManager.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/NoteManager.kt @@ -80,7 +80,7 @@ class NoteManager(private val app: App) { OwnerType.GRADE -> app.db.gradeDao().getByIdNow(note.profileId, note.ownerId) OwnerType.LESSON -> - app.db.timetableDao().getByIdNow(note.profileId, note.ownerId) + app.db.timetableDao().getByOwnerIdNow(note.profileId, note.ownerId) OwnerType.MESSAGE -> app.db.messageDao().getByIdNow(note.profileId, note.ownerId) else -> null @@ -93,10 +93,15 @@ class NoteManager(private val app: App) { return getOwner(note) != null } - suspend fun saveNote(activity: AppCompatActivity, note: Note, wasShared: Boolean): Boolean { + suspend fun saveNote( + activity: AppCompatActivity, + note: Note, + teamId: Long?, + wasShared: Boolean, + ): Boolean { val success = when { !note.isShared && wasShared -> unshareNote(activity, note) - note.isShared -> shareNote(activity, note) + note.isShared -> shareNote(activity, note, teamId) else -> true } @@ -124,9 +129,9 @@ class NoteManager(private val app: App) { return true } - private suspend fun shareNote(activity: AppCompatActivity, note: Note): Boolean { + private suspend fun shareNote(activity: AppCompatActivity, note: Note, teamId: Long?): Boolean { return app.api.runCatching(activity) { - shareNote(note) + shareNote(note, teamId) } != null } @@ -166,7 +171,7 @@ class NoteManager(private val app: App) { activity = activity, simpleMode = true, showDate = true, - showColor = false, + showTypeColor = false, showTime = false, markAsSeen = false, showNotes = false, diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/PermissionManager.kt b/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/PermissionManager.kt index 65430f60..e99f9e3e 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/PermissionManager.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/PermissionManager.kt @@ -36,7 +36,13 @@ class PermissionManager(val app: App) : CoroutineScope { app.checkSelfPermission(name) == PackageManager.PERMISSION_GRANTED else true - + val isNotificationPermissionGranted by lazy { + if (Build.VERSION.SDK_INT >= 33) { + app.checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED + } else { + true + } + } private fun openPermissionSettings(activity: AppCompatActivity) { val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) val uri = Uri.fromParts("package", app.packageName, null) @@ -80,6 +86,10 @@ class PermissionManager(val app: App) : CoroutineScope { .show() } result.hasPermanentDenied() -> { + if (!isRequired) { + onSuccess() + return@launch + } MaterialAlertDialogBuilder(activity) .setTitle(R.string.permissions_required) .setMessage(R.string.permissions_denied) @@ -92,6 +102,18 @@ class PermissionManager(val app: App) : CoroutineScope { } } } + fun requestNotificationsPermission( + activity: AppCompatActivity, + @StringRes permissionMessage: Int, + isRequired: Boolean = false, + onSuccess: suspend CoroutineScope.() -> Unit + ) = requestPermission( + activity, + permissionMessage, + isRequired, + Manifest.permission.POST_NOTIFICATIONS, + onSuccess + ) fun requestStoragePermission( activity: AppCompatActivity, diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/UpdateManager.kt b/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/UpdateManager.kt new file mode 100644 index 00000000..ca2b76e5 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/UpdateManager.kt @@ -0,0 +1,112 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2022-10-22. + */ + +package pl.szczodrzynski.edziennik.utils.managers + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.withContext +import org.greenrobot.eventbus.EventBus +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.BuildConfig +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.data.api.szkolny.response.Update +import pl.szczodrzynski.edziennik.data.api.task.PostNotifications +import pl.szczodrzynski.edziennik.data.db.entity.Notification +import pl.szczodrzynski.edziennik.data.db.enums.NotificationType +import pl.szczodrzynski.edziennik.ext.concat +import pl.szczodrzynski.edziennik.ext.resolveString +import pl.szczodrzynski.edziennik.utils.html.BetterHtml +import kotlin.coroutines.CoroutineContext + +class UpdateManager(val app: App) : CoroutineScope { + companion object { + private const val TAG = "UpdateManager" + } + + private val job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Default + + /** + * Check for updates on the specified [maxChannel]. + * If the running build is of "more-unstable" type, + * that channel is used instead. + * + * Optionally, post a notification if [notify] is true. + * + * @return [Result] containing a newer update, or null if not available + */ + suspend fun checkNow( + maxChannel: Update.Type, + notify: Boolean, + ): Result = withContext(Dispatchers.IO) { + return@withContext checkNowSync(maxChannel, notify) + } + + /** + * Check for updates on the specified [maxChannel]. + * If the running build is of "more-unstable" type, + * that channel is used instead. + * + * Optionally, post a notification if [notify] is true. + * + * @return [Result] containing a newer update, or null if not available + */ + fun checkNowSync( + maxChannel: Update.Type, + notify: Boolean, + ): Result { + val channel = minOf(app.buildManager.releaseType, maxChannel) + val update = app.api.runCatching({ + getUpdate(channel).firstOrNull() + }, { + return Result.failure(it) + }) + return Result.success(process(update, notify)) + } + + /** + * Process the update: check if the version is newer, and optionally + * post a notification. + * + * @return [update] if it's a newer version, null otherwise + */ + fun process(update: Update?, notify: Boolean): Update? { + if (update == null || update.versionCode <= BuildConfig.VERSION_CODE) { + app.config.update = null + return null + } + app.config.update = update + + if (EventBus.getDefault().hasSubscriberForEvent(update::class.java)) { + EventBus.getDefault().postSticky(update) + return update + } + + if (notify) + notify(update) + return update + } + + fun notify(update: Update) { + if (!app.config.sync.notifyAboutUpdates) + return + val bigText = listOf( + app.getString(R.string.notification_updates_text, update.versionName), + update.releaseNotes?.let { BetterHtml.fromHtml(context = null, it) }, + ) + val notification = Notification( + id = System.currentTimeMillis(), + title = R.string.notification_updates_title.resolveString(app), + text = bigText.concat("\n").toString(), + type = NotificationType.UPDATE, + profileId = null, + profileName = R.string.notification_updates_title.resolveString(app), + ).addExtra("action", "updateRequest") + app.db.notificationDao().add(notification) + PostNotifications(app, listOf(notification)) + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/UserActionManager.kt b/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/UserActionManager.kt index dbe05325..9ca4c233 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/UserActionManager.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/UserActionManager.kt @@ -7,58 +7,55 @@ package pl.szczodrzynski.edziennik.utils.managers import android.app.NotificationManager import android.app.PendingIntent import android.content.Context +import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import androidx.core.app.NotificationCompat import org.greenrobot.eventbus.EventBus +import org.greenrobot.eventbus.Subscribe +import org.greenrobot.eventbus.ThreadMode import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.MainActivity import pl.szczodrzynski.edziennik.R -import pl.szczodrzynski.edziennik.data.api.ERROR_CAPTCHA_LIBRUS_PORTAL import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask import pl.szczodrzynski.edziennik.data.api.events.UserActionRequiredEvent -import pl.szczodrzynski.edziennik.data.api.models.ApiError -import pl.szczodrzynski.edziennik.ext.Intent -import pl.szczodrzynski.edziennik.ext.JsonObject -import pl.szczodrzynski.edziennik.ext.pendingIntentFlag -import pl.szczodrzynski.edziennik.ui.captcha.LibrusCaptchaDialog +import pl.szczodrzynski.edziennik.ext.* +import pl.szczodrzynski.edziennik.ui.captcha.RecaptchaPromptDialog +import pl.szczodrzynski.edziennik.ui.login.oauth.OAuthLoginActivity +import pl.szczodrzynski.edziennik.ui.login.oauth.OAuthLoginResult +import pl.szczodrzynski.edziennik.ui.login.recaptcha.RecaptchaActivity +import pl.szczodrzynski.edziennik.ui.login.recaptcha.RecaptchaResult +import pl.szczodrzynski.edziennik.utils.Utils.d class UserActionManager(val app: App) { companion object { private const val TAG = "UserActionManager" } - fun requiresUserAction(apiError: ApiError): Boolean { - return apiError.errorCode == ERROR_CAPTCHA_LIBRUS_PORTAL - } - - fun sendToUser(apiError: ApiError) { - val type = when (apiError.errorCode) { - ERROR_CAPTCHA_LIBRUS_PORTAL -> UserActionRequiredEvent.CAPTCHA_LIBRUS - else -> 0 - } - + fun sendToUser(event: UserActionRequiredEvent) { if (EventBus.getDefault().hasSubscriberForEvent(UserActionRequiredEvent::class.java)) { - EventBus.getDefault().post(UserActionRequiredEvent(apiError.profileId ?: -1, type)) + EventBus.getDefault().post(event) return } val manager = app.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - - val text = app.getString(when (type) { - UserActionRequiredEvent.CAPTCHA_LIBRUS -> R.string.notification_user_action_required_captcha_librus - else -> R.string.notification_user_action_required_text - }, apiError.profileId) - + val text = app.getString(event.errorText, event.profileId) val intent = Intent( - app, - MainActivity::class.java, - "action" to "userActionRequired", - "profileId" to (apiError.profileId ?: -1), - "type" to type + app, + MainActivity::class.java, + "action" to "userActionRequired", + "profileId" to event.profileId, + "type" to event.type, + "params" to event.params, + ) + val pendingIntent = PendingIntent.getActivity( + app, + System.currentTimeMillis().toInt(), + intent, + PendingIntent.FLAG_ONE_SHOT or pendingIntentFlag(), ) - val pendingIntent = PendingIntent.getActivity(app, System.currentTimeMillis().toInt(), intent, PendingIntent.FLAG_ONE_SHOT or pendingIntentFlag()) - val notification = NotificationCompat.Builder(app, app.notificationChannelsManager.userAttention.key) + val notification = + NotificationCompat.Builder(app, app.notificationChannelsManager.userAttention.key) .setContentTitle(app.getString(R.string.notification_user_action_required_title)) .setContentText(text) .setSmallIcon(R.drawable.ic_error_outline) @@ -74,29 +71,133 @@ class UserActionManager(val app: App) { manager.notify(System.currentTimeMillis().toInt(), notification) } - fun execute( - activity: AppCompatActivity, - profileId: Int?, - type: Int, - onSuccess: ((code: String) -> Unit)? = null, - onFailure: (() -> Unit)? = null - ) { - if (type != UserActionRequiredEvent.CAPTCHA_LIBRUS) - return + class UserActionCallback( + val onSuccess: ((data: Bundle) -> Unit)? = null, + val onFailure: (() -> Unit)? = null, + val onCancel: (() -> Unit)? = null, + ) - if (profileId == null) - return - // show captcha dialog - // use passed onSuccess listener, else sync profile - LibrusCaptchaDialog( + fun execute( + activity: AppCompatActivity, + event: UserActionRequiredEvent, + callback: UserActionCallback, + ) { + d(TAG, "Running user action (${event.type}) with params: ${event.params}") + val isSuccessful = when (event.type) { + UserActionRequiredEvent.Type.RECAPTCHA -> executeRecaptcha(activity, event, callback) + UserActionRequiredEvent.Type.OAUTH -> executeOauth(activity, event, callback) + } + if (!isSuccessful) + callback.onFailure?.invoke() + } + + private fun executeRecaptcha( + activity: AppCompatActivity, + event: UserActionRequiredEvent, + callback: UserActionCallback, + ): Boolean { + val siteKey = event.params.getString("siteKey") ?: return false + val referer = event.params.getString("referer") ?: return false + RecaptchaPromptDialog( activity = activity, - onSuccess = onSuccess ?: { code -> - EdziennikTask.syncProfile(profileId, arguments = JsonObject( + siteKey = siteKey, + referer = referer, + onSuccess = { code -> + finishAction(activity, event, callback, Bundle( "recaptchaCode" to code, - "recaptchaTime" to System.currentTimeMillis() - )).enqueue(activity) + "recaptchaTime" to System.currentTimeMillis(), + )) + }, + onCancel = callback.onCancel, + onServerError = { + executeRecaptchaActivity(activity, event, callback) }, - onFailure = onFailure ).show() + return true + } + + private fun executeRecaptchaActivity( + activity: AppCompatActivity, + event: UserActionRequiredEvent, + callback: UserActionCallback, + ): Boolean { + event.params.getString("siteKey") ?: return false + event.params.getString("referer") ?: return false + + var listener: Any? = null + listener = object { + @Subscribe(threadMode = ThreadMode.MAIN) + fun onRecaptchaResult(result: RecaptchaResult) { + EventBus.getDefault().unregister(listener) + when { + result.isError -> callback.onFailure?.invoke() + result.code != null -> { + finishAction(activity, event, callback, Bundle( + "recaptchaCode" to result.code, + "recaptchaTime" to System.currentTimeMillis(), + )) + } + else -> callback.onCancel?.invoke() + } + } + } + EventBus.getDefault().register(listener) + + val intent = Intent(activity, RecaptchaActivity::class.java).putExtras(event.params) + activity.startActivity(intent) + return true + } + + private fun executeOauth( + activity: AppCompatActivity, + event: UserActionRequiredEvent, + callback: UserActionCallback, + ): Boolean { + val storeKey = event.params.getString("responseStoreKey") ?: return false + event.params.getString("authorizeUrl") ?: return false + event.params.getString("redirectUrl") ?: return false + + var listener: Any? = null + listener = object { + @Subscribe(threadMode = ThreadMode.MAIN) + fun onOAuthLoginResult(result: OAuthLoginResult) { + EventBus.getDefault().unregister(listener) + when { + result.isError -> callback.onFailure?.invoke() + result.responseUrl != null -> { + finishAction(activity, event, callback, Bundle( + storeKey to result.responseUrl, + )) + } + else -> callback.onCancel?.invoke() + } + } + } + EventBus.getDefault().register(listener) + + val intent = Intent(activity, OAuthLoginActivity::class.java).putExtras(event.params) + activity.startActivity(intent) + return true + } + + private fun finishAction( + activity: AppCompatActivity, + event: UserActionRequiredEvent, + callback: UserActionCallback, + data: Bundle, + ) { + val extras = event.params.getBundle("extras") + if (extras != null) + data.putAll(extras) + + if (callback.onSuccess != null) + callback.onSuccess.invoke(data) + else if (event.profileId != null) + EdziennikTask.syncProfile( + profileId = event.profileId, + arguments = data.toJsonObject(), + ).enqueue(activity) + else + callback.onFailure?.invoke() } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/utils/models/Date.java b/app/src/main/java/pl/szczodrzynski/edziennik/utils/models/Date.java index 71296b85..9a07ae2c 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/utils/models/Date.java +++ b/app/src/main/java/pl/szczodrzynski/edziennik/utils/models/Date.java @@ -202,6 +202,7 @@ public class Date implements Comparable, Noteable { return Week.getWeekDayFromDate(this); } + @NonNull public Date stepForward(int years, int months, int days) { this.day += days; if (day <= 0) { @@ -425,4 +426,10 @@ public class Date implements Comparable, Noteable { public boolean hasReplacingNotes() { return Noteable.DefaultImpls.hasReplacingNotes(this); } + + @Nullable + @Override + public Long getNoteShareTeamId() { + return Noteable.DefaultImpls.getNoteShareTeamId(this); + } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/utils/models/ItemWidgetTimetableModel.java b/app/src/main/java/pl/szczodrzynski/edziennik/utils/models/ItemWidgetTimetableModel.java index 05c41e3b..964bee93 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/utils/models/ItemWidgetTimetableModel.java +++ b/app/src/main/java/pl/szczodrzynski/edziennik/utils/models/ItemWidgetTimetableModel.java @@ -19,7 +19,7 @@ public class ItemWidgetTimetableModel { public Time endTime; public boolean lessonPassed; public boolean lessonCurrent; - public String subjectName = ""; + public CharSequence subjectName = ""; public String classroomName = ""; public boolean lessonChange = false; public boolean lessonChangeNoClassroom = false; diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/utils/models/NavTarget.kt b/app/src/main/java/pl/szczodrzynski/edziennik/utils/models/NavTarget.kt deleted file mode 100644 index 4e087039..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/utils/models/NavTarget.kt +++ /dev/null @@ -1,97 +0,0 @@ -package pl.szczodrzynski.edziennik.utils.models - -import androidx.fragment.app.Fragment -import com.mikepenz.iconics.typeface.IIcon -import kotlin.reflect.KClass - -data class NavTarget( - val id: Int, - val name: Int, - val fragmentClass: KClass? -) { - var title: Int? = null - var icon: IIcon? = null - var description: Int? = null - var isInDrawer: Boolean = false - var isInProfileList: Boolean = false - var isStatic: Boolean = false - var isBelowSeparator: Boolean = false - var popToHome: Boolean = false - var popTo: Int? = null - var badgeTypeId: Int? = null - var canHideInDrawer: Boolean = true - var canHideInMiniDrawer: Boolean = true - var selectable: Boolean = true - var subItems: Array? = null - - fun withTitle(title: Int?): NavTarget { - this.title = title - return this - } - - fun withIcon(icon: IIcon?): NavTarget{ - this.icon = icon - return this - } - - fun withDescription(description: Int?): NavTarget { - this.description = description - return this - } - - fun isInDrawer(isInDrawer: Boolean): NavTarget { - this.isInDrawer = isInDrawer - this.popToHome = true - return this - } - - fun isInProfileList(isInProfileList: Boolean): NavTarget { - this.isInProfileList = isInProfileList - return this - } - - fun isStatic(isStatic: Boolean): NavTarget { - this.isStatic = isStatic - return this - } - - fun isBelowSeparator(isBelowSeparator: Boolean): NavTarget { - this.isBelowSeparator = isBelowSeparator - return this - } - - fun withPopToHome(popToHome: Boolean): NavTarget { - this.popToHome = popToHome - return this - } - - fun withPopTo(popTo: Int): NavTarget { - this.popTo = popTo - return this - } - - fun withBadgeTypeId(badgeTypeId: Int?): NavTarget { - this.badgeTypeId = badgeTypeId - return this - } - - fun canHideInDrawer(canHideInDrawer: Boolean): NavTarget { - this.canHideInDrawer = canHideInDrawer - return this - } - - fun canHideInMiniDrawer(canHideInMiniDrawer: Boolean): NavTarget { - this.canHideInMiniDrawer = canHideInMiniDrawer - return this - } - - fun withSelectable(selectable: Boolean): NavTarget { - this.selectable = selectable - return this - } - - fun withSubItems(vararg items: NavTarget): NavTarget { - this.subItems = items - return this - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/utils/models/UnreadCounter.kt b/app/src/main/java/pl/szczodrzynski/edziennik/utils/models/UnreadCounter.kt index 73fbb1a8..a34239fb 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/utils/models/UnreadCounter.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/utils/models/UnreadCounter.kt @@ -1,14 +1,16 @@ package pl.szczodrzynski.edziennik.utils.models +import pl.szczodrzynski.edziennik.data.db.enums.MetadataType +import pl.szczodrzynski.edziennik.ext.asMetadataType import pl.szczodrzynski.navlib.drawer.IUnreadCounter class UnreadCounter : IUnreadCounter { override var profileId: Int = 0 override var count: Int = 0 - var thingType: Int = 0 + lateinit var thingType: MetadataType override var drawerItemId: Int? = null override var type: Int - get() = thingType - set(value) { thingType = value } + get() = thingType.id + set(value) { thingType = value.asMetadataType() } } diff --git a/app/src/main/res/drawable/login_logo_usos.png b/app/src/main/res/drawable/login_logo_usos.png new file mode 100644 index 00000000..98d95b34 Binary files /dev/null and b/app/src/main/res/drawable/login_logo_usos.png differ diff --git a/app/src/main/res/drawable/login_mode_usos_api.png b/app/src/main/res/drawable/login_mode_usos_api.png new file mode 100644 index 00000000..6ec32631 Binary files /dev/null and b/app/src/main/res/drawable/login_mode_usos_api.png differ diff --git a/app/src/main/res/layout/activity_counter.xml b/app/src/main/res/layout/activity_counter.xml index 9c7f832d..77b1cde2 100644 --- a/app/src/main/res/layout/activity_counter.xml +++ b/app/src/main/res/layout/activity_counter.xml @@ -1,6 +1,9 @@ - + - + diff --git a/app/src/main/res/layout/dialog_config_agenda.xml b/app/src/main/res/layout/dialog_config_agenda.xml index 4bde57ef..5adc8930 100644 --- a/app/src/main/res/layout/dialog_config_agenda.xml +++ b/app/src/main/res/layout/dialog_config_agenda.xml @@ -11,7 +11,7 @@ + type="pl.szczodrzynski.edziennik.config.ProfileConfig" /> @@ -46,7 +46,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="8dp" - android:checked="@={config.agendaTeacherAbsence}" + android:checked="@={config.ui.agendaTeacherAbsence}" android:minHeight="32dp" android:text="@string/agenda_config_teacher_absence" /> @@ -54,7 +54,15 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="8dp" - android:checked="@={config.agendaCompactMode}" + android:checked="@={config.ui.agendaSubjectImportant}" + android:minHeight="32dp" + android:text="@string/agenda_config_subject_important" /> + + + + diff --git a/app/src/main/res/layout/dialog_event_details.xml b/app/src/main/res/layout/dialog_event_details.xml index 663c920b..f44f289c 100644 --- a/app/src/main/res/layout/dialog_event_details.xml +++ b/app/src/main/res/layout/dialog_event_details.xml @@ -17,12 +17,13 @@ type="pl.szczodrzynski.edziennik.data.db.full.EventFull" /> + + android:layout_height="match_parent"> + android:layout_weight="1"> @@ -65,81 +66,42 @@ android:id="@+id/lessonDate" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="@{details}" - android:textAppearance="?textAppearanceBodyMedium" android:textIsSelectable="true" + android:textAppearance="@style/NavView.TextView.Subtitle" + android:text="@{details}" android:visibility="@{details == null ? View.GONE : View.VISIBLE}" tools:text="język angielski • 2B3T a2" tools:visibility="visible" /> - - - - - - - - - + android:orientation="vertical" + android:gravity="center"> + + - - @@ -161,9 +123,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="8dp" - android:textAppearance="@style/NavView.TextView.BodyMedium" - android:textStyle="bold" - android:textColor="?colorPrimary" + android:textAppearance="@style/NavView.TextView.Helper" android:text="@string/dialog_event_details_teacher" android:visibility="@{event.teacherName != null ? View.VISIBLE : View.GONE}"/> + android:textAppearance="@style/NavView.TextView.Helper" /> @@ -246,56 +200,94 @@ android:layout_height="wrap_content" android:layout_marginTop="8dp" android:text="@string/dialog_event_details_attachments" - android:textAppearance="@style/NavView.TextView.BodyMedium" /> + android:textAppearance="@style/NavView.TextView.Helper" /> - + + + app:justifyContent="flex_end"> + - - + android:layout_margin="8dp" + android:fontFamily="@font/community_material_font_v5_8_55" + android:minWidth="0dp" + android:text="\uf2f4" + android:textSize="20sp" /> + + + + + + + + + diff --git a/app/src/main/res/layout/dialog_event_manual_v2.xml b/app/src/main/res/layout/dialog_event_manual_v2.xml index df37891f..013dbfa2 100644 --- a/app/src/main/res/layout/dialog_event_manual_v2.xml +++ b/app/src/main/res/layout/dialog_event_manual_v2.xml @@ -13,6 +13,7 @@ android:layout_height="match_parent"> +