diff --git a/app/build.gradle b/app/build.gradle index ae884511..9bc32c1a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -145,7 +145,11 @@ dependencies { implementation("com.github.ozodrukh:CircularReveal:2.0.1@aar") {transitive = true} implementation "com.heinrichreimersoftware:material-intro:1.5.8" // do not update implementation "com.jaredrummler:colorpicker:1.0.2" - implementation "com.squareup.okhttp3:okhttp:3.12.2" + implementation("com.squareup.okhttp3:okhttp") { + version { + strictly "3.12.2" + } + } implementation "com.theartofdev.edmodo:android-image-cropper:2.8.0" // do not update implementation "com.wdullaer:materialdatetimepicker:4.1.2" implementation "com.yuyh.json:jsonviewer:1.0.6" @@ -201,6 +205,11 @@ dependencies { implementation 'com.qifan.powerpermission:powerpermission:1.0.0' implementation 'com.qifan.powerpermission:powerpermission-coroutines:1.0.0' + + implementation 'com.github.kuba2k2.FSLogin:lib:master-SNAPSHOT' + implementation 'pl.droidsonroids:jspoon:1.3.2' + implementation "com.squareup.retrofit2:converter-scalars:2.8.1" + implementation "pl.droidsonroids.retrofit2:converter-jspoon:1.3.2" } repositories { mavenCentral() diff --git a/app/proguard/app.pro b/app/proguard/app.pro index 711a62e4..68d1fde8 100644 --- a/app/proguard/app.pro +++ b/app/proguard/app.pro @@ -66,3 +66,4 @@ -keepclassmembernames class pl.szczodrzynski.edziennik.data.api.szkolny.request.** { *; } -keepclassmembernames class pl.szczodrzynski.edziennik.data.api.szkolny.response.** { *; } +-keepclassmembernames class pl.szczodrzynski.edziennik.ui.modules.login.LoginInfo.Platform { *; } diff --git a/app/sampledata/vulcan/edu.lublin.eu.png b/app/sampledata/vulcan/edu.lublin.eu.png new file mode 100644 index 00000000..ece8b925 Binary files /dev/null and b/app/sampledata/vulcan/edu.lublin.eu.png differ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index cc82fed3..6e8fc491 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -24,7 +24,6 @@ android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:networkSecurityConfig="@xml/network_security_config" - android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme.Dark" android:usesCleartextTraffic="true" @@ -135,9 +134,6 @@ android:configChanges="orientation|screenSize" android:launchMode="singleTop" android:theme="@style/AppTheme.Light" /> - Wersja 4.0, 2020-04-19 +

Wersja 4.2, 2020-05-16

    -
  • Wysyłanie wiadomości - funkcja, na którą czekał każdy. Od teraz w Szkolnym można wysyłać oraz odpowiadać na wiadomości do nauczycieli 👏
  • -
  • Przebudowaliśmy cały moduł synchronizacji, co oznacza większą stabilność aplikacji, szybkość oraz poprawność pobieranych danych
  • -
  • Udoskonalony wygląd Szkolnego - sprawi, że korzystanie z aplikacji będzie jeszcze przyjemniejsze
  • -
  • Wyszukiwarka wiadomości, pozwalająca na łatwe znalezienie potrzebnej konwersacji.
  • -
  • Możliwość pobierania załączników do zadań domowych oraz wiadomości w każdym dzienniku.
  • -
  • Nowa Strona główna - ładniejszy wygląd oraz możliwość przestawiania kart na każdym profilu
  • -
  • Nowy Plan lekcji - z doskonałą obsługą lekcji przesuniętych oraz dwóch lekcji o tej samej godzinie
  • -
  • Nowe Oceny - z możliwością zmiany wartości plusów oraz minusów oraz wyłączenia niektórych ocen ze średniej
  • -
  • Opcja wyłączenia wybranych powiadomień z aplikacji
  • -
  • Znaczki nieprzeczytanych informacji na obrazkach profili.
  • -
    -
    -
  • Udoskonalone tłumaczenie na j.angielski (dzięki @Predator)
  • -
  • Nowe okienka informacji o wydarzeniach oraz lekcjach
  • -
  • Nowe, przyjemniejsze powiadomienia
  • -
  • Dużo poprawek w widoku Wiadomości oraz Ogłoszeń
  • -
  • Częściowa Obsługa dziennika EduDziennik
  • -
  • Librus: opcja logowania w dziennikach Jednostek Samorządu Terytorialnego oraz Oświata w Radomiu
  • -
  • Librus: poprawione obliczanie frekwencji
  • -
  • Librus: obsługa Zadań domowych bez posiadania Mobilnych dodatków (przez system Synergia)
  • -
  • Lepsze przekazywanie powiadomień na komputer oraz łatwiejsze parowanie
  • -
  • Łatwiejsze dodawanie własnych wydarzeń
  • -
  • Poprawiliśmy synchronizację w tle na niektórych telefonach
  • -
  • Usunąłem denerwujący brak zaznaczenia w lewym menu
  • -
  • Znaczna ilość błędów z poprzednich wersji już nie występuje
  • +
  • Naprawiony błąd braku dostępu do Wiadomości w Librusie.
  • +
  • Vulcan: wyświetlane dane z 1 semestru w dzienniku.
  • +
  • Odświeżone logo aplikacji.
  • +
  • Obsługa dziennika Podlaskiej Platformy Edukacyjnej (Prymus).
  • +
  • Poprawione liczenie i wyświetlanie niektórych rodzajów frekwencji.
  • +
  • Nowy ekran logowania.


diff --git a/app/src/main/cpp/szkolny-signing.cpp b/app/src/main/cpp/szkolny-signing.cpp index 6463e8af..f90eb9ef 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] = { - 0x38, 0xd4, 0x73, 0xaf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; + 0x3b, 0xa6, 0xd4, 0x50, 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 7194aa04..568538e7 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/App.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/App.kt @@ -66,6 +66,7 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope { 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 db get() = App.db @@ -100,9 +101,9 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope { .followSslRedirects(true) .retryOnConnectionFailure(true) .cookieJar(cookieJar) - .connectTimeout(20, TimeUnit.SECONDS) - .writeTimeout(5, TimeUnit.SECONDS) - .readTimeout(10, TimeUnit.SECONDS) + .connectTimeout(15, TimeUnit.SECONDS) + .writeTimeout(10, TimeUnit.SECONDS) + .readTimeout(30, TimeUnit.SECONDS) builder.installHttpsSupport(this) if (debugMode || BuildConfig.DEBUG) { @@ -172,6 +173,8 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope { } devMode = BuildConfig.DEBUG + if (BuildConfig.DEBUG) + debugMode = true Signing.getCert(this) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/Extensions.kt b/app/src/main/java/pl/szczodrzynski/edziennik/Extensions.kt index e95854e7..2443679b 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/Extensions.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/Extensions.kt @@ -1245,3 +1245,5 @@ val SwipeRefreshLayout.onScrollListener: RecyclerView.OnScrollListener operator fun Iterable>.get(key: K): V? { return firstOrNull { it.first == key }?.second } + +fun ByteArray.toHexString() = joinToString("") { "%02x".format(it) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/MainActivity.kt b/app/src/main/java/pl/szczodrzynski/edziennik/MainActivity.kt index 19b2886d..3033f0f0 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/MainActivity.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/MainActivity.kt @@ -18,6 +18,7 @@ import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.PopupMenu import androidx.core.graphics.ColorUtils +import androidx.core.view.isVisible import androidx.lifecycle.Observer import androidx.navigation.NavOptions import com.danimahardhika.cafebar.CafeBar @@ -294,6 +295,13 @@ class MainActivity : AppCompatActivity(), CoroutineScope { mainSnackbar.setCoordinator(b.navView.coordinator, b.navView.bottomBar) errorSnackbar.setCoordinator(b.navView.coordinator, b.navView.bottomBar) + if (BuildConfig.VERSION_NAME.contains("nightly")) { + b.nightlyText.isVisible = true + b.nightlyText.text = "Nightly\n"+BuildConfig.VERSION_NAME.substringAfterLast(".") + } + else + b.nightlyText.isVisible = false + navLoading = true b.navView.apply { 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 d956c1ec..c4c65ac3 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfig.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfig.kt @@ -30,6 +30,7 @@ class ProfileConfig(val db: AppDb, val profileId: Int, rawEntries: List false } + 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) + /*LoginMethod(LOGIN_TYPE_VULCAN, LOGIN_METHOD_VULCAN_WEB_NEW, VulcanLoginWebNew::class.java) .withIsPossible { _, _ -> false } .withRequiredLoginMethod { _, _ -> LOGIN_METHOD_VULCAN_WEB_MAIN }, @@ -118,7 +119,7 @@ val vulcanLoginMethods = listOf( LoginMethod(LOGIN_TYPE_VULCAN, LOGIN_METHOD_VULCAN_API, VulcanLoginApi::class.java) .withIsPossible { _, _ -> true } .withRequiredLoginMethod { _, loginStore -> - if (loginStore.mode == LOGIN_MODE_VULCAN_WEB) LOGIN_METHOD_VULCAN_WEB_NEW else LOGIN_METHOD_NOT_NEEDED + if (loginStore.mode == LOGIN_MODE_VULCAN_WEB) LOGIN_METHOD_VULCAN_WEB_MAIN else LOGIN_METHOD_NOT_NEEDED } ) @@ -133,6 +134,7 @@ val idziennikLoginMethods = listOf( ) const val LOGIN_TYPE_EDUDZIENNIK = 5 +const val LOGIN_MODE_EDUDZIENNIK_WEB = 0 const val LOGIN_METHOD_EDUDZIENNIK_WEB = 100 val edudziennikLoginMethods = listOf( LoginMethod(LOGIN_TYPE_EDUDZIENNIK, LOGIN_METHOD_EDUDZIENNIK_WEB, EdudziennikLoginWeb::class.java) @@ -140,6 +142,15 @@ val edudziennikLoginMethods = listOf( .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 } 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 57dceb0e..aa3fac8d 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 @@ -68,6 +68,9 @@ object Regexes { } + val MOBIDZIENNIK_ATTENDANCE_TYPES by lazy { + """Legenda:.+?normal;">(.+?)""".toRegex(DOT_MATCHES_ALL) + } val MOBIDZIENNIK_ATTENDANCE_TABLE by lazy { """(.+?)
""".toRegex(DOT_MATCHES_ALL) } @@ -81,7 +84,7 @@ object Regexes { """([0-9:]+) - .+? (.+?)""".toRegex(DOT_MATCHES_ALL) } val MOBIDZIENNIK_ATTENDANCE_LESSON by lazy { - """(.+?) - (.*?).+?.+?\((.+?), .+?(.+?)\)""".toRegex(DOT_MATCHES_ALL) + """(.+?)\s*\s*\((.+?),\s*(.+?)\)""".toRegex(DOT_MATCHES_ALL) } val MOBIDZIENNIK_HOMEWORK_ROW by lazy { @@ -139,12 +142,21 @@ object Regexes { val VULCAN_SHIFT_ANNOTATION by lazy { """\(przeniesiona (z|na) lekcj[ię] ([0-9]+), (.+)\)""".toRegex() } + val VULCAN_WEB_PERMISSIONS by lazy { + """permissions: '([A-z0-9/=+\-_]+?)'""".toRegex() + } + val VULCAN_WEB_SYMBOL_VALIDATE by lazy { + """[A-z0-9]+""".toRegex(IGNORE_CASE) + } val LIBRUS_ATTACHMENT_KEY by lazy { """singleUseKey=([0-9A-z_]+)""".toRegex() } + val LIBRUS_MESSAGE_ID by lazy { + """/wiadomosci/[0-9]+/[0-9]+/([0-9]+?)/""".toRegex() + } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/EdziennikTask.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/EdziennikTask.kt index 13de2f17..4d3f0aca 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/EdziennikTask.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/EdziennikTask.kt @@ -12,6 +12,7 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.Edudziennik import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.Idziennik import pl.szczodrzynski.edziennik.data.api.edziennik.librus.Librus import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.Mobidziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.podlasie.Podlasie import pl.szczodrzynski.edziennik.data.api.edziennik.template.Template import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.Vulcan import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikCallback @@ -19,6 +20,7 @@ import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikInterface import pl.szczodrzynski.edziennik.data.api.models.ApiError import pl.szczodrzynski.edziennik.data.api.task.IApiTask 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.full.AnnouncementFull import pl.szczodrzynski.edziennik.data.db.full.EventFull @@ -28,6 +30,9 @@ open class EdziennikTask(override val profileId: Int, val request: Any) : IApiTa companion object { private const val TAG = "EdziennikTask" + var profile: Profile? = null + var loginStore: LoginStore? = null + fun firstLogin(loginStore: LoginStore) = EdziennikTask(-1, FirstLoginRequest(loginStore)) fun sync() = EdziennikTask(-1, SyncRequest()) fun syncProfile(profileId: Int, viewIds: List>? = null, onlyEndpoints: List? = null, arguments: JsonObject? = null) = EdziennikTask(profileId, SyncProfileRequest(viewIds, onlyEndpoints, arguments)) @@ -59,6 +64,8 @@ open class EdziennikTask(override val profileId: Int, val request: Any) : IApiTa // save the profile ID and name as the current task's taskName = app.getString(R.string.edziennik_notification_api_sync_title_format, profile.name) } + EdziennikTask.profile = this.profile + EdziennikTask.loginStore = this.loginStore } private var edziennikInterface: EdziennikInterface? = null @@ -74,6 +81,7 @@ open class EdziennikTask(override val profileId: Int, val request: Any) : IApiTa LOGIN_TYPE_VULCAN -> Vulcan(app, profile, loginStore, taskCallback) LOGIN_TYPE_IDZIENNIK -> Idziennik(app, profile, loginStore, taskCallback) LOGIN_TYPE_EDUDZIENNIK -> Edudziennik(app, profile, loginStore, taskCallback) + LOGIN_TYPE_PODLASIE -> Podlasie(app, profile, loginStore, taskCallback) LOGIN_TYPE_TEMPLATE -> Template(app, profile, loginStore, taskCallback) else -> null } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebAnnouncements.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebAnnouncements.kt index 11f048b5..32ca353c 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebAnnouncements.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebAnnouncements.kt @@ -45,24 +45,25 @@ class EdudziennikWebAnnouncements(override val data: DataEdudziennik, val addedDate = Date.fromIsoHm(dateString) val announcementObject = Announcement( - profileId, - id, - subject, - null, - startDate, - null, - teacher.id, - longId - ) + profileId = profileId, + id = id, + subject = subject, + text = null, + startDate = startDate, + endDate = null, + teacherId = teacher.id, + addedDate = addedDate + ).also { + it.idString = longId + } - data.announcementIgnoreList.add(announcementObject) + data.announcementList.add(announcementObject) data.metadataList.add(Metadata( profileId, Metadata.TYPE_ANNOUNCEMENT, id, profile.empty, - profile.empty, - addedDate + profile.empty )) } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebAttendance.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebAttendance.kt index 92545117..7fe18171 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebAttendance.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebAttendance.kt @@ -39,12 +39,12 @@ class EdudziennikWebAttendance(override val data: DataEdudziennik, val attendanceTypes = EDUDZIENNIK_ATTENDANCE_TYPES.find(text)?.get(1)?.split(',')?.map { val type = EDUDZIENNIK_ATTENDANCE_TYPE.find(it.trim()) - val symbol = type?.get(1)?.trim() - val name = type?.get(2)?.trim() + val symbol = type?.get(1)?.trim() ?: "?" + val name = type?.get(2)?.trim() ?: "nieznany rodzaj" return@map Triple( symbol, name, - when (name?.toLowerCase(Locale.ROOT)) { + when (name.toLowerCase(Locale.ROOT)) { "obecność" -> Attendance.TYPE_PRESENT "nieobecność" -> Attendance.TYPE_ABSENT "spóźnienie" -> Attendance.TYPE_BELATED @@ -52,7 +52,7 @@ class EdudziennikWebAttendance(override val data: DataEdudziennik, "dzień wolny" -> Attendance.TYPE_DAY_FREE "brak zajęć" -> Attendance.TYPE_DAY_FREE "oddelegowany" -> Attendance.TYPE_RELEASED - else -> Attendance.TYPE_CUSTOM + else -> Attendance.TYPE_UNKNOWN } ) } ?: emptyList() @@ -62,38 +62,42 @@ class EdudziennikWebAttendance(override val data: DataEdudziennik, val lessonNumber = attendanceElement[2].toInt() val attendanceSymbol = attendanceElement[3] - val lessons = data.app.db.timetableDao().getForDateNow(profileId, date) + val lessons = data.app.db.timetableDao().getAllForDateNow(profileId, date) val lesson = lessons.firstOrNull { it.lessonNumber == lessonNumber } val id = "${date.stringY_m_d}:$lessonNumber:$attendanceSymbol".crc32() - val (_, name, type) = attendanceTypes.firstOrNull { (symbol, _, _) -> symbol == attendanceSymbol } + val (typeSymbol, typeName, baseType) = attendanceTypes.firstOrNull { (symbol, _, _) -> symbol == attendanceSymbol } ?: return@forEach val startTime = data.lessonRanges.singleOrNull { it.lessonNumber == lessonNumber }?.startTime ?: return@forEach val attendanceObject = Attendance( - profileId, - id, - lesson?.displayTeacherId ?: -1, - lesson?.displaySubjectId ?: -1, - profile.currentSemester, - name, - date, - lesson?.displayStartTime ?: startTime, - type - ) + profileId = profileId, + id = id, + baseType = baseType, + typeName = typeName, + typeShort = data.app.attendanceManager.getTypeShort(baseType), + typeSymbol = typeSymbol, + typeColor = null, + date = date, + startTime = lesson?.displayStartTime ?: startTime, + semester = profile.currentSemester, + teacherId = lesson?.displayTeacherId ?: -1, + subjectId = lesson?.displaySubjectId ?: -1 + ).also { + it.lessonNumber = lessonNumber + } data.attendanceList.add(attendanceObject) - if(type != Attendance.TYPE_PRESENT) { + if (baseType != Attendance.TYPE_PRESENT) { data.metadataList.add(Metadata( profileId, Metadata.TYPE_ATTENDANCE, id, - profile.empty, - profile.empty, - System.currentTimeMillis() + profile.empty || baseType == Attendance.TYPE_PRESENT_CUSTOM || baseType == Attendance.TYPE_UNKNOWN, + profile.empty || baseType == Attendance.TYPE_PRESENT_CUSTOM || baseType == Attendance.TYPE_UNKNOWN )) } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebEvents.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebEvents.kt index 52833b9a..35c68478 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebEvents.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebEvents.kt @@ -57,8 +57,7 @@ class EdudziennikWebEvents(override val data: DataEdudziennik, Metadata.TYPE_EVENT, id, profile.empty, - profile.empty, - System.currentTimeMillis() + profile.empty )) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebExams.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebExams.kt index 8e7836fd..f6abf637 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebExams.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebExams.kt @@ -46,7 +46,7 @@ class EdudziennikWebExams(override val data: DataEdudziennik, if (dateString.isBlank()) return@forEach val date = Date.fromY_m_d(dateString) - val lessons = data.app.db.timetableDao().getForDateNow(profileId, date) + val lessons = data.app.db.timetableDao().getAllForDateNow(profileId, date) val startTime = lessons.firstOrNull { it.displaySubjectId == subject.id }?.displayStartTime val eventTypeElement = examElement.child(3).child(0) @@ -74,8 +74,7 @@ class EdudziennikWebExams(override val data: DataEdudziennik, Metadata.TYPE_EVENT, id, profile.empty, - profile.empty, - System.currentTimeMillis() + profile.empty )) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebGrades.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebGrades.kt index 50557333..459f13aa 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebGrades.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebGrades.kt @@ -126,7 +126,8 @@ class EdudziennikWebGrades(override val data: DataEdudziennik, comment = null, semester = semester, teacherId = teacher.id, - subjectId = subject.id + subjectId = subject.id, + addedDate = addedDate ) data.gradeList.add(gradeObject) @@ -135,8 +136,7 @@ class EdudziennikWebGrades(override val data: DataEdudziennik, Metadata.TYPE_GRADE, id, profile.empty, - profile.empty, - addedDate + profile.empty )) } @@ -168,8 +168,7 @@ class EdudziennikWebGrades(override val data: DataEdudziennik, Metadata.TYPE_GRADE, proposedGradeObject.id, profile.empty, - profile.empty, - System.currentTimeMillis() + profile.empty )) } @@ -201,8 +200,7 @@ class EdudziennikWebGrades(override val data: DataEdudziennik, Metadata.TYPE_GRADE, finalGradeObject.id, profile.empty, - profile.empty, - System.currentTimeMillis() + profile.empty )) } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebHomework.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebHomework.kt index acabcfa6..169a5127 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebHomework.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebHomework.kt @@ -43,7 +43,7 @@ class EdudziennikWebHomework(override val data: DataEdudziennik, val subjectName = subjectElement.text() val subject = data.getSubject(subjectId, subjectName) - val lessons = data.app.db.timetableDao().getForDateNow(profileId, date) + val lessons = data.app.db.timetableDao().getAllForDateNow(profileId, date) val startTime = lessons.firstOrNull { it.subjectId == subject.id }?.displayStartTime val teacherName = homeworkElement.child(2).text() @@ -72,8 +72,7 @@ class EdudziennikWebHomework(override val data: DataEdudziennik, Metadata.TYPE_HOMEWORK, id, profile.empty, - profile.empty, - System.currentTimeMillis() + profile.empty )) } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebLuckyNumber.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebLuckyNumber.kt index 70c6849a..db8164c8 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebLuckyNumber.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebLuckyNumber.kt @@ -24,9 +24,9 @@ class EdudziennikWebLuckyNumber(override val data: DataEdudziennik, webGet(TAG, data.schoolEndpoint + "Lucky", xhr = true) { text -> text.toIntOrNull()?.also { luckyNumber -> val luckyNumberObject = LuckyNumber( - profileId, - Date.getToday(), - luckyNumber + profileId = profileId, + date = Date.getToday(), + number = luckyNumber ) data.luckyNumberList.add(luckyNumberObject) @@ -35,8 +35,7 @@ class EdudziennikWebLuckyNumber(override val data: DataEdudziennik, Metadata.TYPE_LUCKY_NUMBER, luckyNumberObject.date.value.toLong(), true, - profile.empty, - System.currentTimeMillis() + profile.empty )) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebNotes.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebNotes.kt index a111ef79..35a78168 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebNotes.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebNotes.kt @@ -41,12 +41,15 @@ class EdudziennikWebNotes(override val data: DataEdudziennik, val description = noteElement.child(3).text() val noticeObject = Notice( - profileId, - id, - description, - profile.currentSemester, - Notice.TYPE_NEUTRAL, - teacher.id + profileId = profileId, + id = id, + type = Notice.TYPE_NEUTRAL, + semester = profile.currentSemester, + text = description, + category = null, + points = null, + teacherId = teacher.id, + addedDate = addedDate ) data.noticeList.add(noticeObject) @@ -55,8 +58,7 @@ class EdudziennikWebNotes(override val data: DataEdudziennik, Metadata.TYPE_NOTICE, id, profile.empty, - profile.empty, - addedDate + profile.empty )) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebTimetable.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebTimetable.kt index 3a31e208..94db15ef 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebTimetable.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebTimetable.kt @@ -124,8 +124,7 @@ class EdudziennikWebTimetable(override val data: DataEdudziennik, Metadata.TYPE_LESSON_CHANGE, lessonObject.id, seen, - seen, - System.currentTimeMillis() + seen )) } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/firstlogin/EdudziennikFirstLogin.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/firstlogin/EdudziennikFirstLogin.kt index e47c7842..a93632b1 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/firstlogin/EdudziennikFirstLogin.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/firstlogin/EdudziennikFirstLogin.kt @@ -59,7 +59,7 @@ class EdudziennikFirstLogin(val data: DataEdudziennik, val onSuccess: () -> Unit profileList.add(profile) } - EventBus.getDefault().post(FirstLoginFinishedEvent(profileList, data.loginStore)) + EventBus.getDefault().postSticky(FirstLoginFinishedEvent(profileList, data.loginStore)) onSuccess() } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/helper/DownloadAttachment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/helper/DownloadAttachment.kt new file mode 100644 index 00000000..a968818a --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/helper/DownloadAttachment.kt @@ -0,0 +1,70 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2020-5-14 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.helper + +import im.wangchao.mhttp.Request +import im.wangchao.mhttp.Response +import im.wangchao.mhttp.callback.FileCallbackHandler +import pl.szczodrzynski.edziennik.data.api.ERROR_FILE_DOWNLOAD +import pl.szczodrzynski.edziennik.data.api.ERROR_REQUEST_FAILURE +import pl.szczodrzynski.edziennik.data.api.SYSTEM_USER_AGENT +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.utils.Utils +import java.io.File + +class DownloadAttachment( + fileUrl: String, + val onSuccess: (file: File) -> Unit, + val onProgress: (written: Long, total: Long) -> Unit, + val onError: (apiError: ApiError) -> Unit +) { + companion object { + private const val TAG = "DownloadAttachment" + } + + init { + val targetFile = Utils.getStorageDir() + + val callback = object : FileCallbackHandler(targetFile) { + override fun onSuccess(file: File?, response: Response?) { + if (file == null) { + onError(ApiError(TAG, ERROR_FILE_DOWNLOAD) + .withResponse(response)) + return + } + + try { + onSuccess(file) + } catch (e: Exception) { + onError(ApiError(TAG, ERROR_FILE_DOWNLOAD) + .withResponse(response) + .withThrowable(e)) + } + } + + override fun onProgress(bytesWritten: Long, bytesTotal: Long) { + try { + this@DownloadAttachment.onProgress(bytesWritten, bytesTotal) + } catch (e: Exception) { + onError(ApiError(TAG, ERROR_FILE_DOWNLOAD) + .withThrowable(e)) + } + } + + override fun onFailure(response: Response?, throwable: Throwable?) { + onError(ApiError(TAG, ERROR_REQUEST_FAILURE) + .withResponse(response) + .withThrowable(throwable)) + } + } + + Request.builder() + .url(fileUrl) + .userAgent(SYSTEM_USER_AGENT) + .callback(callback) + .build() + .enqueue() + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/api/IdziennikApiCurrentRegister.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/api/IdziennikApiCurrentRegister.kt index 0e000fb1..d5e59596 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/api/IdziennikApiCurrentRegister.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/api/IdziennikApiCurrentRegister.kt @@ -68,9 +68,9 @@ class IdziennikApiCurrentRegister(override val data: DataIdziennik, val luckyNumberObject = LuckyNumber( - data.profileId, - luckyNumberDate, - luckyNumber + profileId = data.profileId, + date = luckyNumberDate, + number = luckyNumber ) data.luckyNumberList.add(luckyNumberObject) @@ -80,8 +80,7 @@ class IdziennikApiCurrentRegister(override val data: DataIdziennik, Metadata.TYPE_LUCKY_NUMBER, luckyNumberObject.date.value.toLong(), true, - data.profile?.empty ?: false, - System.currentTimeMillis() + data.profile?.empty ?: false )) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/api/IdziennikApiMessagesInbox.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/api/IdziennikApiMessagesInbox.kt index 58e7660c..622068e2 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/api/IdziennikApiMessagesInbox.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/api/IdziennikApiMessagesInbox.kt @@ -69,7 +69,8 @@ class IdziennikApiMessagesInbox(override val data: DataIdziennik, type = if (jMessage.getBoolean("rekordUsuniety") == true) TYPE_DELETED else TYPE_RECEIVED, subject = subject, body = body, - senderId = rTeacher.id + senderId = rTeacher.id, + addedDate = sentDate ) val messageRecipient = MessageRecipient( @@ -87,8 +88,7 @@ class IdziennikApiMessagesInbox(override val data: DataIdziennik, Metadata.TYPE_MESSAGE, message.id, readDate > 0, - readDate > 0 || profile?.empty ?: false, - sentDate + readDate > 0 || profile?.empty ?: false )) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/api/IdziennikApiMessagesSent.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/api/IdziennikApiMessagesSent.kt index 286c9896..87b3264d 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/api/IdziennikApiMessagesSent.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/api/IdziennikApiMessagesSent.kt @@ -51,7 +51,8 @@ class IdziennikApiMessagesSent(override val data: DataIdziennik, type = TYPE_SENT, subject = subject, body = body, - senderId = null + senderId = null, + addedDate = sentDate ) for (recipientEl in jMessage.getAsJsonArray("odbiorcy")) { @@ -76,7 +77,7 @@ class IdziennikApiMessagesSent(override val data: DataIdziennik, } data.messageList.add(message) - data.metadataList.add(Metadata(profileId, Metadata.TYPE_MESSAGE, message.id, true, true, sentDate)) + data.metadataList.add(Metadata(profileId, Metadata.TYPE_MESSAGE, message.id, true, true)) } data.setSyncNext(ENDPOINT_IDZIENNIK_API_MESSAGES_SENT, DAY, DRAWER_ITEM_MESSAGES) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebAnnouncements.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebAnnouncements.kt index f8744efa..531cbeeb 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebAnnouncements.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebAnnouncements.kt @@ -52,14 +52,14 @@ class IdziennikWebAnnouncements(override val data: DataIdziennik, val startDate = jAnnouncement.getString("DataWydarzenia")?.replace("[^\\d]".toRegex(), "")?.toLongOrNull()?.let { Date.fromMillis(it) } val announcementObject = Announcement( - profileId, - announcementId, - jAnnouncement.get("Temat").asString, - jAnnouncement.get("Tresc").asString, - startDate, - null, - rTeacher.id, - null + profileId = profileId, + id = announcementId, + subject = jAnnouncement.get("Temat").asString, + text = jAnnouncement.get("Tresc").asString, + startDate = startDate, + endDate = null, + teacherId = rTeacher.id, + addedDate = addedDate ) data.announcementList.add(announcementObject) data.metadataList.add(Metadata( @@ -67,8 +67,7 @@ class IdziennikWebAnnouncements(override val data: DataIdziennik, Metadata.TYPE_ANNOUNCEMENT, announcementObject.id, profile?.empty ?: false, - profile?.empty ?: false, - addedDate + profile?.empty ?: false )) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebAttendance.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebAttendance.kt index aa0a9998..e77c9eac 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebAttendance.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebAttendance.kt @@ -12,10 +12,18 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.ENDPOINT_IDZIENNI import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikWeb import pl.szczodrzynski.edziennik.data.api.models.ApiError import pl.szczodrzynski.edziennik.data.db.entity.Attendance -import pl.szczodrzynski.edziennik.data.db.entity.Attendance.* +import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_ABSENT +import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_ABSENT_EXCUSED +import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_BELATED +import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_PRESENT +import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_PRESENT_CUSTOM +import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_RELEASED +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.getInt import pl.szczodrzynski.edziennik.getJsonObject +import pl.szczodrzynski.edziennik.getString import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Time @@ -51,71 +59,97 @@ class IdziennikWebAttendance(override val data: DataIdziennik, for (jAttendanceEl in json.getAsJsonArray("Obecnosci")) { val jAttendance = jAttendanceEl.asJsonObject // jAttendance - val attendanceTypeIdziennik = jAttendance.get("TypObecnosci").asInt - if (attendanceTypeIdziennik == 5 || attendanceTypeIdziennik == 7) - continue - val attendanceDate = Date.fromY_m_d(jAttendance.get("Data").asString) - val attendanceTime = Time.fromH_m(jAttendance.get("OdDoGodziny").asString) - if (attendanceDate.combineWith(attendanceTime) > System.currentTimeMillis()) + val type = jAttendance.get("TypObecnosci").asInt + + // skip "zajęcia nie odbyły się" and "Ferie" + if (type == 5 || type == 7) continue - val attendanceId = jAttendance.get("IdLesson").asString.crc16().toLong() + val date = Date.fromY_m_d(jAttendance.get("Data").asString) + val time = Time.fromH_m(jAttendance.get("OdDoGodziny").asString) + if (date.combineWith(time) > System.currentTimeMillis()) + continue + + val id = jAttendance.get("IdLesson").asString.crc16().toLong() val rSubject = data.getSubject(jAttendance.get("Przedmiot").asString, jAttendance.get("IdPrzedmiot").asLong, "") val rTeacher = data.getTeacherByFDotSpaceLast(jAttendance.get("PrzedmiotNauczyciel").asString) - var attendanceName = "obecność" - var attendanceType = Attendance.TYPE_CUSTOM + var baseType = TYPE_UNKNOWN + var typeName = "nieznany rodzaj" + var typeSymbol: String? = null + var typeColor: Long? = null - when (attendanceTypeIdziennik) { - 1 /* nieobecność usprawiedliwiona */ -> { - attendanceName = "nieobecność usprawiedliwiona" - attendanceType = TYPE_ABSENT_EXCUSED + /* https://iuczniowie.progman.pl/idziennik/mod_panelRodzica/obecnosci/obecnosciUcznia_lmt637231494660000000.js */ + /* https://iuczniowie.progman.pl/idziennik/mod_panelRodzica/obecnosci/obecnosci_lmt637231494660000000.css */ + when (type) { + 1 -> { + baseType = TYPE_ABSENT_EXCUSED + typeName = "nieobecność usprawiedliwiona" + typeColor = 0xffffe099 } - 2 /* spóźnienie */ -> { - attendanceName = "spóźnienie" - attendanceType = TYPE_BELATED + 2 -> { + baseType = TYPE_BELATED + typeName = "spóźnienie" + typeColor = 0xffffffaa } - 3 /* nieobecność nieusprawiedliwiona */ -> { - attendanceName = "nieobecność nieusprawiedliwiona" - attendanceType = TYPE_ABSENT + 3 -> { + baseType = TYPE_ABSENT + typeName = "nieobecność nieusprawiedliwiona" + typeColor = 0xffffad99 } - 4 /* zwolnienie */, 9 /* zwolniony / obecny */ -> { - attendanceType = TYPE_RELEASED - if (attendanceTypeIdziennik == 4) - attendanceName = "zwolnienie" - if (attendanceTypeIdziennik == 9) - attendanceName = "zwolnienie / obecność" + 4, 9 -> { + baseType = TYPE_RELEASED + if (type == 4) { + typeName = "zwolnienie" + typeColor = 0xffa8beff + } + if (type == 9) { + typeName = "zwolniony / obecny" + typeSymbol = "zb" + typeColor = 0xffff69b4 + } } - 0 /* obecny */, 8 /* Wycieczka */ -> { - attendanceType = TYPE_PRESENT - if (attendanceTypeIdziennik == 8) - attendanceName = "wycieczka" + 8 -> { + baseType = TYPE_PRESENT_CUSTOM + typeName = "wycieczka" + typeSymbol = "w" + typeColor = null + } + 0 -> { + baseType = TYPE_PRESENT + typeName = "obecny" + typeColor = 0xffccffcc } } - val semester = profile?.dateToSemester(attendanceDate) ?: 1 + val semester = profile?.dateToSemester(date) ?: 1 val attendanceObject = Attendance( - profileId, - attendanceId, - rTeacher.id, - rSubject.id, - semester, - attendanceName, - attendanceDate, - attendanceTime, - attendanceType - ) + profileId = profileId, + id = id, + baseType = baseType, + typeName = typeName, + typeShort = typeSymbol ?: data.app.attendanceManager.getTypeShort(baseType), + typeSymbol = typeSymbol ?: data.app.attendanceManager.getTypeShort(baseType), + typeColor = typeColor?.toInt(), + date = date, + startTime = time, + semester = semester, + teacherId = rTeacher.id, + subjectId = rSubject.id + ).also { + it.lessonTopic = jAttendance.getString("PrzedmiotTemat") + it.lessonNumber = jAttendance.getInt("Godzina") + } data.attendanceList.add(attendanceObject) - if (attendanceObject.type != TYPE_PRESENT) { + if (attendanceObject.baseType != TYPE_PRESENT) { data.metadataList.add(Metadata( profileId, Metadata.TYPE_ATTENDANCE, attendanceObject.id, - profile?.empty ?: false, - profile?.empty ?: false, - System.currentTimeMillis() + profile?.empty ?: false || baseType == TYPE_PRESENT_CUSTOM || baseType == TYPE_UNKNOWN, + profile?.empty ?: false || baseType == TYPE_PRESENT_CUSTOM || baseType == TYPE_UNKNOWN )) } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebExams.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebExams.kt index 82e63e79..25c06945 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebExams.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebExams.kt @@ -68,7 +68,7 @@ class IdziennikWebExams(override val data: DataIdziennik, val teacherId = data.getTeacherByLastFirst(teacherName).id val topic = exam.getString("zakres")?.trim() ?: "" - val lessonList = data.db.timetableDao().getForDateNow(profileId, examDate) + val lessonList = data.db.timetableDao().getAllForDateNow(profileId, examDate) val startTime = lessonList.firstOrNull { it.subjectId == subjectId }?.startTime val eventType = when (exam.getString("rodzaj")?.toLowerCase(Locale.getDefault())) { @@ -98,8 +98,7 @@ class IdziennikWebExams(override val data: DataIdziennik, Metadata.TYPE_EVENT, eventObject.id, profile?.empty ?: false, - profile?.empty ?: false, - System.currentTimeMillis() + profile?.empty ?: false )) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebGetMessage.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebGetMessage.kt index e709122d..bd1808df 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebGetMessage.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebGetMessage.kt @@ -94,8 +94,7 @@ class IdziennikWebGetMessage(override val data: DataIdziennik, Metadata.TYPE_MESSAGE, message.id, message.seen, - message.notified, - message.addedDate + message.notified )) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebGrades.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebGrades.kt index b3237c82..90e17401 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebGrades.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebGrades.kt @@ -63,6 +63,8 @@ class IdziennikWebGrades(override val data: DataIdziennik, colorInt = Color.parseColor("#$gradeColor") } + val addedDate = grade.getString("Data_wystaw")?.let { Date.fromY_m_d(it).inMillis } ?: System.currentTimeMillis() + val gradeObject = Grade( profileId = profileId, id = id, @@ -76,7 +78,9 @@ class IdziennikWebGrades(override val data: DataIdziennik, comment = null, semester = semester, teacherId = teacher.id, - subjectId = subject.id) + subjectId = subject.id, + addedDate = addedDate + ) when (grade.getInt("Typ")) { 0 -> { @@ -100,6 +104,8 @@ class IdziennikWebGrades(override val data: DataIdziennik, colorInt = Color.parseColor("#$historyColor") } + val addedDate = historyItem.getString("Data_wystaw")?.let { Date.fromY_m_d(it).inMillis } ?: System.currentTimeMillis() + val historyObject = Grade( profileId = profileId, id = gradeObject.id * -1, @@ -113,19 +119,18 @@ class IdziennikWebGrades(override val data: DataIdziennik, comment = null, semester = historyItem.getInt("Semestr") ?: 1, teacherId = teacher.id, - subjectId = subject.id) + subjectId = subject.id, + addedDate = addedDate + ) historyObject.parentId = gradeObject.id - val addedDate = historyItem.getString("Data_wystaw")?.let { Date.fromY_m_d(it).inMillis } ?: System.currentTimeMillis() - data.gradeList.add(historyObject) data.metadataList.add(Metadata( profileId, Metadata.TYPE_GRADE, historyObject.id, true, - true, - addedDate + true )) } // update the current grade's value with an average of all historical grades and itself @@ -147,8 +152,6 @@ class IdziennikWebGrades(override val data: DataIdziennik, } } - val addedDate = grade.getString("Data_wystaw")?.let { Date.fromY_m_d(it).inMillis } ?: System.currentTimeMillis() - data.gradeList.add(gradeObject) data.metadataList.add( Metadata( @@ -156,8 +159,7 @@ class IdziennikWebGrades(override val data: DataIdziennik, Metadata.TYPE_GRADE, id, data.profile.empty, - data.profile.empty, - addedDate + data.profile.empty )) } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebHomework.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebHomework.kt index 2289e13d..10b4a0c3 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebHomework.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebHomework.kt @@ -57,7 +57,7 @@ class IdziennikWebHomework(override val data: DataIdziennik, val subjectId = data.getSubject(subjectName, null, subjectName).id val teacherName = homework.getString("usr") ?: return@forEach val teacherId = data.getTeacherByLastFirst(teacherName).id - val lessonList = data.db.timetableDao().getForDateNow(profileId, eventDate) + val lessonList = data.db.timetableDao().getAllForDateNow(profileId, eventDate) val startTime = lessonList.firstOrNull { it.subjectId == subjectId }?.displayStartTime val topic = homework.getString("tytul")?.trim() ?: "" @@ -77,7 +77,8 @@ class IdziennikWebHomework(override val data: DataIdziennik, type = Event.TYPE_HOMEWORK, teacherId = teacherId, subjectId = subjectId, - teamId = data.teamClass?.id ?: -1 + teamId = data.teamClass?.id ?: -1, + addedDate = addedDate.inMillis ) data.eventList.add(eventObject) @@ -86,8 +87,7 @@ class IdziennikWebHomework(override val data: DataIdziennik, Metadata.TYPE_HOMEWORK, eventObject.id, seen, - seen, - addedDate.inMillis + seen )) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebNotices.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebNotices.kt index dcc560d5..64108bfd 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebNotices.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebNotices.kt @@ -13,9 +13,12 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikWeb import pl.szczodrzynski.edziennik.data.api.models.ApiError import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.data.db.entity.Notice -import pl.szczodrzynski.edziennik.data.db.entity.Notice.* +import pl.szczodrzynski.edziennik.data.db.entity.Notice.Companion.TYPE_NEGATIVE +import pl.szczodrzynski.edziennik.data.db.entity.Notice.Companion.TYPE_NEUTRAL +import pl.szczodrzynski.edziennik.data.db.entity.Notice.Companion.TYPE_POSITIVE import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS import pl.szczodrzynski.edziennik.getJsonObject +import pl.szczodrzynski.edziennik.getString import pl.szczodrzynski.edziennik.utils.models.Date class IdziennikWebNotices(override val data: DataIdziennik, @@ -53,20 +56,24 @@ class IdziennikWebNotices(override val data: DataIdziennik, } val noticeObject = Notice( - profileId, - noticeId, - jNotice.get("Tresc").asString, - jNotice.get("Semestr").asInt, - nType, - rTeacher.id) + profileId = profileId, + id = noticeId, + type = nType, + semester = jNotice.get("Semestr").asInt, + text = jNotice.getString("Tresc") ?: "", + category = null, + points = null, + teacherId = rTeacher.id, + addedDate = addedDate.inMillis + ) + data.noticeList.add(noticeObject) data.metadataList.add(Metadata( profileId, Metadata.TYPE_NOTICE, noticeObject.id, profile?.empty ?: false, - profile?.empty ?: false, - addedDate.inMillis + profile?.empty ?: false )) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebProposedGrades.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebProposedGrades.kt index c2639e51..b23e9f98 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebProposedGrades.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebProposedGrades.kt @@ -76,6 +76,11 @@ class IdziennikWebProposedGrades(override val data: DataIdziennik, else -1 if (semester1Proposed != "") { + val addedDate = if (data.profile.empty) + data.profile.dateSemester1Start.inMillis + else + System.currentTimeMillis() + val gradeObject = Grade( profileId = profileId, id = semester1Id, @@ -89,26 +94,26 @@ class IdziennikWebProposedGrades(override val data: DataIdziennik, comment = null, semester = 1, teacherId = -1, - subjectId = subjectObject.id + subjectId = subjectObject.id, + addedDate = addedDate ) - val addedDate = if (data.profile.empty) - data.profile.dateSemester1Start.inMillis - else - System.currentTimeMillis() - data.gradeList.add(gradeObject) data.metadataList.add(Metadata( profileId, Metadata.TYPE_GRADE, gradeObject.id, profile.empty, - profile.empty, - addedDate + profile.empty )) } if (semester2Proposed != "") { + val addedDate = if (data.profile.empty) + data.profile.dateSemester2Start.inMillis + else + System.currentTimeMillis() + val gradeObject = Grade( profileId = profileId, id = semester2Id, @@ -122,22 +127,17 @@ class IdziennikWebProposedGrades(override val data: DataIdziennik, comment = null, semester = 2, teacherId = -1, - subjectId = subjectObject.id + subjectId = subjectObject.id, + addedDate = addedDate ) - val addedDate = if (data.profile.empty) - data.profile.dateSemester2Start.inMillis - else - System.currentTimeMillis() - data.gradeList.add(gradeObject) data.metadataList.add(Metadata( profileId, Metadata.TYPE_GRADE, gradeObject.id, profile.empty, - profile.empty, - addedDate + profile.empty )) } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebSendMessage.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebSendMessage.kt index 973bc74f..343ebcac 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebSendMessage.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebSendMessage.kt @@ -59,7 +59,7 @@ class IdziennikWebSendMessage(override val data: DataIdziennik, IdziennikApiMessagesSent(data, null) { val message = data.messageList.firstOrNull { it.type == Message.TYPE_SENT && it.subject == subject } val metadata = data.metadataList.firstOrNull { it.thingType == Metadata.TYPE_MESSAGE && it.thingId == message?.id } - val event = MessageSentEvent(data.profileId, message, metadata?.addedDate) + val event = MessageSentEvent(data.profileId, message, message?.addedDate) EventBus.getDefault().postSticky(event) onSuccess() diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebTimetable.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebTimetable.kt index 066346cd..e7d293c4 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebTimetable.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebTimetable.kt @@ -165,8 +165,7 @@ class IdziennikWebTimetable(override val data: DataIdziennik, Metadata.TYPE_LESSON_CHANGE, lessonObject.id, seen, - seen, - System.currentTimeMillis() + seen )) } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/firstlogin/IdziennikFirstLogin.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/firstlogin/IdziennikFirstLogin.kt index 892c12c8..1a07c609 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/firstlogin/IdziennikFirstLogin.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/firstlogin/IdziennikFirstLogin.kt @@ -89,7 +89,7 @@ class IdziennikFirstLogin(val data: DataIdziennik, val onSuccess: () -> Unit) { profileList.add(profile) } - EventBus.getDefault().post(FirstLoginFinishedEvent(profileList, data.loginStore)) + EventBus.getDefault().postSticky(FirstLoginFinishedEvent(profileList, data.loginStore)) onSuccess() } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/login/IdziennikLoginWeb.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/login/IdziennikLoginWeb.kt index e15f9410..2c6ad0e7 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/login/IdziennikLoginWeb.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/login/IdziennikLoginWeb.kt @@ -74,9 +74,9 @@ class IdziennikLoginWeb(val data: DataIdziennik, val onSuccess: () -> Unit) { Regexes.IDZIENNIK_WEB_LUCKY_NUMBER.find(text)?.also { val number = it[1].toIntOrNull() ?: return@also val luckyNumberObject = LuckyNumber( - data.profileId, - Date.getToday(), - number + profileId = data.profileId, + date = Date.getToday(), + number = number ) data.luckyNumberList.add(luckyNumberObject) @@ -86,8 +86,7 @@ class IdziennikLoginWeb(val data: DataIdziennik, val onSuccess: () -> Unit) { Metadata.TYPE_LUCKY_NUMBER, luckyNumberObject.date.value.toLong(), true, - profile.empty, - System.currentTimeMillis() + profile.empty )) } } 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 80a85a9f..e77dd50d 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 @@ -120,7 +120,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) ?: return; mApiLogin = value } + set(value) { profile?.putStudentData("accountLogin", value); mApiLogin = value } /** * A Synergia password. * Used: for login (API Login Method) in Synergia mode. @@ -129,7 +129,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) ?: return; mApiPassword = value } + set(value) { profile?.putStudentData("accountPassword", value); mApiPassword = value } /** * A JST login Code. @@ -138,8 +138,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) { - loginStore.putLoginData("accountCode", value); mApiCode = value } + set(value) { profile?.putStudentData("accountCode", value); mApiCode = value } /** * A JST login PIN. * Used only during first login in JST mode. @@ -147,8 +146,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) { - loginStore.putLoginData("accountPin", value); mApiPin = value } + set(value) { profile?.putStudentData("accountPin", value); mApiPin = value } /** * A Synergia API access token. @@ -277,4 +275,10 @@ class DataLibrus(app: App, profile: Profile?, loginStore: LoginStore) : Data(app var timetableNotPublic: Boolean get() { mTimetableNotPublic = mTimetableNotPublic ?: profile?.getStudentData("timetableNotPublic", false); return mTimetableNotPublic ?: false } set(value) { profile?.putStudentData("timetableNotPublic", value) ?: return; mTimetableNotPublic = value } + + /** + * Set to false when Recaptcha helper doesn't provide a working token. + * When it's set to false uses Synergia for messages. + */ + var messagesLoginSuccessful: Boolean = true } 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 a79a66f8..48f308f8 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 @@ -13,9 +13,7 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.messages.Librus import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.messages.LibrusMessagesGetMessage import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.messages.LibrusMessagesGetRecipientList import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.messages.LibrusMessagesSendMessage -import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.synergia.LibrusSynergiaGetHomework -import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.synergia.LibrusSynergiaHomeworkGetAttachment -import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.synergia.LibrusSynergiaMarkAllAnnouncementsAsRead +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.interfaces.EdziennikCallback @@ -91,9 +89,8 @@ class Librus(val app: App, val profile: Profile?, val loginStore: LoginStore, va override fun getMessage(message: MessageFull) { login(LOGIN_METHOD_LIBRUS_MESSAGES) { - LibrusMessagesGetMessage(data, message) { - completed() - } + if (data.messagesLoginSuccessful) LibrusMessagesGetMessage(data, message) { completed() } + else LibrusSynergiaGetMessage(data, message) { completed() } } } @@ -124,10 +121,9 @@ 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_MESSAGES) { - LibrusMessagesGetAttachment(data, owner, attachmentId, attachmentName) { - completed() - } + login(LOGIN_METHOD_LIBRUS_SYNERGIA) { + if (data.messagesLoginSuccessful) LibrusMessagesGetAttachment(data, owner, attachmentId, attachmentName) { completed() } + LibrusSynergiaGetAttachment(data, owner, attachmentId, attachmentName) { completed() } } } is EventFull -> { 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 98caab4e..c14a00df 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 @@ -50,6 +50,8 @@ const val ENDPOINT_LIBRUS_API_CLASS_FREE_DAYS = 1130 const val ENDPOINT_LIBRUS_SYNERGIA_INFO = 2010 const val ENDPOINT_LIBRUS_SYNERGIA_GRADES = 2020 const val ENDPOINT_LIBRUS_SYNERGIA_HOMEWORK = 2030 +const val ENDPOINT_LIBRUS_SYNERGIA_MESSAGES_RECEIVED = 2040 +const val ENDPOINT_LIBRUS_SYNERGIA_MESSAGES_SENT = 2050 const val ENDPOINT_LIBRUS_MESSAGES_RECEIVED = 3010 const val ENDPOINT_LIBRUS_MESSAGES_SENT = 3020 const val ENDPOINT_LIBRUS_MESSAGES_TRASH = 3030 diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/LibrusRecaptchaHelper.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/LibrusRecaptchaHelper.kt new file mode 100644 index 00000000..94ab5ed1 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/LibrusRecaptchaHelper.kt @@ -0,0 +1,56 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-5-8. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus + +import android.content.Context +import android.webkit.WebView +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch +import pl.szczodrzynski.edziennik.startCoroutineTimer +import kotlin.coroutines.CoroutineContext + +class LibrusRecaptchaHelper( + val context: Context, + url: String, + html: String, + val onSuccess: (url: String) -> Unit, + val onTimeout: () -> Unit +) : CoroutineScope { + companion object { + private const val TAG = "LibrusRecaptchaHelper" + } + + private val job: Job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Default + + private val webView by lazy { + WebView(context).also { + it.settings.javaScriptEnabled = true + it.webViewClient = WebViewClient() + } + } + + private var timeout: Job? = null + + inner class WebViewClient : android.webkit.WebViewClient() { + override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean { + timeout?.cancel() + onSuccess(url) + return true + } + } + + init { + launch(Dispatchers.Main) { + webView.loadDataWithBaseURL(url, html, "text/html", "UTF-8", null) + } + timeout = startCoroutineTimer(delayMillis = 10000L) { + onTimeout() + } + } +} 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 bf1fdab4..877d803b 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 @@ -8,6 +8,7 @@ import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.data.api.edziennik.librus.* import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api.* import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.messages.LibrusMessagesGetList +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.synergia.LibrusSynergiaGetMessages import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.synergia.LibrusSynergiaHomework import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.synergia.LibrusSynergiaInfo import pl.szczodrzynski.edziennik.data.db.entity.Message @@ -201,17 +202,27 @@ class LibrusData(val data: DataLibrus, val onSuccess: () -> Unit) { data.startProgress(R.string.edziennik_progress_endpoint_student_info) LibrusSynergiaInfo(data, lastSync, onSuccess) } + ENDPOINT_LIBRUS_SYNERGIA_MESSAGES_RECEIVED -> { + data.startProgress(R.string.edziennik_progress_endpoint_messages_inbox) + LibrusSynergiaGetMessages(data, type = Message.TYPE_RECEIVED, lastSync = lastSync, onSuccess = onSuccess) + } + ENDPOINT_LIBRUS_SYNERGIA_MESSAGES_SENT -> { + data.startProgress(R.string.edziennik_progress_endpoint_messages_outbox) + LibrusSynergiaGetMessages(data, type = Message.TYPE_SENT, lastSync = lastSync, onSuccess = onSuccess) + } /** * MESSAGES */ ENDPOINT_LIBRUS_MESSAGES_RECEIVED -> { data.startProgress(R.string.edziennik_progress_endpoint_messages_inbox) - LibrusMessagesGetList(data, type = Message.TYPE_RECEIVED, lastSync = lastSync, onSuccess = onSuccess) + if (data.messagesLoginSuccessful) LibrusMessagesGetList(data, type = Message.TYPE_RECEIVED, lastSync = lastSync, onSuccess = onSuccess) + else LibrusSynergiaGetMessages(data, type = Message.TYPE_RECEIVED, lastSync = lastSync, onSuccess = onSuccess) } ENDPOINT_LIBRUS_MESSAGES_SENT -> { data.startProgress(R.string.edziennik_progress_endpoint_messages_outbox) - LibrusMessagesGetList(data, type = Message.TYPE_SENT, lastSync = lastSync, onSuccess = onSuccess) + if (data.messagesLoginSuccessful) LibrusMessagesGetList(data, type = Message.TYPE_SENT, lastSync = lastSync, onSuccess = onSuccess) + else LibrusSynergiaGetMessages(data, type = Message.TYPE_SENT, lastSync = lastSync, onSuccess = onSuccess) } else -> onSuccess(endpointId) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/LibrusSynergia.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/LibrusSynergia.kt index 96f28a65..72faf801 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/LibrusSynergia.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/LibrusSynergia.kt @@ -91,6 +91,8 @@ open class LibrusSynergia(open val data: DataLibrus, open val lastSync: Long?) { } fun redirectUrlGet(tag: String, url: String, onSuccess: (url: String) -> Unit) { + d(tag, "Request: Librus/Synergia - $url") + val callback = object : TextCallbackHandler() { override fun onSuccess(text: String?, response: Response) { val redirectUrl = response.headers().get("Location") 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 d40c1a9a..23cfceb4 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 @@ -37,8 +37,7 @@ class LibrusApiAnnouncementMarkAsRead(override val data: DataLibrus, Metadata.TYPE_ANNOUNCEMENT, announcement.id, announcement.seen, - announcement.notified, - announcement.addedDate + announcement.notified )) onSuccess() } 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 8256b859..d19be231 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 @@ -38,15 +38,17 @@ class LibrusApiAnnouncements(override val data: DataLibrus, val read = announcement.getBoolean("WasRead") ?: false val announcementObject = Announcement( - profileId, - id, - subject, - text, - startDate, - endDate, - teacherId, - longId - ) + profileId = profileId, + id = id, + subject = subject, + text = text, + startDate = startDate, + endDate = endDate, + teacherId = teacherId, + addedDate = addedDate + ).also { + it.idString = longId + } data.announcementList.add(announcementObject) data.setSeenMetadataList.add(Metadata( @@ -54,8 +56,7 @@ class LibrusApiAnnouncements(override val data: DataLibrus, Metadata.TYPE_ANNOUNCEMENT, id, read, - profile.empty || read, - addedDate + profile.empty || read )) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiAttendanceTypes.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiAttendanceTypes.kt index d7cbc00c..c620d13d 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiAttendanceTypes.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiAttendanceTypes.kt @@ -26,25 +26,39 @@ class LibrusApiAttendanceTypes(override val data: DataLibrus, attendanceTypes?.forEach { attendanceType -> val id = attendanceType.getLong("Id") ?: return@forEach - val name = attendanceType.getString("Name") ?: "" - val color = attendanceType.getString("ColorRGB")?.let { Color.parseColor("#$it") } ?: -1 - val standardId = when (attendanceType.getBoolean("Standard") ?: false) { - true -> id - false -> attendanceType.getJsonObject("StandardType")?.getLong("Id") ?: id - } - val type = when (standardId) { + val typeName = attendanceType.getString("Name") ?: "" + val typeSymbol = attendanceType.getString("Short") ?: "" + val typeColor = attendanceType.getString("ColorRGB")?.let { Color.parseColor("#$it") } + + val isStandard = attendanceType.getBoolean("Standard") ?: false + val baseType = when (attendanceType.getJsonObject("StandardType")?.getLong("Id") ?: id) { 1L -> Attendance.TYPE_ABSENT 2L -> Attendance.TYPE_BELATED 3L -> Attendance.TYPE_ABSENT_EXCUSED 4L -> Attendance.TYPE_RELEASED - /*100*/else -> Attendance.TYPE_PRESENT + /*100*/else -> when (isStandard) { + true -> Attendance.TYPE_PRESENT + false -> Attendance.TYPE_PRESENT_CUSTOM + } + } + val typeShort = when (isStandard) { + true -> data.app.attendanceManager.getTypeShort(baseType) + false -> typeSymbol } - data.attendanceTypes.put(id, AttendanceType(profileId, id, name, type, color)) + data.attendanceTypes.put(id, AttendanceType( + profileId, + id, + baseType, + typeName, + typeShort, + typeSymbol, + typeColor + )) } - data.setSyncNext(ENDPOINT_LIBRUS_API_ATTENDANCE_TYPES, 4*DAY) + data.setSyncNext(ENDPOINT_LIBRUS_API_ATTENDANCE_TYPES, 2*DAY) onSuccess(ENDPOINT_LIBRUS_API_ATTENDANCE_TYPES) } } 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 3727788d..7857dc2a 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 @@ -13,7 +13,6 @@ 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.utils.models.Date -import pl.szczodrzynski.edziennik.utils.models.Time class LibrusApiAttendances(override val data: DataLibrus, override val lastSync: Long?, @@ -42,9 +41,9 @@ class LibrusApiAttendances(override val data: DataLibrus, val lessonDate = Date.fromY_m_d(attendance.getString("Date")) val teacherId = attendance.getJsonObject("AddedBy")?.getLong("Id") val semester = attendance.getInt("Semester") ?: return@forEach - val type = attendance.getJsonObject("Type")?.getLong("Id") ?: return@forEach - val typeObject = data.attendanceTypes[type] ?: null - val topic = typeObject?.name ?: "" + + val typeId = attendance.getJsonObject("Type")?.getLong("Id") ?: return@forEach + val type = data.attendanceTypes[typeId] ?: null val startTime = data.lessonRanges.get(lessonNo)?.startTime @@ -52,29 +51,34 @@ class LibrusApiAttendances(override val data: DataLibrus, data.librusLessons.singleOrNull { it.lessonId == lessonId } else null - val attendanceObject = Attendance( - profileId, - id, - teacherId ?: lesson?.teacherId ?: -1, - lesson?.subjectId ?: -1, - semester, - topic, - lessonDate, - startTime ?: Time(0, 0, 0), - typeObject?.type ?: Attendance.TYPE_CUSTOM - ) - val addedDate = Date.fromIso(attendance.getString("AddDate") ?: return@forEach) + val attendanceObject = Attendance( + profileId = profileId, + id = id, + baseType = type?.baseType ?: Attendance.TYPE_UNKNOWN, + typeName = type?.typeName ?: "nieznany rodzaj", + typeShort = type?.typeShort ?: "?", + typeSymbol = type?.typeSymbol ?: "?", + typeColor = type?.typeColor, + date = lessonDate, + startTime = startTime, + semester = semester, + teacherId = teacherId ?: lesson?.teacherId ?: -1, + subjectId = lesson?.subjectId ?: -1, + addedDate = addedDate + ).also { + it.lessonNumber = lessonNo + } + data.attendanceList.add(attendanceObject) - if(typeObject?.type != Attendance.TYPE_PRESENT) { + if(type?.baseType != Attendance.TYPE_PRESENT) { data.metadataList.add(Metadata( profileId, Metadata.TYPE_ATTENDANCE, id, - profile?.empty ?: false, - profile?.empty ?: false, - addedDate + 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 5a374ec9..fae79a50 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 @@ -55,7 +55,8 @@ class LibrusApiBehaviourGrades(override val data: DataLibrus, comment = null, semester = 1, teacherId = -1, - subjectId = 1 + subjectId = 1, + addedDate = profile.getSemesterStart(1).inMillis ) data.gradeList.add(semester1StartGradeObject) @@ -64,8 +65,7 @@ class LibrusApiBehaviourGrades(override val data: DataLibrus, Metadata.TYPE_GRADE, semester1StartGradeObject.id, true, - true, - profile.getSemesterStart(1).inMillis + true )) } @@ -83,7 +83,8 @@ class LibrusApiBehaviourGrades(override val data: DataLibrus, comment = null, semester = 2, teacherId = -1, - subjectId = 1 + subjectId = 1, + addedDate = profile.getSemesterStart(2).inMillis ) data.gradeList.add(semester2StartGradeObject) @@ -92,8 +93,7 @@ class LibrusApiBehaviourGrades(override val data: DataLibrus, Metadata.TYPE_GRADE, semester2StartGradeObject.id, true, - true, - profile.getSemesterStart(2).inMillis + true )) } @@ -155,7 +155,8 @@ class LibrusApiBehaviourGrades(override val data: DataLibrus, comment = if (text != null) description.join(" - ") else null, semester = semester, teacherId = teacherId, - subjectId = 1 + subjectId = 1, + addedDate = addedDate ).apply { valueMax = valueTo } @@ -166,8 +167,7 @@ class LibrusApiBehaviourGrades(override val data: DataLibrus, Metadata.TYPE_GRADE, id, profile.empty, - profile.empty, - addedDate + 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 42e318e0..8cc4f1c1 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 @@ -65,7 +65,8 @@ class LibrusApiDescriptiveGrades(override val data: DataLibrus, comment = null, semester = semester, teacherId = teacherId, - subjectId = subjectId + subjectId = subjectId, + addedDate = addedDate ) data.gradeList.add(gradeObject) @@ -74,8 +75,7 @@ class LibrusApiDescriptiveGrades(override val data: DataLibrus, Metadata.TYPE_GRADE, id, profile.empty, - profile.empty, - addedDate + 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 52f52844..7a76f88e 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 @@ -35,7 +35,7 @@ class LibrusApiEvents(override val data: DataLibrus, events?.forEach { event -> val id = event.getLong("Id") ?: return@forEach val eventDate = Date.fromY_m_d(event.getString("Date")) - val topic = event.getString("Content")?.trim() ?: "" + var topic = event.getString("Content")?.trim() ?: "" val type = event.getJsonObject("Category")?.getLong("Id") ?: -1 val teacherId = event.getJsonObject("CreatedBy")?.getLong("Id") ?: -1 val subjectId = event.getJsonObject("Subject")?.getLong("Id") ?: -1 @@ -46,6 +46,12 @@ class LibrusApiEvents(override val data: DataLibrus, val startTime = lessonRange?.startTime ?: Time.fromH_m(event.getString("TimeFrom")) val addedDate = Date.fromIso(event.getString("AddDate")) + event.getJsonObject("onlineLessonUrl")?.let { onlineLesson -> + val text = onlineLesson.getString("text")?.let { "$it - " } ?: "" + val url = onlineLesson.getString("url") + topic += "\n\n$text$url" + } + val eventObject = Event( profileId = profileId, id = id, @@ -56,7 +62,8 @@ class LibrusApiEvents(override val data: DataLibrus, type = type, teacherId = teacherId, subjectId = subjectId, - teamId = teamId + teamId = teamId, + addedDate = addedDate ) data.eventList.add(eventObject) @@ -66,8 +73,7 @@ class LibrusApiEvents(override val data: DataLibrus, Metadata.TYPE_EVENT, id, profile?.empty ?: false, - profile?.empty ?: false, - addedDate + profile?.empty ?: false )) } 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 7ff42fde..c2e2d0ce 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 @@ -79,7 +79,8 @@ class LibrusApiGrades(override val data: DataLibrus, comment = null, semester = semester, teacherId = teacherId, - subjectId = subjectId + subjectId = subjectId, + addedDate = addedDate ) grade.getJsonObject("Improvement")?.also { @@ -98,8 +99,7 @@ class LibrusApiGrades(override val data: DataLibrus, Metadata.TYPE_GRADE, id, profile.empty, - profile.empty, - addedDate + 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 d47fbfdf..c1183c19 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 @@ -43,7 +43,8 @@ class LibrusApiHomework(override val data: DataLibrus, type = -1, teacherId = teacherId, subjectId = -1, - teamId = -1 + teamId = -1, + addedDate = addedDate.inMillis ) data.eventList.add(eventObject) @@ -52,8 +53,7 @@ class LibrusApiHomework(override val data: DataLibrus, Metadata.TYPE_HOMEWORK, id, profile?.empty ?: false, - profile?.empty ?: false, - addedDate.inMillis + profile?.empty ?: false )) } 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 5a52cd69..80e6e299 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 @@ -33,9 +33,9 @@ class LibrusApiLuckyNumber(override val data: DataLibrus, val luckyNumberDate = Date.fromY_m_d(luckyNumberEl.getString("LuckyNumberDay")) ?: Date.getToday() val luckyNumber = luckyNumberEl.getInt("LuckyNumber") ?: -1 val luckyNumberObject = LuckyNumber( - profileId, - luckyNumberDate, - luckyNumber + profileId = profileId, + date = luckyNumberDate, + number = luckyNumber ) if (luckyNumberDate >= Date.getToday()) @@ -50,8 +50,7 @@ class LibrusApiLuckyNumber(override val data: DataLibrus, Metadata.TYPE_LUCKY_NUMBER, luckyNumberObject.date.value.toLong(), true, - profile?.empty ?: false, - System.currentTimeMillis() + 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 e05cbfd7..ba531afe 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 @@ -46,12 +46,15 @@ class LibrusApiNotices(override val data: DataLibrus, val semester = profile?.dateToSemester(addedDate) ?: 1 val noticeObject = Notice( - profileId, - id, - categoryText + "\n" + text, - semester, - type, - teacherId + profileId = profileId, + id = id, + type = type, + semester = semester, + text = text, + category = categoryText, + points = null, + teacherId = teacherId, + addedDate = addedDate.inMillis ) data.noticeList.add(noticeObject) @@ -61,8 +64,7 @@ class LibrusApiNotices(override val data: DataLibrus, Metadata.TYPE_NOTICE, id, profile?.empty ?: false, - profile?.empty ?: false, - addedDate.inMillis + 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 fae45318..9da2c435 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 @@ -56,7 +56,8 @@ class LibrusApiPointGrades(override val data: DataLibrus, comment = null, semester = semester, teacherId = teacherId, - subjectId = subjectId + subjectId = subjectId, + addedDate = addedDate ).apply { valueMax = category?.valueTo ?: 0f } @@ -67,8 +68,7 @@ class LibrusApiPointGrades(override val data: DataLibrus, Metadata.TYPE_GRADE, id, profile.empty, - profile.empty, - addedDate + 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 de696a99..6c0a9dd7 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 @@ -58,8 +58,7 @@ class LibrusApiPtMeetings(override val data: DataLibrus, Metadata.TYPE_EVENT, id, profile?.empty ?: false, - profile?.empty ?: false, - System.currentTimeMillis() + profile?.empty ?: false )) } 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 3d4db1aa..973ada46 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 @@ -43,15 +43,15 @@ class LibrusApiTeacherFreeDays(override val data: DataLibrus, val timeTo = teacherAbsence.getString("TimeTo")?.let { Time.fromH_m_s(it) } val teacherAbsenceObject = TeacherAbsence( - profileId, - id, - teacherId, - type, - name, - dateFrom, - dateTo, - timeFrom, - timeTo + profileId = profileId, + id = id, + type = type, + name = name, + dateFrom = dateFrom, + dateTo = dateTo, + timeFrom = timeFrom, + timeTo = timeTo, + teacherId = teacherId ) data.teacherAbsenceList.add(teacherAbsenceObject) @@ -60,8 +60,7 @@ class LibrusApiTeacherFreeDays(override val data: DataLibrus, Metadata.TYPE_TEACHER_ABSENCE, id, true, - profile?.empty ?: false, - System.currentTimeMillis() + profile?.empty ?: false )) } 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 cf020425..7b7c8c6f 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 @@ -60,7 +60,8 @@ class LibrusApiTextGrades(override val data: DataLibrus, comment = grade.getString("Phrase") /* whatever it is */, semester = semester, teacherId = teacherId, - subjectId = subjectId + subjectId = subjectId, + addedDate = addedDate ) data.gradeList.add(gradeObject) @@ -69,8 +70,7 @@ class LibrusApiTextGrades(override val data: DataLibrus, Metadata.TYPE_GRADE, id, profile.empty, - profile.empty, - addedDate + 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 7c1eec61..9a463a6b 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 @@ -198,8 +198,7 @@ class LibrusApiTimetables(override val data: DataLibrus, Metadata.TYPE_LESSON_CHANGE, lessonObject.id, seen, - seen, - System.currentTimeMillis() + seen )) } data.lessonList.add(lessonObject) 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 35148cef..9f962bf9 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 @@ -97,7 +97,8 @@ class LibrusMessagesGetList(override val data: DataLibrus, type = type, subject = subject, body = null, - senderId = senderId + senderId = senderId, + addedDate = sentDate ) val messageRecipientObject = MessageRecipient( @@ -120,8 +121,7 @@ class LibrusMessagesGetList(override val data: DataLibrus, Metadata.TYPE_MESSAGE, id, notified, - notified, - sentDate + notified )) } 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 bb4c620b..49ee0d52 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 @@ -150,8 +150,7 @@ class LibrusMessagesGetMessage(override val data: DataLibrus, Metadata.TYPE_MESSAGE, messageObject.id, true, - true, - messageObject.addedDate + 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 9364f2b8..d568135e 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 @@ -50,7 +50,7 @@ class LibrusMessagesSendMessage(override val data: DataLibrus, LibrusMessagesGetList(data, type = Message.TYPE_SENT, lastSync = null) { val message = data.messageList.firstOrNull { it.type == Message.TYPE_SENT && it.id == id } val metadata = data.metadataList.firstOrNull { it.thingType == Metadata.TYPE_MESSAGE && it.thingId == message?.id } - val event = MessageSentEvent(data.profileId, message, metadata?.addedDate) + val event = MessageSentEvent(data.profileId, message, message?.addedDate) EventBus.getDefault().postSticky(event) onSuccess() diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaGetAttachment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaGetAttachment.kt new file mode 100644 index 00000000..6b1f2eba --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaGetAttachment.kt @@ -0,0 +1,24 @@ +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.synergia + +import pl.szczodrzynski.edziennik.data.api.LIBRUS_SYNERGIA_MESSAGES_ATTACHMENT_URL +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusSynergia +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.messages.LibrusSandboxDownloadAttachment +import pl.szczodrzynski.edziennik.data.db.entity.Message + +class LibrusSynergiaGetAttachment(override val data: DataLibrus, + val message: Message, + val attachmentId: Long, + val attachmentName: String, + val onSuccess: () -> Unit +) : LibrusSynergia(data, null) { + companion object { + const val TAG = "LibrusSynergiaGetAttachment" + } + + init { + redirectUrlGet(TAG, "$LIBRUS_SYNERGIA_MESSAGES_ATTACHMENT_URL/${message.id}/$attachmentId") { url -> + LibrusSandboxDownloadAttachment(data, url, message, attachmentId, attachmentName, onSuccess) + } + } +} 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 new file mode 100644 index 00000000..4cb1e246 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaGetMessage.kt @@ -0,0 +1,160 @@ +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.synergia + +import org.greenrobot.eventbus.EventBus +import org.jsoup.Jsoup +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusSynergia +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.full.MessageFull +import pl.szczodrzynski.edziennik.data.db.full.MessageRecipientFull +import pl.szczodrzynski.edziennik.get +import pl.szczodrzynski.edziennik.isNotNullNorEmpty +import pl.szczodrzynski.edziennik.singleOrNull +import pl.szczodrzynski.edziennik.swapFirstLastName +import pl.szczodrzynski.edziennik.utils.models.Date + +class LibrusSynergiaGetMessage(override val data: DataLibrus, + private val messageObject: MessageFull, + val onSuccess: () -> Unit) : LibrusSynergia(data, null) { + companion object { + const val TAG = "LibrusSynergiaGetMessage" + } + + init { + val endpoint = when (messageObject.type) { + Message.TYPE_SENT -> "wiadomosci/1/6/${messageObject.id}/f0" + else -> "wiadomosci/1/5/${messageObject.id}/f0" + } + + data.profile?.also { profile -> + synergiaGet(TAG, endpoint) { text -> + val doc = Jsoup.parse(text) + + val messageElement = doc.select(".container-message tr")[0].child(1) + val detailsElement = messageElement.child(1) + val readElement = messageElement.children().last() + + val body = messageElement.select(".container-message-content").html() + + messageObject.apply { + this.body = body + + clearAttachments() + if (messageElement.children().size >= 5) { + messageElement.child(3).select("tr").forEachIndexed { i, attachment -> + if (i == 0) return@forEachIndexed // Skip the header + val filename = attachment.child(0).text().trim() + val attachmentId = "wiadomosci\\\\/pobierz_zalacznik\\\\/[0-9]+?\\\\/([0-9]+)\"".toRegex() + .find(attachment.select("img").attr("onclick"))?.get(1) + ?: return@forEachIndexed + addAttachment(attachmentId.toLong(), filename, -1) + } + } + } + + val messageRecipientList = mutableListOf() + + when (messageObject.type) { + Message.TYPE_RECEIVED -> { + val senderFullName = detailsElement.child(0).select(".left").text() + val senderGroupName = "\\[(.+?)]".toRegex().find(senderFullName)?.get(1)?.trim() + + data.teacherList.singleOrNull { it.id == messageObject.senderId }?.apply { + setTeacherType(when (senderGroupName) { + /* https://api.librus.pl/2.0/Messages/Role */ + "Pomoc techniczna Librus", "SuperAdministrator" -> Teacher.TYPE_SUPER_ADMIN + "Administrator szkoły" -> Teacher.TYPE_SCHOOL_ADMIN + "Dyrektor Szkoły" -> Teacher.TYPE_PRINCIPAL + "Nauczyciel" -> Teacher.TYPE_TEACHER + "Rodzic", "Opiekun" -> Teacher.TYPE_PARENT + "Sekretariat" -> Teacher.TYPE_SECRETARIAT + "Uczeń" -> Teacher.TYPE_STUDENT + "Pedagog/Psycholog szkolny" -> Teacher.TYPE_PEDAGOGUE + "Pracownik biblioteki" -> Teacher.TYPE_LIBRARIAN + "Inny specjalista" -> Teacher.TYPE_SPECIALIST + "Jednostka Nadrzędna" -> { + typeDescription = "Jednostka Nadrzędna" + Teacher.TYPE_OTHER + } + "Jednostka Samorządu Terytorialnego" -> { + typeDescription = "Jednostka Samorządu Terytorialnego" + Teacher.TYPE_OTHER + } + else -> Teacher.TYPE_OTHER + }) + } + + val readDateText = readElement.select(".left").text() + val readDate = when (readDateText.isNotNullNorEmpty()) { + true -> Date.fromIso(readDateText) + else -> 0 + } + + val messageRecipientObject = MessageRecipientFull( + profileId = profileId, + id = -1, + messageId = messageObject.id, + readDate = readDate + ) + + messageRecipientObject.fullName = profile.accountName + ?: profile.studentNameLong + + messageRecipientList.add(messageRecipientObject) + } + + Message.TYPE_SENT -> { + + readElement.select("tr").forEachIndexed { i, receiver -> + if (i == 0) return@forEachIndexed // Skip the header + + val receiverFullName = receiver.child(0).text() + val receiverName = receiverFullName.split('(')[0].swapFirstLastName() + + val teacher = data.teacherList.singleOrNull { it.fullName == receiverName } + val receiverId = teacher?.id ?: -1 + + val readDate = when (val readDateText = receiver.child(1).text().trim()) { + "NIE" -> 0 + else -> Date.fromIso(readDateText) + } + + val messageRecipientObject = MessageRecipientFull( + profileId = profileId, + id = receiverId, + messageId = messageObject.id, + readDate = readDate + ) + + messageRecipientObject.fullName = receiverName + + messageRecipientList.add(messageRecipientObject) + } + } + } + + if (!messageObject.seen) { + data.setSeenMetadataList.add(Metadata( + messageObject.profileId, + Metadata.TYPE_MESSAGE, + messageObject.id, + true, + true + )) + } + + messageObject.recipients = messageRecipientList + data.messageRecipientList.addAll(messageRecipientList) + + data.messageList.add(messageObject) + data.messageListReplace = true + + EventBus.getDefault().postSticky(MessageGetEvent(messageObject)) + onSuccess() + } + } ?: onSuccess() + } +} 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 new file mode 100644 index 00000000..ffc8133a --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaGetMessages.kt @@ -0,0 +1,116 @@ +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.utils.Utils +import pl.szczodrzynski.edziennik.utils.models.Date + +class LibrusSynergiaGetMessages(override val data: DataLibrus, + override val lastSync: Long?, + private val type: Int = Message.TYPE_RECEIVED, + archived: Boolean = false, + val onSuccess: (Int) -> Unit) : LibrusSynergia(data, lastSync) { + companion object { + const val TAG = "LibrusSynergiaGetMessages" + } + + init { + val endpoint = when (type) { + Message.TYPE_RECEIVED -> "wiadomosci/5" + Message.TYPE_SENT -> "wiadomosci/6" + else -> null + } + val endpointId = when (type) { + Message.TYPE_RECEIVED -> ENDPOINT_LIBRUS_SYNERGIA_MESSAGES_RECEIVED + else -> ENDPOINT_LIBRUS_SYNERGIA_MESSAGES_SENT + } + + if (endpoint != null) { + synergiaGet(TAG, endpoint) { text -> + val doc = Jsoup.parse(text) + + fun getRecipientId(name: String): Long = data.teacherList.singleOrNull { + it.fullNameLastFirst == name + }?.id ?: { + val teacherObject = Teacher( + profileId, + -1 * Utils.crc16(name.swapFirstLastName().toByteArray()).toLong(), + name.splitName()?.second!!, + name.splitName()?.first!! + ) + data.teacherList.put(teacherObject.id, teacherObject) + teacherObject.id + }.invoke() + + doc.select(".decorated.stretch tbody > tr").forEach { messageElement -> + val url = messageElement.select("a").first().attr("href") + val id = Regexes.LIBRUS_MESSAGE_ID.find(url)?.get(1)?.toLong() ?: return@forEach + val subject = messageElement.child(3).text() + val sentDate = Date.fromIso(messageElement.child(4).text()) + val recipientName = messageElement.child(2).text().split('(')[0].fixName() + val recipientId = getRecipientId(recipientName) + val read = messageElement.child(2).attr("style").isNullOrBlank() + + val senderId = when (type) { + Message.TYPE_RECEIVED -> recipientId + else -> null + } + + val receiverId = when (type) { + Message.TYPE_RECEIVED -> -1 + else -> recipientId + } + + val notified = when (type) { + Message.TYPE_SENT -> true + else -> read || profile?.empty ?: false + } + + val messageObject = Message( + profileId = profileId, + id = id, + type = type, + subject = subject, + body = null, + senderId = senderId, + addedDate = sentDate + ) + + val messageRecipientObject = MessageRecipient( + profileId, + receiverId, + -1, + if (read) 1 else 0, + id + ) + + messageObject.hasAttachments = !messageElement.child(1).select("img").isEmpty() + + data.messageList.add(messageObject) + data.messageRecipientList.add(messageRecipientObject) + data.setSeenMetadataList.add(Metadata( + profileId, + Metadata.TYPE_MESSAGE, + id, + notified, + notified + )) + } + + 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) + } + onSuccess(endpointId) + } + } else { + data.error(TAG, ERROR_NOT_IMPLEMENTED) + 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 48f88ef4..d33f6911 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 @@ -58,7 +58,7 @@ class LibrusSynergiaHomework(override val data: DataLibrus, elements[9].select("input").attr("onclick") )?.get(1)?.toLong() ?: return@forEachIndexed - val lessons = data.db.timetableDao().getForDateNow(profileId, eventDate) + val lessons = data.db.timetableDao().getAllForDateNow(profileId, eventDate) val startTime = lessons.firstOrNull { it.subjectId == subjectId }?.startTime val seen = when (profile.empty) { @@ -76,7 +76,8 @@ class LibrusSynergiaHomework(override val data: DataLibrus, type = Event.TYPE_HOMEWORK, teacherId = teacherId, subjectId = subjectId, - teamId = data.teamClass?.id ?: -1 + teamId = data.teamClass?.id ?: -1, + addedDate = addedDate.inMillis ) data.eventList.add(eventObject) @@ -85,8 +86,7 @@ class LibrusSynergiaHomework(override val data: DataLibrus, Metadata.TYPE_HOMEWORK, id, seen, - seen, - addedDate.inMillis + seen )) } } 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 f693c41a..68f44269 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 @@ -33,7 +33,7 @@ class LibrusFirstLogin(val data: DataLibrus, val onSuccess: () -> Unit) { val accounts = json.getJsonArray("accounts") if (accounts == null || accounts.size() < 1) { - EventBus.getDefault().post(FirstLoginFinishedEvent(listOf(), data.loginStore)) + EventBus.getDefault().postSticky(FirstLoginFinishedEvent(listOf(), data.loginStore)) onSuccess() return@portalGet } @@ -81,7 +81,7 @@ class LibrusFirstLogin(val data: DataLibrus, val onSuccess: () -> Unit) { profileList.add(profile) } - EventBus.getDefault().post(FirstLoginFinishedEvent(profileList, data.loginStore)) + EventBus.getDefault().postSticky(FirstLoginFinishedEvent(profileList, data.loginStore)) onSuccess() } } @@ -116,14 +116,15 @@ class LibrusFirstLogin(val data: DataLibrus, val onSuccess: () -> Unit) { ).apply { studentData["isPremium"] = account?.getBoolean("IsPremium") == true || account?.getBoolean("IsPremiumDemo") == true studentData["accountId"] = account.getInt("Id") ?: 0 - studentData["accountLogin"] = login + studentData["accountLogin"] = data.apiLogin ?: login + studentData["accountPassword"] = data.apiPassword studentData["accountToken"] = data.apiAccessToken studentData["accountTokenTime"] = data.apiTokenExpiryTime studentData["accountRefreshToken"] = data.apiRefreshToken } profileList.add(profile) - EventBus.getDefault().post(FirstLoginFinishedEvent(profileList, data.loginStore)) + EventBus.getDefault().postSticky(FirstLoginFinishedEvent(profileList, data.loginStore)) onSuccess() } } 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 fe155abe..7151cf29 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 @@ -18,6 +18,7 @@ import pl.szczodrzynski.edziennik.getUnixDate import pl.szczodrzynski.edziennik.utils.Utils.d import java.net.HttpURLConnection.* +@Suppress("ConvertSecondaryConstructorToPrimary") class LibrusLoginApi { companion object { private const val TAG = "LoginLibrusApi" 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 ba8bcf76..9bfad99f 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 @@ -10,6 +10,7 @@ import im.wangchao.mhttp.body.MediaTypeUtils import im.wangchao.mhttp.callback.TextCallbackHandler 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.getUnixDate import pl.szczodrzynski.edziennik.utils.Utils.d @@ -35,17 +36,39 @@ class LibrusLoginMessages(val data: DataLibrus, val onSuccess: () -> Unit) { onSuccess() } + text?.contains("grecaptcha.ready") == true -> { + val url = response?.request()?.url()?.toString() ?: run { + //data.error(TAG, ERROR_LIBRUS_MESSAGES_OTHER, response, text) + data.messagesLoginSuccessful = false + onSuccess() + return + } + + LibrusRecaptchaHelper(data.app, url, text, onSuccess = { newUrl -> + loginWithSynergia(newUrl) + }, onTimeout = { + //data.error(TAG, ERROR_LOGIN_LIBRUS_MESSAGES_TIMEOUT, response, text) + data.messagesLoginSuccessful = false + onSuccess() + }) + } + text?.contains("ok") == true -> { saveSessionId(response, text) onSuccess() } text?.contains("Niepoprawny login i/lub hasło.") == true -> data.error(TAG, ERROR_LOGIN_LIBRUS_MESSAGES_INVALID_LOGIN, response, text) text?.contains("stop.png") == true -> data.error(TAG, ERROR_LIBRUS_SYNERGIA_ACCESS_DENIED, response, text) - text?.contains("eAccessDeny") == true -> data.error(TAG, ERROR_LIBRUS_MESSAGES_ACCESS_DENIED, response, text) + text?.contains("eAccessDeny") == true -> { + // data.error(TAG, ERROR_LIBRUS_MESSAGES_ACCESS_DENIED, response, text) + data.messagesLoginSuccessful = false + onSuccess() + } text?.contains("OffLine") == true -> data.error(TAG, ERROR_LIBRUS_MESSAGES_MAINTENANCE, response, text) text?.contains("error") == true -> data.error(TAG, ERROR_LIBRUS_MESSAGES_ERROR, response, text) text?.contains("eVarWhitThisNameNotExists") == true -> data.error(TAG, ERROR_LIBRUS_MESSAGES_ACCESS_DENIED, response, text) text?.contains("") == true -> data.error(TAG, ERROR_LIBRUS_MESSAGES_OTHER, response, text) + else -> data.error(TAG, ERROR_LIBRUS_MESSAGES_OTHER, response, text) } } 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 166a4ae5..5198d155 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 @@ -146,12 +146,14 @@ class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) { } 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) 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 be68de4d..473b3691 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 @@ -6,7 +6,10 @@ 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.Attendance -import pl.szczodrzynski.edziennik.data.db.entity.Attendance.* +import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_ABSENT +import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_ABSENT_EXCUSED +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 class MobidziennikApiAttendance(val data: DataMobidziennik, rows: List) { @@ -23,7 +26,7 @@ class MobidziennikApiAttendance(val data: DataMobidziennik, rows: List) val id = cols[0].toLong() val lessonId = cols[1].toLong() data.mobiLessons.singleOrNull { it.id == lessonId }?.let { lesson -> - val type = when (cols[4]) { + val baseType = when (cols[4]) { "2" -> TYPE_ABSENT "5" -> TYPE_ABSENT_EXCUSED "4" -> TYPE_RELEASED @@ -31,16 +34,37 @@ class MobidziennikApiAttendance(val data: DataMobidziennik, rows: List) } val semester = data.profile?.dateToSemester(lesson.date) ?: 1 + val typeName = when (baseType) { + TYPE_ABSENT -> "nieobecność" + TYPE_ABSENT_EXCUSED -> "nieobecność usprawiedliwiona" + TYPE_RELEASED -> "zwolnienie" + TYPE_PRESENT -> "obecność" + else -> "nieznany rodzaj" + } + val typeSymbol = when (baseType) { + TYPE_ABSENT -> "|" + TYPE_ABSENT_EXCUSED -> "+" + TYPE_RELEASED -> "z" + TYPE_PRESENT -> "." + else -> "?" + } + val attendanceObject = Attendance( - data.profileId, - id, - lesson.teacherId, - lesson.subjectId, - semester, - lesson.topic, - lesson.date, - lesson.startTime, - type) + profileId = data.profileId, + id = id, + baseType = baseType, + typeName = typeName, + typeShort = data.app.attendanceManager.getTypeShort(baseType), + typeSymbol = typeSymbol, + typeColor = null, + date = lesson.date, + startTime = lesson.startTime, + semester = semester, + teacherId = lesson.teacherId, + subjectId = lesson.subjectId + ).also { + it.lessonTopic = lesson.topic + } data.attendanceList.add(attendanceObject) data.metadataList.add( @@ -48,9 +72,8 @@ class MobidziennikApiAttendance(val data: DataMobidziennik, rows: List) data.profileId, Metadata.TYPE_ATTENDANCE, id, - data.profile?.empty ?: false, - data.profile?.empty ?: false, - System.currentTimeMillis() + 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 02fbb67e..7a72d3de 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 @@ -60,7 +60,9 @@ class MobidziennikApiEvents(val data: DataMobidziennik, rows: List) { type = type, teacherId = teacherId, subjectId = subjectId, - teamId = teamId) + teamId = teamId, + addedDate = addedDate + ) data.eventList.add(eventObject) data.metadataList.add( @@ -69,8 +71,7 @@ class MobidziennikApiEvents(val data: DataMobidziennik, rows: List) { Metadata.TYPE_EVENT, id, data.profile?.empty ?: false, - data.profile?.empty ?: false, - addedDate + 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 609c895c..c3c1b449 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 @@ -79,7 +79,9 @@ class MobidziennikApiGrades(val data: DataMobidziennik, rows: List) { comment = null, semester = semester, teacherId = teacherId, - subjectId = subjectId) + subjectId = subjectId, + addedDate = addedDate + ) if (data.profile?.empty == true) { addedDate = data.profile.dateSemester1Start.inMillis @@ -92,8 +94,7 @@ class MobidziennikApiGrades(val data: DataMobidziennik, rows: List) { Metadata.TYPE_GRADE, id, data.profile?.empty ?: false, - data.profile?.empty ?: false, - addedDate + data.profile?.empty ?: false )) addedDate++ } 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 4623a26d..683a8820 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 @@ -40,7 +40,8 @@ class MobidziennikApiHomework(val data: DataMobidziennik, rows: List) { type = Event.TYPE_HOMEWORK, teacherId = teacherId, subjectId = subjectId, - teamId = teamId) + teamId = teamId + ) data.eventList.add(eventObject) data.metadataList.add( @@ -49,8 +50,7 @@ class MobidziennikApiHomework(val data: DataMobidziennik, rows: List) { Metadata.TYPE_HOMEWORK, id, data.profile?.empty ?: false, - data.profile?.empty ?: false, - System.currentTimeMillis() + 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 f1d9aed2..66bfe1fd 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 @@ -33,12 +33,16 @@ class MobidziennikApiNotices(val data: DataMobidziennik, rows: List) { val addedDate = Date.fromYmd(cols[7]).inMillis val noticeObject = Notice( - data.profileId, - id, - text, - semester, - type, - teacherId) + profileId = data.profileId, + id = id, + type = type, + semester = semester, + text = text, + category = null, + points = null, + teacherId = teacherId, + addedDate = addedDate + ) data.noticeList.add(noticeObject) data.metadataList.add( @@ -47,8 +51,7 @@ class MobidziennikApiNotices(val data: DataMobidziennik, rows: List) { Metadata.TYPE_NOTICE, id, data.profile?.empty ?: false, - data.profile?.empty ?: false, - addedDate + 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 6a7ce02d..a82ab4ef 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 @@ -8,9 +8,9 @@ import android.util.SparseArray import androidx.core.util.set import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.DataMobidziennik 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.entity.Lesson import pl.szczodrzynski.edziennik.fixName import pl.szczodrzynski.edziennik.keys import pl.szczodrzynski.edziennik.singleOrNull @@ -97,8 +97,7 @@ class MobidziennikApiTimetable(val data: DataMobidziennik, rows: List) { Metadata.TYPE_LESSON_CHANGE, it.id, seen, - seen, - System.currentTimeMillis() + seen )) } data.lessonList += it 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 5e1445ec..ece23d7f 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 @@ -17,9 +17,9 @@ class MobidziennikLuckyNumberExtractor(val data: DataMobidziennik, text: String) val luckyNumber = it.groupValues[1].toInt() val luckyNumberObject = LuckyNumber( - data.profileId, - Date.getToday(), - luckyNumber + profileId = data.profileId, + date = Date.getToday(), + number = luckyNumber ) data.luckyNumberList.add(luckyNumberObject) @@ -29,8 +29,7 @@ class MobidziennikLuckyNumberExtractor(val data: DataMobidziennik, text: String) Metadata.TYPE_LUCKY_NUMBER, luckyNumberObject.date.value.toLong(), true, - data.profile?.empty ?: false, - System.currentTimeMillis() + data.profile?.empty ?: false )) } catch (_: Exception){} } 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 c56c56d9..36dbe1e9 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 @@ -11,7 +11,13 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.ENDPOINT_MOBID import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.MobidziennikWeb import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel import pl.szczodrzynski.edziennik.data.db.entity.Attendance -import pl.szczodrzynski.edziennik.data.db.entity.Attendance.* +import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_ABSENT +import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_ABSENT_EXCUSED +import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_BELATED +import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_PRESENT +import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_PRESENT_CUSTOM +import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_RELEASED +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.fixName @@ -71,6 +77,18 @@ class MobidziennikWebAttendance(override val data: DataMobidziennik, val start = System.currentTimeMillis() + val types = Regexes.MOBIDZIENNIK_ATTENDANCE_TYPES + .find(text) + ?.get(1) + ?.split("
") + ?.map { + it.trimEnd(',') + .split(" ", limit = 2) + .let { it.getOrNull(0) to it.getOrNull(1) } + } + ?.toMap() + val typeSymbols = types?.keys?.filterNotNull() ?: listOf() + Regexes.MOBIDZIENNIK_ATTENDANCE_TABLE.findAll(text).forEach { tableResult -> val table = tableResult[1] val lessonDates = mutableListOf() @@ -92,55 +110,90 @@ class MobidziennikWebAttendance(override val data: DataMobidziennik, return@forEach ranges.forEach { range -> val lessonDate = dateIterator.next() - val entry = entriesIterator.next() + var entry = entriesIterator.next() if (entry.isBlank()) return@forEach val startTime = Time.fromH_m(range[1]) - val entryIterator = entry.iterator() + range[2].split(" / ").mapNotNull { Regexes.MOBIDZIENNIK_ATTENDANCE_LESSON.find(it) }.forEachIndexed { index, lesson -> - val topic = lesson[2] - if (topic.startsWith("Lekcja odwołana: ") || !entryIterator.hasNext()) + val topic = lesson[1].substringAfter(" - ", missingDelimiterValue = "").takeIf { it.isNotBlank() } + if (topic?.startsWith("Lekcja odwołana: ") == true || entry.isEmpty()) return@forEachIndexed - val subjectName = lesson[1] + val subjectName = lesson[1].substringBefore(" - ") //val team = lesson[3] - val teacherName = lesson[4].fixName() + val teacherName = lesson[3].fixName() val teacherId = data.teacherList.singleOrNull { it.fullNameLastFirst == teacherName }?.id ?: -1 val subjectId = data.subjectList.singleOrNull { it.longName == subjectName }?.id ?: -1 - val type = when (entryIterator.nextChar()) { - '.' -> TYPE_PRESENT - '|' -> TYPE_ABSENT - '+' -> TYPE_ABSENT_EXCUSED - 's' -> TYPE_BELATED - 'z' -> TYPE_RELEASED - else -> TYPE_PRESENT + var typeSymbol = "" + for (symbol in typeSymbols) { + if (entry.startsWith(symbol) && symbol.length > typeSymbol.length) + typeSymbol = symbol } + entry = entry.removePrefix(typeSymbol) + + var isCounted = true + val baseType = when (typeSymbol) { + "." -> TYPE_PRESENT + "|" -> TYPE_ABSENT + "+" -> TYPE_ABSENT_EXCUSED + "s" -> TYPE_BELATED + "z" -> TYPE_RELEASED + else -> { + isCounted = false + when (typeSymbol) { + "e" -> TYPE_PRESENT_CUSTOM + "en" -> TYPE_ABSENT + "ep" -> TYPE_PRESENT_CUSTOM + else -> TYPE_UNKNOWN + } + } + } + val typeName = types?.get(typeSymbol) ?: "" + val typeColor = when (typeSymbol) { + "e" -> 0xff673ab7 + "en" -> 0xffec407a + "ep" -> 0xff4caf50 + else -> null + }?.toInt() + + val typeShort = if (isCounted) + data.app.attendanceManager.getTypeShort(baseType) + else + typeSymbol + val semester = data.profile?.dateToSemester(lessonDate) ?: 1 val id = lessonDate.combineWith(startTime) / 6L * 10L + (lesson[0].hashCode() and 0xFFFF) + index val attendanceObject = Attendance( - data.profileId, - id, - teacherId, - subjectId, - semester, - topic, - lessonDate, - startTime, - type) + profileId = profileId, + id = id, + baseType = baseType, + typeName = typeName, + typeShort = typeShort, + typeSymbol = typeSymbol, + typeColor = typeColor, + date = lessonDate, + startTime = startTime, + semester = semester, + teacherId = teacherId, + subjectId = subjectId + ).also { + it.lessonTopic = topic + it.isCounted = isCounted + } data.attendanceList.add(attendanceObject) - if (type != TYPE_PRESENT) { + if (baseType != TYPE_PRESENT) { data.metadataList.add( Metadata( data.profileId, Metadata.TYPE_ATTENDANCE, id, - data.profile?.empty ?: false, - data.profile?.empty ?: false, - System.currentTimeMillis() + 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 c1749d76..95c1f6df 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 @@ -89,8 +89,8 @@ class MobidziennikWebCalendar(override val data: DataMobidziennik, Metadata.TYPE_EVENT, eventObject.id, profile?.empty ?: false, - profile?.empty ?: false, - System.currentTimeMillis() /* no addedDate here though */ + profile?.empty ?: false + /* no addedDate here though */ )) } } 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 1c44a219..105c0943 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 @@ -139,8 +139,7 @@ class MobidziennikWebGetMessage(override val data: DataMobidziennik, Metadata.TYPE_MESSAGE, message.id, true, - true, - message.addedDate + 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 de058c91..9f5fab95 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 @@ -125,7 +125,8 @@ class MobidziennikWebGrades(override val data: DataMobidziennik, comment = null, semester = gradeSemester, teacherId = teacherId, - subjectId = subjectId + subjectId = subjectId, + addedDate = gradeAddedDateMillis ) gradeObject.classAverage = gradeClassAverage @@ -137,8 +138,7 @@ class MobidziennikWebGrades(override val data: DataMobidziennik, Metadata.TYPE_GRADE, gradeObject.id, profile.empty, - profile.empty, - gradeAddedDateMillis + 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 c0373c54..4e8f900a 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 @@ -77,11 +77,12 @@ class MobidziennikWebMessagesAll(override val data: DataMobidziennik, type = type, subject = subject, body = null, - senderId = senderId + senderId = senderId, + addedDate = addedDate ) data.messageList.add(message) - data.metadataList.add(Metadata(profileId, Metadata.TYPE_MESSAGE, message.id, true, true, addedDate)) + data.metadataList.add(Metadata(profileId, Metadata.TYPE_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 385ea71e..be762228 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 @@ -63,7 +63,8 @@ class MobidziennikWebMessagesInbox(override val data: DataMobidziennik, type = Message.TYPE_RECEIVED, subject = subject, body = null, - senderId = senderId + senderId = senderId, + addedDate = addedDate ) if (hasAttachments) @@ -76,8 +77,7 @@ class MobidziennikWebMessagesInbox(override val data: DataMobidziennik, Metadata.TYPE_MESSAGE, message.id, isRead, - isRead || profile?.empty ?: false, - addedDate + 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 918816c8..543df329 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 @@ -78,7 +78,8 @@ class MobidziennikWebMessagesSent(override val data: DataMobidziennik, type = Message.TYPE_SENT, subject = subject, body = null, - senderId = null + senderId = null, + addedDate = addedDate ) if (hasAttachments) @@ -91,8 +92,7 @@ class MobidziennikWebMessagesSent(override val data: DataMobidziennik, Metadata.TYPE_MESSAGE, message.id, true, - true, - addedDate + true )) } 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 d0cee4dc..89178b8b 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 @@ -45,7 +45,7 @@ class MobidziennikWebSendMessage(override val data: DataMobidziennik, MobidziennikWebMessagesAll(data, null) { val message = data.messageList.firstOrNull { it.type == Message.TYPE_SENT && it.subject == subject } val metadata = data.metadataList.firstOrNull { it.thingType == Metadata.TYPE_MESSAGE && it.thingId == message?.id } - val event = MessageSentEvent(data.profileId, message, metadata?.addedDate) + val event = MessageSentEvent(data.profileId, message, message?.addedDate) EventBus.getDefault().postSticky(event) onSuccess() 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 4857bd09..11b668f9 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 @@ -85,7 +85,7 @@ class MobidziennikFirstLogin(val data: DataMobidziennik, val onSuccess: () -> Un profileList.add(profile) } - EventBus.getDefault().post(FirstLoginFinishedEvent(profileList, data.loginStore)) + EventBus.getDefault().postSticky(FirstLoginFinishedEvent(profileList, data.loginStore)) 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 new file mode 100644 index 00000000..d117c139 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/DataPodlasie.kt @@ -0,0 +1,119 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2020-5-12 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.podlasie + +import pl.szczodrzynski.edziennik.* +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.* +import kotlin.text.replace + +class DataPodlasie(app: App, profile: Profile?, loginStore: LoginStore) : Data(app, profile, loginStore) { + + fun isApiLoginValid() = apiToken.isNotNullNorEmpty() + + override fun satisfyLoginMethods() { + loginMethods.clear() + if (isApiLoginValid()) + loginMethods += LOGIN_METHOD_PODLASIE_API + } + + override fun generateUserCode(): String = "$schoolShortName:$loginShort:${studentId?.crc32()}" + + /* _ + /\ (_) + / \ _ __ _ + / /\ \ | '_ \| | + / ____ \| |_) | | + /_/ \_\ .__/|_| + | | + |*/ + private var mApiToken: String? = null + var apiToken: String? + get() { mApiToken = mApiToken ?: loginStore.getLoginData("apiToken", null); return mApiToken } + set(value) { loginStore.putLoginData("apiToken", value); mApiToken = value } + + 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 } + + /* ____ _ _ + / __ \| | | | + | | | | |_| |__ ___ _ __ + | | | | __| '_ \ / _ \ '__| + | |__| | |_| | | | __/ | + \____/ \__|_| |_|\___|*/ + 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 } + + 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 } + + 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 } + + 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 } + + 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 } + + 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 } + + val schoolShortName: String? + get() = studentLogin?.split('@')?.get(1)?.replace(".podlaskie.pl", "") + + val loginShort: String? + get() = studentLogin?.split('@')?.get(0) + + fun getSubject(name: String): Subject { + val id = name.crc32() + return subjectList.singleOrNull { it.id == id } ?: run { + val subject = Subject(profileId, id, name, name) + subjectList.put(id, subject) + subject + } + } + + fun getTeacher(firstName: String, lastName: String): Teacher { + val name = "$firstName $lastName".fixName() + return teacherList.singleOrNull { it.fullName == name } ?: run { + val id = name.crc32() + val teacher = Teacher(profileId, id, firstName, lastName) + teacherList.put(id, teacher) + teacher + } + } + + fun getTeam(name: String? = null): Team { + if (name == "cała klasa" || name == null) return teamClass ?: run { + val id = className!!.crc32() + val teamCode = "$schoolShortName:$className" + val team = Team(profileId, id, className, Team.TYPE_CLASS, teamCode, -1) + teamList.put(id, team) + return team + } else { + val id = name.crc32() + val teamCode = "$schoolShortName:$name" + val team = Team(profileId, id, name, Team.TYPE_VIRTUAL, teamCode, -1) + teamList.put(id, team) + return team + } + } +} 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 new file mode 100644 index 00000000..30da6e02 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/Podlasie.kt @@ -0,0 +1,167 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2020-5-12 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.podlasie + +import com.google.gson.JsonObject +import org.greenrobot.eventbus.EventBus +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.data.api.edziennik.helper.DownloadAttachment +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.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.full.AnnouncementFull +import pl.szczodrzynski.edziennik.data.db.full.EventFull +import pl.szczodrzynski.edziennik.data.db.full.MessageFull +import pl.szczodrzynski.edziennik.utils.Utils +import java.io.File + +class Podlasie(val app: App, val profile: Profile?, val loginStore: LoginStore, val callback: EdziennikCallback) : EdziennikInterface { + companion object { + const val TAG = "Podlasie" + } + + val internalErrorList = mutableListOf() + val data: DataPodlasie + + init { + data = DataPodlasie(app, profile, loginStore).apply { + callback = wrapCallback(this@Podlasie.callback) + satisfyLoginMethods() + } + } + + private fun completed() { + data.saveData() + callback.onCompleted() + } + + /* _______ _ _ _ _ _ + |__ __| | /\ | | (_) | | | + | | | |__ ___ / \ | | __ _ ___ _ __ _| |_| |__ _ __ ___ + | | | '_ \ / _ \ / /\ \ | |/ _` |/ _ \| '__| | __| '_ \| '_ ` _ \ + | | | | | | __/ / ____ \| | (_| | (_) | | | | |_| | | | | | | | | + |_| |_| |_|\___| /_/ \_\_|\__, |\___/|_| |_|\__|_| |_|_| |_| |_| + __/ | + |__*/ + override fun sync(featureIds: List, viewId: Int?, onlyEndpoints: List?, 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}") + PodlasieLogin(data) { + PodlasieData(data) { + completed() + } + } + } + + override fun getMessage(message: MessageFull) { + + } + + override fun sendMessage(recipients: List, subject: String, text: String) { + + } + + override fun markAllAnnouncementsAsRead() { + + } + + override fun getAnnouncement(announcement: AnnouncementFull) { + + } + + override fun getAttachment(owner: Any, attachmentId: Long, attachmentName: String) { + val fileUrl = attachmentName.substringAfter(":") + DownloadAttachment(fileUrl, + onSuccess = { file -> + val event = AttachmentGetEvent( + data.profileId, + owner, + attachmentId, + AttachmentGetEvent.TYPE_FINISHED, + file.absolutePath + ) + + val attachmentDataFile = File(Utils.getStorageDir(), ".${data.profileId}_${event.ownerId}_${event.attachmentId}") + Utils.writeStringToFile(attachmentDataFile, event.fileName) + + EventBus.getDefault().postSticky(event) + + completed() + }, + onProgress = { written, _ -> + val event = AttachmentGetEvent( + data.profileId, + owner, + attachmentId, + AttachmentGetEvent.TYPE_PROGRESS, + bytesWritten = written + ) + + EventBus.getDefault().postSticky(event) + }, + onError = { apiError -> + data.error(apiError) + }) + } + + override fun getRecipientList() { + + } + + override fun getEvent(eventFull: EventFull) { + + } + + override fun firstLogin() { + PodlasieFirstLogin(data) { + completed() + } + } + + override fun cancel() { + Utils.d(TAG, "Cancelled") + data.cancel() + callback.onCompleted() + } + + private fun wrapCallback(callback: EdziennikCallback): EdziennikCallback { + return object : EdziennikCallback { + override fun onCompleted() { + callback.onCompleted() + } + + override fun onProgress(step: Float) { + callback.onProgress(step) + } + + override fun onStartProgress(stringRes: Int) { + callback.onStartProgress(stringRes) + } + + override fun onError(apiError: ApiError) { + // TODO Error handling + 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/podlasie/PodlasieFeatures.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/PodlasieFeatures.kt new file mode 100644 index 00000000..82c6f659 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/PodlasieFeatures.kt @@ -0,0 +1,18 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2020-5-12 + */ + +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 + +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)) +) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/PodlasieApi.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/PodlasieApi.kt new file mode 100644 index 00000000..af5ff9d7 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/PodlasieApi.kt @@ -0,0 +1,108 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2020-5-12 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.podlasie.data + +import com.google.gson.JsonObject +import im.wangchao.mhttp.Request +import im.wangchao.mhttp.RequestParams +import im.wangchao.mhttp.Response +import im.wangchao.mhttp.callback.JsonCallbackHandler +import pl.szczodrzynski.edziennik.data.api.* +import pl.szczodrzynski.edziennik.data.api.edziennik.podlasie.DataPodlasie +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.getInt +import pl.szczodrzynski.edziennik.getJsonObject +import pl.szczodrzynski.edziennik.toHexString +import pl.szczodrzynski.edziennik.utils.Utils +import java.security.MessageDigest +import java.text.SimpleDateFormat +import java.util.* + +open class PodlasieApi(open val data: DataPodlasie, open val lastSync: Long?) { + companion object { + const val TAG = "PodlasieApi" + } + + val profileId + get() = data.profile?.id ?: -1 + + val profile + get() = data.profile + + fun apiGet(tag: String, endpoint: String, onSuccess: (json: JsonObject) -> Unit) { + val url = PODLASIE_API_URL + endpoint + + Utils.d(tag, "Request: Podlasie/Api - $url") + + if (data.apiToken == null) { + data.error(tag, ERROR_PODLASIE_API_NO_TOKEN) + return + } + + val callback = object : JsonCallbackHandler() { + override fun onSuccess(json: JsonObject?, response: Response?) { + if (json == null || response == null) { + data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY) + .withResponse(response)) + return + } + + val error = json.getJsonObject("system_message")?.getInt("code") + + error?.let { code -> + when (code) { + 0 -> ERROR_PODLASIE_API_DATA_MISSING + 4 -> ERROR_LOGIN_PODLASIE_API_DEVICE_LIMIT + 5 -> ERROR_LOGIN_PODLASIE_API_INVALID_TOKEN + 200 -> null // Not an error + else -> ERROR_PODLASIE_API_OTHER + }?.let { errorCode -> + data.error(ApiError(tag, errorCode) + .withApiResponse(json) + .withResponse(response)) + return@onSuccess + } + } + + try { + onSuccess(json) + } catch (e: Exception) { + data.error(ApiError(tag, EXCEPTION_PODLASIE_API_REQUEST) + .withResponse(response) + .withThrowable(e) + .withApiResponse(json)) + } + } + + override fun onFailure(response: Response?, throwable: Throwable?) { + data.error(ApiError(tag, ERROR_REQUEST_FAILURE) + .withResponse(response) + .withThrowable(throwable)) + } + } + + Request.builder() + .url(url) + .userAgent(SYSTEM_USER_AGENT) + .requestParams(RequestParams(mapOf( + "token" to data.apiToken, + "securityToken" to getSecurityToken(), + "mobileId" to data.app.deviceId, + "ver" to PODLASIE_API_VERSION + ))) + .callback(callback) + .build() + .enqueue() + } + + private fun getSecurityToken(): String { + val format = SimpleDateFormat("yyyy-MM-dd HH", Locale.ENGLISH) + .also { it.timeZone = TimeZone.getTimeZone("Europe/Warsaw") }.format(System.currentTimeMillis()) + val instance = MessageDigest.getInstance("SHA-256") + val digest = instance.digest("-EYlwYu8u16miVd8tT?oO7cvoUVQrQN0vr!$format".toByteArray()).toHexString() + val digest2 = instance.digest(data.apiToken!!.toByteArray()).toHexString() + return instance.digest("$digest$digest2".toByteArray()).toHexString() + } +} 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 new file mode 100644 index 00000000..ce1197b8 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/PodlasieData.kt @@ -0,0 +1,49 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2020-5-12 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.podlasie.data + +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.data.api.edziennik.podlasie.DataPodlasie +import pl.szczodrzynski.edziennik.data.api.edziennik.podlasie.ENDPOINT_PODLASIE_API_MAIN +import pl.szczodrzynski.edziennik.data.api.edziennik.podlasie.data.api.PodlasieApiMain +import pl.szczodrzynski.edziennik.utils.Utils + +class PodlasieData(val data: DataPodlasie, val onSuccess: () -> Unit) { + companion object { + const val TAG = "PodlasieData" + } + + init { + nextEndpoint(onSuccess) + } + + private fun nextEndpoint(onSuccess: () -> Unit) { + if (data.targetEndpointIds.isEmpty()) { + onSuccess() + return + } + if (data.cancelled) { + onSuccess() + return + } + val id = data.targetEndpointIds.firstKey() + val lastSync = data.targetEndpointIds.remove(id) + useEndpoint(id, lastSync) { + data.progress(data.progressStep) + nextEndpoint(onSuccess) + } + } + + private fun useEndpoint(endpointId: Int, lastSync: Long?, onSuccess: (endpointId: Int) -> Unit) { + Utils.d(TAG, "Using endpoint $endpointId. Last sync time = $lastSync") + when (endpointId) { + ENDPOINT_PODLASIE_API_MAIN -> { + data.startProgress(R.string.edziennik_progress_endpoint_data) + PodlasieApiMain(data, lastSync, onSuccess) + } + else -> onSuccess(endpointId) + } + } +} 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 new file mode 100644 index 00000000..8a169ee7 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/api/PodlasieApiEvents.kt @@ -0,0 +1,75 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2020-5-13 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.podlasie.data.api + +import com.google.gson.JsonObject +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.getLong +import pl.szczodrzynski.edziennik.getString +import pl.szczodrzynski.edziennik.utils.models.Date +import pl.szczodrzynski.edziennik.utils.models.Time +import java.util.* + +class PodlasieApiEvents(val data: DataPodlasie, val rows: List) { + init { + rows.forEach { event -> + val id = event.getLong("ExternalId") ?: return@forEach + val date = event.getString("DateFrom")?.let { Date.fromY_m_d(it) } ?: return@forEach + val time = event.getString("DateFrom")?.let { Time.fromY_m_d_H_m_s(it) } + ?: return@forEach + + val name = event.getString("Name")?.replace(""", "\"") ?: "" + val description = event.getString("Description")?.replace(""", "\"") ?: "" + + val type = when (event.getString("Category")?.toLowerCase(Locale.getDefault())) { + "klasówka" -> Event.TYPE_EXAM + "praca domowa" -> Event.TYPE_HOMEWORK + "wycieczka" -> Event.TYPE_EXCURSION + else -> Event.TYPE_DEFAULT + } + + val teacherFirstName = event.getString("PersonEnteringDataFirstName") ?: return@forEach + val teacherLastName = event.getString("PersonEnteringDataLastName") ?: return@forEach + val teacher = data.getTeacher(teacherFirstName, teacherLastName) + + val lessonList = data.db.timetableDao().getAllForDateNow(data.profileId, date) + val lesson = lessonList.firstOrNull { it.startTime == time } + + val addedDate = event.getString("CreateDate")?.let { Date.fromIso(it) } + ?: System.currentTimeMillis() + + val eventObject = Event( + profileId = data.profileId, + id = id, + date = date, + time = time, + topic = name, + color = null, + type = type, + teacherId = teacher.id, + subjectId = lesson?.subjectId ?: -1, + teamId = data.teamClass?.id ?: -1, + addedDate = addedDate + ).apply { + homeworkBody = description + } + + data.eventList.add(eventObject) + data.metadataList.add( + Metadata( + data.profileId, + if (type == Event.TYPE_HOMEWORK) Metadata.TYPE_HOMEWORK else Metadata.TYPE_EVENT, + id, + data.profile?.empty ?: false, + data.profile?.empty ?: false + )) + } + + data.toRemove.add(DataRemoveModel.Events.future()) + } +} 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 new file mode 100644 index 00000000..7c4575aa --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/api/PodlasieApiFinalGrades.kt @@ -0,0 +1,121 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2020-5-13 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.podlasie.data.api + +import com.google.gson.JsonObject +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.Grade.Companion.TYPE_SEMESTER1_FINAL +import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_SEMESTER1_PROPOSED +import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_SEMESTER2_FINAL +import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_SEMESTER2_PROPOSED +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.getLong +import pl.szczodrzynski.edziennik.getString + +class PodlasieApiFinalGrades(val data: DataPodlasie, val rows: List) { + init { data.profile?.also { profile -> + rows.forEach { grade -> + val id = grade.getLong("ExternalId") ?: return@forEach + val mark = grade.getString("Mark") ?: return@forEach + val proposedMark = grade.getString("ProposedMark") ?: "0" + val name = data.app.gradesManager.getGradeNumberName(mark) + val value = data.app.gradesManager.getGradeValue(name) + val semester = grade.getString("TermShortcut")?.length ?: return@forEach + + val typeName = grade.getString("Type") ?: return@forEach + val type = when (typeName) { + "S" -> if (semester == 1) TYPE_SEMESTER1_FINAL else TYPE_SEMESTER2_FINAL + "Y", "R" -> TYPE_YEAR_FINAL + else -> return@forEach + } + + val subjectName = grade.getString("SchoolSubject") ?: return@forEach + val subject = data.getSubject(subjectName) + + val addedDate = if (profile.empty) profile.getSemesterStart(semester).inMillis + else System.currentTimeMillis() + + val gradeObject = Grade( + profileId = data.profileId, + id = id, + name = name, + type = type, + value = value, + weight = 0f, + color = -1, + category = null, + description = null, + comment = null, + semester = semester, + teacherId = -1, + subjectId = subject.id, + addedDate = addedDate + ) + + data.gradeList.add(gradeObject) + data.metadataList.add( + Metadata( + data.profileId, + Metadata.TYPE_GRADE, + id, + profile.empty, + profile.empty + )) + + if (proposedMark != "0") { + val proposedName = data.app.gradesManager.getGradeNumberName(proposedMark) + val proposedValue = data.app.gradesManager.getGradeValue(proposedName) + + val proposedType = when (typeName) { + "S" -> if (semester == 1) TYPE_SEMESTER1_PROPOSED else TYPE_SEMESTER2_PROPOSED + "Y", "R" -> TYPE_YEAR_PROPOSED + else -> return@forEach + } + + val proposedGradeObject = Grade( + profileId = data.profileId, + id = id * (-1), + name = proposedName, + type = proposedType, + value = proposedValue, + weight = 0f, + color = -1, + category = null, + description = null, + comment = null, + semester = semester, + teacherId = -1, + subjectId = subject.id, + addedDate = addedDate + ) + + data.gradeList.add(proposedGradeObject) + data.metadataList.add( + Metadata( + data.profileId, + Metadata.TYPE_GRADE, + proposedGradeObject.id, + profile.empty, + profile.empty + )) + } + } + + data.toRemove.addAll(listOf( + TYPE_SEMESTER1_FINAL, + TYPE_SEMESTER1_PROPOSED, + TYPE_SEMESTER2_FINAL, + TYPE_SEMESTER2_PROPOSED, + TYPE_YEAR_FINAL, + TYPE_YEAR_PROPOSED + ).map { + DataRemoveModel.Grades.allWithType(it) + }) + }} +} 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 new file mode 100644 index 00000000..d5696567 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/api/PodlasieApiGrades.kt @@ -0,0 +1,72 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2020-5-13 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.podlasie.data.api + +import android.graphics.Color +import com.google.gson.JsonObject +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.getFloat +import pl.szczodrzynski.edziennik.getInt +import pl.szczodrzynski.edziennik.getLong +import pl.szczodrzynski.edziennik.getString +import pl.szczodrzynski.edziennik.utils.models.Date + +class PodlasieApiGrades(val data: DataPodlasie, val rows: List) { + init { + rows.forEach { grade -> + val id = grade.getLong("ExternalId") ?: return@forEach + val name = grade.getString("Mark") ?: return@forEach + val value = data.app.gradesManager.getGradeValue(name) + val weight = grade.getFloat("Weight") ?: 0f + val includeToAverage = grade.getInt("IncludeToAverage") != 0 + val color = grade.getString("Color")?.let { Color.parseColor(it) } ?: -1 + val category = grade.getString("Category") ?: "" + val comment = grade.getString("Comment") ?: "" + val semester = grade.getString("TermShortcut")?.length ?: data.currentSemester + + val teacherFirstName = grade.getString("TeacherFirstName") ?: return@forEach + val teacherLastName = grade.getString("TeacherLastName") ?: return@forEach + val teacher = data.getTeacher(teacherFirstName, teacherLastName) + + val subjectName = grade.getString("SchoolSubject") ?: return@forEach + val subject = data.getSubject(subjectName) + + val addedDate = grade.getString("ReceivedDate")?.let { Date.fromY_m_d(it).inMillis } + ?: System.currentTimeMillis() + + val gradeObject = Grade( + profileId = data.profileId, + id = id, + name = name, + type = Grade.TYPE_NORMAL, + value = value, + weight = if (includeToAverage) weight else 0f, + color = color, + category = category, + description = null, + comment = comment, + semester = semester, + teacherId = teacher.id, + subjectId = subject.id, + addedDate = addedDate + ) + + data.gradeList.add(gradeObject) + data.metadataList.add( + Metadata( + data.profileId, + Metadata.TYPE_GRADE, + id, + data.profile?.empty ?: false, + data.profile?.empty ?: false + )) + } + + data.toRemove.add(DataRemoveModel.Grades.allWithType(Grade.TYPE_NORMAL)) + } +} 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 new file mode 100644 index 00000000..6ab53328 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/api/PodlasieApiHomework.kt @@ -0,0 +1,64 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2020-5-14 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.podlasie.data.api + +import com.google.gson.JsonObject +import pl.szczodrzynski.edziennik.crc32 +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.get +import pl.szczodrzynski.edziennik.getString +import pl.szczodrzynski.edziennik.utils.models.Date + +class PodlasieApiHomework(val data: DataPodlasie, val rows: List) { + init { + rows.reversed().forEach { homework -> + val id = homework.getString("ExternalId")?.crc32() ?: return@forEach + val topic = homework.getString("Title")?.replace(""", "\"") ?: "" + val description = homework.getString("Message")?.replace(""", "\"") ?: "" + val date = Date.getToday() + val addedDate = System.currentTimeMillis() + + val eventObject = Event( + profileId = data.profileId, + id = id, + date = date, + time = null, + topic = topic, + color = null, + type = Event.TYPE_HOMEWORK, + teacherId = -1, + subjectId = -1, + teamId = data.teamClass?.id ?: -1, + addedDate = addedDate + ).apply { + homeworkBody = description + } + + eventObject.attachmentIds = mutableListOf() + eventObject.attachmentNames = mutableListOf() + homework.getString("Attachments")?.split(',')?.onEach { url -> + val filename = "&filename=(.*?)&".toRegex().find(url)?.get(1) ?: return@onEach + val ext = "&ext=(.*?)&".toRegex().find(url)?.get(1) ?: return@onEach + eventObject.attachmentIds?.add(url.crc32()) + eventObject.attachmentNames?.add("$filename.$ext:$url") + } + + data.eventList.add(eventObject) + data.metadataList.add( + Metadata( + data.profileId, + Metadata.TYPE_HOMEWORK, + id, + data.profile?.empty ?: false, + data.profile?.empty ?: false + )) + } + + data.toRemove.add(DataRemoveModel.Events.futureWithType(Event.TYPE_HOMEWORK)) + } +} 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 new file mode 100644 index 00000000..725ec727 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/api/PodlasieApiLuckyNumber.kt @@ -0,0 +1,30 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2020-5-13 + */ + +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.utils.models.Date + +class PodlasieApiLuckyNumber(val data: DataPodlasie, val luckyNumber: Int) { + init { + val luckyNumberObject = LuckyNumber( + profileId = data.profileId, + date = Date.getToday(), + number = luckyNumber + ) + + data.luckyNumberList.add(luckyNumberObject) + data.metadataList.add( + Metadata( + data.profileId, + Metadata.TYPE_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/PodlasieApiMain.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/api/PodlasieApiMain.kt new file mode 100644 index 00000000..6fe77b5c --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/api/PodlasieApiMain.kt @@ -0,0 +1,39 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2020-5-12 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.podlasie.data.api + +import pl.szczodrzynski.edziennik.asJsonObjectList +import pl.szczodrzynski.edziennik.data.api.PODLASIE_API_USER_ENDPOINT +import pl.szczodrzynski.edziennik.data.api.edziennik.podlasie.DataPodlasie +import pl.szczodrzynski.edziennik.data.api.edziennik.podlasie.ENDPOINT_PODLASIE_API_MAIN +import pl.szczodrzynski.edziennik.data.api.edziennik.podlasie.data.PodlasieApi +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.getInt +import pl.szczodrzynski.edziennik.getJsonArray + +class PodlasieApiMain(override val data: DataPodlasie, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit) : PodlasieApi(data, lastSync) { + companion object { + const val TAG = "PodlasieApiTimetable" + } + + init { + apiGet(TAG, PODLASIE_API_USER_ENDPOINT) { json -> + data.getTeam() // Save the class team when it doesn't exist. + + json.getInt("LuckyNumber")?.let { PodlasieApiLuckyNumber(data, it) } + json.getJsonArray("Teacher")?.asJsonObjectList()?.let { PodlasieApiTeachers(data, it) } + json.getJsonArray("Timetable")?.asJsonObjectList()?.let { PodlasieApiTimetable(data, it) } + json.getJsonArray("Marks")?.asJsonObjectList()?.let { PodlasieApiGrades(data, it) } + json.getJsonArray("MarkFinal")?.asJsonObjectList()?.let { PodlasieApiFinalGrades(data, it) } + json.getJsonArray("News")?.asJsonObjectList()?.let { PodlasieApiEvents(data, it) } + json.getJsonArray("Tasks")?.asJsonObjectList()?.let { PodlasieApiHomework(data, it) } + + data.setSyncNext(ENDPOINT_PODLASIE_API_MAIN, SYNC_ALWAYS) + onSuccess(ENDPOINT_PODLASIE_API_MAIN) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/api/PodlasieApiTeachers.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/api/PodlasieApiTeachers.kt new file mode 100644 index 00000000..ca1fa7b4 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/api/PodlasieApiTeachers.kt @@ -0,0 +1,40 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2020-5-13 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.podlasie.data.api + +import com.google.gson.JsonObject +import pl.szczodrzynski.edziennik.data.api.edziennik.podlasie.DataPodlasie +import pl.szczodrzynski.edziennik.data.db.entity.Teacher +import pl.szczodrzynski.edziennik.getInt +import pl.szczodrzynski.edziennik.getLong +import pl.szczodrzynski.edziennik.getString + +class PodlasieApiTeachers(val data: DataPodlasie, val rows: List) { + init { + rows.forEach { teacher -> + val id = teacher.getLong("ExternalId") ?: return@forEach + val firstName = teacher.getString("FirstName") ?: return@forEach + val lastName = teacher.getString("LastName") ?: return@forEach + val isEducator = teacher.getInt("Educator") == 1 + + val teacherObject = Teacher( + profileId = data.profileId, + id = id, + name = firstName, + surname = lastName, + loginId = null + ) + + data.teacherList.put(id, teacherObject) + + val teamClass = data.teamClass + if (isEducator && teamClass != null) { + data.teamList.put(teamClass.id, teamClass.apply { + teacherId = id + }) + } + } + } +} 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 new file mode 100644 index 00000000..a9a12d5d --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/api/PodlasieApiTimetable.kt @@ -0,0 +1,89 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2020-5-12 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.podlasie.data.api + +import com.google.gson.JsonObject +import pl.szczodrzynski.edziennik.data.api.edziennik.podlasie.DataPodlasie +import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel +import pl.szczodrzynski.edziennik.data.db.entity.Lesson +import pl.szczodrzynski.edziennik.getInt +import pl.szczodrzynski.edziennik.getString +import pl.szczodrzynski.edziennik.utils.models.Date +import pl.szczodrzynski.edziennik.utils.models.Time +import pl.szczodrzynski.edziennik.utils.models.Week + +class PodlasieApiTimetable(val data: DataPodlasie, rows: List) { + init { + val currentWeekStart = Week.getWeekStart() + + if (Date.getToday().weekDay > 4) { + currentWeekStart.stepForward(0, 0, 7) + } + + val getDate = data.arguments?.getString("weekStart") ?: currentWeekStart.stringY_m_d + + val weekStart = Date.fromY_m_d(getDate) + val weekEnd = weekStart.clone().stepForward(0, 0, 6) + + val days = mutableListOf() + var startDate: Date? = null + var endDate: Date? = null + + rows.forEach { lesson -> + val date = lesson.getString("Date")?.let { Date.fromY_m_d(it) } ?: return@forEach + + if ((date > weekEnd || date < weekStart) && data.profile?.empty != true) return@forEach + if (startDate == null) startDate = date.clone() + endDate = date.clone() + if (date.value !in days) days += date.value + + val lessonNumber = lesson.getInt("LessonNumber") ?: return@forEach + val startTime = lesson.getString("TimeFrom")?.let { Time.fromH_m_s(it) } + ?: return@forEach + val endTime = lesson.getString("TimeTo")?.let { Time.fromH_m_s(it) } ?: return@forEach + val subject = lesson.getString("SchoolSubject")?.let { data.getSubject(it) } + ?: return@forEach + + val teacherFirstName = lesson.getString("TeacherFirstName") ?: return@forEach + val teacherLastName = lesson.getString("TeacherLastName") ?: return@forEach + val teacher = data.getTeacher(teacherFirstName, teacherLastName) + + val team = lesson.getString("Group")?.let { data.getTeam(it) } ?: return@forEach + val classroom = lesson.getString("Room") + + Lesson(data.profileId, -1).also { + it.type = Lesson.TYPE_NORMAL + it.date = date + it.lessonNumber = lessonNumber + it.startTime = startTime + it.endTime = endTime + it.subjectId = subject.id + it.teacherId = teacher.id + it.teamId = team.id + it.classroom = classroom + + it.id = it.buildId() + data.lessonList += it + } + } + + if (startDate != null && endDate != null) { + if (weekEnd > endDate!!) endDate = weekEnd + + while (startDate!! <= endDate!!) { + if (startDate!!.value !in days) { + val lessonDate = startDate!!.clone() + data.lessonList += Lesson(data.profileId, lessonDate.value.toLong()).apply { + type = Lesson.TYPE_NO_LESSONS + date = lessonDate + } + } + startDate!!.stepForward(0, 0, 1) + } + } + + data.toRemove.add(DataRemoveModel.Timetable.between(weekStart, weekEnd)) + } +} 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 new file mode 100644 index 00000000..9b92d541 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/firstlogin/PodlasieFirstLogin.kt @@ -0,0 +1,71 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2020-5-12 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.podlasie.firstlogin + +import org.greenrobot.eventbus.EventBus +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_PODLASIE +import pl.szczodrzynski.edziennik.data.api.PODLASIE_API_USER_ENDPOINT +import pl.szczodrzynski.edziennik.data.api.edziennik.podlasie.DataPodlasie +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 + +class PodlasieFirstLogin(val data: DataPodlasie, val onSuccess: () -> Unit) { + companion object { + const val TAG = "PodlasieFirstLogin" + } + + private val api = PodlasieApi(data, null) + + init { + val loginStoreId = data.loginStore.id + val loginStoreType = LOGIN_TYPE_PODLASIE + + PodlasieLoginApi(data) { + api.apiGet(TAG, PODLASIE_API_USER_ENDPOINT) { json -> + val uuid = json.getString("Uuid") + val login = json.getString("Login") + val firstName = json.getString("FirstName") + val lastName = json.getString("LastName") + val studentNameLong = "$firstName $lastName".fixName() + val studentNameShort = studentNameLong.getShortName() + val schoolName = json.getString("SchoolName") + val className = json.getString("SchoolClass") + val schoolYear = json.getString("ActualSchoolYear")?.replace(' ', '/') + val semester = json.getString("ActualTermShortcut")?.length + val apiUrl = json.getString("URL") + + val profile = Profile( + loginStoreId, + loginStoreId, + loginStoreType, + studentNameLong, + login, + studentNameLong, + studentNameShort, + null + ).apply { + studentData["studentId"] = uuid + studentData["studentLogin"] = login + studentData["schoolName"] = schoolName + studentData["className"] = className + studentData["schoolYear"] = schoolYear + studentData["currentSemester"] = semester ?: 1 + studentData["apiUrl"] = apiUrl + + schoolYear?.split('/')?.get(0)?.toInt()?.let { + studentSchoolYearStart = it + } + studentClassName = className + } + + EventBus.getDefault().postSticky(FirstLoginFinishedEvent(listOf(profile), data.loginStore)) + onSuccess() + } + } + } +} 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 new file mode 100644 index 00000000..406bb997 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/login/PodlasieLogin.kt @@ -0,0 +1,54 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2020-5-12 + */ + +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.utils.Utils + +class PodlasieLogin(val data: DataPodlasie, val onSuccess: () -> Unit) { + companion object { + const val TAG = "PodlasieLogin" + } + + private var cancelled = false + + init { + nextLoginMethod(onSuccess) + } + + private fun nextLoginMethod(onSuccess: () -> Unit) { + if (data.targetLoginMethodIds.isEmpty()) { + onSuccess() + return + } + if (cancelled) { + onSuccess() + return + } + useLoginMethod(data.targetLoginMethodIds.removeAt(0)) { usedMethodId -> + data.progress(data.progressStep) + if (usedMethodId != -1) + data.loginMethods.add(usedMethodId) + nextLoginMethod(onSuccess) + } + } + + private fun useLoginMethod(loginMethodId: Int, onSuccess: (usedMethodId: Int) -> Unit) { + // this should never be true + if (data.loginMethods.contains(loginMethodId)) { + onSuccess(-1) + return + } + Utils.d(TAG, "Using login method $loginMethodId") + when (loginMethodId) { + LOGIN_METHOD_PODLASIE_API -> { + data.startProgress(R.string.edziennik_progress_login_podlasie_api) + PodlasieLoginApi(data) { onSuccess(loginMethodId) } + } + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/login/PodlasieLoginApi.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/login/PodlasieLoginApi.kt new file mode 100644 index 00000000..6eb70d06 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/login/PodlasieLoginApi.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2020-5-12 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.podlasie.login + +import pl.szczodrzynski.edziennik.data.api.ERROR_LOGIN_DATA_MISSING +import pl.szczodrzynski.edziennik.data.api.edziennik.podlasie.DataPodlasie +import pl.szczodrzynski.edziennik.data.api.models.ApiError + +class PodlasieLoginApi(val data: DataPodlasie, val onSuccess: () -> Unit) { + companion object { + const val TAG = "PodlasieLoginApi" + } + + init { run { + if (data.isApiLoginValid()) { + onSuccess() + } else { + data.error(ApiError(TAG, ERROR_LOGIN_DATA_MISSING)) + } + }} +} 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 d326ebd4..4b397f5d 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 @@ -17,9 +17,14 @@ import pl.szczodrzynski.edziennik.values class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app, profile, loginStore) { + fun isWebMainLoginValid() = webExpiryTime-30 > currentTimeUnix() + && webAuthCookie.isNotNullNorEmpty() + && webHost.isNotNullNorEmpty() + && webType.isNotNullNorEmpty() + && symbol.isNotNullNorEmpty() fun isApiLoginValid() = currentSemesterEndDate-30 > currentTimeUnix() - && apiCertificateKey.isNotNullNorEmpty() - && apiCertificatePrivate.isNotNullNorEmpty() + && apiFingerprint[symbol].isNotNullNorEmpty() + && apiPrivateKey[symbol].isNotNullNorEmpty() && symbol.isNotNullNorEmpty() override fun satisfyLoginMethods() { @@ -40,7 +45,7 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app id, name, Team.TYPE_CLASS, - "$schoolName:$name", + "$schoolCode:$name", -1 ) teamList.put(id, teamObject) @@ -48,7 +53,7 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app } } - override fun generateUserCode() = "$schoolName:$studentId" + override fun generateUserCode() = "$schoolCode:$studentId" /** * A UONET+ client symbol. @@ -59,8 +64,8 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app */ private var mSymbol: String? = null var symbol: String? - get() { mSymbol = mSymbol ?: loginStore.getLoginData("deviceSymbol", null); return mSymbol } - set(value) { loginStore.putLoginData("deviceSymbol", value); mSymbol = value } + get() { mSymbol = mSymbol ?: profile?.getStudentData("symbol", null); return mSymbol } + set(value) { profile?.putStudentData("symbol", value); mSymbol = value } /** * Group symbol/number of the student's school. @@ -75,16 +80,26 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app set(value) { profile?.putStudentData("schoolSymbol", value) ?: return; mSchoolSymbol = value } /** - * A school ID consisting of the [symbol] and [schoolSymbol]. + * Short name of the school, used in some places. + * + * ListaUczniow/JednostkaSprawozdawczaSkrot, e.g. "SP Wilkow" + */ + 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 } + + /** + * A school code consisting of the [symbol] and [schoolSymbol]. * * [symbol]_[schoolSymbol] * * e.g. "poznan_000088" */ - 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 } + 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 } /** * ID of the student. @@ -124,6 +139,15 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app get() { mStudentSemesterId = mStudentSemesterId ?: profile?.getStudentData("studentSemesterId", 0); return mStudentSemesterId ?: 0 } set(value) { profile?.putStudentData("studentSemesterId", value) ?: return; mStudentSemesterId = 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 } + 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 } + /** * ListaUczniow/OkresNumer, e.g. 1 or 2 */ @@ -154,45 +178,34 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app * After first login only 3 first characters are stored here. * This is later used to determine the API URL address. */ - private var mApiToken: String? = null - var apiToken: String? - get() { mApiToken = mApiToken ?: loginStore.getLoginData("deviceToken", null); return mApiToken } - set(value) { loginStore.putLoginData("deviceToken", value); mApiToken = value } + private var mApiToken: Map? = null + var apiToken: Map = mapOf() + get() { mApiToken = mApiToken ?: loginStore.getLoginData("apiToken", null)?.let { app.gson.fromJson(it, field.toMutableMap()::class.java) }; return mApiToken ?: mapOf() } + set(value) { loginStore.putLoginData("apiToken", app.gson.toJson(value)); mApiToken = value } /** * A mobile API registration PIN. * * After first login, this is removed and/or set to null. */ - private var mApiPin: String? = null - var apiPin: String? - get() { mApiPin = mApiPin ?: loginStore.getLoginData("devicePin", null); return mApiPin } - set(value) { loginStore.putLoginData("devicePin", value); mApiPin = value } + private var mApiPin: Map? = null + var apiPin: Map = mapOf() + get() { mApiPin = mApiPin ?: loginStore.getLoginData("apiPin", null)?.let { app.gson.fromJson(it, field.toMutableMap()::class.java) }; return mApiPin ?: mapOf() } + set(value) { loginStore.putLoginData("apiPin", app.gson.toJson(value)); mApiPin = value } - private var mApiCertificateKey: String? = null - var apiCertificateKey: String? - get() { mApiCertificateKey = mApiCertificateKey ?: loginStore.getLoginData("certificateKey", null); return mApiCertificateKey } - set(value) { loginStore.putLoginData("certificateKey", value); mApiCertificateKey = value } + private var mApiFingerprint: Map? = null + var apiFingerprint: Map = mapOf() + get() { mApiFingerprint = mApiFingerprint ?: loginStore.getLoginData("apiFingerprint", null)?.let { app.gson.fromJson(it, field.toMutableMap()::class.java) }; return mApiFingerprint ?: mapOf() } + set(value) { loginStore.putLoginData("apiFingerprint", app.gson.toJson(value)); mApiFingerprint = value } - /** - * This is not meant for normal usage. - * - * It provides a backward compatibility (<4.0) in order - * to migrate and use private keys instead of PFX. - */ - private var mApiCertificatePfx: String? = null - var apiCertificatePfx: String? - get() { mApiCertificatePfx = mApiCertificatePfx ?: loginStore.getLoginData("certificatePfx", null); return mApiCertificatePfx } - set(value) { loginStore.putLoginData("certificatePfx", value); mApiCertificatePfx = value } - - private var mApiCertificatePrivate: String? = null - var apiCertificatePrivate: String? - get() { mApiCertificatePrivate = mApiCertificatePrivate ?: loginStore.getLoginData("certificatePrivate", null); return mApiCertificatePrivate } - set(value) { loginStore.putLoginData("certificatePrivate", value); mApiCertificatePrivate = value } + private var mApiPrivateKey: Map? = null + var apiPrivateKey: Map = mapOf() + get() { mApiPrivateKey = mApiPrivateKey ?: loginStore.getLoginData("apiPrivateKey", null)?.let { app.gson.fromJson(it, field.toMutableMap()::class.java) }; return mApiPrivateKey ?: mapOf() } + set(value) { loginStore.putLoginData("apiPrivateKey", app.gson.toJson(value)); mApiPrivateKey = value } val apiUrl: String? get() { - val url = when (apiToken?.substring(0, 3)) { + val url = when (apiToken[symbol]?.substring(0, 3)) { "3S1" -> "https://lekcjaplus.vulcan.net.pl" "TA1" -> "https://uonetplus-komunikacja.umt.tarnow.pl" "OP1" -> "https://uonetplus-komunikacja.eszkola.opolskie.pl" @@ -217,4 +230,95 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app get() { return "$apiUrl$schoolSymbol/" } + + /* __ __ _ ______ _____ _ _ + \ \ / / | | | ____/ ____| | | (_) + \ \ /\ / /__| |__ | |__ | (___ | | ___ __ _ _ _ __ + \ \/ \/ / _ \ '_ \ | __| \___ \ | | / _ \ / _` | | '_ \ + \ /\ / __/ |_) | | | ____) | | |___| (_) | (_| | | | | | + \/ \/ \___|_.__/ |_| |_____/ |______\___/ \__, |_|_| |_| + __/ | + |__*/ + /** + * Federation Services login type. + * This might be one of: cufs, adfs, adfslight. + */ + var webType: String? + get() { mWebType = mWebType ?: loginStore.getLoginData("webType", null); return mWebType } + set(value) { loginStore.putLoginData("webType", value); mWebType = value } + private var mWebType: String? = null + + /** + * Web server providing the federation services login. + * If this is present, WEB_MAIN login is considered as available. + */ + var webHost: String? + get() { mWebHost = mWebHost ?: loginStore.getLoginData("webHost", null); return mWebHost } + set(value) { loginStore.putLoginData("webHost", value); mWebHost = value } + private var mWebHost: String? = null + + /** + * An ID used in ADFS & ADFSLight login types. + */ + var webAdfsId: String? + get() { mWebAdfsId = mWebAdfsId ?: loginStore.getLoginData("webAdfsId", null); return mWebAdfsId } + set(value) { loginStore.putLoginData("webAdfsId", value); mWebAdfsId = value } + private var mWebAdfsId: String? = null + + /** + * A domain override for ADFS Light. + */ + var webAdfsDomain: String? + get() { mWebAdfsDomain = mWebAdfsDomain ?: loginStore.getLoginData("webAdfsDomain", null); return mWebAdfsDomain } + set(value) { loginStore.putLoginData("webAdfsDomain", value); mWebAdfsDomain = value } + private var mWebAdfsDomain: String? = null + + var webIsHttpCufs: Boolean + get() { mWebIsHttpCufs = mWebIsHttpCufs ?: loginStore.getLoginData("webIsHttpCufs", false); return mWebIsHttpCufs ?: false } + set(value) { loginStore.putLoginData("webIsHttpCufs", value); mWebIsHttpCufs = value } + private var mWebIsHttpCufs: Boolean? = null + + var webIsScopedAdfs: Boolean + get() { mWebIsScopedAdfs = mWebIsScopedAdfs ?: loginStore.getLoginData("webIsScopedAdfs", false); return mWebIsScopedAdfs ?: false } + set(value) { loginStore.putLoginData("webIsScopedAdfs", value); mWebIsScopedAdfs = value } + private var mWebIsScopedAdfs: Boolean? = null + + var webEmail: String? + get() { mWebEmail = mWebEmail ?: loginStore.getLoginData("webEmail", null); return mWebEmail } + set(value) { loginStore.putLoginData("webEmail", value); mWebEmail = value } + private var mWebEmail: String? = null + var webUsername: String? + get() { mWebUsername = mWebUsername ?: loginStore.getLoginData("webUsername", null); return mWebUsername } + set(value) { loginStore.putLoginData("webUsername", value); mWebUsername = value } + private var mWebUsername: String? = null + var webPassword: String? + get() { mWebPassword = mWebPassword ?: loginStore.getLoginData("webPassword", null); return mWebPassword } + set(value) { loginStore.putLoginData("webPassword", value); mWebPassword = value } + private var mWebPassword: String? = null + + /** + * Expiry time of a certificate POSTed to a LoginEndpoint of the specific symbol. + * If the time passes, the certificate needs to be POSTed again (if valid) + * or re-generated. + */ + var webExpiryTime: Long + get() { mWebExpiryTime = mWebExpiryTime ?: profile?.getStudentData("webExpiryTime", 0L); return mWebExpiryTime ?: 0L } + set(value) { profile?.putStudentData("webExpiryTime", value); mWebExpiryTime = value } + private var mWebExpiryTime: Long? = null + + /** + * EfebSsoAuthCookie retrieved after posting a certificate + */ + var webAuthCookie: String? + get() { mWebAuthCookie = mWebAuthCookie ?: profile?.getStudentData("webAuthCookie", null); return mWebAuthCookie } + set(value) { profile?.putStudentData("webAuthCookie", value); mWebAuthCookie = value } + private var mWebAuthCookie: String? = null + + /** + * Permissions needed to get JSONs from home page + */ + var webPermissions: String? + get() { mWebPermissions = mWebPermissions ?: profile?.getStudentData("webPermissions", null); return mWebPermissions } + set(value) { profile?.putStudentData("webPermissions", value); mWebPermissions = value } + private var mWebPermissions: String? = null } 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 89b60543..e17d059f 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 @@ -157,7 +157,7 @@ class Vulcan(val app: App, val profile: Profile?, val loginStore: LoginStore, va completed() }, - onProgress = { written, total -> + onProgress = { written, _ -> val event = AttachmentGetEvent( data.profileId, owner, 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 d1f11822..ab032e0c 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 @@ -19,6 +19,7 @@ const val ENDPOINT_VULCAN_API_NOTICES = 1070 const val ENDPOINT_VULCAN_API_ATTENDANCE = 1080 const val ENDPOINT_VULCAN_API_MESSAGES_INBOX = 1090 const val ENDPOINT_VULCAN_API_MESSAGES_SENT = 1100 +const val ENDPOINT_VULCAN_WEB_LUCKY_NUMBERS = 2010 val VulcanFeatures = listOf( // timetable @@ -61,6 +62,13 @@ val VulcanFeatures = listOf( !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)).withShouldSync { data -> data.shouldSyncLuckyNumber() }, + Feature(LOGIN_TYPE_VULCAN, FEATURE_ALWAYS_NEEDED, listOf( ENDPOINT_VULCAN_API_UPDATE_SEMESTER to LOGIN_METHOD_VULCAN_API, ENDPOINT_VULCAN_API_DICTIONARIES to LOGIN_METHOD_VULCAN_API diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/VulcanApi.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/VulcanApi.kt index 172be3c0..c500e3e7 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/VulcanApi.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/VulcanApi.kt @@ -106,11 +106,11 @@ open class VulcanApi(open val data: DataVulcan, open val lastSync: Long?) { Request.builder() .url(url) .userAgent(VULCAN_API_USER_AGENT) - .addHeader("RequestCertificateKey", data.apiCertificateKey) + .addHeader("RequestCertificateKey", data.apiFingerprint[data.symbol]) .addHeader("RequestSignatureValue", try { signContent( - data.apiCertificatePrivate ?: "", + data.apiPrivateKey[data.symbol] ?: "", finalPayload.toString() ) } catch (e: Exception) {e.printStackTrace();""}) 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 9ab94c30..491e8ea6 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 @@ -7,6 +7,7 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.* import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.api.* +import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.web.VulcanWebLuckyNumber import pl.szczodrzynski.edziennik.utils.Utils class VulcanData(val data: DataVulcan, val onSuccess: () -> Unit) { @@ -86,6 +87,10 @@ class VulcanData(val data: DataVulcan, val onSuccess: () -> Unit) { data.startProgress(R.string.edziennik_progress_endpoint_messages_outbox) VulcanApiMessagesSent(data, lastSync, onSuccess) } + ENDPOINT_VULCAN_WEB_LUCKY_NUMBERS -> { + data.startProgress(R.string.edziennik_progress_endpoint_lucky_number) + VulcanWebLuckyNumber(data, lastSync, onSuccess) + } else -> onSuccess(endpointId) } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/VulcanWebMain.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/VulcanWebMain.kt new file mode 100644 index 00000000..39433657 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/VulcanWebMain.kt @@ -0,0 +1,289 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-17. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data + +import com.google.gson.JsonArray +import com.google.gson.JsonObject +import com.google.gson.JsonParser +import im.wangchao.mhttp.Request +import im.wangchao.mhttp.Response +import im.wangchao.mhttp.callback.TextCallbackHandler +import pl.droidsonroids.jspoon.Jspoon +import pl.szczodrzynski.edziennik.data.api.* +import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan +import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.login.CufsCertificate +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.get +import pl.szczodrzynski.edziennik.isNotNullNorBlank +import pl.szczodrzynski.edziennik.utils.Utils +import pl.szczodrzynski.edziennik.utils.models.Date +import java.io.File +import java.net.HttpURLConnection + +open class VulcanWebMain(open val data: DataVulcan, open val lastSync: Long?) { + companion object { + const val TAG = "VulcanWebMain" + const val WEB_MAIN = 0 + const val WEB_OLD = 1 + const val WEB_NEW = 2 + const val WEB_MESSAGES = 3 + const val STATE_SUCCESS = 0 + const val STATE_NO_REGISTER = 1 + const val STATE_LOGGED_OUT = 2 + } + + val profileId + get() = data.profile?.id ?: -1 + + val profile + get() = data.profile + + private val certificateAdapter by lazy { + Jspoon.create().adapter(CufsCertificate::class.java) + } + + fun saveCertificate(xml: String) { + val file = File(data.app.filesDir, "cert_"+(data.webUsername ?: data.webEmail)+".xml") + file.writeText(xml) + } + + fun readCertificate(): String? { + val file = File(data.app.filesDir, "cert_"+(data.webUsername ?: data.webEmail)+".xml") + if (file.canRead()) + return file.readText() + return null + } + + fun parseCertificate(xml: String): CufsCertificate { + val xmlParsed = xml + .replace("<[a-z]+?:".toRegex(), "<") + .replace(" Unit): Boolean { + // check if the certificate is valid + if (Date.fromIso(certificate.expiryDate) < System.currentTimeMillis()) + return false + + val callback = object : TextCallbackHandler() { + override fun onSuccess(text: String?, response: Response?) { + if (response?.headers()?.get("Location")?.contains("LoginEndpoint.aspx") == true + || response?.headers()?.get("Location")?.contains("?logout=true") == true) { + onResult(symbol, STATE_LOGGED_OUT) + return + } + if (text?.contains("LoginEndpoint.aspx?logout=true") == true) { + onResult(symbol, STATE_NO_REGISTER) + return + } + if (!validateCallback(text, response, jsonResponse = false)) { + return + } + data.webExpiryTime = Date.fromIso(certificate.expiryDate) / 1000L + onResult(symbol, STATE_SUCCESS) + } + + override fun onFailure(response: Response?, throwable: Throwable?) { + data.error(ApiError(TAG, ERROR_REQUEST_FAILURE) + .withResponse(response) + .withThrowable(throwable)) + } + } + + Request.builder() + .url("https://uonetplus.${data.webHost}/$symbol/LoginEndpoint.aspx") + .withClient(data.app.httpLazy) + .userAgent(SYSTEM_USER_AGENT) + .post() + .addParameter("wa", "wsignin1.0") + .addParameter("wctx", certificate.targetUrl) + .addParameter("wresult", certificate.xml) + .allowErrorCode(HttpURLConnection.HTTP_BAD_REQUEST) + .allowErrorCode(HttpURLConnection.HTTP_FORBIDDEN) + .allowErrorCode(HttpURLConnection.HTTP_UNAUTHORIZED) + .allowErrorCode(HttpURLConnection.HTTP_UNAVAILABLE) + .allowErrorCode(429) + .callback(callback) + .build() + .enqueue() + + return true + } + + fun getStartPage(symbol: String = data.symbol ?: "default", postErrors: Boolean = true, onSuccess: (html: String, schoolSymbols: List) -> Unit) { + val callback = object : TextCallbackHandler() { + override fun onSuccess(text: String?, response: Response?) { + if (!validateCallback(text, response, jsonResponse = false) || text == null) { + return + } + + if (postErrors) { + when { + text.contains("status absolwenta") -> ERROR_VULCAN_WEB_GRADUATE_ACCOUNT + else -> null + }?.let { errorCode -> + data.error(ApiError(TAG, errorCode) + .withResponse(response) + .withApiResponse(text)) + return + } + } + + data.webPermissions = Regexes.VULCAN_WEB_PERMISSIONS.find(text)?.let { it[1] } + + val schoolSymbols = mutableListOf() + val clientUrl = "://uonetplus-uczen.${data.webHost}/$symbol/" + var clientIndex = text.indexOf(clientUrl) + var count = 0 + while (clientIndex != -1 && count < 100) { + val startIndex = clientIndex + clientUrl.length + val endIndex = text.indexOf('/', startIndex = startIndex) + val schoolSymbol = text.substring(startIndex, endIndex) + schoolSymbols += schoolSymbol + clientIndex = text.indexOf(clientUrl, startIndex = endIndex) + count++ + } + schoolSymbols.removeAll { + it.toLowerCase() == "default" + || !it.matches(Regexes.VULCAN_WEB_SYMBOL_VALIDATE) + } + + if (postErrors && schoolSymbols.isEmpty()) { + data.error(ApiError(TAG, ERROR_VULCAN_WEB_NO_SCHOOLS) + .withResponse(response) + .withApiResponse(text)) + return + } + + onSuccess(text, schoolSymbols) + } + + override fun onFailure(response: Response?, throwable: Throwable?) { + data.error(ApiError(TAG, ERROR_REQUEST_FAILURE) + .withResponse(response) + .withThrowable(throwable)) + } + } + + Request.builder() + .url("https://uonetplus.${data.webHost}/$symbol/Start.mvc/Index") + .userAgent(SYSTEM_USER_AGENT) + .get() + .allowErrorCode(HttpURLConnection.HTTP_BAD_REQUEST) + .allowErrorCode(HttpURLConnection.HTTP_FORBIDDEN) + .allowErrorCode(HttpURLConnection.HTTP_UNAUTHORIZED) + .allowErrorCode(HttpURLConnection.HTTP_UNAVAILABLE) + .allowErrorCode(429) + .callback(callback) + .build() + .enqueue() + } + + private fun validateCallback(text: String?, response: Response?, jsonResponse: Boolean = true): Boolean { + if (text == null) { + data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY) + .withResponse(response)) + return false + } + + if (response?.code() !in 200..302 || (jsonResponse && !text.startsWith("{"))) { + when { + text.contains("The custom error module") -> ERROR_VULCAN_WEB_429 + else -> ERROR_VULCAN_WEB_OTHER + }.let { errorCode -> + data.error(ApiError(TAG, errorCode) + .withApiResponse(text) + .withResponse(response)) + return false + } + } + + val cookies = data.app.cookieJar.getAll(data.webHost ?: "vulcan.net.pl") + val authCookie = cookies["EfebSsoAuthCookie"] + if ((authCookie == null || authCookie == "null") && data.webAuthCookie != null) { + data.app.cookieJar.set(data.webHost ?: "vulcan.net.pl", "EfebSsoAuthCookie", data.webAuthCookie) + } + else if (authCookie.isNotNullNorBlank() && authCookie != "null" && authCookie != data.webAuthCookie) { + data.webAuthCookie = authCookie + } + return true + } + + fun webGetJson( + tag: String, + webType: Int, + endpoint: String, + method: Int = POST, + parameters: Map = emptyMap(), + onSuccess: (json: JsonObject, response: Response?) -> Unit + ) { + val url = "https://" + when (webType) { + WEB_MAIN -> "uonetplus" + WEB_OLD -> "uonetplus-opiekun" + WEB_NEW -> "uonetplus-uczen" + WEB_MESSAGES -> "uonetplus-uzytkownik" + else -> "uonetplus" + } + ".${data.webHost}/${data.symbol}/$endpoint" + + Utils.d(tag, "Request: Vulcan/WebMain - $url") + + val payload = JsonObject() + parameters.map { (name, value) -> + when (value) { + is JsonObject -> payload.add(name, value) + is JsonArray -> payload.add(name, value) + is String -> payload.addProperty(name, value) + is Int -> payload.addProperty(name, value) + is Long -> payload.addProperty(name, value) + is Float -> payload.addProperty(name, value) + is Char -> payload.addProperty(name, value) + is Boolean -> payload.addProperty(name, value) + } + } + + val callback = object : TextCallbackHandler() { + override fun onSuccess(text: String?, response: Response?) { + if (!validateCallback(text, response)) + return + + try { + val json = JsonParser().parse(text).asJsonObject + onSuccess(json, response) + } catch (e: Exception) { + data.error(ApiError(tag, EXCEPTION_VULCAN_WEB_REQUEST) + .withResponse(response) + .withThrowable(e) + .withApiResponse(text)) + } + } + + override fun onFailure(response: Response?, throwable: Throwable?) { + data.error(ApiError(tag, ERROR_REQUEST_FAILURE) + .withResponse(response) + .withThrowable(throwable)) + } + } + + Request.builder() + .url(url) + .userAgent(SYSTEM_USER_AGENT) + .apply { + when (method) { + GET -> get() + POST -> post() + } + } + .setJsonBody(payload) + .allowErrorCode(429) + .callback(callback) + .build() + .enqueue() + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiAttendance.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiAttendance.kt index 2ec240a4..2814a1cf 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiAttendance.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiAttendance.kt @@ -7,8 +7,9 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_API_ATTENDANCE import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanApi import pl.szczodrzynski.edziennik.data.db.entity.Attendance -import pl.szczodrzynski.edziennik.data.db.entity.Attendance.TYPE_PRESENT +import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_PRESENT import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.Profile import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS import pl.szczodrzynski.edziennik.utils.models.Date @@ -25,57 +26,85 @@ class VulcanApiAttendance(override val data: DataVulcan, data.db.attendanceTypeDao().getAllNow(profileId).toSparseArray(data.attendanceTypes) { it.id } } - val startDate: String = profile.getSemesterStart(profile.currentSemester).stringY_m_d - val endDate: String = profile.getSemesterEnd(profile.currentSemester).stringY_m_d + val semesterId = data.studentSemesterId + val semesterNumber = data.studentSemesterNumber + if (semesterNumber == 2 && lastSync ?: 0 < profile.dateSemester1Start.inMillis) { + getAttendance(profile, semesterId - 1, semesterNumber - 1) { + getAttendance(profile, semesterId, semesterNumber) { + finish() + } + } + } + else { + getAttendance(profile, semesterId, semesterNumber) { + finish() + } + } + + } ?: onSuccess(ENDPOINT_VULCAN_API_ATTENDANCE) } + + private fun finish() { + data.setSyncNext(ENDPOINT_VULCAN_API_ATTENDANCE, SYNC_ALWAYS) + onSuccess(ENDPOINT_VULCAN_API_ATTENDANCE) + } + + private fun getAttendance(profile: Profile, semesterId: Int, semesterNumber: Int, onSuccess: () -> Unit) { + val startDate = profile.getSemesterStart(semesterNumber).stringY_m_d + val endDate = profile.getSemesterEnd(semesterNumber).stringY_m_d apiGet(TAG, VULCAN_API_ENDPOINT_ATTENDANCE, parameters = mapOf( "DataPoczatkowa" to startDate, "DataKoncowa" to endDate, "IdOddzial" to data.studentClassId, "IdUczen" to data.studentId, - "IdOkresKlasyfikacyjny" to data.studentSemesterId + "IdOkresKlasyfikacyjny" to semesterId )) { json, _ -> json.getJsonObject("Data")?.getJsonArray("Frekwencje")?.forEach { attendanceEl -> val attendance = attendanceEl.asJsonObject - val attendanceCategory = data.attendanceTypes.get(attendance.getLong("IdKategoria") ?: return@forEach) + val type = data.attendanceTypes.get(attendance.getLong("IdKategoria") ?: return@forEach) ?: return@forEach - val type = attendanceCategory.type - val id = (attendance.getInt("Dzien") ?: 0) + (attendance.getInt("Numer") ?: 0) val lessonDateMillis = Date.fromY_m_d(attendance.getString("DzienTekst")).inMillis val lessonDate = Date.fromMillis(lessonDateMillis) + val startTime = data.lessonRanges.get(attendance.getInt("Numer") ?: 0)?.startTime - val lessonSemester = profile.dateToSemester(lessonDate) + val lessonSemester = semesterNumber val attendanceObject = Attendance( - profileId, - id.toLong(), - -1, - attendance.getLong("IdPrzedmiot") ?: -1, - lessonSemester, - attendance.getString("PrzedmiotNazwa") + attendanceCategory.name.let { " - $it" }, - lessonDate, - data.lessonRanges.get(attendance.getInt("Numer") ?: 0)?.startTime, - type) + profileId = profileId, + id = id.toLong(), + baseType = type.baseType, + typeName = type.typeName, + typeShort = type.typeShort, + typeSymbol = type.typeSymbol, + typeColor = type.typeColor, + date = lessonDate, + startTime = startTime, + semester = lessonSemester, + teacherId = -1, + subjectId = attendance.getLong("IdPrzedmiot") ?: -1, + addedDate = lessonDate.combineWith(startTime) + ).also { + it.lessonNumber = attendance.getInt("Numer") + it.isCounted = it.baseType != Attendance.TYPE_RELEASED + } data.attendanceList.add(attendanceObject) - if (attendanceObject.type != TYPE_PRESENT) { + if (type.baseType != TYPE_PRESENT) { data.metadataList.add(Metadata( profileId, Metadata.TYPE_ATTENDANCE, attendanceObject.id, - profile.empty, - profile.empty, - attendanceObject.lessonDate.combineWith(attendanceObject.startTime) + profile.empty || type.baseType == Attendance.TYPE_PRESENT_CUSTOM || type.baseType == Attendance.TYPE_UNKNOWN, + profile.empty || type.baseType == Attendance.TYPE_PRESENT_CUSTOM || type.baseType == Attendance.TYPE_UNKNOWN )) } } - data.setSyncNext(ENDPOINT_VULCAN_API_ATTENDANCE, SYNC_ALWAYS) - onSuccess(ENDPOINT_VULCAN_API_ATTENDANCE) + onSuccess() } - } ?: onSuccess(ENDPOINT_VULCAN_API_ATTENDANCE) } + } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiDictionaries.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiDictionaries.kt index 76f53cf5..2dc9f6bc 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiDictionaries.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiDictionaries.kt @@ -114,11 +114,11 @@ class VulcanApiDictionaries(override val data: DataVulcan, private fun saveAttendanceType(attendanceType: JsonObject) { val id = attendanceType.getLong("Id") ?: return - val name = attendanceType.getString("Nazwa") ?: "" + val typeName = attendanceType.getString("Nazwa") ?: "" val absent = attendanceType.getBoolean("Nieobecnosc") ?: false val excused = attendanceType.getBoolean("Usprawiedliwione") ?: false - val type = if (absent) { + val baseType = if (absent) { if (excused) Attendance.TYPE_ABSENT_EXCUSED else @@ -137,15 +137,35 @@ class VulcanApiDictionaries(override val data: DataVulcan, else if (present) Attendance.TYPE_PRESENT else - Attendance.TYPE_CUSTOM + Attendance.TYPE_UNKNOWN + } + + val (typeColor, typeSymbol) = when (id.toInt()) { + 1 -> 0xffffffff to "●" // obecność + 2 -> 0xffffa687 to "—" // nieobecność + 3 -> 0xfffcc150 to "u" // nieobecność usprawiedliwiona + 4 -> 0xffede049 to "s" // spóźnienie + 5 -> 0xffbbdd5f to "su" // spóźnienie usprawiedliwione + 6 -> 0xffa9c9fd to "ns" // nieobecny z przyczyn szkolnych + 7 -> 0xffddbbe5 to "z" // zwolniony + 8 -> 0xffffffff to "" // usunięty wpis + else -> null to "?" + } + + val typeShort = when (id.toInt()) { + 6 -> "ns" // nieobecny z przyczyn szkolnych + 8 -> "" // usunięty wpis + else -> data.app.attendanceManager.getTypeShort(baseType) } val attendanceTypeObject = AttendanceType( profileId, id, - name, - type, - -1 + baseType, + typeName, + typeShort, + typeSymbol, + typeColor?.toInt() ) data.attendanceTypes.put(id, attendanceTypeObject) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiEvents.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiEvents.kt index 4ba7c460..6f045828 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiEvents.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiEvents.kt @@ -13,6 +13,7 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanApi 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.Profile import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS import pl.szczodrzynski.edziennik.getBoolean import pl.szczodrzynski.edziennik.getJsonArray @@ -31,11 +32,43 @@ class VulcanApiEvents(override val data: DataVulcan, init { data.profile?.also { profile -> - val startDate: String = when (profile.empty) { - true -> profile.getSemesterStart(profile.currentSemester).stringY_m_d + val semesterId = data.studentSemesterId + val semesterNumber = data.studentSemesterNumber + if (semesterNumber == 2 && lastSync ?: 0 < profile.dateSemester1Start.inMillis) { + getEvents(profile, semesterId - 1, semesterNumber - 1) { + getEvents(profile, semesterId, semesterNumber) { + finish() + } + } + } + else { + getEvents(profile, semesterId, semesterNumber) { + finish() + } + } + + } ?: onSuccess(if (isHomework) ENDPOINT_VULCAN_API_HOMEWORK else ENDPOINT_VULCAN_API_EVENTS) } + + private fun finish() { + when (isHomework) { + true -> { + data.toRemove.add(DataRemoveModel.Events.futureWithType(Event.TYPE_HOMEWORK)) + data.setSyncNext(ENDPOINT_VULCAN_API_HOMEWORK, SYNC_ALWAYS) + } + false -> { + data.toRemove.add(DataRemoveModel.Events.futureExceptType(Event.TYPE_HOMEWORK)) + data.setSyncNext(ENDPOINT_VULCAN_API_EVENTS, SYNC_ALWAYS) + } + } + onSuccess(if (isHomework) ENDPOINT_VULCAN_API_HOMEWORK else ENDPOINT_VULCAN_API_EVENTS) + } + + private fun getEvents(profile: Profile, semesterId: Int, semesterNumber: Int, onSuccess: () -> Unit) { + val startDate = when (profile.empty) { + true -> profile.getSemesterStart(semesterNumber).stringY_m_d else -> Date.getToday().stepForward(0, -1, 0).stringY_m_d } - val endDate: String = profile.getSemesterEnd(profile.currentSemester).stringY_m_d + val endDate = profile.getSemesterEnd(semesterNumber).stringY_m_d val endpoint = when (isHomework) { true -> VULCAN_API_ENDPOINT_HOMEWORK @@ -46,7 +79,7 @@ class VulcanApiEvents(override val data: DataVulcan, "DataKoncowa" to endDate, "IdOddzial" to data.studentClassId, "IdUczen" to data.studentId, - "IdOkresKlasyfikacyjny" to data.studentSemesterId + "IdOkresKlasyfikacyjny" to semesterId )) { json, _ -> val events = json.getJsonArray("Data") @@ -59,7 +92,7 @@ class VulcanApiEvents(override val data: DataVulcan, val teacherId = event.getLong("IdPracownik") ?: -1 val topic = event.getString("Opis")?.trim() ?: "" - val lessonList = data.db.timetableDao().getForDateNow(profileId, eventDate) + val lessonList = data.db.timetableDao().getAllForDateNow(profileId, eventDate) val startTime = lessonList.firstOrNull { it.subjectId == subjectId }?.startTime val type = when (isHomework) { @@ -90,22 +123,11 @@ class VulcanApiEvents(override val data: DataVulcan, if (isHomework) Metadata.TYPE_HOMEWORK else Metadata.TYPE_EVENT, id, profile.empty, - profile.empty, - System.currentTimeMillis() + profile.empty )) } - when (isHomework) { - true -> { - data.toRemove.add(DataRemoveModel.Events.futureWithType(Event.TYPE_HOMEWORK)) - data.setSyncNext(ENDPOINT_VULCAN_API_HOMEWORK, SYNC_ALWAYS) - } - false -> { - data.toRemove.add(DataRemoveModel.Events.futureExceptType(Event.TYPE_HOMEWORK)) - data.setSyncNext(ENDPOINT_VULCAN_API_EVENTS, SYNC_ALWAYS) - } - } - onSuccess(if (isHomework) ENDPOINT_VULCAN_API_HOMEWORK else ENDPOINT_VULCAN_API_EVENTS) + onSuccess() } - } ?: onSuccess(if (isHomework) ENDPOINT_VULCAN_API_HOMEWORK else ENDPOINT_VULCAN_API_EVENTS) } + } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiGrades.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiGrades.kt index f62778fc..9a354ea5 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiGrades.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiGrades.kt @@ -13,6 +13,7 @@ import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel 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.Profile import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS import java.text.DecimalFormat import kotlin.math.roundToInt @@ -27,9 +28,33 @@ class VulcanApiGrades(override val data: DataVulcan, init { data.profile?.also { profile -> + val semesterId = data.studentSemesterId + val semesterNumber = data.studentSemesterNumber + if (semesterNumber == 2 && lastSync ?: 0 < profile.dateSemester1Start.inMillis) { + getGrades(profile, semesterId - 1, semesterNumber - 1) { + getGrades(profile, semesterId, semesterNumber) { + finish() + } + } + } + else { + getGrades(profile, semesterId, semesterNumber) { + finish() + } + } + + } ?: onSuccess(ENDPOINT_VULCAN_API_GRADES) } + + private fun finish() { + data.toRemove.add(DataRemoveModel.Grades.semesterWithType(data.studentSemesterNumber, TYPE_NORMAL)) + data.setSyncNext(ENDPOINT_VULCAN_API_GRADES, SYNC_ALWAYS) + onSuccess(ENDPOINT_VULCAN_API_GRADES) + } + + private fun getGrades(profile: Profile, semesterId: Int, semesterNumber: Int, onSuccess: () -> Unit) { apiGet(TAG, VULCAN_API_ENDPOINT_GRADES, parameters = mapOf( "IdUczen" to data.studentId, - "IdOkresKlasyfikacyjny" to data.studentSemesterId + "IdOkresKlasyfikacyjny" to semesterId )) { json, _ -> val grades = json.getJsonArray("Data") @@ -99,9 +124,10 @@ class VulcanApiGrades(override val data: DataVulcan, category = category, description = finalDescription, comment = null, - semester = data.studentSemesterNumber, + semester = semesterNumber, teacherId = teacherId, - subjectId = subjectId + subjectId = subjectId, + addedDate = addedDate ) data.gradeList.add(gradeObject) @@ -110,15 +136,11 @@ class VulcanApiGrades(override val data: DataVulcan, Metadata.TYPE_GRADE, id, profile.empty, - profile.empty, - addedDate - + profile.empty )) } - data.toRemove.add(DataRemoveModel.Grades.semesterWithType(data.studentSemesterNumber, Grade.TYPE_NORMAL)) - data.setSyncNext(ENDPOINT_VULCAN_API_GRADES, SYNC_ALWAYS) - onSuccess(ENDPOINT_VULCAN_API_GRADES) + onSuccess() } - } ?: onSuccess(ENDPOINT_VULCAN_API_GRADES) } + } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiMessagesChangeStatus.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiMessagesChangeStatus.kt index 5e226849..b0b7795d 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiMessagesChangeStatus.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiMessagesChangeStatus.kt @@ -37,8 +37,7 @@ class VulcanApiMessagesChangeStatus(override val data: DataVulcan, Metadata.TYPE_MESSAGE, messageObject.id, true, - true, - messageObject.addedDate + true )) messageObject.seen = true diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiMessagesInbox.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiMessagesInbox.kt index 388a97ad..dea913c5 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiMessagesInbox.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiMessagesInbox.kt @@ -72,7 +72,8 @@ class VulcanApiMessagesInbox(override val data: DataVulcan, type = TYPE_RECEIVED, subject = subject, body = body.replace("\n", "
"), - senderId = senderId + senderId = senderId, + addedDate = sentDate ) val messageRecipientObject = MessageRecipient( @@ -90,8 +91,7 @@ class VulcanApiMessagesInbox(override val data: DataVulcan, Metadata.TYPE_MESSAGE, id, readDate > 0, - readDate > 0, - sentDate + readDate > 0 )) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiMessagesSent.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiMessagesSent.kt index d190c00f..94cfdcba 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiMessagesSent.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiMessagesSent.kt @@ -97,7 +97,8 @@ class VulcanApiMessagesSent(override val data: DataVulcan, type = TYPE_SENT, subject = subject, body = body.replace("\n", "
"), - senderId = null + senderId = null, + addedDate = sentDate ) data.messageList.add(messageObject) @@ -106,8 +107,7 @@ class VulcanApiMessagesSent(override val data: DataVulcan, Metadata.TYPE_MESSAGE, id, true, - true, - sentDate + true )) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiNotices.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiNotices.kt index b2202981..45155a5b 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiNotices.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiNotices.kt @@ -11,12 +11,12 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_API_ import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanApi import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.data.db.entity.Notice +import pl.szczodrzynski.edziennik.data.db.entity.Profile import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS import pl.szczodrzynski.edziennik.getJsonArray import pl.szczodrzynski.edziennik.getLong import pl.szczodrzynski.edziennik.getString import pl.szczodrzynski.edziennik.toSparseArray -import pl.szczodrzynski.edziennik.utils.models.Date class VulcanApiNotices(override val data: DataVulcan, override val lastSync: Long?, @@ -31,6 +31,29 @@ class VulcanApiNotices(override val data: DataVulcan, data.db.noticeTypeDao().getAllNow(profileId).toSparseArray(data.noticeTypes) { it.id } } + val semesterId = data.studentSemesterId + val semesterNumber = data.studentSemesterNumber + if (semesterNumber == 2 && lastSync ?: 0 < profile.dateSemester1Start.inMillis) { + getNotices(profile, semesterId - 1, semesterNumber - 1) { + getNotices(profile, semesterId, semesterNumber) { + finish() + } + } + } + else { + getNotices(profile, semesterId, semesterNumber) { + finish() + } + } + + } ?: onSuccess(ENDPOINT_VULCAN_API_NOTICES) } + + private fun finish() { + data.setSyncNext(ENDPOINT_VULCAN_API_NOTICES, SYNC_ALWAYS) + onSuccess(ENDPOINT_VULCAN_API_NOTICES) + } + + private fun getNotices(profile: Profile, semesterId: Int, semesterNumber: Int, onSuccess: () -> Unit) { apiGet(TAG, VULCAN_API_ENDPOINT_NOTICES, parameters = mapOf( "IdUczen" to data.studentId, "IdOkresKlasyfikacyjny" to data.studentSemesterId @@ -41,15 +64,21 @@ class VulcanApiNotices(override val data: DataVulcan, val id = notice.getLong("Id") ?: return@forEach val text = notice.getString("TrescUwagi") ?: return@forEach val teacherId = notice.getLong("IdPracownik") ?: -1 - val addedDate = Date.fromY_m_d(notice.getString("DataWpisuTekst")).inMillis + val addedDate = notice.getLong("DataModyfikacji")?.times(1000) ?: System.currentTimeMillis() + + val categoryId = notice.getLong("IdKategoriaUwag") ?: -1 + val categoryText = data.noticeTypes[categoryId]?.name ?: "" val noticeObject = Notice( - profileId, - id, - text, - profile.currentSemester, - Notice.TYPE_NEUTRAL, - teacherId + profileId = profileId, + id = id, + type = Notice.TYPE_NEUTRAL, + semester = profile.currentSemester, + text = text, + category = categoryText, + points = null, + teacherId = teacherId, + addedDate = addedDate ) data.noticeList.add(noticeObject) @@ -58,13 +87,11 @@ class VulcanApiNotices(override val data: DataVulcan, Metadata.TYPE_NOTICE, id, profile.empty, - profile.empty, - addedDate + profile.empty )) } - data.setSyncNext(ENDPOINT_VULCAN_API_NOTICES, SYNC_ALWAYS) - onSuccess(ENDPOINT_VULCAN_API_NOTICES) + onSuccess() } - } ?: onSuccess(ENDPOINT_VULCAN_API_NOTICES) } + } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiProposedGrades.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiProposedGrades.kt index ee1e250f..e897df92 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiProposedGrades.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiProposedGrades.kt @@ -13,6 +13,7 @@ import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_SEMESTER1_ import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_SEMESTER2_FINAL import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_SEMESTER2_PROPOSED import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.Profile import pl.szczodrzynski.edziennik.getJsonArray import pl.szczodrzynski.edziennik.getJsonObject import pl.szczodrzynski.edziennik.utils.Utils @@ -27,32 +28,54 @@ class VulcanApiProposedGrades(override val data: DataVulcan, init { data.profile?.also { profile -> + val semesterId = data.studentSemesterId + val semesterNumber = data.studentSemesterNumber + if (semesterNumber == 2 && lastSync ?: 0 < profile.dateSemester1Start.inMillis) { + getProposedGrades(profile, semesterId - 1, semesterNumber - 1) { + getProposedGrades(profile, semesterId, semesterNumber) { + finish() + } + } + } + else { + getProposedGrades(profile, semesterId, semesterNumber) { + finish() + } + } + + } ?: onSuccess(ENDPOINT_VULCAN_API_GRADES_SUMMARY) } + + private fun finish() { + data.setSyncNext(ENDPOINT_VULCAN_API_GRADES_SUMMARY, 6*HOUR) + onSuccess(ENDPOINT_VULCAN_API_GRADES_SUMMARY) + } + + private fun getProposedGrades(profile: Profile, semesterId: Int, semesterNumber: Int, onSuccess: () -> Unit) { apiGet(TAG, VULCAN_API_ENDPOINT_GRADES_PROPOSITIONS, parameters = mapOf( "IdUczen" to data.studentId, - "IdOkresKlasyfikacyjny" to data.studentSemesterId + "IdOkresKlasyfikacyjny" to semesterId )) { json, _ -> val grades = json.getJsonObject("Data") grades.getJsonArray("OcenyPrzewidywane")?.let { - processGradeList(it, isFinal = false) + processGradeList(it, semesterNumber, isFinal = false) } grades.getJsonArray("OcenyKlasyfikacyjne")?.let { - processGradeList(it, isFinal = true) + processGradeList(it, semesterNumber, isFinal = true) } - data.setSyncNext(ENDPOINT_VULCAN_API_GRADES_SUMMARY, 6*HOUR) - onSuccess(ENDPOINT_VULCAN_API_GRADES_SUMMARY) + onSuccess() } - } ?: onSuccess(ENDPOINT_VULCAN_API_GRADES_SUMMARY) } + } - private fun processGradeList(grades: JsonArray, isFinal: Boolean) { - grades.asJsonObjectList()?.forEach { grade -> + private fun processGradeList(grades: JsonArray, semesterNumber: Int, isFinal: Boolean) { + grades.asJsonObjectList().forEach { grade -> val name = grade.get("Wpis").asString val value = Utils.getGradeValue(name) val subjectId = grade.get("IdPrzedmiot").asLong - val id = subjectId * -100 - data.studentSemesterNumber + val id = subjectId * -100 - semesterNumber val color = Utils.getVulcanGradeColor(name) @@ -60,7 +83,7 @@ class VulcanApiProposedGrades(override val data: DataVulcan, profileId = profileId, id = id, name = name, - type = if (data.studentSemesterNumber == 1) { + type = if (semesterNumber == 1) { if (isFinal) TYPE_SEMESTER1_FINAL else TYPE_SEMESTER1_PROPOSED } else { if (isFinal) TYPE_SEMESTER2_FINAL else TYPE_SEMESTER2_PROPOSED @@ -71,7 +94,7 @@ class VulcanApiProposedGrades(override val data: DataVulcan, category = "", description = null, comment = null, - semester = data.studentSemesterNumber, + semester = semesterNumber, teacherId = -1, subjectId = subjectId ) @@ -82,8 +105,7 @@ class VulcanApiProposedGrades(override val data: DataVulcan, Metadata.TYPE_GRADE, gradeObject.id, data.profile?.empty ?: false, - data.profile?.empty ?: false, - System.currentTimeMillis() + data.profile?.empty ?: false )) } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiSendMessage.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiSendMessage.kt index b88ccdb6..8c89b620 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiSendMessage.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiSendMessage.kt @@ -54,7 +54,7 @@ class VulcanApiSendMessage(override val data: DataVulcan, VulcanApiMessagesSent(data, null) { val message = data.messageList.firstOrNull { it.type == Message.TYPE_SENT && it.subject == subject } val metadata = data.metadataList.firstOrNull { it.thingType == Metadata.TYPE_MESSAGE && it.thingId == messageId } - val event = MessageSentEvent(data.profileId, message, metadata?.addedDate) + val event = MessageSentEvent(data.profileId, message, message?.addedDate) EventBus.getDefault().postSticky(event) onSuccess() diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiTimetable.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiTimetable.kt index d8899dbf..46b52de5 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiTimetable.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiTimetable.kt @@ -80,7 +80,7 @@ class VulcanApiTimetable(override val data: DataVulcan, id, name, Team.TYPE_VIRTUAL, - "${data.schoolName}:$name", + "${data.schoolCode}:$name", teacherId ?: oldTeacherId ?: -1 ) data.teamList[id] = team @@ -184,8 +184,7 @@ class VulcanApiTimetable(override val data: DataVulcan, Metadata.TYPE_LESSON_CHANGE, lessonObject.id, seen, - seen, - System.currentTimeMillis() + seen )) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiUpdateSemester.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiUpdateSemester.kt index 1f03484c..50fb178a 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiUpdateSemester.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiUpdateSemester.kt @@ -60,6 +60,7 @@ class VulcanApiUpdateSemester(override val data: DataVulcan, data.studentClassId = studentClassId data.studentSemesterId = studentSemesterId data.studentSemesterNumber = studentSemesterNumber + data.profile.studentData["semester${studentSemesterNumber}Id"] = studentSemesterId data.currentSemesterEndDate = currentSemesterEndDate profile.studentClassName = studentClassName dateSemester1Start?.let { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/web/HomepageTile.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/web/HomepageTile.kt new file mode 100644 index 00000000..4d657ac7 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/web/HomepageTile.kt @@ -0,0 +1,16 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-20. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.web + +import com.google.gson.annotations.SerializedName + +data class HomepageTile( + @SerializedName("Nazwa") + val name: String?, + @SerializedName("Url") + val url: String?, + @SerializedName("Zawartosc") + val children: List +) 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 new file mode 100644 index 00000000..cbf24c2a --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/web/VulcanWebLuckyNumber.kt @@ -0,0 +1,86 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-20. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.web + +import pl.szczodrzynski.edziennik.DAY +import pl.szczodrzynski.edziennik.data.api.VULCAN_WEB_ENDPOINT_LUCKY_NUMBER +import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan +import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_WEB_LUCKY_NUMBERS +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.getJsonArray +import pl.szczodrzynski.edziennik.utils.models.Date +import pl.szczodrzynski.edziennik.utils.models.Time +import pl.szczodrzynski.edziennik.utils.models.Week + +class VulcanWebLuckyNumber(override val data: DataVulcan, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : VulcanWebMain(data, lastSync) { + companion object { + const val TAG = "VulcanWebLuckyNumber" + } + + init { + webGetJson(TAG, WEB_MAIN, VULCAN_WEB_ENDPOINT_LUCKY_NUMBER, parameters = mapOf( + "permissions" to data.webPermissions + )) { json, _ -> + val tiles = json + .getJsonArray("data") + ?.mapNotNull { data.app.gson.fromJson(it.toString(), HomepageTile::class.java) } + ?.flatMap { it.children } + + if (tiles == null) { + data.setSyncNext(ENDPOINT_VULCAN_WEB_LUCKY_NUMBERS, SYNC_ALWAYS) + onSuccess(ENDPOINT_VULCAN_WEB_LUCKY_NUMBERS) + return@webGetJson + } + + var nextSync = System.currentTimeMillis() + 1* DAY *1000 + + tiles.firstOrNull { it.name == data.schoolShort }?.children?.firstOrNull()?.let { tile -> + // "Szczęśliwy numer w dzienniku: 16" + return@let tile.name?.substringAfterLast(' ')?.toIntOrNull()?.let { number -> + // lucky number present + val luckyNumberObject = LuckyNumber( + profileId, + Date.getToday(), + number + ) + + data.luckyNumberList.add(luckyNumberObject) + data.metadataList.add( + Metadata( + profileId, + Metadata.TYPE_LUCKY_NUMBER, + luckyNumberObject.date.value.toLong(), + true, + profile?.empty ?: false + )) + } + } ?: { + // no lucky number + if (Date.getToday().weekDay <= Week.FRIDAY && Time.getNow().hour >= 22) { + // working days, after 10PM + // consider the lucky number is disabled; sync in 4 days + nextSync = System.currentTimeMillis() + 4*DAY*1000 + } + else if (Date.getToday().weekDay <= Week.FRIDAY && Time.getNow().hour < 22) { + // working days, before 10PM + + } + else { + // weekends + nextSync = Week.getNearestWeekDayDate(Week.MONDAY).combineWith(Time(5, 0, 0)) + } + }() + + data.setSyncNext(ENDPOINT_VULCAN_WEB_LUCKY_NUMBERS, SYNC_ALWAYS) + onSuccess(ENDPOINT_VULCAN_WEB_LUCKY_NUMBERS) + } + } +} 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 291cb37b..ed541c69 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 @@ -6,12 +6,15 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.firstlogin import org.greenrobot.eventbus.EventBus import pl.szczodrzynski.edziennik.* -import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_VULCAN -import pl.szczodrzynski.edziennik.data.api.VULCAN_API_ENDPOINT_STUDENT_LIST +import pl.szczodrzynski.edziennik.data.api.* import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanApi +import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanWebMain +import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.login.CufsCertificate import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.login.VulcanLoginApi +import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.login.VulcanLoginWebMain 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.utils.models.Date @@ -21,19 +24,97 @@ class VulcanFirstLogin(val data: DataVulcan, val onSuccess: () -> Unit) { } private val api = VulcanApi(data, null) + private val web = VulcanWebMain(data, null) private val profileList = mutableListOf() + private val loginStoreId = data.loginStore.id + private var firstProfileId = loginStoreId + private val tryingSymbols = mutableListOf() init { - val loginStoreId = data.loginStore.id - val loginStoreType = LOGIN_TYPE_VULCAN - var firstProfileId = loginStoreId + if (data.loginStore.mode == LOGIN_MODE_VULCAN_WEB) { + VulcanLoginWebMain(data) { + val xml = web.readCertificate() ?: run { + data.error(ApiError(TAG, ERROR_VULCAN_WEB_NO_CERTIFICATE)) + return@VulcanLoginWebMain + } + val certificate = web.parseCertificate(xml) + if (data.symbol != null && data.symbol != "default") { + tryingSymbols += data.symbol ?: "default" + } + else { + + tryingSymbols += certificate.userInstances + } + + checkSymbol(certificate) + } + } + else { + registerDevice { + EventBus.getDefault().postSticky(FirstLoginFinishedEvent(profileList, data.loginStore)) + onSuccess() + } + } + } + + private fun checkSymbol(certificate: CufsCertificate) { + if (tryingSymbols.isEmpty()) { + EventBus.getDefault().postSticky(FirstLoginFinishedEvent(profileList, data.loginStore)) + onSuccess() + return + } + + val result = web.postCertificate(certificate, tryingSymbols.removeAt(0)) { symbol, state -> + when (state) { + VulcanWebMain.STATE_NO_REGISTER -> { + checkSymbol(certificate) + } + VulcanWebMain.STATE_LOGGED_OUT -> data.error(ApiError(TAG, ERROR_VULCAN_WEB_LOGGED_OUT)) + VulcanWebMain.STATE_SUCCESS -> { + webRegisterDevice(symbol) { + checkSymbol(certificate) + } + } + } + } + + // postCertificate returns false if the cert is not valid anymore + if (!result) { + data.error(ApiError(TAG, ERROR_VULCAN_WEB_CERTIFICATE_EXPIRED) + .withApiResponse(certificate.xml)) + } + } + + private fun webRegisterDevice(symbol: String, onSuccess: () -> Unit) { + web.getStartPage(symbol, postErrors = false) { _, schoolSymbols -> + if (schoolSymbols.isEmpty()) { + onSuccess() + return@getStartPage + } + data.symbol = symbol + val schoolSymbol = data.schoolSymbol ?: schoolSymbols.firstOrNull() + web.webGetJson(TAG, VulcanWebMain.WEB_NEW, "$schoolSymbol/$VULCAN_WEB_ENDPOINT_REGISTER_DEVICE") { result, _ -> + val json = result.getJsonObject("data") + data.symbol = symbol + data.apiToken = data.apiToken.toMutableMap().also { + it[symbol] = json.getString("TokenKey") + } + data.apiPin = data.apiPin.toMutableMap().also { + it[symbol] = json.getString("PIN") + } + registerDevice(onSuccess) + } + } + } + + private fun registerDevice(onSuccess: () -> Unit) { VulcanLoginApi(data) { - api.apiGet(TAG, VULCAN_API_ENDPOINT_STUDENT_LIST, baseUrl = true) { json, response -> + api.apiGet(TAG, VULCAN_API_ENDPOINT_STUDENT_LIST, baseUrl = true) { json, _ -> val students = json.getJsonArray("Data") if (students == null || students.isEmpty()) { - EventBus.getDefault().post(FirstLoginFinishedEvent(listOf(), data.loginStore)) + EventBus.getDefault().postSticky(FirstLoginFinishedEvent(listOf(), data.loginStore)) onSuccess() return@apiGet } @@ -42,7 +123,8 @@ class VulcanFirstLogin(val data: DataVulcan, val onSuccess: () -> Unit) { val student = studentEl.asJsonObject val schoolSymbol = student.getString("JednostkaSprawozdawczaSymbol") ?: return@forEach - val schoolName = "${data.symbol}_$schoolSymbol" + val schoolShort = student.getString("JednostkaSprawozdawczaSkrot") ?: return@forEach + val schoolCode = "${data.symbol}_$schoolSymbol" val studentId = student.getInt("Id") ?: return@forEach val studentLoginId = student.getInt("UzytkownikLoginId") ?: return@forEach val studentClassId = student.getInt("IdOddzial") ?: return@forEach @@ -80,7 +162,7 @@ class VulcanFirstLogin(val data: DataVulcan, val onSuccess: () -> Unit) { val profile = Profile( firstProfileId++, loginStoreId, - loginStoreType, + LOGIN_TYPE_VULCAN, studentNameLong, userLogin, studentNameLong, @@ -88,13 +170,17 @@ class VulcanFirstLogin(val data: DataVulcan, val onSuccess: () -> Unit) { accountName ).apply { this.studentClassName = studentClassName + studentData["symbol"] = data.symbol + studentData["studentId"] = studentId studentData["studentLoginId"] = studentLoginId studentData["studentClassId"] = studentClassId studentData["studentSemesterId"] = studentSemesterId studentData["studentSemesterNumber"] = studentSemesterNumber + studentData["semester${studentSemesterNumber}Id"] = studentSemesterId studentData["schoolSymbol"] = schoolSymbol - studentData["schoolName"] = schoolName + studentData["schoolShort"] = schoolShort + studentData["schoolName"] = schoolCode studentData["currentSemesterEndDate"] = currentSemesterEndDate } dateSemester1Start?.let { @@ -107,7 +193,6 @@ class VulcanFirstLogin(val data: DataVulcan, val onSuccess: () -> Unit) { profileList.add(profile) } - EventBus.getDefault().post(FirstLoginFinishedEvent(profileList, data.loginStore)) onSuccess() } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/login/CufsCertificate.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/login/CufsCertificate.kt new file mode 100644 index 00000000..45fc96d2 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/login/CufsCertificate.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-17. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.login + +import pl.droidsonroids.jspoon.annotation.Selector + +class CufsCertificate { + @Selector(value = "EndpointReference Address") + var targetUrl: String = "" + + @Selector(value = "Lifetime Created") + var createdDate: String = "" + + @Selector(value = "Lifetime Expires") + var expiryDate: String = "" + + @Selector(value = "Attribute[AttributeName=UserInstance] AttributeValue") + var userInstances: List = listOf() + + var xml = "" +} 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 4c11caf3..45c0153d 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 @@ -6,6 +6,7 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.login import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_VULCAN_API +import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_VULCAN_WEB_MAIN import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan import pl.szczodrzynski.edziennik.utils.Utils @@ -45,6 +46,10 @@ class VulcanLogin(val data: DataVulcan, val onSuccess: () -> Unit) { } Utils.d(TAG, "Using login method $loginMethodId") when (loginMethodId) { + LOGIN_METHOD_VULCAN_WEB_MAIN -> { + data.startProgress(R.string.edziennik_progress_login_vulcan_web_main) + VulcanLoginWebMain(data) { onSuccess(loginMethodId) } + } LOGIN_METHOD_VULCAN_API -> { data.startProgress(R.string.edziennik_progress_login_vulcan_api) VulcanLoginApi(data) { onSuccess(loginMethodId) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/login/VulcanLoginApi.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/login/VulcanLoginApi.kt index 4385b18a..0cf1c895 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/login/VulcanLoginApi.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/login/VulcanLoginApi.kt @@ -10,14 +10,11 @@ import im.wangchao.mhttp.Request import im.wangchao.mhttp.Response import im.wangchao.mhttp.callback.JsonCallbackHandler import io.github.wulkanowy.signer.android.getPrivateKeyFromCert -import pl.szczodrzynski.edziennik.currentTimeUnix +import pl.szczodrzynski.edziennik.* import pl.szczodrzynski.edziennik.data.api.* import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.api.VulcanApiUpdateSemester import pl.szczodrzynski.edziennik.data.api.models.ApiError -import pl.szczodrzynski.edziennik.getJsonObject -import pl.szczodrzynski.edziennik.getString -import pl.szczodrzynski.edziennik.isNotNullNorEmpty import pl.szczodrzynski.edziennik.utils.Utils.d import java.net.HttpURLConnection.HTTP_BAD_REQUEST import java.util.* @@ -29,28 +26,19 @@ class VulcanLoginApi(val data: DataVulcan, val onSuccess: () -> Unit) { } init { run { + if (data.studentSemesterNumber == 1 && data.semester1Id == 0) + data.semester1Id = data.studentSemesterNumber + if (data.studentSemesterNumber == 2 && data.semester2Id == 0) + data.semester2Id = data.studentSemesterNumber + + copyFromLoginStore() + if (data.profile != null && data.isApiLoginValid()) { onSuccess() } else { - // < v4.0 - PFX to Private Key migration - if (data.apiCertificatePfx.isNotNullNorEmpty()) { - try { - data.apiCertificatePrivate = getPrivateKeyFromCert( - if (data.apiToken?.get(0) == 'F') VULCAN_API_PASSWORD_FAKELOG else VULCAN_API_PASSWORD, - data.apiCertificatePfx ?: "" - ) - data.loginStore.removeLoginData("certificatePfx") - } catch (e: Throwable) { - e.printStackTrace() - } finally { - onSuccess() - return@run - } - } - - if (data.apiCertificateKey.isNotNullNorEmpty() - && data.apiCertificatePrivate.isNotNullNorEmpty() + if (data.apiFingerprint[data.symbol].isNotNullNorEmpty() + && data.apiPrivateKey[data.symbol].isNotNullNorEmpty() && data.symbol.isNotNullNorEmpty()) { // (see data.isApiLoginValid()) // the semester end date is over @@ -58,7 +46,7 @@ class VulcanLoginApi(val data: DataVulcan, val onSuccess: () -> Unit) { return@run } - if (data.symbol.isNotNullNorEmpty() && data.apiToken.isNotNullNorEmpty() && data.apiPin.isNotNullNorEmpty()) { + if (data.symbol.isNotNullNorEmpty() && data.apiToken[data.symbol].isNotNullNorEmpty() && data.apiPin[data.symbol].isNotNullNorEmpty()) { loginWithToken() } else { @@ -67,6 +55,64 @@ class VulcanLoginApi(val data: DataVulcan, val onSuccess: () -> Unit) { } }} + private fun copyFromLoginStore() { + data.loginStore.data.apply { + // < v4.0 - PFX to Private Key migration + if (has("certificatePfx")) { + try { + val privateKey = getPrivateKeyFromCert( + if (data.apiToken[data.symbol]?.get(0) == 'F') VULCAN_API_PASSWORD_FAKELOG else VULCAN_API_PASSWORD, + getString("certificatePfx") ?: "" + ) + data.apiPrivateKey = mapOf( + data.symbol to privateKey + ) + remove("certificatePfx") + } catch (e: Throwable) { + e.printStackTrace() + } + } + + // 4.0 - new login form - copy user input to profile + if (has("symbol")) { + data.symbol = getString("symbol") + remove("symbol") + } + + // 4.0 - before Vulcan Web impl - migrate from strings to Map of Symbol to String + if (has("deviceSymbol")) { + data.symbol = getString("deviceSymbol") + remove("deviceSymbol") + } + if (has("certificateKey")) { + data.apiFingerprint = data.apiFingerprint.toMutableMap().also { + it[data.symbol] = getString("certificateKey") + } + remove("certificateKey") + } + if (has("certificatePrivate")) { + data.apiPrivateKey = data.apiPrivateKey.toMutableMap().also { + it[data.symbol] = getString("certificatePrivate") + } + remove("certificatePrivate") + } + + // map form inputs to the symbol + if (has("deviceToken")) { + data.apiToken = data.apiToken.toMutableMap().also { + it[data.symbol] = getString("deviceToken") + } + remove("deviceToken") + } + if (has("devicePin")) { + data.apiPin = data.apiPin.toMutableMap().also { + it[data.symbol] = getString("devicePin") + } + remove("devicePin") + } + } + } + private fun loginWithToken() { d(TAG, "Request: Vulcan/Login/Api - ${data.apiUrl}/$VULCAN_API_ENDPOINT_CERTIFICATE") @@ -118,14 +164,22 @@ class VulcanLoginApi(val data: DataVulcan, val onSuccess: () -> Unit) { return } - data.apiCertificateKey = cert.getString("CertyfikatKlucz") - data.apiToken = data.apiToken?.substring(0, 3) - data.apiCertificatePrivate = getPrivateKeyFromCert( - if (data.apiToken?.get(0) == 'F') VULCAN_API_PASSWORD_FAKELOG else VULCAN_API_PASSWORD, + val privateKey = getPrivateKeyFromCert( + if (data.apiToken[data.symbol]?.get(0) == 'F') VULCAN_API_PASSWORD_FAKELOG else VULCAN_API_PASSWORD, cert.getString("CertyfikatPfx") ?: "" ) + + data.apiFingerprint = data.apiFingerprint.toMutableMap().also { + it[data.symbol] = cert.getString("CertyfikatKlucz") + } + data.apiToken = data.apiToken.toMutableMap().also { + it[data.symbol] = it[data.symbol]?.substring(0, 3) + } + data.apiPrivateKey = data.apiPrivateKey.toMutableMap().also { + it[data.symbol] = privateKey + } data.loginStore.removeLoginData("certificatePfx") - data.loginStore.removeLoginData("devicePin") + data.loginStore.removeLoginData("apiPin") onSuccess() } @@ -136,14 +190,26 @@ class VulcanLoginApi(val data: DataVulcan, val onSuccess: () -> Unit) { } } + val deviceId = data.app.deviceId.padStart(16, '0') + val loginStoreId = data.loginStore.id.toString(16).padStart(4, '0') + val symbol = data.symbol?.crc16()?.toString(16)?.take(2) ?: "00" + val uuid = + deviceId.substring(0..7) + + "-" + deviceId.substring(8..11) + + "-" + deviceId.substring(12..15) + + "-" + loginStoreId + + "-" + symbol + "6f72616e7a" + + val deviceNameSuffix = " - nie usuwać" + Request.builder() .url("${data.apiUrl}$VULCAN_API_ENDPOINT_CERTIFICATE") .userAgent(VULCAN_API_USER_AGENT) .addHeader("RequestMobileType", "RegisterDevice") - .addParameter("PIN", data.apiPin) - .addParameter("TokenKey", data.apiToken) - .addParameter("DeviceId", UUID.randomUUID().toString()) - .addParameter("DeviceName", VULCAN_API_DEVICE_NAME) + .addParameter("PIN", data.apiPin[data.symbol]) + .addParameter("TokenKey", data.apiToken[data.symbol]) + .addParameter("DeviceId", uuid) + .addParameter("DeviceName", VULCAN_API_DEVICE_NAME.take(50 - deviceNameSuffix.length) + deviceNameSuffix) .addParameter("DeviceNameUser", "") .addParameter("DeviceDescription", "") .addParameter("DeviceSystemType", "Android") diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/login/VulcanLoginWebMain.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/login/VulcanLoginWebMain.kt new file mode 100644 index 00000000..467beea3 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/login/VulcanLoginWebMain.kt @@ -0,0 +1,133 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-16. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.login + +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.data.api.* +import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan +import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanWebMain +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.getString +import pl.szczodrzynski.edziennik.isNotNullNorEmpty +import pl.szczodrzynski.edziennik.utils.models.Date +import pl.szczodrzynski.fslogin.FSLogin +import pl.szczodrzynski.fslogin.realm.CufsRealm + +class VulcanLoginWebMain(val data: DataVulcan, val onSuccess: () -> Unit) { + companion object { + private const val TAG = "VulcanLoginWebMain" + } + + private val web by lazy { VulcanWebMain(data, null) } + + init { run { + copyFromLoginStore() + + if (data.profile != null && data.isWebMainLoginValid()) { + onSuccess() + } + else { + if (data.symbol.isNotNullNorEmpty() + && data.webType.isNotNullNorEmpty() + && data.webHost.isNotNullNorEmpty() + && (data.webEmail.isNotNullNorEmpty() || data.webUsername.isNotNullNorEmpty()) + && data.webPassword.isNotNullNorEmpty()) { + try { + val success = loginWithCredentials() + if (!success) + data.error(ApiError(TAG, ERROR_VULCAN_WEB_DATA_MISSING)) + } catch (e: Exception) { + data.error(ApiError(TAG, EXCEPTION_VULCAN_WEB_LOGIN) + .withThrowable(e)) + } + } + else { + data.error(ApiError(TAG, ERROR_LOGIN_DATA_MISSING)) + } + } + }} + + private fun copyFromLoginStore() { + data.loginStore.data.apply { + // 4.0 - new login form - copy user input to profile + if (has("symbol")) { + data.symbol = getString("symbol") + remove("symbol") + } + } + } + + private fun loginWithCredentials(): Boolean { + val realm = when (data.webType) { + "cufs" -> CufsRealm( + host = data.webHost ?: return false, + symbol = data.symbol ?: "default", + httpCufs = data.webIsHttpCufs + ) + "adfs" -> CufsRealm( + host = data.webHost ?: return false, + symbol = data.symbol ?: "default", + httpCufs = data.webIsHttpCufs + ).toAdfsRealm(id = data.webAdfsId ?: return false) + "adfslight" -> CufsRealm( + host = data.webHost ?: return false, + symbol = data.symbol ?: "default", + httpCufs = data.webIsHttpCufs + ).toAdfsLightRealm( + id = data.webAdfsId ?: return false, + domain = data.webAdfsDomain ?: "adfslight", + isScoped = data.webIsScopedAdfs + ) + else -> return false + } + + val certificate = web.readCertificate()?.let { web.parseCertificate(it) } + if (certificate != null && Date.fromIso(certificate.expiryDate) > System.currentTimeMillis()) { + useCertificate(certificate) + return true + } + + val fsLogin = FSLogin(data.app.http, debug = App.debugMode) + fsLogin.performLogin( + realm = realm, + username = data.webUsername ?: data.webEmail ?: return false, + password = data.webPassword ?: return false, + onSuccess = { fsCertificate -> + web.saveCertificate(fsCertificate.wresult) + useCertificate(web.parseCertificate(fsCertificate.wresult)) + }, + onFailure = { errorText -> + // TODO + data.error(ApiError(TAG, 0).withThrowable(RuntimeException(errorText))) + } + ) + + return true + } + + private fun useCertificate(certificate: CufsCertificate) { + // auto-post certificate when not first login + if (data.profile != null && data.symbol != null && data.symbol != "default") { + val result = web.postCertificate(certificate, data.symbol ?: "default") { _, state -> + when (state) { + VulcanWebMain.STATE_SUCCESS -> { + web.getStartPage { _, _ -> onSuccess() } + } + VulcanWebMain.STATE_NO_REGISTER -> data.error(ApiError(TAG, ERROR_VULCAN_WEB_NO_REGISTER)) + VulcanWebMain.STATE_LOGGED_OUT -> data.error(ApiError(TAG, ERROR_VULCAN_WEB_LOGGED_OUT)) + } + } + // postCertificate returns false if the cert is not valid anymore + if (!result) { + data.error(ApiError(TAG, ERROR_VULCAN_WEB_CERTIFICATE_EXPIRED) + .withApiResponse(certificate.xml)) + } + } + else { + // first login - succeed immediately + onSuccess() + } + } +} 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 2f0c88af..6bd9aa83 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 @@ -88,6 +88,7 @@ abstract class Data(val app: App, val profile: Profile?, val loginStore: LoginSt var teacherOnConflictStrategy = OnConflictStrategy.IGNORE var eventListReplace = false var messageListReplace = false + var announcementListReplace = false val classrooms = LongSparseArray() val attendanceTypes = LongSparseArray() @@ -120,7 +121,6 @@ abstract class Data(val app: App, val profile: Profile?, val loginStore: LoginSt val attendanceList = mutableListOf() val announcementList = mutableListOf() - val announcementIgnoreList = mutableListOf() val luckyNumberList = mutableListOf() @@ -178,7 +178,6 @@ abstract class Data(val app: App, val profile: Profile?, val loginStore: LoginSt noticeList.clear() attendanceList.clear() announcementList.clear() - announcementIgnoreList.clear() luckyNumberList.clear() teacherAbsenceList.clear() messageList.clear() @@ -202,7 +201,7 @@ abstract class Data(val app: App, val profile: Profile?, val loginStore: LoginSt " - ", profile.studentClassName, "${profile.studentSchoolYearStart}/${profile.studentSchoolYearStart + 1}" - ) + " " + app.getString(if (profile.isParent) R.string.login_summary_account_parent else R.string.login_summary_account_child) + ) + " " + app.getString(if (profile.isParent) R.string.account_type_parent else R.string.account_type_child) db.profileDao().add(profile) db.loginStoreDao().add(loginStore) @@ -284,39 +283,19 @@ abstract class Data(val app: App, val profile: Profile?, val loginStore: LoginSt d("Metadata saved in ${System.currentTimeMillis()-startTime} ms") startTime = System.currentTimeMillis() - if (lessonList.isNotEmpty()) { - db.timetableDao() += lessonList - } - if (gradeList.isNotEmpty()) { - db.gradeDao().addAll(gradeList) - } - if (eventList.isNotEmpty()) { - if (eventListReplace) - db.eventDao().replaceAll(eventList) - else - db.eventDao().upsertAll(eventList, removeNotKept = true) - } + db.timetableDao().putAll(lessonList, removeNotKept = true) + db.gradeDao().putAll(gradeList, removeNotKept = true) + db.eventDao().putAll(eventList, forceReplace = eventListReplace, removeNotKept = true) if (noticeList.isNotEmpty()) { db.noticeDao().clear(profile.id) - db.noticeDao().addAll(noticeList) + db.noticeDao().putAll(noticeList) } - if (attendanceList.isNotEmpty()) - db.attendanceDao().addAll(attendanceList) - if (announcementList.isNotEmpty()) - db.announcementDao().addAll(announcementList) - if (announcementIgnoreList.isNotEmpty()) - db.announcementDao().addAllIgnore(announcementIgnoreList) - if (luckyNumberList.isNotEmpty()) - db.luckyNumberDao().addAll(luckyNumberList) - if (teacherAbsenceList.isNotEmpty()) - db.teacherAbsenceDao().addAll(teacherAbsenceList) + db.attendanceDao().putAll(attendanceList, removeNotKept = true) + db.announcementDao().putAll(announcementList, forceReplace = announcementListReplace, removeNotKept = false) + db.luckyNumberDao().putAll(luckyNumberList) + db.teacherAbsenceDao().putAll(teacherAbsenceList) - if (messageList.isNotEmpty()) { - if (messageListReplace) - db.messageDao().replaceAll(messageList) - else - db.messageDao().upsertAll(messageList, removeNotKept = false) // TODO dataRemoveModel for messages - } + db.messageDao().putAll(messageList, forceReplace = messageListReplace, removeNotKept = false) if (messageRecipientList.isNotEmpty()) db.messageRecipientDao().addAll(messageRecipientList) if (messageRecipientIgnoreList.isNotEmpty()) @@ -357,7 +336,7 @@ abstract class Data(val app: App, val profile: Profile?, val loginStore: LoginSt } fun shouldSyncLuckyNumber(): Boolean { - return (db.luckyNumberDao().getNearestFutureNow(profileId, Date.getToday().value) ?: -1) == -1 + return db.luckyNumberDao().getNearestFutureNow(profileId, Date.getToday()) == null } /*fun error(tag: String, errorCode: Int, response: Response? = null, throwable: Throwable? = null, apiResponse: JsonObject? = null) { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/models/DataRemoveModel.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/models/DataRemoveModel.kt index f247ee19..49b57999 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/models/DataRemoveModel.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/models/DataRemoveModel.kt @@ -20,10 +20,10 @@ open class DataRemoveModel { fun commit(profileId: Int, dao: TimetableDao) { if (dateFrom != null && dateTo != null) { - dao.clearBetweenDates(profileId, dateFrom, dateTo) + dao.dontKeepBetweenDates(profileId, dateFrom, dateTo) } else { - dateFrom?.let { dateFrom -> dao.clearFromDate(profileId, dateFrom) } - dateTo?.let { dateTo -> dao.clearToDate(profileId, dateTo) } + dateFrom?.let { dateFrom -> dao.dontKeepFromDate(profileId, dateFrom) } + dateTo?.let { dateTo -> dao.dontKeepToDate(profileId, dateTo) } } } } @@ -38,12 +38,12 @@ open class DataRemoveModel { fun commit(profileId: Int, dao: GradeDao) { if (all) { - if (type != null) dao.clearWithType(profileId, type) + if (type != null) dao.dontKeepWithType(profileId, type) else dao.clear(profileId) } semester?.let { - if (type != null) dao.clearForSemesterWithType(profileId, it, type) - else dao.clearForSemester(profileId, it) + if (type != null) dao.dontKeepForSemesterWithType(profileId, it, type) + else dao.dontKeepForSemester(profileId, it) } } } @@ -53,12 +53,15 @@ open class DataRemoveModel { fun futureExceptType(exceptType: Long) = Events(null, exceptType, null) fun futureExceptTypes(exceptTypes: List) = Events(null, null, exceptTypes) fun futureWithType(type: Long) = Events(type, null, null) + fun future() = Events(null, null, null) } fun commit(profileId: Int, dao: EventDao) { type?.let { dao.dontKeepFutureWithType(profileId, Date.getToday(), it) } exceptType?.let { dao.dontKeepFutureExceptType(profileId, Date.getToday(), it) } exceptTypes?.let { dao.dontKeepFutureExceptTypes(profileId, Date.getToday(), it) } + if (type == null && exceptType == null && exceptTypes == null) + dao.dontKeepFuture(profileId, Date.getToday()) } } @@ -69,7 +72,7 @@ open class DataRemoveModel { fun commit(profileId: Int, dao: AttendanceDao) { if (dateFrom != null) { - dao.clearAfterDate(profileId, dateFrom) + dao.dontKeepAfterDate(profileId, dateFrom) } } } 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 bdd14f33..ae82ff8f 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 @@ -27,6 +27,7 @@ import pl.szczodrzynski.edziennik.data.db.entity.Profile import pl.szczodrzynski.edziennik.data.db.full.EventFull import pl.szczodrzynski.edziennik.ui.modules.error.ErrorDetailsDialog import pl.szczodrzynski.edziennik.ui.modules.error.ErrorSnackbar +import pl.szczodrzynski.edziennik.ui.modules.login.LoginInfo import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Time import retrofit2.Response @@ -73,7 +74,7 @@ class SzkolnyApi(val app: App) : CoroutineScope { suspend inline fun runCatching(errorSnackbar: ErrorSnackbar, crossinline block: SzkolnyApi.() -> T?): T? { return try { - withContext(Dispatchers.Default) { block() } + withContext(Dispatchers.Default) { block.invoke(this@SzkolnyApi) } } catch (e: Exception) { errorSnackbar.addError(e.toApiError(TAG)).show() @@ -82,7 +83,7 @@ class SzkolnyApi(val app: App) : CoroutineScope { } suspend inline fun runCatching(activity: AppCompatActivity, crossinline block: SzkolnyApi.() -> T?): T? { return try { - withContext(Dispatchers.Default) { block() } + withContext(Dispatchers.Default) { block.invoke(this@SzkolnyApi) } } catch (e: Exception) { ErrorDetailsDialog( @@ -95,7 +96,7 @@ class SzkolnyApi(val app: App) : CoroutineScope { } inline fun runCatching(block: SzkolnyApi.() -> T, onError: (e: Throwable) -> Unit): T? { return try { - block() + block.invoke(this@SzkolnyApi) } catch (e: Exception) { onError(e) @@ -327,4 +328,11 @@ class SzkolnyApi(val app: App) : CoroutineScope { return parseResponse(response).message } + + @Throws(Exception::class) + fun getPlatforms(registerName: String): List { + val response = api.appLoginPlatforms(registerName).execute() + + return parseResponse(response) + } } 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 0e3dd574..bce3aef0 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 @@ -6,11 +6,9 @@ package pl.szczodrzynski.edziennik.data.api.szkolny import pl.szczodrzynski.edziennik.data.api.szkolny.request.* import pl.szczodrzynski.edziennik.data.api.szkolny.response.* +import pl.szczodrzynski.edziennik.ui.modules.login.LoginInfo import retrofit2.Call -import retrofit2.http.Body -import retrofit2.http.GET -import retrofit2.http.POST -import retrofit2.http.Query +import retrofit2.http.* interface SzkolnyService { @@ -34,4 +32,7 @@ interface SzkolnyService { @POST("feedbackMessage") fun feedbackMessage(@Body request: FeedbackMessageRequest): Call> + + @GET("appLogin/platforms/{registerName}") + fun appLoginPlatforms(@Path("registerName") registerName: String): Call>> } 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 65a3be61..6741a01a 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 @@ -39,13 +39,13 @@ object Signing { val appPassword by lazy { iLoveApple( "ThisIsOurHardWorkPleaseDoNotCopyOrSteal(c)2019.KubaSz".sha256(), - BuildConfig.VERSION_NAME, + BuildConfig.VERSION_NAME.substringBeforeLast('+'), BuildConfig.VERSION_CODE.toLong() ) } /*fun provideKey(param1: String, param2: Long): ByteArray {*/ fun pleaseStopRightNow(param1: String, param2: Long): ByteArray { - return "$param1.MTIzNDU2Nzg5MDP/4SAI6B===.$param2".sha256() + return "$param1.MTIzNDU2Nzg5MDCEfzNqNH===.$param2".sha256() } } 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 63d4ec88..c10bab3e 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 @@ -40,8 +40,7 @@ class AppSync(val app: App, val notifications: MutableList, val pr Metadata.TYPE_EVENT, event.id, isPast || markAsSeen || event.seen, - isPast || markAsSeen || event.notified, - event.addedDate + isPast || markAsSeen || event.notified ) }) return app.db.eventDao().upsertAll(events).size 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 39b68499..21a5c112 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 @@ -53,7 +53,7 @@ class Notifications(val app: App, val notifications: MutableList, profileId = lesson.profileId, profileName = profiles.singleOrNull { it.id == lesson.profileId }?.name, viewId = MainActivity.DRAWER_ITEM_TIMETABLE, - addedDate = lesson.addedDate + addedDate = System.currentTimeMillis() ).addExtra("timetableDate", lesson.displayDate?.stringY_m_d ?: "") } } @@ -117,7 +117,7 @@ class Notifications(val app: App, val notifications: MutableList, } private fun gradeNotifications() { - for (grade in app.db.gradeDao().notNotifiedNow) { + for (grade in app.db.gradeDao().getNotNotifiedNow()) { val gradeName = when (grade.type) { Grade.TYPE_SEMESTER1_PROPOSED, Grade.TYPE_SEMESTER2_PROPOSED -> app.getString(R.string.grade_semester_proposed_format_2, grade.name) Grade.TYPE_SEMESTER1_FINAL, Grade.TYPE_SEMESTER2_FINAL -> app.getString(R.string.grade_semester_final_format_2, grade.name) @@ -144,7 +144,7 @@ class Notifications(val app: App, val notifications: MutableList, } private fun behaviourNotifications() { - for (notice in app.db.noticeDao().notNotifiedNow) { + for (notice in app.db.noticeDao().getNotNotifiedNow()) { val noticeTypeStr = when (notice.type) { Notice.TYPE_POSITIVE -> app.getString(R.string.notification_notice_praise) @@ -155,7 +155,7 @@ class Notifications(val app: App, val notifications: MutableList, val text = app.getString( R.string.notification_notice_format, noticeTypeStr, - notice.teacherFullName, + notice.teacherName, Date.fromMillis(notice.addedDate).formattedString ) notifications += Notification( @@ -172,9 +172,9 @@ class Notifications(val app: App, val notifications: MutableList, } private fun attendanceNotifications() { - for (attendance in app.db.attendanceDao().notNotifiedNow) { + for (attendance in app.db.attendanceDao().getNotNotifiedNow()) { - val attendanceTypeStr = when (attendance.type) { + val attendanceTypeStr = when (attendance.baseType) { Attendance.TYPE_ABSENT -> app.getString(R.string.notification_absence) Attendance.TYPE_ABSENT_EXCUSED -> app.getString(R.string.notification_absence_excused) Attendance.TYPE_BELATED -> app.getString(R.string.notification_belated) @@ -191,7 +191,7 @@ class Notifications(val app: App, val notifications: MutableList, R.string.notification_attendance_format, attendanceTypeStr, attendance.subjectLongName, - attendance.lessonDate.formattedString + attendance.date.formattedString ) notifications += Notification( id = Notification.buildId(attendance.profileId, Notification.TYPE_NEW_ATTENDANCE, attendance.id), @@ -207,10 +207,10 @@ class Notifications(val app: App, val notifications: MutableList, } private fun announcementNotifications() { - for (announcement in app.db.announcementDao().notNotifiedNow) { + for (announcement in app.db.announcementDao().getNotNotifiedNow()) { val text = app.getString( R.string.notification_announcement_format, - announcement.teacherFullName, + announcement.teacherName, announcement.subject ) notifications += Notification( @@ -247,9 +247,9 @@ class Notifications(val app: App, val notifications: MutableList, } private fun luckyNumberNotifications() { - val luckyNumbers = app.db.luckyNumberDao().notNotifiedNow - luckyNumbers?.removeAll { it.date < today } - luckyNumbers?.forEach { luckyNumber -> + val luckyNumbers = app.db.luckyNumberDao().getNotNotifiedNow().toMutableList() + luckyNumbers.removeAll { it.date < today } + luckyNumbers.forEach { luckyNumber -> val profile = profiles.singleOrNull { it.id == luckyNumber.profileId } ?: return@forEach val text = when (profile.studentNumber != -1 && profile.studentNumber == luckyNumber.number) { true -> when (luckyNumber.date.value) { @@ -271,7 +271,7 @@ class Notifications(val app: App, val notifications: MutableList, profileId = luckyNumber.profileId, profileName = profile.name, viewId = MainActivity.DRAWER_ITEM_HOME, - addedDate = luckyNumber.addedDate + addedDate = System.currentTimeMillis() ) } } @@ -280,7 +280,7 @@ class Notifications(val app: App, val notifications: MutableList, for (teacherAbsence in app.db.teacherAbsenceDao().getNotNotifiedNow()) { val message = app.getString( R.string.notification_teacher_absence_new_format, - teacherAbsence.teacherFullName + teacherAbsence.teacherName ) notifications += Notification( id = Notification.buildId(teacherAbsence.profileId, Notification.TYPE_TEACHER_ABSENCE, teacherAbsence.id), 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 b57cc4d4..333967b0 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 @@ -43,7 +43,7 @@ import pl.szczodrzynski.edziennik.data.db.migration.* LibrusLesson::class, TimetableManual::class, Metadata::class -], version = 85) +], version = 88) @TypeConverters( ConverterTime::class, ConverterDate::class, @@ -170,7 +170,10 @@ abstract class AppDb : RoomDatabase() { Migration82(), Migration83(), Migration84(), - Migration85() + Migration85(), + Migration86(), + Migration87(), + Migration88() ).allowMainThreadQueries().build() } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/AnnouncementDao.java b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/AnnouncementDao.java deleted file mode 100644 index ab32fb7c..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/AnnouncementDao.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (c) Kacper Ziubryniewicz 2020-1-6 - */ - -package pl.szczodrzynski.edziennik.data.db.dao; - -import androidx.lifecycle.LiveData; -import androidx.room.Dao; -import androidx.room.Insert; -import androidx.room.OnConflictStrategy; -import androidx.room.Query; -import androidx.room.RawQuery; -import androidx.sqlite.db.SimpleSQLiteQuery; -import androidx.sqlite.db.SupportSQLiteQuery; - -import java.util.List; - -import pl.szczodrzynski.edziennik.data.db.entity.Announcement; -import pl.szczodrzynski.edziennik.data.db.entity.Metadata; -import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull; - -import static pl.szczodrzynski.edziennik.data.db.entity.Metadata.TYPE_ANNOUNCEMENT; - -@Dao -public abstract class AnnouncementDao { - @Insert(onConflict = OnConflictStrategy.REPLACE) - public abstract long add(Announcement announcement); - - @Insert(onConflict = OnConflictStrategy.REPLACE) - public abstract void addAll(List announcementList); - - @Insert(onConflict = OnConflictStrategy.IGNORE) - public abstract void addAllIgnore(List announcementList); - - @Query("DELETE FROM announcements WHERE profileId = :profileId") - public abstract void clear(int profileId); - - @RawQuery(observedEntities = {Announcement.class, Metadata.class}) - abstract LiveData> getAll(SupportSQLiteQuery query); - public LiveData> getAll(int profileId, String filter) { - return getAll(new SimpleSQLiteQuery("SELECT \n" + - "*, \n" + - "teachers.teacherName || ' ' || teachers.teacherSurname AS teacherFullName\n" + - "FROM announcements \n" + - "LEFT JOIN teachers USING(profileId, teacherId)\n" + - "LEFT JOIN metadata ON announcementId = thingId AND thingType = "+TYPE_ANNOUNCEMENT+" AND metadata.profileId = "+profileId+"\n" + - "WHERE announcements.profileId = "+profileId+" AND "+filter+"\n" + - "ORDER BY addedDate DESC")); - } - public LiveData> getAll(int profileId) { - return getAll(profileId, "1"); - } - public LiveData> getAllWhere(int profileId, String filter) { - return getAll(profileId, filter); - } - - @RawQuery(observedEntities = {Announcement.class, Metadata.class}) - abstract List getAllNow(SupportSQLiteQuery query); - public List getAllNow(int profileId, String filter) { - return getAllNow(new SimpleSQLiteQuery("SELECT \n" + - "*, \n" + - "teachers.teacherName || ' ' || teachers.teacherSurname AS teacherFullName\n" + - "FROM announcements \n" + - "LEFT JOIN teachers USING(profileId, teacherId)\n" + - "LEFT JOIN metadata ON announcementId = thingId AND thingType = "+TYPE_ANNOUNCEMENT+" AND metadata.profileId = "+profileId+"\n" + - "WHERE announcements.profileId = "+profileId+" AND "+filter+"\n" + - "ORDER BY addedDate DESC")); - } - public List getNotNotifiedNow(int profileId) { - return getAllNow(profileId, "notified = 0"); - } - - @Query("SELECT " + - "*, " + - "teachers.teacherName || ' ' || teachers.teacherSurname AS teacherFullName " + - "FROM announcements " + - "LEFT JOIN teachers USING(profileId, teacherId) " + - "LEFT JOIN metadata ON announcementId = thingId AND thingType = "+TYPE_ANNOUNCEMENT+" AND metadata.profileId = announcements.profileId " + - "WHERE notified = 0 " + - "ORDER BY addedDate DESC") - public abstract List getNotNotifiedNow(); -} 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 new file mode 100644 index 00000000..67e034c5 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/AnnouncementDao.kt @@ -0,0 +1,69 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-25. + */ +package pl.szczodrzynski.edziennik.data.db.dao + +import androidx.lifecycle.LiveData +import androidx.room.Dao +import androidx.room.Query +import androidx.room.RawQuery +import androidx.sqlite.db.SupportSQLiteQuery +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.annotation.SelectiveDao +import pl.szczodrzynski.edziennik.annotation.UpdateSelective +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.full.AnnouncementFull + +@Dao +@SelectiveDao(db = AppDb::class) +abstract class AnnouncementDao : BaseDao { + companion object { + private const val QUERY = """ + SELECT + *, + 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 + """ + + private const val ORDER_BY = """ORDER BY addedDate DESC""" + } + + private val selective by lazy { AnnouncementDaoSelective(App.db) } + + @RawQuery(observedEntities = [Announcement::class]) + abstract override fun getRaw(query: SupportSQLiteQuery): LiveData> + @RawQuery(observedEntities = [Announcement::class]) + abstract override fun getOne(query: SupportSQLiteQuery): LiveData + + // SELECTIVE UPDATE + @UpdateSelective(primaryKeys = ["profileId", "announcementId"], skippedColumns = ["addedDate", "announcementText"]) + override fun update(item: Announcement) = selective.update(item) + override fun updateAll(items: List) = selective.updateAll(items) + + // CLEAR + @Query("DELETE FROM announcements WHERE profileId = :profileId") + abstract override fun clear(profileId: Int) + // REMOVE NOT KEPT + @Query("DELETE FROM announcements WHERE keep = 0") + abstract override fun removeNotKept() + + // GET ALL - LIVE DATA + fun getAll(profileId: Int) = + getRaw("$QUERY WHERE announcements.profileId = $profileId $ORDER_BY") + + // GET ALL - NOW + fun getAllNow(profileId: Int) = + getRawNow("$QUERY WHERE announcements.profileId = $profileId $ORDER_BY") + fun getNotNotifiedNow() = + getRawNow("$QUERY WHERE notified = 0 $ORDER_BY") + fun getNotNotifiedNow(profileId: Int) = + getRawNow("$QUERY WHERE announcements.profileId = $profileId AND notified = 0 $ORDER_BY") + + // GET ONE - NOW + fun getByIdNow(profileId: Int, id: Long) = + getOneNow("$QUERY WHERE announcements.profileId = $profileId AND announcementId = $id") +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/AttendanceDao.java b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/AttendanceDao.java deleted file mode 100644 index 95c3147f..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/AttendanceDao.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright (c) Kacper Ziubryniewicz 2020-1-6 - */ - -package pl.szczodrzynski.edziennik.data.db.dao; - -import androidx.lifecycle.LiveData; -import androidx.room.Dao; -import androidx.room.Insert; -import androidx.room.OnConflictStrategy; -import androidx.room.Query; -import androidx.room.RawQuery; -import androidx.sqlite.db.SimpleSQLiteQuery; -import androidx.sqlite.db.SupportSQLiteQuery; - -import java.util.List; - -import pl.szczodrzynski.edziennik.data.db.entity.Attendance; -import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull; -import pl.szczodrzynski.edziennik.utils.models.Date; - -import static pl.szczodrzynski.edziennik.data.db.entity.Metadata.TYPE_ATTENDANCE; - -@Dao -public abstract class AttendanceDao { - @Insert(onConflict = OnConflictStrategy.REPLACE) - public abstract long add(Attendance attendance); - - @Insert(onConflict = OnConflictStrategy.REPLACE) - public abstract void addAll(List attendanceList); - - @Query("DELETE FROM attendances WHERE profileId = :profileId") - public abstract void clear(int profileId); - - @Query("DELETE FROM attendances WHERE profileId = :profileId AND attendanceLessonDate > :date") - public abstract void clearAfterDate(int profileId, Date date); - - @RawQuery(observedEntities = {Attendance.class}) - abstract LiveData> getAll(SupportSQLiteQuery query); - public LiveData> getAll(int profileId, String filter) { - return getAll(new SimpleSQLiteQuery("SELECT \n" + - "*, \n" + - "teachers.teacherName || ' ' || teachers.teacherSurname AS teacherFullName\n" + - "FROM attendances \n" + - "LEFT JOIN teachers USING(profileId, teacherId)\n" + - "LEFT JOIN subjects USING(profileId, subjectId)\n" + - "LEFT JOIN metadata ON attendanceId = thingId AND thingType = " + TYPE_ATTENDANCE + " AND metadata.profileId = "+profileId+"\n" + - "WHERE attendances.profileId = "+profileId+" AND "+filter+"\n" + - "ORDER BY attendanceLessonDate DESC, attendanceStartTime DESC")); - } - public LiveData> getAll(int profileId) { - return getAll(profileId, "1"); - } - public LiveData> getAllWhere(int profileId, String filter) { - return getAll(profileId, filter); - } - - @RawQuery - abstract List getAllNow(SupportSQLiteQuery query); - public List getAllNow(int profileId, String filter) { - return getAllNow(new SimpleSQLiteQuery("SELECT \n" + - "*, \n" + - "teachers.teacherName || ' ' || teachers.teacherSurname AS teacherFullName\n" + - "FROM attendances \n" + - "LEFT JOIN teachers USING(profileId, teacherId)\n" + - "LEFT JOIN subjects USING(profileId, subjectId)\n" + - "LEFT JOIN metadata ON attendanceId = thingId AND thingType = " + TYPE_ATTENDANCE + " AND metadata.profileId = "+profileId+"\n" + - "WHERE attendances.profileId = "+profileId+" AND "+filter+"\n" + - "ORDER BY attendanceLessonDate DESC, attendanceStartTime DESC")); - } - public List getNotNotifiedNow(int profileId) { - return getAllNow(profileId, "notified = 0"); - } - - @Query("SELECT * FROM attendances " + - "LEFT JOIN subjects USING(profileId, subjectId) " + - "LEFT JOIN metadata ON attendanceId = thingId AND thingType = " + TYPE_ATTENDANCE + " AND metadata.profileId = attendances.profileId " + - "WHERE notified = 0 " + - "ORDER BY attendanceLessonDate DESC, attendanceStartTime DESC") - public abstract List getNotNotifiedNow(); - - // only absent and absent_excused count as absences - // all the other types are counted as being present - @Query("SELECT \n" + - "CAST(SUM(CASE WHEN attendanceType != "+Attendance.TYPE_ABSENT+" AND attendanceType != "+ Attendance.TYPE_ABSENT_EXCUSED+" THEN 1 ELSE 0 END) AS float)\n" + - " / \n" + - "CAST(count() AS float)*100 \n" + - "FROM attendances \n" + - "WHERE profileId = :profileId") - public abstract LiveData getAttendancePercentage(int profileId); - - @Query("SELECT \n" + - "CAST(SUM(CASE WHEN attendanceType != "+Attendance.TYPE_ABSENT+" AND attendanceType != "+Attendance.TYPE_ABSENT_EXCUSED+" THEN 1 ELSE 0 END) AS float)\n" + - " / \n" + - "CAST(count() AS float)*100 \n" + - "FROM attendances \n" + - "WHERE profileId = :profileId AND attendanceSemester = :semester") - public abstract float getAttendancePercentageNow(int profileId, int semester); - - @Query("SELECT \n" + - "CAST(SUM(CASE WHEN attendanceType != "+Attendance.TYPE_ABSENT+" AND attendanceType != "+Attendance.TYPE_ABSENT_EXCUSED+" THEN 1 ELSE 0 END) AS float)\n" + - " / \n" + - "CAST(count() AS float)*100 \n" + - "FROM attendances \n" + - "WHERE profileId = :profileId") - public abstract float getAttendancePercentageNow(int profileId); -} - 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 new file mode 100644 index 00000000..97513689 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/AttendanceDao.kt @@ -0,0 +1,74 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-24. + */ +package pl.szczodrzynski.edziennik.data.db.dao + +import androidx.lifecycle.LiveData +import androidx.room.Dao +import androidx.room.Query +import androidx.room.RawQuery +import androidx.sqlite.db.SupportSQLiteQuery +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.annotation.SelectiveDao +import pl.szczodrzynski.edziennik.annotation.UpdateSelective +import pl.szczodrzynski.edziennik.data.db.AppDb +import pl.szczodrzynski.edziennik.data.db.entity.Attendance +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull +import pl.szczodrzynski.edziennik.utils.models.Date + +@Dao +@SelectiveDao(db = AppDb::class) +abstract class AttendanceDao : BaseDao { + companion object { + private const val QUERY = """ + SELECT + *, + teachers.teacherName ||" "|| teachers.teacherSurname AS teacherName + 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 + """ + + private const val ORDER_BY = """ORDER BY attendanceDate DESC, attendanceTime DESC""" + } + + private val selective by lazy { AttendanceDaoSelective(App.db) } + + @RawQuery(observedEntities = [Attendance::class]) + abstract override fun getRaw(query: SupportSQLiteQuery): LiveData> + @RawQuery(observedEntities = [Attendance::class]) + abstract override fun getOne(query: SupportSQLiteQuery): LiveData + + // SELECTIVE UPDATE + @UpdateSelective(primaryKeys = ["profileId", "attendanceId"], skippedColumns = ["addedDate", "announcementText"]) + override fun update(item: Attendance) = selective.update(item) + override fun updateAll(items: List) = selective.updateAll(items) + + // CLEAR + @Query("DELETE FROM attendances WHERE profileId = :profileId") + abstract override fun clear(profileId: Int) + // REMOVE NOT KEPT + @Query("DELETE FROM attendances WHERE keep = 0") + abstract override fun removeNotKept() + + // GET ALL - LIVE DATA + fun getAll(profileId: Int) = + getRaw("$QUERY WHERE attendances.profileId = $profileId $ORDER_BY") + + // GET ALL - NOW + fun getAllNow(profileId: Int) = + getRawNow("$QUERY WHERE attendances.profileId = $profileId $ORDER_BY") + fun getNotNotifiedNow() = + getRawNow("$QUERY WHERE notified = 0 $ORDER_BY") + fun getNotNotifiedNow(profileId: Int) = + getRawNow("$QUERY WHERE attendances.profileId = $profileId AND notified = 0 $ORDER_BY") + + // GET ONE - NOW + fun getByIdNow(profileId: Int, id: Long) = + getOneNow("$QUERY WHERE attendances.profileId = $profileId AND attendanceId = $id") + + @Query("UPDATE attendances SET keep = 0 WHERE profileId = :profileId AND attendanceDate >= :date") + abstract fun dontKeepAfterDate(profileId: Int, date: Date?) +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/BaseDao.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/BaseDao.kt index 8c13dca9..0ae961db 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/BaseDao.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/BaseDao.kt @@ -16,13 +16,15 @@ interface BaseDao { fun getRaw(query: SupportSQLiteQuery): LiveData> fun getRaw(query: String) = getRaw(SimpleSQLiteQuery(query)) @RawQuery + fun getOne(query: SupportSQLiteQuery): LiveData + fun getOne(query: String) = getOne(SimpleSQLiteQuery(query)) + @RawQuery fun getRawNow(query: SupportSQLiteQuery): List fun getRawNow(query: String) = getRawNow(SimpleSQLiteQuery(query)) @RawQuery fun getOneNow(query: SupportSQLiteQuery): F? fun getOneNow(query: String) = getOneNow(SimpleSQLiteQuery(query)) - @Query("DELETE FROM events WHERE keep = 0") fun removeNotKept() /** @@ -41,12 +43,14 @@ interface BaseDao { /** * REPLACE an [item] in the database, * removing any conflicting rows. + * Creates the item if it does not exist yet. */ @Insert(onConflict = OnConflictStrategy.REPLACE) fun replace(item: T) /** * REPLACE [items] in the database, * removing any conflicting rows. + * Creates items if it does not exist yet. */ @Insert(onConflict = OnConflictStrategy.REPLACE) fun replaceAll(items: List) @@ -97,4 +101,23 @@ interface BaseDao { if (removeNotKept) removeNotKept() return insertResult } + + /** + * Make sure that [items] are in the database. + * When [forceReplace] == false, do a selective update (UPSERT). + * When [forceReplace] == true, add all items replacing any conflicting ones (REPLACE). + * + * @param forceReplace whether to replace all items instead of selectively updating + * @param removeNotKept whether to remove all items whose [keep] parameter is false + */ + fun putAll(items: List, forceReplace: Boolean = false, removeNotKept: Boolean = false) { + if (items.isEmpty()) + return + if (forceReplace) + replaceAll(items) + else + upsertAll(items, removeNotKept = false) + + if (removeNotKept) removeNotKept() + } } 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 b2017cc5..a532602b 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 @@ -31,8 +31,8 @@ abstract class EventDao : BaseDao { eventTypes.eventTypeName AS typeName, eventTypes.eventTypeColor AS typeColor FROM events - LEFT JOIN subjects USING(profileId, subjectId) LEFT JOIN teachers USING(profileId, teacherId) + 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 @@ -47,6 +47,8 @@ abstract class EventDao : BaseDao { @RawQuery(observedEntities = [Event::class]) abstract override fun getRaw(query: SupportSQLiteQuery): LiveData> + @RawQuery(observedEntities = [Event::class]) + abstract override fun getOne(query: SupportSQLiteQuery): LiveData // SELECTIVE UPDATE @UpdateSelective(primaryKeys = ["profileId", "eventId"], skippedColumns = ["eventIsDone", "eventBlacklisted", "homeworkBody", "attachmentIds", "attachmentNames"]) @@ -56,6 +58,9 @@ abstract class EventDao : BaseDao { // CLEAR @Query("DELETE FROM events WHERE profileId = :profileId") abstract override fun clear(profileId: Int) + // REMOVE NOT KEPT + @Query("DELETE FROM events WHERE keep = 0") + abstract override fun removeNotKept() // GET ALL - LIVE DATA fun getAll(profileId: Int) = @@ -109,6 +114,9 @@ abstract class EventDao : BaseDao { " AND " + filter)) } + @Query("UPDATE events SET keep = 0 WHERE profileId = :profileId AND eventAddedManually = 0 AND eventDate >= :todayDate") + abstract fun dontKeepFuture(profileId: Int, todayDate: Date) + @Query("UPDATE events SET keep = 0 WHERE profileId = :profileId AND eventAddedManually = 0 AND eventDate >= :todayDate AND eventType = :type") abstract fun dontKeepFutureWithType(profileId: Int, todayDate: Date, type: Long) 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 9c3e93c5..4fcfc721 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 @@ -1,102 +1,103 @@ /* - * Copyright (c) Kacper Ziubryniewicz 2020-1-6 + * Copyright (c) Kuba Szczodrzyński 2020-4-24. */ package pl.szczodrzynski.edziennik.data.db.dao import androidx.lifecycle.LiveData -import androidx.room.* -import androidx.sqlite.db.SimpleSQLiteQuery +import androidx.room.Dao +import androidx.room.Query +import androidx.room.RawQuery +import androidx.room.Transaction import androidx.sqlite.db.SupportSQLiteQuery +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.annotation.SelectiveDao +import pl.szczodrzynski.edziennik.annotation.UpdateSelective +import pl.szczodrzynski.edziennik.data.db.AppDb import pl.szczodrzynski.edziennik.data.db.entity.Grade import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.data.db.full.GradeFull +import pl.szczodrzynski.edziennik.utils.models.Date import java.util.* -import kotlin.collections.List import kotlin.collections.component1 import kotlin.collections.component2 -import kotlin.collections.iterator import kotlin.collections.set @Dao -abstract class GradeDao { - @Insert(onConflict = OnConflictStrategy.REPLACE) - abstract fun add(grade: Grade): Long +@SelectiveDao(db = AppDb::class) +abstract class GradeDao : BaseDao { + companion object { + private const val QUERY = """ + SELECT + *, + teachers.teacherName ||" "|| teachers.teacherSurname AS teacherName + 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 + """ - @Insert(onConflict = OnConflictStrategy.REPLACE) - abstract fun addAll(gradeList: List) + private const val ORDER_BY = """ORDER BY addedDate DESC""" + } - @Query("DELETE FROM grades WHERE profileId = :profileId") - abstract fun clear(profileId: Int) - - @Query("DELETE FROM grades WHERE profileId = :profileId AND gradeType = :type") - abstract fun clearWithType(profileId: Int, type: Int) - - @Query("DELETE FROM grades WHERE profileId = :profileId AND gradeSemester = :semester") - abstract fun clearForSemester(profileId: Int, semester: Int) - - @Query("DELETE FROM grades WHERE profileId = :profileId AND gradeSemester = :semester AND gradeType = :type") - abstract fun clearForSemesterWithType(profileId: Int, semester: Int, type: Int) + private val selective by lazy { GradeDaoSelective(App.db) } @RawQuery(observedEntities = [Grade::class]) - abstract fun getAll(query: SupportSQLiteQuery?): LiveData> + abstract override fun getRaw(query: SupportSQLiteQuery): LiveData> + @RawQuery(observedEntities = [Grade::class]) + abstract override fun getOne(query: SupportSQLiteQuery): LiveData - fun getAll(profileId: Int, filter: String, orderBy: String): LiveData> { - return getAll(SimpleSQLiteQuery("SELECT \n" + - "*, \n" + - "teachers.teacherName || ' ' || teachers.teacherSurname AS teacherFullName\n" + - "FROM grades \n" + - "LEFT JOIN subjects USING(profileId, subjectId)\n" + - "LEFT JOIN teachers USING(profileId, teacherId)\n" + - "LEFT JOIN metadata ON gradeId = thingId AND thingType = " + Metadata.TYPE_GRADE + " AND metadata.profileId = " + profileId + "\n" + - "WHERE grades.profileId = " + profileId + " AND " + filter + "\n" + - "ORDER BY " + orderBy)) // TODO: 2019-04-30 why did I add sorting by gradeType??? - } + // SELECTIVE UPDATE + @UpdateSelective(primaryKeys = ["profileId", "gradeId"], skippedColumns = ["addedDate", "gradeClassAverage"]) + override fun update(item: Grade) = selective.update(item) + override fun updateAll(items: List) = selective.updateAll(items) - fun getAllOrderBy(profileId: Int, orderBy: String): LiveData> { - return getAll(profileId, "1", orderBy) - } + // CLEAR + @Query("DELETE FROM grades WHERE profileId = :profileId") + abstract override fun clear(profileId: Int) + // REMOVE NOT KEPT + @Query("DELETE FROM grades WHERE keep = 0") + abstract override fun removeNotKept() - fun getAllWhere(profileId: Int, filter: String): LiveData> { - return getAll(profileId, filter, "addedDate DESC") - } + // GET ALL - LIVE DATA + fun getAll(profileId: Int) = + getRaw("$QUERY WHERE grades.profileId = $profileId $ORDER_BY") + fun getAllFromDate(profileId: Int, date: Date) = + getRaw("$QUERY WHERE grades.profileId = $profileId AND addedDate > ${date.inMillis} $ORDER_BY") + fun getAllBySubject(profileId: Int, subjectId: Long) = + getRaw("$QUERY WHERE grades.profileId = $profileId AND subjectId = $subjectId $ORDER_BY") + fun getAllOrderBy(profileId: Int, orderBy: String) = + getRaw("$QUERY WHERE grades.profileId = $profileId ORDER BY $orderBy") - @RawQuery - abstract fun getAllNow(query: SupportSQLiteQuery?): List + // GET ALL - NOW + fun getAllNow(profileId: Int) = + getRawNow("$QUERY WHERE grades.profileId = $profileId $ORDER_BY") + fun getNotNotifiedNow() = + getRawNow("$QUERY WHERE notified = 0 $ORDER_BY") + fun getNotNotifiedNow(profileId: Int) = + getRawNow("$QUERY WHERE grades.profileId = $profileId AND notified = 0 $ORDER_BY") + fun getByParentIdNow(profileId: Int, parentId: Long) = + getRawNow("$QUERY WHERE grades.profileId = $profileId AND gradeParentId = $parentId $ORDER_BY") - fun getAllNow(profileId: Int, filter: String): List { - return getAllNow(SimpleSQLiteQuery("SELECT \n" + - "*, \n" + - "teachers.teacherName || ' ' || teachers.teacherSurname AS teacherFullName\n" + - "FROM grades \n" + - "LEFT JOIN subjects USING(profileId, subjectId)\n" + - "LEFT JOIN teachers USING(profileId, teacherId)\n" + - "LEFT JOIN metadata ON gradeId = thingId AND thingType = " + Metadata.TYPE_GRADE + " AND metadata.profileId = " + profileId + "\n" + - "WHERE grades.profileId = " + profileId + " AND " + filter + "\n" + - "ORDER BY addedDate DESC")) - } + // GET ONE - NOW + fun getByIdNow(profileId: Int, id: Long) = + getOneNow("$QUERY WHERE grades.profileId = $profileId AND gradeId = $id") - fun getNotNotifiedNow(profileId: Int): List { - return getAllNow(profileId, "notified = 0") - } + @Query("UPDATE grades SET keep = 0 WHERE profileId = :profileId AND gradeType = :type") + abstract fun dontKeepWithType(profileId: Int, type: Int) - fun getAllWithParentIdNow(profileId: Int, parentId: Long): List { - return getAllNow(profileId, "gradeParentId = $parentId") - } + @Query("UPDATE grades SET keep = 0 WHERE profileId = :profileId AND gradeSemester = :semester") + abstract fun dontKeepForSemester(profileId: Int, semester: Int) - @get:Query("SELECT * FROM grades " + - "LEFT JOIN subjects USING(profileId, subjectId) " + - "LEFT JOIN metadata ON gradeId = thingId AND thingType = " + Metadata.TYPE_GRADE + " AND metadata.profileId = grades.profileId " + - "WHERE notified = 0 " + - "ORDER BY addedDate DESC") - abstract val notNotifiedNow: List + @Query("UPDATE grades SET keep = 0 WHERE profileId = :profileId AND gradeSemester = :semester AND gradeType = :type") + abstract fun dontKeepForSemesterWithType(profileId: Int, semester: Int, type: Int) - @RawQuery - abstract fun getNow(query: SupportSQLiteQuery): GradeFull? + + // GRADE DETAILS - MOBIDZIENNIK @Query("UPDATE grades SET gradeClassAverage = :classAverage, gradeColor = :color WHERE profileId = :profileId AND gradeId = :gradeId") abstract fun updateDetailsById(profileId: Int, gradeId: Long, classAverage: Float, color: Int) - @Query("UPDATE metadata SET addedDate = :addedDate WHERE profileId = :profileId AND thingType = " + Metadata.TYPE_GRADE + " AND thingId = :gradeId") + @Query("UPDATE grades SET addedDate = :addedDate WHERE profileId = :profileId AND gradeId = :gradeId") abstract fun updateAddedDateById(profileId: Int, gradeId: Long, addedDate: Long) @Transaction @@ -118,7 +119,7 @@ abstract class GradeDao { @Query("SELECT gradeColor FROM grades WHERE profileId = :profileId ORDER BY gradeId") abstract fun getColors(profileId: Int): List - @Query("SELECT addedDate FROM metadata WHERE profileId = :profileId AND thingType = " + Metadata.TYPE_GRADE + " ORDER BY thingId") + @Query("SELECT addedDate FROM grades WHERE profileId = :profileId ORDER BY gradeId") abstract fun getAddedDates(profileId: Int): List @Transaction @@ -134,8 +135,4 @@ abstract class GradeDao { gradeAddedDates[gradeId] = addedDates.next() } } - - fun getAllFromDate(profileId: Int, date: Long): LiveData> { - return getAllWhere(profileId, "addedDate > $date") - } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/LuckyNumberDao.java b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/LuckyNumberDao.java deleted file mode 100644 index c1c33c23..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/LuckyNumberDao.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (c) Kacper Ziubryniewicz 2020-1-6 - */ - -package pl.szczodrzynski.edziennik.data.db.dao; - -import androidx.annotation.Nullable; -import androidx.lifecycle.LiveData; -import androidx.room.Dao; -import androidx.room.Insert; -import androidx.room.OnConflictStrategy; -import androidx.room.Query; -import androidx.room.RawQuery; -import androidx.sqlite.db.SimpleSQLiteQuery; -import androidx.sqlite.db.SupportSQLiteQuery; - -import java.util.List; - -import pl.szczodrzynski.edziennik.data.db.entity.LuckyNumber; -import pl.szczodrzynski.edziennik.data.db.entity.Metadata; -import pl.szczodrzynski.edziennik.data.db.entity.Notice; -import pl.szczodrzynski.edziennik.data.db.full.LuckyNumberFull; -import pl.szczodrzynski.edziennik.utils.models.Date; - -import static pl.szczodrzynski.edziennik.data.db.entity.Metadata.TYPE_LUCKY_NUMBER; - -@Dao -public abstract class LuckyNumberDao { - @Insert(onConflict = OnConflictStrategy.REPLACE) - public abstract void add(LuckyNumber luckyNumber); - - @Insert(onConflict = OnConflictStrategy.REPLACE) - public abstract void addAll(List luckyNumberList); - - @Query("DELETE FROM luckyNumbers WHERE profileId = :profileId") - public abstract void clear(int profileId); - - @Query("SELECT * FROM luckyNumbers WHERE profileId = :profileId AND luckyNumberDate = :date") - public abstract LiveData getByDate(int profileId, Date date); - - @Query("SELECT * FROM luckyNumbers WHERE profileId = :profileId AND luckyNumberDate = :date") - public abstract LuckyNumber getByDateNow(int profileId, Date date); - - @Nullable - @Query("SELECT * FROM luckyNumbers WHERE profileId = :profileId AND luckyNumberDate >= :date ORDER BY luckyNumberDate DESC LIMIT 1") - public abstract LuckyNumber getNearestFutureNow(int profileId, int date); - - @Query("SELECT * FROM luckyNumbers WHERE profileId = :profileId AND luckyNumberDate >= :date ORDER BY luckyNumberDate DESC LIMIT 1") - public abstract LiveData getNearestFuture(int profileId, int date); - - @RawQuery(observedEntities = {LuckyNumber.class}) - abstract LiveData> getAll(SupportSQLiteQuery query); - public LiveData> getAll(int profileId, String filter) { - return getAll(new SimpleSQLiteQuery("SELECT\n" + - "*\n" + - "FROM luckyNumbers\n" + - "LEFT JOIN metadata ON luckyNumberDate = thingId AND thingType = "+TYPE_LUCKY_NUMBER+" AND metadata.profileId = "+profileId+"\n" + - "WHERE luckyNumbers.profileId = "+profileId+" AND "+filter+"\n" + - "ORDER BY addedDate DESC")); - } - public LiveData> getAll(int profileId) { - return getAll(profileId, "1"); - } - public LiveData> getAllWhere(int profileId, String filter) { - return getAll(profileId, filter); - } - - @RawQuery(observedEntities = {Notice.class, Metadata.class}) - abstract List getAllNow(SupportSQLiteQuery query); - public List getAllNow(int profileId, String filter) { - return getAllNow(new SimpleSQLiteQuery("SELECT\n" + - "*\n" + - "FROM luckyNumbers\n" + - "LEFT JOIN metadata ON luckyNumberDate = thingId AND thingType = "+TYPE_LUCKY_NUMBER+" AND metadata.profileId = "+profileId+"\n" + - "WHERE luckyNumbers.profileId = "+profileId+" AND "+filter+"\n" + - "ORDER BY addedDate DESC")); - } - public List getNotNotifiedNow(int profileId) { - return getAllNow(profileId, "notified = 0"); - } - - @Query("SELECT * FROM luckyNumbers\n" + - "LEFT JOIN metadata ON luckyNumberDate = thingId AND thingType = "+TYPE_LUCKY_NUMBER+" AND metadata.profileId = luckyNumbers.profileId " + - "WHERE notified = 0 " + - "ORDER BY addedDate DESC") - public abstract List getNotNotifiedNow(); -} 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 new file mode 100644 index 00000000..ad44c0d3 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/LuckyNumberDao.kt @@ -0,0 +1,74 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-25. + */ +package pl.szczodrzynski.edziennik.data.db.dao + +import androidx.lifecycle.LiveData +import androidx.room.Dao +import androidx.room.Query +import androidx.room.RawQuery +import androidx.sqlite.db.SupportSQLiteQuery +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.annotation.SelectiveDao +import pl.szczodrzynski.edziennik.annotation.UpdateSelective +import pl.szczodrzynski.edziennik.data.db.AppDb +import pl.szczodrzynski.edziennik.data.db.entity.LuckyNumber +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.full.LuckyNumberFull +import pl.szczodrzynski.edziennik.utils.models.Date + +@Dao +@SelectiveDao(db = AppDb::class) +abstract class LuckyNumberDao : BaseDao { + companion object { + private const val QUERY = """ + SELECT + * + FROM luckyNumbers + LEFT JOIN metadata ON luckyNumberDate = thingId AND thingType = ${Metadata.TYPE_LUCKY_NUMBER} AND metadata.profileId = luckyNumbers.profileId + """ + + private const val ORDER_BY = """ORDER BY luckyNumberDate DESC""" + } + + private val selective by lazy { LuckyNumberDaoSelective(App.db) } + + @RawQuery(observedEntities = [LuckyNumber::class]) + abstract override fun getRaw(query: SupportSQLiteQuery): LiveData> + @RawQuery(observedEntities = [LuckyNumber::class]) + abstract override fun getOne(query: SupportSQLiteQuery): LiveData + + // SELECTIVE UPDATE + @UpdateSelective(primaryKeys = ["profileId", "luckyNumberDate"], skippedColumns = ["addedDate"]) + override fun update(item: LuckyNumber) = selective.update(item) + override fun updateAll(items: List) = selective.updateAll(items) + + // CLEAR + @Query("DELETE FROM luckyNumbers WHERE profileId = :profileId") + abstract override fun clear(profileId: Int) + // REMOVE NOT KEPT + @Query("DELETE FROM luckyNumbers WHERE keep = 0") + abstract override fun removeNotKept() + + // GET ALL - LIVE DATA + fun getAll(profileId: Int) = + getRaw("$QUERY WHERE luckyNumbers.profileId = $profileId $ORDER_BY") + + // GET ALL - NOW + fun getAllNow(profileId: Int) = + getRawNow("$QUERY WHERE luckyNumbers.profileId = $profileId $ORDER_BY") + fun getNotNotifiedNow() = + getRawNow("$QUERY WHERE notified = 0 $ORDER_BY") + fun getNotNotifiedNow(profileId: Int) = + getRawNow("$QUERY WHERE luckyNumbers.profileId = $profileId AND notified = 0 $ORDER_BY") + + // GET ONE - LIVE DATA + fun getNearestFuture(profileId: Int, today: Date) = + getOne("$QUERY WHERE luckyNumbers.profileId = $profileId AND luckyNumberDate >= ${today.value} $ORDER_BY LIMIT 1") + + // GET ONE - NOW + fun getByIdNow(profileId: Int, id: Long) = + getOneNow("$QUERY WHERE attendances.profileId = $profileId AND attendanceId = $id") + fun getNearestFutureNow(profileId: Int, today: Date) = + getOneNow("$QUERY WHERE luckyNumbers.profileId = $profileId AND luckyNumberDate >= ${today.value} $ORDER_BY LIMIT 1") +} 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 606f29a7..a0be36ab 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) Kacper Ziubryniewicz 2020-1-6 + * Copyright (c) Kuba Szczodrzyński 2020-4-25. */ package pl.szczodrzynski.edziennik.data.db.dao @@ -36,7 +36,10 @@ abstract class MessageDao : BaseDao { @RawQuery(observedEntities = [Message::class]) abstract override fun getRaw(query: SupportSQLiteQuery): LiveData> + @RawQuery(observedEntities = [Message::class]) + abstract override fun getOne(query: SupportSQLiteQuery): LiveData + // SELECTIVE UPDATE @UpdateSelective(primaryKeys = ["profileId", "messageId"], skippedColumns = ["messageType", "messageBody", "messageIsPinned", "attachmentIds", "attachmentNames", "attachmentSizes"]) override fun update(item: Message) = selective.update(item) override fun updateAll(items: List) = selective.updateAll(items) @@ -44,6 +47,9 @@ abstract class MessageDao : BaseDao { // CLEAR @Query("DELETE FROM messages WHERE profileId = :profileId") abstract override fun clear(profileId: Int) + // REMOVE NOT KEPT + @Query("DELETE FROM messages WHERE keep = 0") + abstract override fun removeNotKept() // GET ALL - LIVE DATA fun getAll(profileId: Int) = @@ -64,4 +70,6 @@ abstract class MessageDao : BaseDao { // GET ONE - NOW fun getByIdNow(profileId: Int, id: Long) = getOneNow("$QUERY WHERE messages.profileId = $profileId AND messageId = $id") + + } 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 7a074bca..eac92f21 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 @@ -63,37 +63,37 @@ 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, 0)) == -1) { + if (add(new Metadata(profileId, TYPE_GRADE, ((Grade) o).getId(), seen, false)) == -1) { updateSeen(profileId, TYPE_GRADE, ((Grade) o).getId(), seen); } } if (o instanceof Attendance) { - if (add(new Metadata(profileId, TYPE_ATTENDANCE, ((Attendance) o).id, seen, false, 0)) == -1) { - updateSeen(profileId, TYPE_ATTENDANCE, ((Attendance) o).id, seen); + if (add(new Metadata(profileId, TYPE_ATTENDANCE, ((Attendance) o).getId(), seen, false)) == -1) { + updateSeen(profileId, TYPE_ATTENDANCE, ((Attendance) o).getId(), seen); } } if (o instanceof Notice) { - if (add(new Metadata(profileId, TYPE_NOTICE, ((Notice) o).id, seen, false, 0)) == -1) { - updateSeen(profileId, TYPE_NOTICE, ((Notice) o).id, seen); + if (add(new Metadata(profileId, TYPE_NOTICE, ((Notice) o).getId(), seen, false)) == -1) { + updateSeen(profileId, TYPE_NOTICE, ((Notice) o).getId(), seen); } } if (o instanceof Event) { - if (add(new Metadata(profileId, ((Event) o).getType() == Event.TYPE_HOMEWORK ? TYPE_HOMEWORK : TYPE_EVENT, ((Event) o).getId(), seen, false, 0)) == -1) { + if (add(new Metadata(profileId, ((Event) o).getType() == Event.TYPE_HOMEWORK ? TYPE_HOMEWORK : TYPE_EVENT, ((Event) o).getId(), seen, false)) == -1) { updateSeen(profileId, ((Event) o).getType() == Event.TYPE_HOMEWORK ? TYPE_HOMEWORK : TYPE_EVENT, ((Event) o).getId(), seen); } } if (o instanceof LessonFull) { - if (add(new Metadata(profileId, TYPE_LESSON_CHANGE, ((LessonFull) o).getId(), seen, false, 0)) == -1) { + if (add(new Metadata(profileId, TYPE_LESSON_CHANGE, ((LessonFull) o).getId(), seen, false)) == -1) { updateSeen(profileId, TYPE_LESSON_CHANGE, ((LessonFull) o).getId(), seen); } } if (o instanceof Announcement) { - if (add(new Metadata(profileId, TYPE_ANNOUNCEMENT, ((Announcement) o).id, seen, false, 0)) == -1) { - updateSeen(profileId, TYPE_ANNOUNCEMENT, ((Announcement) o).id, seen); + if (add(new Metadata(profileId, TYPE_ANNOUNCEMENT, ((Announcement) o).getId(), seen, false)) == -1) { + updateSeen(profileId, TYPE_ANNOUNCEMENT, ((Announcement) o).getId(), seen); } } if (o instanceof Message) { - if (add(new Metadata(profileId, TYPE_MESSAGE, ((Message) o).getId(), seen, false, 0)) == -1) { + if (add(new Metadata(profileId, TYPE_MESSAGE, ((Message) o).getId(), seen, false)) == -1) { updateSeen(profileId, TYPE_MESSAGE, ((Message) o).getId(), seen); } } @@ -102,37 +102,37 @@ 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, 0)) == -1) { + if (add(new Metadata(profileId, TYPE_GRADE, ((Grade) o).getId(), false, notified)) == -1) { updateNotified(profileId, TYPE_GRADE, ((Grade) o).getId(), notified); } } if (o instanceof Attendance) { - if (add(new Metadata(profileId, TYPE_ATTENDANCE, ((Attendance) o).id, false, notified, 0)) == -1) { - updateNotified(profileId, TYPE_ATTENDANCE, ((Attendance) o).id, notified); + if (add(new Metadata(profileId, TYPE_ATTENDANCE, ((Attendance) o).getId(), false, notified)) == -1) { + updateNotified(profileId, TYPE_ATTENDANCE, ((Attendance) o).getId(), notified); } } if (o instanceof Notice) { - if (add(new Metadata(profileId, TYPE_NOTICE, ((Notice) o).id, false, notified, 0)) == -1) { - updateNotified(profileId, TYPE_NOTICE, ((Notice) o).id, notified); + if (add(new Metadata(profileId, TYPE_NOTICE, ((Notice) o).getId(), false, notified)) == -1) { + updateNotified(profileId, TYPE_NOTICE, ((Notice) o).getId(), notified); } } if (o instanceof Event) { - if (add(new Metadata(profileId, ((Event) o).getType() == Event.TYPE_HOMEWORK ? TYPE_HOMEWORK : TYPE_EVENT, ((Event) o).getId(), false, notified, 0)) == -1) { + if (add(new Metadata(profileId, ((Event) o).getType() == Event.TYPE_HOMEWORK ? TYPE_HOMEWORK : TYPE_EVENT, ((Event) o).getId(), false, notified)) == -1) { updateNotified(profileId, ((Event) o).getType() == Event.TYPE_HOMEWORK ? TYPE_HOMEWORK : TYPE_EVENT, ((Event) o).getId(), notified); } } if (o instanceof LessonFull) { - if (add(new Metadata(profileId, TYPE_LESSON_CHANGE, ((LessonFull) o).getId(), false, notified, 0)) == -1) { + if (add(new Metadata(profileId, TYPE_LESSON_CHANGE, ((LessonFull) o).getId(), false, notified)) == -1) { updateNotified(profileId, TYPE_LESSON_CHANGE, ((LessonFull) o).getId(), notified); } } if (o instanceof Announcement) { - if (add(new Metadata(profileId, TYPE_ANNOUNCEMENT, ((Announcement) o).id, false, notified, 0)) == -1) { - updateNotified(profileId, TYPE_ANNOUNCEMENT, ((Announcement) o).id, notified); + if (add(new Metadata(profileId, TYPE_ANNOUNCEMENT, ((Announcement) o).getId(), false, notified)) == -1) { + updateNotified(profileId, TYPE_ANNOUNCEMENT, ((Announcement) o).getId(), notified); } } if (o instanceof Message) { - if (add(new Metadata(profileId, TYPE_MESSAGE, ((Message) o).getId(), false, notified, 0)) == -1) { + if (add(new Metadata(profileId, TYPE_MESSAGE, ((Message) o).getId(), false, notified)) == -1) { updateNotified(profileId, TYPE_MESSAGE, ((Message) o).getId(), notified); } } @@ -141,7 +141,7 @@ 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.getType() == Event.TYPE_HOMEWORK ? TYPE_HOMEWORK : TYPE_EVENT, o.getId(), seen, notified, addedDate)) == -1) { + if (add(new Metadata(profileId, o.getType() == Event.TYPE_HOMEWORK ? TYPE_HOMEWORK : TYPE_EVENT, o.getId(), seen, notified)) == -1) { updateSeen(profileId, o.getType() == Event.TYPE_HOMEWORK ? TYPE_HOMEWORK : TYPE_EVENT, o.getId(), seen); updateNotified(profileId, o.getType() == Event.TYPE_HOMEWORK ? TYPE_HOMEWORK : TYPE_EVENT, o.getId(), notified); } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/NoticeDao.java b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/NoticeDao.java deleted file mode 100644 index 110544e4..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/NoticeDao.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (c) Kacper Ziubryniewicz 2020-1-6 - */ - -package pl.szczodrzynski.edziennik.data.db.dao; - -import androidx.lifecycle.LiveData; -import androidx.room.Dao; -import androidx.room.Insert; -import androidx.room.OnConflictStrategy; -import androidx.room.Query; -import androidx.room.RawQuery; -import androidx.sqlite.db.SimpleSQLiteQuery; -import androidx.sqlite.db.SupportSQLiteQuery; - -import java.util.List; - -import pl.szczodrzynski.edziennik.data.db.entity.Metadata; -import pl.szczodrzynski.edziennik.data.db.entity.Notice; -import pl.szczodrzynski.edziennik.data.db.full.NoticeFull; - -import static pl.szczodrzynski.edziennik.data.db.entity.Metadata.TYPE_NOTICE; - -@Dao -public abstract class NoticeDao { - @Insert(onConflict = OnConflictStrategy.REPLACE) - public abstract long add(Notice notice); - - @Insert(onConflict = OnConflictStrategy.REPLACE) - public abstract void addAll(List noticeList); - - @Query("DELETE FROM notices WHERE profileId = :profileId") - public abstract void clear(int profileId); - - @Query("DELETE FROM notices WHERE profileId = :profileId AND noticeSemester = :semester") - public abstract void clearForSemester(int profileId, int semester); - - @RawQuery(observedEntities = {Notice.class}) - abstract LiveData> getAll(SupportSQLiteQuery query); - public LiveData> getAll(int profileId, String filter) { - return getAll(new SimpleSQLiteQuery("SELECT \n" + - "*, \n" + - "teachers.teacherName || ' ' || teachers.teacherSurname AS teacherFullName\n" + - "FROM notices \n" + - "LEFT JOIN teachers USING(profileId, teacherId)\n" + - "LEFT JOIN metadata ON noticeId = thingId AND thingType = "+TYPE_NOTICE+" AND metadata.profileId = "+profileId+"\n" + - "WHERE notices.profileId = "+profileId+" AND "+filter+"\n" + - "ORDER BY addedDate DESC")); - } - public LiveData> getAll(int profileId) { - return getAll(profileId, "1"); - } - public LiveData> getAllWhere(int profileId, String filter) { - return getAll(profileId, filter); - } - - @RawQuery(observedEntities = {Notice.class, Metadata.class}) - abstract List getAllNow(SupportSQLiteQuery query); - public List getAllNow(int profileId, String filter) { - return getAllNow(new SimpleSQLiteQuery("SELECT \n" + - "*, \n" + - "teachers.teacherName || ' ' || teachers.teacherSurname AS teacherFullName\n" + - "FROM notices \n" + - "LEFT JOIN teachers USING(profileId, teacherId)\n" + - "LEFT JOIN metadata ON noticeId = thingId AND thingType = "+TYPE_NOTICE+" AND metadata.profileId = "+profileId+"\n" + - "WHERE notices.profileId = "+profileId+" AND "+filter+"\n" + - "ORDER BY addedDate DESC")); - } - public List getNotNotifiedNow(int profileId) { - return getAllNow(profileId, "notified = 0"); - } - - @Query("SELECT " + - "*, " + - "teachers.teacherName || ' ' || teachers.teacherSurname AS teacherFullName " + - "FROM notices " + - "LEFT JOIN teachers USING(profileId, teacherId) " + - "LEFT JOIN metadata ON noticeId = thingId AND thingType = "+TYPE_NOTICE+" AND metadata.profileId = notices.profileId " + - "WHERE notified = 0 " + - "ORDER BY addedDate DESC") - public abstract List getNotNotifiedNow(); -} 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 new file mode 100644 index 00000000..79bae175 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/NoticeDao.kt @@ -0,0 +1,72 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-25. + */ +package pl.szczodrzynski.edziennik.data.db.dao + +import androidx.lifecycle.LiveData +import androidx.room.Dao +import androidx.room.Query +import androidx.room.RawQuery +import androidx.sqlite.db.SupportSQLiteQuery +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.annotation.SelectiveDao +import pl.szczodrzynski.edziennik.annotation.UpdateSelective +import pl.szczodrzynski.edziennik.data.db.AppDb +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.Notice +import pl.szczodrzynski.edziennik.data.db.full.NoticeFull + +@Dao +@SelectiveDao(db = AppDb::class) +abstract class NoticeDao : BaseDao { + companion object { + private const val QUERY = """ + SELECT + *, + 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 + """ + + private const val ORDER_BY = """ORDER BY addedDate DESC""" + } + + private val selective by lazy { NoticeDaoSelective(App.db) } + + @RawQuery(observedEntities = [Notice::class]) + abstract override fun getRaw(query: SupportSQLiteQuery): LiveData> + @RawQuery(observedEntities = [Notice::class]) + abstract override fun getOne(query: SupportSQLiteQuery): LiveData + + // SELECTIVE UPDATE + @UpdateSelective(primaryKeys = ["profileId", "noticeId"], skippedColumns = ["addedDate"]) + override fun update(item: Notice) = selective.update(item) + override fun updateAll(items: List) = selective.updateAll(items) + + // CLEAR + @Query("DELETE FROM notices WHERE profileId = :profileId") + abstract override fun clear(profileId: Int) + // REMOVE NOT KEPT + @Query("DELETE FROM notices WHERE keep = 0") + abstract override fun removeNotKept() + + // GET ALL - LIVE DATA + fun getAll(profileId: Int) = + getRaw("$QUERY WHERE notices.profileId = $profileId $ORDER_BY") + + // GET ALL - NOW + fun getAllNow(profileId: Int) = + getRawNow("$QUERY WHERE notices.profileId = $profileId $ORDER_BY") + fun getNotNotifiedNow() = + getRawNow("$QUERY WHERE notified = 0 $ORDER_BY") + fun getNotNotifiedNow(profileId: Int) = + getRawNow("$QUERY WHERE notices.profileId = $profileId AND notified = 0 $ORDER_BY") + + // GET ONE - NOW + fun getByIdNow(profileId: Int, id: Long) = + getOneNow("$QUERY WHERE notices.profileId = $profileId AND noticeId = $id") + + @Query("UPDATE notices SET keep = 0 WHERE profileId = :profileId AND noticeSemester = :semester") + abstract fun dontKeepSemester(profileId: Int, semester: Int) +} 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 1d4881bb..1528adf1 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 @@ -1,65 +1,74 @@ /* - * Copyright (c) Kacper Ziubryniewicz 2020-1-6 + * Copyright (c) Kuba Szczodrzyński 2020-4-25. */ - package pl.szczodrzynski.edziennik.data.db.dao import androidx.lifecycle.LiveData import androidx.room.Dao -import androidx.room.Insert -import androidx.room.OnConflictStrategy import androidx.room.Query +import androidx.room.RawQuery +import androidx.sqlite.db.SupportSQLiteQuery +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.annotation.SelectiveDao +import pl.szczodrzynski.edziennik.annotation.UpdateSelective +import pl.szczodrzynski.edziennik.data.db.AppDb import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.data.db.entity.TeacherAbsence import pl.szczodrzynski.edziennik.data.db.full.TeacherAbsenceFull import pl.szczodrzynski.edziennik.utils.models.Date @Dao -interface TeacherAbsenceDao { +@SelectiveDao(db = AppDb::class) +abstract class TeacherAbsenceDao : BaseDao { + companion object { + private const val QUERY = """ + SELECT + *, + 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 + """ - @Insert(onConflict = OnConflictStrategy.REPLACE) - fun add(teacherAbsence: TeacherAbsence) + private const val ORDER_BY = """ORDER BY teacherAbsenceDateFrom ASC""" + } - @Insert(onConflict = OnConflictStrategy.REPLACE) - fun addAll(teacherAbsenceList: List) + private val selective by lazy { TeacherAbsenceDaoSelective(App.db) } - @Query("SELECT * FROM teacherAbsence WHERE profileId = :profileId") - fun getAll(profileId: Int): List + @RawQuery(observedEntities = [TeacherAbsence::class]) + abstract override fun getRaw(query: SupportSQLiteQuery): LiveData> + @RawQuery(observedEntities = [TeacherAbsence::class]) + abstract override fun getOne(query: SupportSQLiteQuery): LiveData - @Query("SELECT *, teachers.teacherName || ' ' || teachers.teacherSurname as teacherFullName, " + - "metadata.seen, metadata.notified, metadata.addedDate FROM teacherAbsence " + - "LEFT JOIN teachers USING (profileId, teacherId) " + - "LEFT JOIN metadata ON teacherAbsenceId = thingId AND metadata.thingType = " + Metadata.TYPE_TEACHER_ABSENCE + - " AND metadata.profileId = :profileId WHERE teachers.profileId = :profileId") - fun getAllFullNow(profileId: Int): List - - @Query("SELECT *, teachers.teacherName || ' ' || teachers.teacherSurname as teacherFullName, " + - "metadata.seen, metadata.notified, metadata.addedDate FROM teacherAbsence " + - "LEFT JOIN teachers USING (profileId, teacherId) " + - "LEFT JOIN metadata ON teacherAbsenceId = thingId AND metadata.thingType = " + Metadata.TYPE_TEACHER_ABSENCE + - " AND metadata.profileId = :profileId WHERE teachers.profileId = :profileId " + - "AND :date BETWEEN teacherAbsenceDateFrom AND teacherAbsenceDateTo") - fun getAllByDateFull(profileId: Int, date: Date): LiveData> - - @Query("SELECT *, teachers.teacherName || ' ' || teachers.teacherSurname as teacherFullName, " + - "metadata.seen, metadata.notified, metadata.addedDate FROM teacherAbsence " + - "LEFT JOIN teachers USING (profileId, teacherId) " + - "LEFT JOIN metadata ON teacherAbsenceId = thingId AND metadata.thingType = " + Metadata.TYPE_TEACHER_ABSENCE + - " AND metadata.profileId = :profileId WHERE teachers.profileId = :profileId " + - "AND :date BETWEEN teacherAbsenceDateFrom AND teacherAbsenceDateTo") - fun getAllByDateNow(profileId: Int, date: Date): List - - @Query(""" - SELECT *, - teachers.teacherName || ' ' || teachers.teacherSurname as teacherFullName - FROM teacherAbsence - LEFT JOIN teachers USING (profileId, teacherId) - LEFT JOIN metadata ON teacherAbsenceId = thingId AND metadata.thingType = ${Metadata.TYPE_TEACHER_ABSENCE} - AND teachers.profileId = metadata.profileId WHERE metadata.notified = 0 - ORDER BY addedDate DESC - """) - fun getNotNotifiedNow(): List + // SELECTIVE UPDATE + @UpdateSelective(primaryKeys = ["profileId", "teacherAbsenceId"], skippedColumns = ["addedDate"]) + override fun update(item: TeacherAbsence) = selective.update(item) + override fun updateAll(items: List) = selective.updateAll(items) + // CLEAR @Query("DELETE FROM teacherAbsence WHERE profileId = :profileId") - fun clear(profileId: Int) + abstract override fun clear(profileId: Int) + // REMOVE NOT KEPT + @Query("DELETE FROM teacherAbsence WHERE keep = 0") + abstract override fun removeNotKept() + + // GET ALL - LIVE DATA + fun getAll(profileId: Int) = + getRaw("$QUERY WHERE teacherAbsence.profileId = $profileId $ORDER_BY") + fun getAllByDate(profileId: Int, date: Date) = + getRaw("$QUERY WHERE teacherAbsence.profileId = $profileId AND '${date.stringY_m_d}' BETWEEN teacherAbsenceDateFrom AND teacherAbsenceDateTo $ORDER_BY") + + // GET ALL - NOW + fun getAllNow(profileId: Int) = + getRawNow("$QUERY WHERE teacherAbsence.profileId = $profileId $ORDER_BY") + fun getNotNotifiedNow() = + getRawNow("$QUERY WHERE notified = 0 $ORDER_BY") + fun getNotNotifiedNow(profileId: Int) = + getRawNow("$QUERY WHERE teacherAbsence.profileId = $profileId AND notified = 0 $ORDER_BY") + fun getAllByDateNow(profileId: Int, date: Date) = + getRawNow("$QUERY WHERE teacherAbsence.profileId = $profileId AND '${date.stringY_m_d}' BETWEEN teacherAbsenceDateFrom AND teacherAbsenceDateTo $ORDER_BY") + + // GET ONE - NOW + fun getByIdNow(profileId: Int, id: Long) = + getOneNow("$QUERY WHERE teacherAbsence.profileId = $profileId AND teacherAbsenceId = $id") } 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 9ac9709b..cc236dc6 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 @@ -1,20 +1,25 @@ /* - * Copyright (c) Kacper Ziubryniewicz 2020-1-6 + * Copyright (c) Kuba Szczodrzyński 2020-4-25. */ - package pl.szczodrzynski.edziennik.data.db.dao import androidx.lifecycle.LiveData -import androidx.room.* -import androidx.sqlite.db.SimpleSQLiteQuery +import androidx.room.Dao +import androidx.room.Query +import androidx.room.RawQuery import androidx.sqlite.db.SupportSQLiteQuery +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.annotation.SelectiveDao +import pl.szczodrzynski.edziennik.annotation.UpdateSelective +import pl.szczodrzynski.edziennik.data.db.AppDb import pl.szczodrzynski.edziennik.data.db.entity.Lesson import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.data.db.full.LessonFull import pl.szczodrzynski.edziennik.utils.models.Date @Dao -interface TimetableDao { +@SelectiveDao(db = AppDb::class) +abstract class TimetableDao : BaseDao { companion object { private const val QUERY = """ SELECT @@ -25,7 +30,7 @@ interface TimetableDao { oldS.subjectLongName AS oldSubjectName, oldT.teacherName ||" "|| oldT.teacherSurname AS oldTeacherName, oldG.teamName AS oldTeamName, - metadata.seen, metadata.notified, metadata.addedDate + metadata.seen, metadata.notified FROM timetable LEFT JOIN subjects USING(profileId, subjectId) LEFT JOIN teachers USING(profileId, teacherId) @@ -35,111 +40,77 @@ interface TimetableDao { 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 """ + + private const val ORDER_BY = """ORDER BY profileId, id, type""" + private const val IS_CHANGED = """type != -1 AND type != 0""" } - @Insert(onConflict = OnConflictStrategy.REPLACE) - operator fun plusAssign(lessonList: List) - - @Query("DELETE FROM timetable WHERE profileId = :profileId") - fun clear(profileId: Int) - - @Query("DELETE FROM timetable WHERE profileId = :profileId AND type != -1 AND ((type != 3 AND date >= :dateFrom) OR ((type = 3 OR type = 1) AND oldDate >= :dateFrom))") - fun clearFromDate(profileId: Int, dateFrom: Date) - - @Query("DELETE FROM timetable WHERE profileId = :profileId AND type != -1 AND ((type != 3 AND date <= :dateTo) OR ((type = 3 OR type = 1) AND oldDate <= :dateTo))") - fun clearToDate(profileId: Int, dateTo: Date) - - @Query("DELETE FROM timetable WHERE profileId = :profileId AND type != -1 AND ((type != 3 AND date >= :dateFrom AND date <= :dateTo) OR ((type = 3 OR type = 1) AND oldDate >= :dateFrom AND oldDate <= :dateTo))") - fun clearBetweenDates(profileId: Int, dateFrom: Date, dateTo: Date) + private val selective by lazy { TimetableDaoSelective(App.db) } @RawQuery(observedEntities = [Lesson::class]) - fun getRaw(query: SupportSQLiteQuery): LiveData> + abstract override fun getRaw(query: SupportSQLiteQuery): LiveData> + @RawQuery(observedEntities = [Lesson::class]) + abstract override fun getOne(query: SupportSQLiteQuery): LiveData - @Query(""" - $QUERY - WHERE timetable.profileId = :profileId AND type != -1 AND type != 0 - ORDER BY id, type - """) - fun getAllChangesNow(profileId: Int): List + // SELECTIVE UPDATE + @UpdateSelective(primaryKeys = ["profileId", "id"], skippedColumns = ["addedDate"]) + override fun update(item: Lesson) = selective.update(item) + override fun updateAll(items: List) = selective.updateAll(items) - @Query(""" - $QUERY - WHERE timetable.profileId = :profileId AND type != -1 AND type != 0 AND ((type != 3 AND date = :date) OR ((type = 3 OR type = 1) AND oldDate = :date)) - ORDER BY id, type - """) - fun getChangesForDateNow(profileId: Int, date: Date): List + // CLEAR + @Query("DELETE FROM timetable WHERE profileId = :profileId") + abstract override fun clear(profileId: Int) + // REMOVE NOT KEPT + @Query("DELETE FROM timetable WHERE keep = 0") + abstract override fun removeNotKept() - fun getForDate(profileId: Int, date: Date) = getRaw(SimpleSQLiteQuery(""" - $QUERY - WHERE timetable.profileId = $profileId AND ((type != 3 AND date = "${date.stringY_m_d}") OR ((type = 3 OR type = 1) AND oldDate = "${date.stringY_m_d}")) - ORDER BY id, type - """)) + // GET ALL - LIVE DATA + fun getAll(profileId: Int) = + getRaw("$QUERY WHERE timetable.profileId = $profileId $ORDER_BY") + fun getAllForDate(profileId: Int, date: Date) = + getRaw("$QUERY WHERE timetable.profileId = $profileId AND ((type != 3 AND date = '${date.stringY_m_d}') OR ((type = 3 OR type = 1) AND oldDate = '${date.stringY_m_d}')) $ORDER_BY") + fun getNextWithSubject(profileId: Int, date: Date, subjectId: Long) = + getOne("$QUERY " + + "WHERE timetable.profileId = $profileId " + + "AND ((type != 3 AND date > '${date.stringY_m_d}') OR ((type = 3 OR type = 1) AND oldDate > '${date.stringY_m_d}')) " + + "AND timetable.subjectId = $subjectId " + + "LIMIT 1") + fun getNextWithSubjectAndTeam(profileId: Int, date: Date, subjectId: Long, teamId: Long) = + getOne("$QUERY " + + "WHERE timetable.profileId = $profileId " + + "AND ((type != 3 AND date > '${date.stringY_m_d}') OR ((type = 3 OR type = 1) AND oldDate > '${date.stringY_m_d}')) " + + "AND timetable.subjectId = $subjectId " + + "AND timetable.teamId = $teamId " + + "LIMIT 1") + fun getBetweenDates(dateFrom: Date, dateTo: Date) = + getRaw("$QUERY WHERE (type != 3 AND date >= '${dateFrom.stringY_m_d}' AND date <= '${dateTo.stringY_m_d}') OR ((type = 3 OR type = 1) AND oldDate >= '${dateFrom.stringY_m_d}' AND oldDate <= '${dateTo.stringY_m_d}') $ORDER_BY") - @Query(""" - $QUERY - WHERE timetable.profileId = :profileId AND ((type != 3 AND date = :date) OR ((type = 3 OR type = 1) AND oldDate = :date)) - ORDER BY id, type - """) - fun getForDateNow(profileId: Int, date: Date): List + // GET ALL - NOW + fun getAllNow(profileId: Int) = + getRawNow("$QUERY WHERE timetable.profileId = $profileId $ORDER_BY") + fun getNotNotifiedNow() = + getRawNow("$QUERY WHERE notified = 0 AND timetable.type NOT IN (${Lesson.TYPE_NORMAL}, ${Lesson.TYPE_NO_LESSONS}, ${Lesson.TYPE_SHIFTED_SOURCE}) $ORDER_BY") + fun getNotNotifiedNow(profileId: Int) = + getRawNow("$QUERY WHERE timetable.profileId = $profileId AND notified = 0 AND timetable.type NOT IN (${Lesson.TYPE_NORMAL}, ${Lesson.TYPE_NO_LESSONS}, ${Lesson.TYPE_SHIFTED_SOURCE}) $ORDER_BY") + fun getAllForDateNow(profileId: Int, date: Date) = + getRawNow("$QUERY WHERE timetable.profileId = $profileId AND ((type != 3 AND date = '${date.stringY_m_d}') OR ((type = 3 OR type = 1) AND oldDate = '${date.stringY_m_d}')) $ORDER_BY") + fun getChangesNow(profileId: Int) = + getRawNow("$QUERY WHERE timetable.profileId = $profileId AND $IS_CHANGED $ORDER_BY") + fun getChangesForDateNow(profileId: Int, date: Date) = + getRawNow("$QUERY WHERE timetable.profileId = $profileId AND $IS_CHANGED AND ((type != 3 AND date = '${date.stringY_m_d}') OR ((type = 3 OR type = 1) AND oldDate = '${date.stringY_m_d}')) $ORDER_BY") + fun getBetweenDatesNow(dateFrom: Date, dateTo: Date) = + getRawNow("$QUERY WHERE (type != 3 AND date >= '${dateFrom.stringY_m_d}' AND date <= '${dateTo.stringY_m_d}') OR ((type = 3 OR type = 1) AND oldDate >= '${dateFrom.stringY_m_d}' AND oldDate <= '${dateTo.stringY_m_d}') $ORDER_BY") - @Query(""" - $QUERY - WHERE timetable.profileId = :profileId AND ((type != 3 AND date > :today) OR ((type = 3 OR type = 1) AND oldDate > :today)) AND timetable.subjectId = :subjectId - ORDER BY id, type - LIMIT 1 - """) - fun getNextWithSubject(profileId: Int, today: Date, subjectId: Long): LiveData + // GET ONE - NOW + fun getByIdNow(profileId: Int, id: Long) = + getOneNow("$QUERY WHERE timetable.profileId = $profileId AND timetable.id = $id") - @Query(""" - $QUERY - WHERE timetable.profileId = :profileId AND ((type != 3 AND date > :today) OR ((type = 3 OR type = 1) AND oldDate > :today)) AND timetable.subjectId = :subjectId AND timetable.teamId = :teamId - ORDER BY id, type - LIMIT 1 - """) - fun getNextWithSubjectAndTeam(profileId: Int, today: Date, subjectId: Long, teamId: Long): LiveData + @Query("UPDATE timetable SET keep = 0 WHERE profileId = :profileId 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) - @Query(""" - $QUERY - WHERE (type != 3 AND date >= :dateFrom AND date <= :dateTo) OR ((type = 3 OR type = 1) AND oldDate >= :dateFrom AND oldDate <= :dateTo) - ORDER BY profileId, id, type - """) - fun getBetweenDatesNow(dateFrom: Date, dateTo: Date): List + @Query("UPDATE timetable SET keep = 0 WHERE profileId = :profileId AND type != -1 AND ((type != 3 AND date <= :dateTo) OR ((type = 3 OR type = 1) AND oldDate <= :dateTo))") + abstract fun dontKeepToDate(profileId: Int, dateTo: Date) - @Query(""" - $QUERY - WHERE (type != 3 AND date >= :dateFrom AND date <= :dateTo) OR ((type = 3 OR type = 1) AND oldDate >= :dateFrom AND oldDate <= :dateTo) - ORDER BY profileId, id, type - """) - fun getBetweenDates(dateFrom: Date, dateTo: Date): LiveData> - - @Query(""" - $QUERY - WHERE timetable.profileId = :profileId AND timetable.id = :lessonId - ORDER BY id, type - """) - fun getByIdNow(profileId: Int, lessonId: Long): LessonFull? - - @Query(""" - $QUERY - WHERE timetable.profileId = :profileId AND timetable.type NOT IN (${Lesson.TYPE_NORMAL}, ${Lesson.TYPE_NO_LESSONS}, ${Lesson.TYPE_SHIFTED_SOURCE}) AND metadata.notified = 0 - """) - fun getNotNotifiedNow(profileId: Int): List - - @Query(""" - SELECT - timetable.*, - subjects.subjectLongName AS subjectName, - teachers.teacherName ||" "|| teachers.teacherSurname AS teacherName, - oldS.subjectLongName AS oldSubjectName, - oldT.teacherName ||" "|| oldT.teacherSurname AS oldTeacherName, - metadata.seen, metadata.notified, metadata.addedDate - FROM timetable - LEFT JOIN subjects USING(profileId, subjectId) - LEFT JOIN teachers USING(profileId, teacherId) - 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 metadata ON id = thingId AND thingType = ${Metadata.TYPE_LESSON_CHANGE} AND metadata.profileId = timetable.profileId - WHERE timetable.type NOT IN (${Lesson.TYPE_NORMAL}, ${Lesson.TYPE_NO_LESSONS}, ${Lesson.TYPE_SHIFTED_SOURCE}) AND metadata.notified = 0 - """) - fun getNotNotifiedNow(): List + @Query("UPDATE timetable SET keep = 0 WHERE profileId = :profileId AND type != -1 AND ((type != 3 AND date >= :dateFrom AND date <= :dateTo) OR ((type = 3 OR type = 1) AND oldDate >= :dateFrom AND oldDate <= :dateTo))") + abstract fun dontKeepBetweenDates(profileId: Int, dateFrom: Date, dateTo: Date) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Announcement.java b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Announcement.java deleted file mode 100644 index af17e0c3..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Announcement.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (c) Kacper Ziubryniewicz 2020-1-6 - */ - -package pl.szczodrzynski.edziennik.data.db.entity; - -import androidx.annotation.Nullable; -import androidx.room.ColumnInfo; -import androidx.room.Entity; -import androidx.room.Ignore; -import androidx.room.Index; - -import pl.szczodrzynski.edziennik.utils.models.Date; - -@Entity(tableName = "announcements", - primaryKeys = {"profileId", "announcementId"}, - indices = {@Index(value = {"profileId"})}) -public class Announcement { - public int profileId; - - @ColumnInfo(name = "announcementId") - public long id; - - @ColumnInfo(name = "announcementSubject") - public String subject; - @Nullable - @ColumnInfo(name = "announcementText") - public String text; - @Nullable - @ColumnInfo(name = "announcementStartDate") - public Date startDate; - @Nullable - @ColumnInfo(name = "announcementEndDate") - public Date endDate; - - public long teacherId; - - @Nullable - @ColumnInfo(name = "announcementIdString") - public String idString; - - @Ignore - public Announcement() {} - - public Announcement(int profileId, long id, String subject, String text, @Nullable Date startDate, @Nullable Date endDate, long teacherId, @Nullable String idString) { - this.profileId = profileId; - this.id = id; - this.subject = subject; - this.text = text; - this.startDate = startDate; - this.endDate = endDate; - this.teacherId = teacherId; - this.idString = idString; - } -} - - - diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Announcement.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Announcement.kt new file mode 100644 index 00000000..82f7bb78 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Announcement.kt @@ -0,0 +1,40 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-24. + */ +package pl.szczodrzynski.edziennik.data.db.entity + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.Ignore +import androidx.room.Index +import pl.szczodrzynski.edziennik.utils.models.Date + +@Entity(tableName = "announcements", + primaryKeys = ["profileId", "announcementId"], + indices = [ + Index(value = ["profileId"]) + ]) +open class Announcement( + val profileId: Int, + @ColumnInfo(name = "announcementId") + var id: Long, + @ColumnInfo(name = "announcementSubject") + var subject: String, + @ColumnInfo(name = "announcementText") + var text: String?, + + @ColumnInfo(name = "announcementStartDate") + var startDate: Date?, + @ColumnInfo(name = "announcementEndDate") + var endDate: Date?, + + var teacherId: Long, + var addedDate: Long = System.currentTimeMillis() +) : Keepable() { + + @ColumnInfo(name = "announcementIdString") + var idString: String? = null + + @Ignore + var showAsUnseen: Boolean? = null +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Attendance.java b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Attendance.java deleted file mode 100644 index 039dcb0c..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Attendance.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (c) Kacper Ziubryniewicz 2020-1-6 - */ - -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 pl.szczodrzynski.edziennik.utils.models.Date; -import pl.szczodrzynski.edziennik.utils.models.Time; - -@Entity(tableName = "attendances", - primaryKeys = {"profileId", "attendanceId", "attendanceLessonDate", "attendanceStartTime"}, - indices = {@Index(value = {"profileId"})}) -public class Attendance { - public int profileId; - - @ColumnInfo(name = "attendanceId") - public long id; - - @NonNull - @ColumnInfo(name = "attendanceLessonDate") - public Date lessonDate; - @NonNull - @ColumnInfo(name = "attendanceStartTime") - public Time startTime; - @ColumnInfo(name = "attendanceLessonTopic") - public String lessonTopic; - @ColumnInfo(name = "attendanceSemester") - public int semester; - public static final int TYPE_PRESENT = 0; - public static final int TYPE_ABSENT = 1; - public static final int TYPE_ABSENT_EXCUSED = 2; - public static final int TYPE_RELEASED = 3; - public static final int TYPE_BELATED = 4; - public static final int TYPE_BELATED_EXCUSED = 5; - public static final int TYPE_CUSTOM = 6; - public static final int TYPE_DAY_FREE = 7; - @ColumnInfo(name = "attendanceType") - public int type = TYPE_PRESENT; - - public long teacherId; - public long subjectId; - - @Ignore - public Attendance() { - this(-1, -1, -1, -1, 0, "", Date.getToday(), Time.getNow(), TYPE_PRESENT); - } - - public Attendance(int profileId, long id, long teacherId, long subjectId, int semester, String lessonTopic, Date lessonDate, Time startTime, int type) { - this.profileId = profileId; - this.id = id; - this.teacherId = teacherId; - this.subjectId = subjectId; - this.semester = semester; - this.lessonTopic = lessonTopic; - this.lessonDate = lessonDate; - this.startTime = startTime; - this.type = type; - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Attendance.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Attendance.kt new file mode 100644 index 00000000..b2ebb5a0 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Attendance.kt @@ -0,0 +1,78 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-24. + */ +package pl.szczodrzynski.edziennik.data.db.entity + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.Ignore +import androidx.room.Index +import pl.szczodrzynski.edziennik.utils.models.Date +import pl.szczodrzynski.edziennik.utils.models.Time + +@Entity(tableName = "attendances", + primaryKeys = ["profileId", "attendanceId"], + indices = [ + Index(value = ["profileId"]) + ]) +open class Attendance( + val profileId: Int, + @ColumnInfo(name = "attendanceId") + var id: Long, + /** Base type ID used to count attendance stats */ + @ColumnInfo(name = "attendanceBaseType") + var baseType: Int, + /** A full type name coming from the e-register */ + @ColumnInfo(name = "attendanceTypeName") + var typeName: String, + /** A short name to display by default, might be different for non-standard types */ + @ColumnInfo(name = "attendanceTypeShort") + val typeShort: String, + /** A short name that the e-register would display */ + @ColumnInfo(name = "attendanceTypeSymbol") + var typeSymbol: String, + /** A color that the e-register would display, null falls back to app's default */ + @ColumnInfo(name = "attendanceTypeColor") + var typeColor: Int?, + + @ColumnInfo(name = "attendanceDate") + var date: Date, + @ColumnInfo(name = "attendanceTime") + var startTime: Time?, + @ColumnInfo(name = "attendanceSemester") + var semester: Int, + + var teacherId: Long, + var subjectId: Long, + var addedDate: Long = System.currentTimeMillis() +) : Keepable() { + companion object { + const val TYPE_UNKNOWN = -1 // #3f51b5 + const val TYPE_PRESENT = 0 // #009688 + const val TYPE_PRESENT_CUSTOM = 10 // count as presence AND show in the list + custom color, fallback: #3f51b5 + const val TYPE_ABSENT = 1 // #ff3d00 + const val TYPE_ABSENT_EXCUSED = 2 // #76ff03 + const val TYPE_RELEASED = 3 // #9e9e9e + const val TYPE_BELATED = 4 // #ffc107 + const val TYPE_BELATED_EXCUSED = 5 // #ffc107 + const val TYPE_DAY_FREE = 6 // #43a047 + + // attendance bar order: + // day_free, present, present_custom, unknown, belated_excused, belated, released, absent_excused, absent, + } + + @ColumnInfo(name = "attendanceLessonTopic") + var lessonTopic: String? = null + @ColumnInfo(name = "attendanceLessonNumber") + var lessonNumber: Int? = null + @ColumnInfo(name = "attendanceIsCounted") + var isCounted: Boolean = true + + @Ignore + var showAsUnseen: Boolean? = null + + @delegate:Ignore + val typeObject by lazy { + AttendanceType(profileId, baseType.toLong(), baseType, typeName, typeShort, typeSymbol, typeColor).also { it.isCounted = isCounted } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/AttendanceType.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/AttendanceType.kt index 0e3e86f9..c5fccc4b 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/AttendanceType.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/AttendanceType.kt @@ -5,19 +5,55 @@ package pl.szczodrzynski.edziennik.data.db.entity import androidx.room.Entity +import androidx.room.Ignore @Entity(tableName = "attendanceTypes", primaryKeys = ["profileId", "id"]) data class AttendanceType ( - val profileId: Int, - val id: Long, + /** Base type ID used to count attendance stats */ + val baseType: Int, + /** A full type name coming from the e-register */ + val typeName: String, + /** A short name to display by default, might be different for non-standard types */ + val typeShort: String, + /** A short name that the e-register would display */ + val typeSymbol: String, + /** A color that the e-register would display, null falls back to app's default */ + val typeColor: Int? +) : Comparable { - val name: String, + @Ignore + var isCounted: Boolean = true - val type: Int, - - val color: Int - -) + // attendance bar order: + // day_free, present, present_custom, unknown, belated_excused, belated, released, absent_excused, absent, + override fun compareTo(other: AttendanceType): Int { + val type1 = when (baseType) { + Attendance.TYPE_DAY_FREE -> 0 + Attendance.TYPE_PRESENT -> 1 + Attendance.TYPE_PRESENT_CUSTOM -> 2 + Attendance.TYPE_UNKNOWN -> 3 + Attendance.TYPE_BELATED_EXCUSED -> 4 + Attendance.TYPE_BELATED -> 5 + Attendance.TYPE_RELEASED -> 6 + Attendance.TYPE_ABSENT_EXCUSED -> 7 + Attendance.TYPE_ABSENT -> 8 + else -> 9 + } + val type2 = when (other.baseType) { + Attendance.TYPE_DAY_FREE -> 0 + Attendance.TYPE_PRESENT -> 1 + Attendance.TYPE_PRESENT_CUSTOM -> 2 + Attendance.TYPE_UNKNOWN -> 3 + Attendance.TYPE_BELATED_EXCUSED -> 4 + Attendance.TYPE_BELATED -> 5 + Attendance.TYPE_RELEASED -> 6 + Attendance.TYPE_ABSENT_EXCUSED -> 7 + Attendance.TYPE_ABSENT -> 8 + else -> 9 + } + return type1 - type2 + } +} 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 b811ff61..9d13e883 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 @@ -41,7 +41,8 @@ open class Event( var teacherId: Long, var subjectId: Long, - var teamId: Long + var teamId: Long, + var addedDate: Long = System.currentTimeMillis() ) : Keepable() { companion object { const val TYPE_UNDEFINED = -2L diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Grade.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Grade.kt index 393ab332..5bf3bd86 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Grade.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Grade.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) Kacper Ziubryniewicz 2020-1-6 + * Copyright (c) Kuba Szczodrzyński 2020-4-24. */ package pl.szczodrzynski.edziennik.data.db.entity @@ -8,23 +8,11 @@ import androidx.room.Entity import androidx.room.Ignore import androidx.room.Index -/*public Grade(int profileId, long id, String category, int color, String description, String name, float value, float weight, int semester, long teacherId, long subjectId) { - this.profileId = profileId; - this.id = id; - this.category = category; - this.color = color; - this.description = description; - this.name = name; - this.value = value; - this.weight = weight; - this.semester = semester; - this.teacherId = teacherId; - this.subjectId = subjectId; - }*/ - @Entity(tableName = "grades", primaryKeys = ["profileId", "gradeId"], - indices = [Index(value = ["profileId"])]) + indices = [ + Index(value = ["profileId"]) + ]) open class Grade( val profileId: Int, @ColumnInfo(name = "gradeId") @@ -40,6 +28,7 @@ open class Grade( var weight: Float, @ColumnInfo(name = "gradeColor") var color: Int, + @ColumnInfo(name = "gradeCategory") var category: String?, @ColumnInfo(name = "gradeDescription") @@ -50,8 +39,9 @@ open class Grade( @ColumnInfo(name = "gradeSemester") val semester: Int, val teacherId: Long, - val subjectId: Long -) { + val subjectId: Long, + var addedDate: Long = System.currentTimeMillis() +) : Keepable() { companion object { const val TYPE_NORMAL = 0 const val TYPE_SEMESTER1_PROPOSED = 1 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 ceb6cf9c..0c97ba24 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 @@ -1,7 +1,6 @@ /* - * Copyright (c) Kacper Ziubryniewicz 2020-1-6 + * Copyright (c) Kuba Szczodrzyński 2020-4-25. */ - package pl.szczodrzynski.edziennik.data.db.entity import androidx.room.Entity @@ -11,12 +10,15 @@ import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Time @Entity(tableName = "timetable", + primaryKeys = ["profileId", "id"], indices = [ Index(value = ["profileId", "type", "date"]), Index(value = ["profileId", "type", "oldDate"]) - ], - primaryKeys = ["profileId", "id"]) -open class Lesson(val profileId: Int, var id: Long) { + ]) +open class Lesson( + val profileId: Int, + var id: Long +) : Keepable() { companion object { const val TYPE_NO_LESSONS = -1 const val TYPE_NORMAL = 0 diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/LuckyNumber.java b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/LuckyNumber.java deleted file mode 100644 index bca49ec5..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/LuckyNumber.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (c) Kacper Ziubryniewicz 2020-1-6 - */ - -package pl.szczodrzynski.edziennik.data.db.entity; - -import androidx.annotation.NonNull; -import androidx.room.ColumnInfo; -import androidx.room.Entity; -import androidx.room.Ignore; - -import pl.szczodrzynski.edziennik.utils.models.Date; - -@Entity(tableName = "luckyNumbers", - primaryKeys = {"profileId", "luckyNumberDate"}) -public class LuckyNumber { - public int profileId; - - @NonNull - @ColumnInfo(name = "luckyNumberDate", typeAffinity = 3) - public Date date; - @ColumnInfo(name = "luckyNumber") - public int number; - - public LuckyNumber(int profileId, @NonNull Date date, int number) { - this.profileId = profileId; - this.date = date; - this.number = number; - } - - @Ignore - public LuckyNumber() { - this.date = Date.getToday(); - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/LuckyNumber.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/LuckyNumber.kt new file mode 100644 index 00000000..6fb943c9 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/LuckyNumber.kt @@ -0,0 +1,22 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-24. + */ +package pl.szczodrzynski.edziennik.data.db.entity + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.Ignore +import pl.szczodrzynski.edziennik.utils.models.Date + +@Entity(tableName = "luckyNumbers", + primaryKeys = ["profileId", "luckyNumberDate"]) +open class LuckyNumber( + val profileId: Int, + @ColumnInfo(name = "luckyNumberDate", typeAffinity = ColumnInfo.INTEGER) + var date: Date, + @ColumnInfo(name = "luckyNumber") + var number: Int +) : Keepable() { + @Ignore + var showAsUnseen = false +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Message.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Message.kt index 1cf9b3ba..98105057 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Message.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Message.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) Kacper Ziubryniewicz 2020-1-6 + * Copyright (c) Kuba Szczodrzyński 2020-4-25. */ package pl.szczodrzynski.edziennik.data.db.entity @@ -30,7 +30,8 @@ open class Message( * Keep in mind that this being null does NOT * necessarily mean the message is sent. */ - var senderId: Long? + var senderId: Long?, + var addedDate: Long = System.currentTimeMillis() ) : Keepable() { companion object { const val TYPE_RECEIVED = 0 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 6ea94f4e..7374d915 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 @@ -36,7 +36,6 @@ public class Metadata { public boolean seen; public boolean notified; - public long addedDate; @Ignore public Metadata() { @@ -45,13 +44,12 @@ public class Metadata { this.notified = false; } - public Metadata(int profileId, int thingType, long thingId, boolean seen, boolean notified, long addedDate) { + public Metadata(int profileId, int thingType, long thingId, boolean seen, boolean notified) { this.profileId = profileId; this.thingType = thingType; this.thingId = thingId; this.seen = seen; this.notified = notified; - this.addedDate = addedDate; } public String thingType() { @@ -86,7 +84,6 @@ public class Metadata { ", thingId=" + thingId + ", seen=" + seen + ", notified=" + notified + - ", addedDate=" + addedDate + '}'; } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Notice.java b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Notice.java deleted file mode 100644 index 306e6f92..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Notice.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) Kacper Ziubryniewicz 2020-1-6 - */ - -package pl.szczodrzynski.edziennik.data.db.entity; - -import androidx.room.ColumnInfo; -import androidx.room.Entity; -import androidx.room.Ignore; -import androidx.room.Index; - -@Entity(tableName = "notices", - primaryKeys = {"profileId", "noticeId"}, - indices = {@Index(value = {"profileId"})}) -public class Notice { - public int profileId; - - @ColumnInfo(name = "noticeId") - public long id; - - @ColumnInfo(name = "noticeText") - public String text; - @ColumnInfo(name = "noticeSemester") - public int semester; - public static final int TYPE_NEUTRAL = 0; - public static final int TYPE_POSITIVE = 1; - public static final int TYPE_NEGATIVE = 2; - @ColumnInfo(name = "noticeType") - public int type = TYPE_NEUTRAL; - - public float points = 0; - public String category = null; - - public long teacherId; - - @Ignore - public Notice() {} - - public Notice(int profileId, long id, String text, int semester, int type, long teacherId) { - this.profileId = profileId; - this.id = id; - this.text = text; - this.semester = semester; - this.type = type; - this.teacherId = teacherId; - } -} - - diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Notice.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Notice.kt new file mode 100644 index 00000000..0e1f21fe --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Notice.kt @@ -0,0 +1,43 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-25. + */ +package pl.szczodrzynski.edziennik.data.db.entity + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.Ignore +import androidx.room.Index + +@Entity(tableName = "notices", + primaryKeys = ["profileId", "noticeId"], + indices = [ + Index(value = ["profileId"]) + ]) +open class Notice( + val profileId: Int, + @ColumnInfo(name = "noticeId") + var id: Long, + @ColumnInfo(name = "noticeType") + var type: Int, + @ColumnInfo(name = "noticeSemester") + var semester: Int, + + @ColumnInfo(name = "noticeText") + var text: String, + @ColumnInfo(name = "noticeCategory") + var category: String?, + @ColumnInfo(name = "noticePoints") + var points: Float?, + + var teacherId: Long, + var addedDate: Long = System.currentTimeMillis() +) : Keepable() { + companion object { + const val TYPE_NEUTRAL = 0 + const val TYPE_POSITIVE = 1 + const val TYPE_NEGATIVE = 2 + } + + @Ignore + var showAsUnseen = false +} 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 1442f40c..50ad82a7 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 @@ -17,6 +17,7 @@ import com.google.gson.JsonObject import pl.droidsonroids.gif.GifDrawable import pl.szczodrzynski.edziennik.* import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_EDUDZIENNIK +import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_PODLASIE import pl.szczodrzynski.edziennik.utils.ProfileImageHolder import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.navlib.ImageHolder @@ -80,7 +81,7 @@ open class Profile( 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.value >= getSemesterStart(2).value) 2 else 1 + fun dateToSemester(date: Date) = if (date >= dateSemester2Start) 2 else 1 @delegate:Ignore val currentSemester by lazy { dateToSemester(Date.getToday()) } @@ -175,6 +176,12 @@ open class Profile( 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, diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/TeacherAbsence.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/TeacherAbsence.kt index af037851..9ec5f828 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/TeacherAbsence.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/TeacherAbsence.kt @@ -1,37 +1,41 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-25. + */ package pl.szczodrzynski.edziennik.data.db.entity import androidx.room.ColumnInfo import androidx.room.Entity +import androidx.room.Ignore +import androidx.room.Index import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Time @Entity(tableName = "teacherAbsence", - primaryKeys = ["profileId", "teacherAbsenceId"]) -open class TeacherAbsence ( + primaryKeys = ["profileId", "teacherAbsenceId"], + indices = [ + Index(value = ["profileId"]) + ]) +open class TeacherAbsence( + val profileId: Int, + @ColumnInfo(name = "teacherAbsenceId") + val id: Long, + @ColumnInfo(name = "teacherAbsenceType") + val type: Long, + @ColumnInfo(name = "teacherAbsenceName") + val name: String?, - val profileId: Int, + @ColumnInfo(name = "teacherAbsenceDateFrom") + val dateFrom: Date, + @ColumnInfo(name = "teacherAbsenceDateTo") + val dateTo: Date, + @ColumnInfo(name = "teacherAbsenceTimeFrom") + val timeFrom: Time?, + @ColumnInfo(name = "teacherAbsenceTimeTo") + val timeTo: Time?, - @ColumnInfo(name = "teacherAbsenceId") - val id: Long, - - val teacherId: Long, - - @ColumnInfo(name = "teacherAbsenceType") - val type: Long, - - @ColumnInfo(name = "teacherAbsenceName") - val name: String?, - - @ColumnInfo(name = "teacherAbsenceDateFrom") - val dateFrom: Date, - - @ColumnInfo(name = "teacherAbsenceDateTo") - val dateTo: Date, - - @ColumnInfo(name = "teacherAbsenceTimeFrom") - val timeFrom: Time?, - - @ColumnInfo(name = "teacherAbsenceTimeTo") - val timeTo: Time? - -) + val teacherId: Long, + var addedDate: Long = System.currentTimeMillis() +) : Keepable() { + @Ignore + var showAsUnseen = false +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/AnnouncementFull.java b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/AnnouncementFull.java deleted file mode 100644 index 2811a0c7..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/AnnouncementFull.java +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright (c) Kacper Ziubryniewicz 2020-1-6 - */ - -package pl.szczodrzynski.edziennik.data.db.full; - -import pl.szczodrzynski.edziennik.data.db.entity.Announcement; - -public class AnnouncementFull extends Announcement { - public String teacherFullName = ""; - - // metadata - public boolean seen; - public boolean notified; - public long addedDate; -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/AnnouncementFull.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/AnnouncementFull.kt new file mode 100644 index 00000000..5b4cb711 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/AnnouncementFull.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-25. + */ +package pl.szczodrzynski.edziennik.data.db.full + +import pl.szczodrzynski.edziennik.data.db.entity.Announcement +import pl.szczodrzynski.edziennik.utils.models.Date + +class AnnouncementFull( + profileId: Int, id: Long, + subject: String, text: String?, + startDate: Date?, endDate: Date?, + teacherId: Long, addedDate: Long = System.currentTimeMillis() +) : Announcement( + profileId, id, + subject, text, + startDate, endDate, + teacherId, addedDate +) { + var teacherName: String? = null + + // metadata + var seen = false + var notified = false +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/AttendanceFull.java b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/AttendanceFull.java deleted file mode 100644 index 605d06b2..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/AttendanceFull.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (c) Kacper Ziubryniewicz 2020-1-6 - */ - -package pl.szczodrzynski.edziennik.data.db.full; - -import pl.szczodrzynski.edziennik.data.db.entity.Attendance; - -public class AttendanceFull extends Attendance { - public String teacherFullName = ""; - - public String subjectLongName = ""; - public String subjectShortName = ""; - - // metadata - public boolean seen; - public boolean notified; - public long addedDate; -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/AttendanceFull.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/AttendanceFull.kt new file mode 100644 index 00000000..f235fafd --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/AttendanceFull.kt @@ -0,0 +1,29 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-24. + */ +package pl.szczodrzynski.edziennik.data.db.full + +import pl.szczodrzynski.edziennik.data.db.entity.Attendance +import pl.szczodrzynski.edziennik.utils.models.Date +import pl.szczodrzynski.edziennik.utils.models.Time + +class AttendanceFull( + profileId: Int, id: Long, + baseType: Int, typeName: String, typeShort: String, typeSymbol: String, typeColor: Int?, + date: Date, startTime: Time, semester: Int, + teacherId: Long, subjectId: Long, addedDate: Long = System.currentTimeMillis() +) : Attendance( + profileId, id, + baseType, typeName, typeShort, typeSymbol, typeColor, + date, startTime, semester, + teacherId, subjectId, addedDate +) { + var teacherName: String? = null + var subjectLongName: String? = null + var subjectShortName: String? = null + + // metadata + var seen = false + get() = field || baseType == TYPE_PRESENT + var notified = false +} 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 a3dcc2cb..01e85395 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) Kacper Ziubryniewicz 2020-1-6 + * Copyright (c) Kuba Szczodrzyński 2020-4-25. */ package pl.szczodrzynski.edziennik.data.db.full @@ -11,16 +11,16 @@ import pl.szczodrzynski.edziennik.utils.models.Time class EventFull( profileId: Int, id: Long, date: Date, time: Time?, topic: String, color: Int?, type: Long, - teacherId: Long, subjectId: Long, teamId: Long + teacherId: Long, subjectId: Long, teamId: Long, addedDate: Long = System.currentTimeMillis() ) : Event( profileId, id, date, time, topic, color, type, - teacherId, subjectId, teamId + teacherId, subjectId, teamId, addedDate ) { constructor(event: Event, metadata: Metadata? = null) : this( event.profileId, event.id, event.date, event.time, event.topic, event.color, event.type, - event.teacherId, event.subjectId, event.teamId) { + event.teacherId, event.subjectId, event.teamId, event.addedDate) { event.let { addedManually = it.addedManually sharedBy = it.sharedBy @@ -33,7 +33,6 @@ class EventFull( metadata?.let { seen = it.seen notified = it.notified - addedDate = it.addedDate } } @@ -45,10 +44,10 @@ class EventFull( var subjectShortName: String? = null var teamName: String? = null var teamCode: String? = null + // metadata var seen = false var notified = false - var addedDate: Long = 0 val eventColor get() = color ?: typeColor ?: 0xff2196f3.toInt() diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/GradeFull.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/GradeFull.kt index 8cc80974..f8d90c05 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/GradeFull.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/GradeFull.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) Kacper Ziubryniewicz 2020-1-6 + * Copyright (c) Kuba Szczodrzyński 2020-4-24. */ package pl.szczodrzynski.edziennik.data.db.full @@ -9,18 +9,18 @@ class GradeFull( profileId: Int, id: Long, name: String, type: Int, value: Float, weight: Float, color: Int, category: String?, description: String?, comment: String?, - semester: Int, teacherId: Long, subjectId: Long + semester: Int, teacherId: Long, subjectId: Long, addedDate: Long = System.currentTimeMillis() ) : Grade( profileId, id, name, type, value, weight, color, category, description, comment, - semester, teacherId, subjectId + semester, teacherId, subjectId, addedDate ) { + var teacherName: String? = null var subjectLongName: String? = null var subjectShortName: String? = null - var teacherFullName: String? = null + // metadata var seen = false var notified = false - var addedDate: Long = 0 } 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 5661a614..0e9542b6 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 @@ -1,3 +1,6 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-25. + */ package pl.szczodrzynski.edziennik.data.db.full import android.content.Context @@ -5,7 +8,11 @@ import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.data.db.entity.Lesson import pl.szczodrzynski.edziennik.utils.models.Time -class LessonFull(profileId: Int, id: Long) : Lesson(profileId, id) { +class LessonFull( + profileId: Int, id: Long +) : Lesson( + profileId, id +) { var subjectName: String? = null var teacherName: String? = null var teamName: String? = null @@ -126,5 +133,4 @@ class LessonFull(profileId: Int, id: Long) : Lesson(profileId, id) { // metadata var seen: Boolean = false var notified: Boolean = false - var addedDate: Long = 0 } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/LuckyNumberFull.java b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/LuckyNumberFull.java deleted file mode 100644 index d73cb95f..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/LuckyNumberFull.java +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright (c) Kacper Ziubryniewicz 2020-1-6 - */ - -package pl.szczodrzynski.edziennik.data.db.full; - -import pl.szczodrzynski.edziennik.data.db.entity.LuckyNumber; - -public class LuckyNumberFull extends LuckyNumber { - // metadata - public boolean seen; - public boolean notified; - public long addedDate; -} - diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/LuckyNumberFull.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/LuckyNumberFull.kt new file mode 100644 index 00000000..bb24724a --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/LuckyNumberFull.kt @@ -0,0 +1,19 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-25. + */ +package pl.szczodrzynski.edziennik.data.db.full + +import pl.szczodrzynski.edziennik.data.db.entity.LuckyNumber +import pl.szczodrzynski.edziennik.utils.models.Date + +class LuckyNumberFull( + profileId: Int, date: Date, + number: Int +) : LuckyNumber( + profileId, date, + number +) { + // metadata + var seen = false + var notified = false +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/MessageFull.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/MessageFull.kt index 5b873b7a..3fdcdd0d 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/MessageFull.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/MessageFull.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) Kacper Ziubryniewicz 2020-1-6 + * Copyright (c) Kuba Szczodrzyński 2020-4-25. */ package pl.szczodrzynski.edziennik.data.db.full @@ -10,10 +10,12 @@ import pl.szczodrzynski.edziennik.data.db.entity.MessageRecipient class MessageFull( profileId: Int, id: Long, type: Int, - subject: String, body: String?, senderId: Long? + subject: String, body: String?, senderId: Long?, + addedDate: Long = System.currentTimeMillis() ) : Message( profileId, id, type, - subject, body, senderId + subject, body, senderId, + addedDate ) { var senderName: String? = null @Relation(parentColumn = "messageId", entityColumn = "messageId", entity = MessageRecipient::class) @@ -33,5 +35,4 @@ class MessageFull( // metadata var seen = false var notified = false - var addedDate: Long = 0 } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/NoticeFull.java b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/NoticeFull.java deleted file mode 100644 index 372650fd..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/NoticeFull.java +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright (c) Kacper Ziubryniewicz 2020-1-6 - */ - -package pl.szczodrzynski.edziennik.data.db.full; - -import pl.szczodrzynski.edziennik.data.db.entity.Notice; - -public class NoticeFull extends Notice { - public String teacherFullName = ""; - - // metadata - public boolean seen; - public boolean notified; - public long addedDate; -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/NoticeFull.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/NoticeFull.kt new file mode 100644 index 00000000..25aee14d --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/NoticeFull.kt @@ -0,0 +1,22 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-25. + */ +package pl.szczodrzynski.edziennik.data.db.full + +import pl.szczodrzynski.edziennik.data.db.entity.Notice + +class NoticeFull( + profileId: Int, id: Long, type: Int, semester: Int, + text: String, category: String?, points: Float?, + teacherId: Long, addedDate: Long = System.currentTimeMillis() +) : Notice( + profileId, id, type, semester, + text, category, points, + teacherId, addedDate +) { + var teacherName: String? = null + + // metadata + var seen = false + var notified = false +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/TeacherAbsenceFull.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/TeacherAbsenceFull.kt index e9d53a59..39a88e0f 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/TeacherAbsenceFull.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/TeacherAbsenceFull.kt @@ -1,17 +1,24 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-25. + */ package pl.szczodrzynski.edziennik.data.db.full import pl.szczodrzynski.edziennik.data.db.entity.TeacherAbsence import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Time -class TeacherAbsenceFull(profileId: Int, id: Long, teacherId: Long, type: Long, name: String?, - dateFrom: Date, dateTo: Date, timeFrom: Time?, timeTo: Time?) - : TeacherAbsence(profileId, id, teacherId, type, name, dateFrom, dateTo, timeFrom, timeTo) { - - var teacherFullName = "" +class TeacherAbsenceFull( + profileId: Int, id: Long, type: Long, name: String?, + dateFrom: Date, dateTo: Date, timeFrom: Time?, timeTo: Time?, + teacherId: Long, addedDate: Long = System.currentTimeMillis() +) : TeacherAbsence( + profileId, id, type, name, + dateFrom, dateTo, timeFrom, timeTo, + teacherId, addedDate +) { + var teacherName: String? = null // metadata var seen: Boolean = false var notified: Boolean = false - var addedDate: Long = 0 } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration86.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration86.kt new file mode 100644 index 00000000..add43d71 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration86.kt @@ -0,0 +1,178 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-29. + */ + +package pl.szczodrzynski.edziennik.data.db.migration + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase + +class Migration86 : Migration(85, 86) { + override fun migrate(database: SupportSQLiteDatabase) { + // Migrating some models, moving addedDate from metadata to entities + + // Announcements + database.execSQL("""ALTER TABLE announcements RENAME TO _announcements""") + database.execSQL("""CREATE TABLE `announcements` ( + `announcementIdString` TEXT, + `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, + `keep` INTEGER NOT NULL, + PRIMARY KEY(`profileId`, `announcementId`) + )""") + database.execSQL("""DROP INDEX IF EXISTS index_announcements_profileId""") + database.execSQL("""CREATE INDEX `index_announcements_profileId` ON `announcements` (`profileId`)""") + database.execSQL("""REPLACE INTO announcements ( + announcementIdString, profileId, announcementId, announcementSubject, announcementText, announcementStartDate, announcementEndDate, teacherId, addedDate, keep + ) SELECT + announcementIdString, profileId, announcementId, IFNULL(announcementSubject, ""), announcementText, announcementStartDate, announcementEndDate, teacherId, 0, 1 + FROM _announcements""") + database.execSQL("""DROP TABLE _announcements""") + + // Attendance Types + database.execSQL("""ALTER TABLE attendanceTypes RENAME TO _attendanceTypes""") + database.execSQL("""CREATE TABLE `attendanceTypes` ( + `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`) + )""") + database.execSQL("""REPLACE INTO attendanceTypes ( + profileId, id, + baseType, + typeName, + typeShort, + typeSymbol, + typeColor + ) SELECT + profileId, id, + CASE WHEN id > 100 AND type = 0 THEN 10 ELSE type END, + name, + CASE type WHEN 0 THEN "ob" WHEN 1 THEN "nb" WHEN 2 THEN "u" WHEN 3 THEN "zw" WHEN 4 THEN "sp" WHEN 5 THEN "su" WHEN 6 THEN "w" ELSE "?" END, + CASE type WHEN 0 THEN "ob" WHEN 1 THEN "nb" WHEN 2 THEN "u" WHEN 3 THEN "zw" WHEN 4 THEN "sp" WHEN 5 THEN "su" WHEN 6 THEN "w" ELSE "?" END, + CASE color WHEN -1 THEN NULL ELSE color END + FROM _attendanceTypes""") + database.execSQL("""DROP TABLE _attendanceTypes""") + + // Attendance + database.execSQL("""ALTER TABLE attendances RENAME TO _attendances""") + database.execSQL("""CREATE TABLE `attendances` ( + `attendanceLessonTopic` TEXT, + `attendanceLessonNumber` INTEGER, + `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, + `keep` INTEGER NOT NULL, + PRIMARY KEY(`profileId`, `attendanceId`) + )""") + database.execSQL("""DROP INDEX IF EXISTS index_attendances_profileId""") + database.execSQL("""CREATE INDEX `index_attendances_profileId` ON `attendances` (`profileId`)""") + database.execSQL("""REPLACE INTO attendances ( + attendanceLessonTopic, attendanceLessonNumber, profileId, attendanceId, + attendanceBaseType, + attendanceTypeName, + attendanceTypeShort, + attendanceTypeSymbol, + attendanceTypeColor, attendanceDate, attendanceTime, attendanceSemester, teacherId, subjectId, addedDate, keep + ) SELECT + attendanceLessonTopic, NULL, profileId, attendanceId, + attendanceType, + CASE attendanceType WHEN 0 THEN "ob" WHEN 1 THEN "nb" WHEN 2 THEN "u" WHEN 3 THEN "zw" WHEN 4 THEN "sp" WHEN 5 THEN "su" WHEN 6 THEN "w" ELSE "?" END, + CASE attendanceType WHEN 0 THEN "ob" WHEN 1 THEN "nb" WHEN 2 THEN "u" WHEN 3 THEN "zw" WHEN 4 THEN "sp" WHEN 5 THEN "su" WHEN 6 THEN "w" ELSE "?" END, + CASE attendanceType WHEN 0 THEN "ob" WHEN 1 THEN "nb" WHEN 2 THEN "u" WHEN 3 THEN "zw" WHEN 4 THEN "sp" WHEN 5 THEN "su" WHEN 6 THEN "w" ELSE "?" END, + NULL, attendanceLessonDate, attendanceStartTime, attendanceSemester, teacherId, subjectId, 0, 1 + FROM _attendances""") + database.execSQL("""DROP TABLE _attendances""") + + // Events + database.execSQL("""ALTER TABLE events ADD COLUMN addedDate INTEGER NOT NULL DEFAULT 0""") + + // Grades + database.execSQL("""ALTER TABLE grades ADD COLUMN addedDate INTEGER NOT NULL DEFAULT 0""") + database.execSQL("""ALTER TABLE grades ADD COLUMN keep INTEGER NOT NULL DEFAULT 1""") + + // Lucky Numbers + database.execSQL("""ALTER TABLE luckyNumbers ADD COLUMN keep INTEGER NOT NULL DEFAULT 1""") + + // Messages + database.execSQL("""ALTER TABLE messages ADD COLUMN addedDate INTEGER NOT NULL DEFAULT 0""") + + // Notices + database.execSQL("""ALTER TABLE notices RENAME TO _notices""") + database.execSQL("""CREATE TABLE `notices` ( + `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`) + )""") + database.execSQL("""DROP INDEX IF EXISTS index_notices_profileId""") + database.execSQL("""CREATE INDEX `index_notices_profileId` ON `notices` (`profileId`)""") + database.execSQL("""REPLACE INTO notices ( + profileId, noticeId, noticeType, noticeSemester, + noticeText, + noticeCategory, + noticePoints, + teacherId, addedDate, keep + ) SELECT + profileId, noticeId, noticeType, noticeSemester, + CASE noticeText WHEN NULL THEN "" ELSE noticeText END, + category, + CASE points WHEN 0 THEN NULL ELSE points END, + teacherId, 0, 1 + FROM _notices""") + database.execSQL("""DROP TABLE _notices""") + + // Teacher Absence + database.execSQL("""ALTER TABLE teacherAbsence ADD COLUMN addedDate INTEGER NOT NULL DEFAULT 0""") + database.execSQL("""ALTER TABLE teacherAbsence ADD COLUMN keep INTEGER NOT NULL DEFAULT 1""") + database.execSQL("""CREATE INDEX IF NOT EXISTS `index_teacherAbsence_profileId` ON `teacherAbsence` (`profileId`)""") + + // Timetable + database.execSQL("""ALTER TABLE timetable ADD COLUMN keep INTEGER NOT NULL DEFAULT 1""") + + // Metadata - copy AddedDate to entities + database.execSQL("""UPDATE grades SET addedDate = IFNULL((SELECT metadata.addedDate FROM metadata WHERE metadata.profileId = grades.profileId AND metadata.thingId = grades.gradeId AND metadata.thingType = 1), 0)""") + database.execSQL("""UPDATE notices SET addedDate = IFNULL((SELECT metadata.addedDate FROM metadata WHERE metadata.profileId = notices.profileId AND metadata.thingId = notices.noticeId AND metadata.thingType = 2), 0)""") + database.execSQL("""UPDATE attendances SET addedDate = IFNULL((SELECT metadata.addedDate FROM metadata WHERE metadata.profileId = attendances.profileId AND metadata.thingId = attendances.attendanceId AND metadata.thingType = 3), 0)""") + database.execSQL("""UPDATE events SET addedDate = IFNULL((SELECT metadata.addedDate FROM metadata WHERE metadata.profileId = events.profileId AND metadata.thingId = events.eventId AND (metadata.thingType = 4 OR metadata.thingType = 5)), 0)""") + database.execSQL("""UPDATE announcements SET addedDate = IFNULL((SELECT metadata.addedDate FROM metadata WHERE metadata.profileId = announcements.profileId AND metadata.thingId = announcements.announcementId AND metadata.thingType = 7), 0)""") + database.execSQL("""UPDATE messages SET addedDate = IFNULL((SELECT metadata.addedDate FROM metadata WHERE metadata.profileId = messages.profileId AND metadata.thingId = messages.messageId AND metadata.thingType = 8), 0)""") + + // Metadata - drop AddedDate column + database.execSQL("""ALTER TABLE metadata RENAME TO _metadata""") + database.execSQL("""CREATE TABLE metadata (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)""") + database.execSQL("""DROP INDEX IF EXISTS index_metadata_profileId_thingType_thingId""") + database.execSQL("""CREATE UNIQUE INDEX index_metadata_profileId_thingType_thingId ON "metadata" (profileId, thingType, thingId)""") + database.execSQL("""INSERT INTO metadata (profileId, metadataId, thingType, thingId, seen, notified) SELECT profileId, metadataId, thingType, thingId, seen, notified FROM _metadata""") + database.execSQL("""DROP TABLE _metadata""") + + database.execSQL("""DELETE FROM endpointTimers WHERE endpointId IN (1080, 1081, 2050, 1090, 1010, 1014);""") + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration87.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration87.kt new file mode 100644 index 00000000..524cb559 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration87.kt @@ -0,0 +1,14 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-5-8. + */ + +package pl.szczodrzynski.edziennik.data.db.migration + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase + +class Migration87 : Migration(86, 87) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("ALTER TABLE attendances ADD COLUMN attendanceIsCounted INTEGER NOT NULL DEFAULT 1") + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration88.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration88.kt new file mode 100644 index 00000000..0dcd2f8c --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration88.kt @@ -0,0 +1,15 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-5-9. + */ + +package pl.szczodrzynski.edziennik.data.db.migration + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase + +class Migration88 : Migration(87, 88) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("UPDATE endpointTimers SET endpointLastSync = 0 WHERE endpointId IN (1030, 1040, 1050, 1060, 1070, 1080);") + database.execSQL("UPDATE profiles SET empty = 1 WHERE loginStoreType = 4") + } +} 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 c394c7cc..2ebb8c66 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 @@ -114,7 +114,8 @@ class SzkolnyAppFirebase(val app: App, val profiles: List, val message: type = json.getLong("type") ?: 0, teacherId = json.getLong("teacherId") ?: -1, subjectId = json.getLong("subjectId") ?: -1, - teamId = team.id + teamId = team.id, + addedDate = json.getLong("addedDate") ?: System.currentTimeMillis() ) if (event.color == -1) event.color = null @@ -128,8 +129,7 @@ class SzkolnyAppFirebase(val app: App, val profiles: List, val message: if (event.type == Event.TYPE_HOMEWORK) Metadata.TYPE_HOMEWORK else Metadata.TYPE_EVENT, event.id, false, - true, - json.getLong("addedDate") ?: System.currentTimeMillis() + true ) val type = if (event.type == Event.TYPE_HOMEWORK) Notification.TYPE_NEW_SHARED_HOMEWORK else Notification.TYPE_NEW_SHARED_EVENT @@ -144,7 +144,7 @@ class SzkolnyAppFirebase(val app: App, val profiles: List, val message: profileId = profile.id, profileName = profile.name, viewId = if (event.type == Event.TYPE_HOMEWORK) MainActivity.DRAWER_ITEM_HOMEWORK else MainActivity.DRAWER_ITEM_AGENDA, - addedDate = metadata.addedDate + addedDate = event.addedDate ).addExtra("eventId", event.id).addExtra("eventDate", event.date.value.toLong()) notificationList += notification } 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 ca11996f..39cec6eb 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 @@ -26,7 +26,7 @@ class DumbCookieJar( ) : CookieJar { private val prefs = context.getSharedPreferences("cookies", Context.MODE_PRIVATE) - private val sessionCookies = mutableSetOf() + val sessionCookies = mutableSetOf() private val savedCookies = mutableSetOf() private fun save(dc: DumbCookie) { sessionCookies.remove(dc) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/bell/BellSyncTimeChooseDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/bell/BellSyncTimeChooseDialog.kt index 63601c76..4116ab83 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/bell/BellSyncTimeChooseDialog.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/bell/BellSyncTimeChooseDialog.kt @@ -100,7 +100,7 @@ class BellSyncTimeChooseDialog( private fun loadTimeList() { launch { val timeItems = withContext(Dispatchers.Default) { - val lessons = app.db.timetableDao().getForDateNow(App.profileId, today) + val lessons = app.db.timetableDao().getAllForDateNow(App.profileId, today) val items = mutableListOf() lessons.forEach { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/day/DayDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/day/DayDialog.kt index 03cc4879..c6a7b2f9 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/day/DayDialog.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/day/DayDialog.kt @@ -86,7 +86,7 @@ class DayDialog( ) val lessons = withContext(Dispatchers.Default) { - app.db.timetableDao().getForDateNow(profileId, date) + app.db.timetableDao().getAllForDateNow(profileId, date) }.filter { it.type != Lesson.TYPE_NO_LESSONS } if (lessons.isNotEmpty()) { run { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventManualDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventManualDialog.kt index d6d175d9..06b5f1a6 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventManualDialog.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventManualDialog.kt @@ -484,7 +484,8 @@ class EventManualDialog( type = type ?: Event.TYPE_DEFAULT, teacherId = teacherId ?: -1, subjectId = subjectId ?: -1, - teamId = teamId ?: -1 + teamId = teamId ?: -1, + addedDate = editingEvent?.addedDate ?: System.currentTimeMillis() ).also { it.addedManually = true } @@ -497,8 +498,7 @@ class EventManualDialog( }, eventObject.id, true, - true, - editingEvent?.addedDate ?: System.currentTimeMillis() + true ) launch { @@ -536,10 +536,9 @@ class EventManualDialog( eventObject.apply { sharedBy = profile?.userCode sharedByName = profile?.studentNameLong + addedDate = System.currentTimeMillis() } - metadataObject.addedDate = System.currentTimeMillis() - api.runCatching(activity) { shareEvent(eventObject.withMetadata(metadataObject)) } ?: run { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/grade/GradeDetailsDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/grade/GradeDetailsDialog.kt index 0e063758..20f3ad21 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/grade/GradeDetailsDialog.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/grade/GradeDetailsDialog.kt @@ -71,7 +71,7 @@ class GradeDetailsDialog( launch { val historyList = withContext(Dispatchers.Default) { - app.db.gradeDao().getAllWithParentIdNow(App.profileId, grade.id) + app.db.gradeDao().getByParentIdNow(App.profileId, grade.id) } if (historyList.isEmpty()) { b.historyVisible = false 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 new file mode 100644 index 00000000..e7d66ed5 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/settings/AttendanceConfigDialog.kt @@ -0,0 +1,75 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-5-4. + */ + +package pl.szczodrzynski.edziennik.ui.dialogs.settings + +import android.annotation.SuppressLint +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.MainActivity +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.databinding.AttendanceConfigDialogBinding +import pl.szczodrzynski.edziennik.onChange + +class AttendanceConfigDialog( + val activity: AppCompatActivity, + private val reloadOnDismiss: Boolean = true, + val onShowListener: ((tag: String) -> Unit)? = null, + val onDismissListener: ((tag: String) -> Unit)? = null +) { + companion object { + const val TAG = "GradesConfigDialog" + } + + private val app by lazy { activity.application as App } + private val profileConfig by lazy { app.config.getFor(app.profileId).attendance } + + private lateinit var b: AttendanceConfigDialogBinding + private lateinit var dialog: AlertDialog + + init { run { + if (activity.isFinishing) + return@run + b = AttendanceConfigDialogBinding.inflate(activity.layoutInflater) + onShowListener?.invoke(TAG) + dialog = MaterialAlertDialogBuilder(activity) + .setTitle(R.string.menu_attendance_config) + .setView(b.root) + .setPositiveButton(R.string.ok) { dialog, _ -> dialog.dismiss() } + .setOnDismissListener { + saveConfig() + onDismissListener?.invoke(TAG) + if (reloadOnDismiss) (activity as? MainActivity)?.reloadTarget() + } + .create() + initView() + loadConfig() + dialog.show() + }} + + @SuppressLint("SetTextI18n") + private fun loadConfig() { + b.useSymbols.isChecked = profileConfig.useSymbols + b.groupConsecutiveDays.isChecked = profileConfig.groupConsecutiveDays + b.showPresenceInMonth.isChecked = profileConfig.showPresenceInMonth + } + + private fun saveConfig() { + // nothing to do here, yet + } + + private fun initView() { + b.useSymbols.onChange { _, isChecked -> + profileConfig.useSymbols = isChecked + } + b.groupConsecutiveDays.onChange { _, isChecked -> + profileConfig.groupConsecutiveDays = isChecked + } + b.showPresenceInMonth.onChange { _, isChecked -> + profileConfig.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 ad29e8f7..e74f3577 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 @@ -4,6 +4,7 @@ package pl.szczodrzynski.edziennik.ui.dialogs.settings +import android.annotation.SuppressLint import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import androidx.core.view.isVisible @@ -59,6 +60,7 @@ class GradesConfigDialog( dialog.show() }} + @SuppressLint("SetTextI18n") private fun loadConfig() { b.customPlusCheckBox.isChecked = profileConfig.plusValue != null b.customPlusValue.isVisible = b.customPlusCheckBox.isChecked diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/teacherabsence/TeacherAbsenceAdapter.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/teacherabsence/TeacherAbsenceAdapter.kt index d8c11307..443d4734 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/teacherabsence/TeacherAbsenceAdapter.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/teacherabsence/TeacherAbsenceAdapter.kt @@ -29,7 +29,7 @@ class TeacherAbsenceAdapter( override fun onBindViewHolder(holder: ViewHolder, position: Int) { val teacherAbsence: TeacherAbsenceFull = teacherAbsenceList[position] - holder.teacherAbsenceTeacher.text = teacherAbsence.teacherFullName + holder.teacherAbsenceTeacher.text = teacherAbsence.teacherName val time = when (teacherAbsence.timeFrom != null && teacherAbsence.timeTo != null) { true -> when (teacherAbsence.dateFrom.compareTo(teacherAbsence.dateTo)) { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/teacherabsence/TeacherAbsenceDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/teacherabsence/TeacherAbsenceDialog.kt index 836197f0..7ffab515 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/teacherabsence/TeacherAbsenceDialog.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/teacherabsence/TeacherAbsenceDialog.kt @@ -46,7 +46,7 @@ class TeacherAbsenceDialog( b.teacherAbsenceView.setHasFixedSize(true) b.teacherAbsenceView.layoutManager = LinearLayoutManager(activity) - app.db.teacherAbsenceDao().getAllByDateFull(profileId, date).observe(activity as LifecycleOwner, Observer { absenceList -> + app.db.teacherAbsenceDao().getAllByDate(profileId, date).observe(activity as LifecycleOwner, Observer { absenceList -> val adapter = TeacherAbsenceAdapter(activity, date, absenceList) b.teacherAbsenceView.adapter = adapter b.teacherAbsenceView.visibility = View.VISIBLE diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/AgendaFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/AgendaFragment.kt index 9ee93be9..ec212cea 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/AgendaFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/AgendaFragment.kt @@ -144,7 +144,7 @@ class AgendaFragment : Fragment(), CoroutineScope { if (!isAdded) return@launch - val lessons = withContext(Dispatchers.Default) { app.db.timetableDao().getAllChangesNow(app.profileId) } + val lessons = withContext(Dispatchers.Default) { app.db.timetableDao().getChangesNow(app.profileId) } val lessonChangeCounters = mutableListOf() lessons.forEach { lesson -> @@ -180,7 +180,7 @@ class AgendaFragment : Fragment(), CoroutineScope { val showTeacherAbsences = app.profile.getStudentData("showTeacherAbsences", true) if (showTeacherAbsences) { - val teacherAbsenceList = withContext(Dispatchers.Default) { app.db.teacherAbsenceDao().getAllFullNow(app.profileId) } + val teacherAbsenceList = withContext(Dispatchers.Default) { app.db.teacherAbsenceDao().getAllNow(app.profileId) } val teacherAbsenceCounters = mutableListOf() teacherAbsenceList.forEach { absence -> diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/announcements/AnnouncementsAdapter.java b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/announcements/AnnouncementsAdapter.java index 7f9b600d..ee88da07 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/announcements/AnnouncementsAdapter.java +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/announcements/AnnouncementsAdapter.java @@ -59,17 +59,20 @@ public class AnnouncementsAdapter extends RecyclerView.Adapter { - if (announcement.text == null || (app.getProfile().getLoginStoreType() == LOGIN_TYPE_LIBRUS && !announcement.seen && app.getNetworkUtils().isOnline())) { + if (announcement.getText() == null || (app.getProfile().getLoginStoreType() == LOGIN_TYPE_LIBRUS && !announcement.getSeen() && app.getNetworkUtils().isOnline())) { EdziennikTask.Companion.announcementGet(App.Companion.getProfileId(), announcement).enqueue(requireContext()); } else { showAnnouncementDetailsDialog(announcement); @@ -162,14 +162,14 @@ public class AnnouncementsFragment extends Fragment { private void showAnnouncementDetailsDialog(AnnouncementFull announcement) { MaterialDialog dialog = new MaterialDialog.Builder(activity) - .title(announcement.subject) + .title(announcement.getSubject()) .customView(R.layout.dialog_announcement, true) .positiveText(R.string.ok) .show(); DialogAnnouncementBinding b = DialogAnnouncementBinding.bind(dialog.getCustomView()); - b.text.setText(announcement.teacherFullName+"\n\n"+ (announcement.startDate != null ? announcement.startDate.getFormattedString() : "-") + (announcement.endDate != null ? " do " + announcement.endDate.getFormattedString() : "")+"\n\n" +announcement.text); - if (!announcement.seen && app.getProfile().getLoginStoreType() != LOGIN_TYPE_LIBRUS) { - announcement.seen = true; + 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) { + announcement.setSeen(true); AsyncTask.execute(() -> App.db.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/modules/attendance/AttendanceAdapter.java b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceAdapter.java deleted file mode 100644 index 7b057395..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceAdapter.java +++ /dev/null @@ -1,134 +0,0 @@ -package pl.szczodrzynski.edziennik.ui.modules.attendance; - -import android.content.Context; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffColorFilter; -import android.os.AsyncTask; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.RecyclerView; - -import java.util.List; - -import pl.szczodrzynski.edziennik.App; -import pl.szczodrzynski.edziennik.R; -import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull; - -import static pl.szczodrzynski.edziennik.data.db.entity.Attendance.TYPE_ABSENT; -import static pl.szczodrzynski.edziennik.data.db.entity.Attendance.TYPE_ABSENT_EXCUSED; -import static pl.szczodrzynski.edziennik.data.db.entity.Attendance.TYPE_BELATED; -import static pl.szczodrzynski.edziennik.data.db.entity.Attendance.TYPE_BELATED_EXCUSED; -import static pl.szczodrzynski.edziennik.data.db.entity.Attendance.TYPE_DAY_FREE; -import static pl.szczodrzynski.edziennik.data.db.entity.Attendance.TYPE_PRESENT; -import static pl.szczodrzynski.edziennik.data.db.entity.Attendance.TYPE_RELEASED; - -public class AttendanceAdapter extends RecyclerView.Adapter { - private Context context; - public List attendanceList; - - //getting the context and product list with constructor - public AttendanceAdapter(Context mCtx, List noticeList) { - this.context = mCtx; - this.attendanceList = noticeList; - } - - @NonNull - @Override - public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - //inflating and returning our view holder - LayoutInflater inflater = LayoutInflater.from(context); - View view = inflater.inflate(R.layout.row_attendance_item, parent, false); - return new ViewHolder(view); - } - - @Override - public void onBindViewHolder(@NonNull ViewHolder holder, int position) { - App app = (App) context.getApplicationContext(); - - AttendanceFull attendance = attendanceList.get(position); - - holder.attendanceLessonTopic.setText(attendance.lessonTopic); - holder.attendanceTeacher.setText(attendance.teacherFullName); - holder.attendanceSubject.setText(attendance.subjectLongName); - holder.attendanceDate.setText(attendance.lessonDate.getStringDmy()); - holder.attendanceTime.setText(attendance.startTime.getStringHM()); - - switch (attendance.type) { - case TYPE_DAY_FREE: - holder.attendanceType.getBackground().setColorFilter(new PorterDuffColorFilter(0xff166ee0, PorterDuff.Mode.MULTIPLY)); - holder.attendanceType.setText(R.string.attendance_free_day); - break; - case TYPE_ABSENT: - holder.attendanceType.getBackground().setColorFilter(new PorterDuffColorFilter(0xfff44336, PorterDuff.Mode.MULTIPLY)); - holder.attendanceType.setText(R.string.attendance_absent); - break; - case TYPE_ABSENT_EXCUSED: - holder.attendanceType.getBackground().setColorFilter(new PorterDuffColorFilter(0xffaeea00, PorterDuff.Mode.MULTIPLY)); - holder.attendanceType.setText(R.string.attendance_absent_excused); - break; - case TYPE_BELATED: - holder.attendanceType.getBackground().setColorFilter(new PorterDuffColorFilter(0xffffca28, PorterDuff.Mode.MULTIPLY)); - holder.attendanceType.setText(R.string.attendance_belated); - break; - case TYPE_BELATED_EXCUSED: - holder.attendanceType.getBackground().setColorFilter(new PorterDuffColorFilter(0xff4bb733, PorterDuff.Mode.MULTIPLY)); - holder.attendanceType.setText(R.string.attendance_belated_excused); - break; - case TYPE_RELEASED: - holder.attendanceType.getBackground().setColorFilter(new PorterDuffColorFilter(0xff9e9e9e, PorterDuff.Mode.MULTIPLY)); - holder.attendanceType.setText(R.string.attendance_released); - break; - case TYPE_PRESENT: - holder.attendanceType.getBackground().setColorFilter(new PorterDuffColorFilter(0xffffae00, PorterDuff.Mode.MULTIPLY)); - holder.attendanceType.setText(R.string.attendance_present); - break; - default: - holder.attendanceType.getBackground().setColorFilter(new PorterDuffColorFilter(0xff03a9f4, PorterDuff.Mode.MULTIPLY)); - holder.attendanceType.setText("?"); - break; - } - - if (!attendance.seen) { - holder.attendanceLessonTopic.setBackground(context.getResources().getDrawable(R.drawable.bg_rounded_8dp)); - holder.attendanceLessonTopic.getBackground().setColorFilter(new PorterDuffColorFilter(0x692196f3, PorterDuff.Mode.MULTIPLY)); - attendance.seen = true; - AsyncTask.execute(() -> { - App.db.metadataDao().setSeen(App.Companion.getProfileId(), attendance, true); - //Intent i = new Intent("android.intent.action.MAIN").putExtra(MainActivity.ACTION_UPDATE_BADGES, "yes, sure"); - //context.sendBroadcast(i); - }); - } - else { - holder.attendanceLessonTopic.setBackground(null); - } - } - - @Override - public int getItemCount() { - return attendanceList.size(); - } - - class ViewHolder extends RecyclerView.ViewHolder { - - TextView attendanceType; - TextView attendanceLessonTopic; - TextView attendanceSubject; - TextView attendanceTeacher; - TextView attendanceDate; - TextView attendanceTime; - - ViewHolder(View itemView) { - super(itemView); - attendanceType = itemView.findViewById(R.id.attendanceType); - attendanceLessonTopic = itemView.findViewById(R.id.attendanceLessonTopic); - attendanceSubject = itemView.findViewById(R.id.attendanceSubject); - attendanceTeacher = itemView.findViewById(R.id.attendanceTeacher); - attendanceDate = itemView.findViewById(R.id.attendanceDate); - attendanceTime = itemView.findViewById(R.id.attendanceTime); - } - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceAdapter.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceAdapter.kt new file mode 100644 index 00000000..0dcae97e --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceAdapter.kt @@ -0,0 +1,190 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-29. + */ + +package pl.szczodrzynski.edziennik.ui.modules.attendance + +import android.animation.ObjectAnimator +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.isInvisible +import androidx.core.view.isVisible +import androidx.recyclerview.widget.RecyclerView +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull +import pl.szczodrzynski.edziennik.startCoroutineTimer +import pl.szczodrzynski.edziennik.ui.modules.attendance.models.* +import pl.szczodrzynski.edziennik.ui.modules.attendance.viewholder.* +import pl.szczodrzynski.edziennik.ui.modules.grades.models.ExpandableItemModel +import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.BindableViewHolder +import kotlin.coroutines.CoroutineContext + +class AttendanceAdapter( + val activity: AppCompatActivity, + val type: Int, + var onAttendanceClick: ((item: AttendanceFull) -> Unit)? = null +) : RecyclerView.Adapter(), CoroutineScope { + companion object { + private const val TAG = "AttendanceAdapter" + private const val ITEM_TYPE_ATTENDANCE = 0 + private const val ITEM_TYPE_DAY_RANGE = 1 + private const val ITEM_TYPE_MONTH = 2 + private const val ITEM_TYPE_SUBJECT = 3 + private const val ITEM_TYPE_TYPE = 4 + private const val ITEM_TYPE_EMPTY = 5 + const val STATE_CLOSED = 0 + const val STATE_OPENED = 1 + } + + private val app = activity.applicationContext as App + // optional: place the manager here + + private val job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Main + + var items = mutableListOf() + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + val inflater = LayoutInflater.from(parent.context) + return when (viewType) { + ITEM_TYPE_ATTENDANCE -> AttendanceViewHolder(inflater, parent) + ITEM_TYPE_DAY_RANGE -> DayRangeViewHolder(inflater, parent) + ITEM_TYPE_MONTH -> MonthViewHolder(inflater, parent) + ITEM_TYPE_SUBJECT -> SubjectViewHolder(inflater, parent) + ITEM_TYPE_TYPE -> TypeViewHolder(inflater, parent) + ITEM_TYPE_EMPTY -> EmptyViewHolder(inflater, parent) + else -> throw IllegalArgumentException("Incorrect viewType") + } + } + + override fun getItemViewType(position: Int): Int { + return when (items[position]) { + is AttendanceFull -> ITEM_TYPE_ATTENDANCE + is AttendanceDayRange -> ITEM_TYPE_DAY_RANGE + is AttendanceMonth -> ITEM_TYPE_MONTH + is AttendanceSubject -> ITEM_TYPE_SUBJECT + is AttendanceTypeGroup -> ITEM_TYPE_TYPE + is AttendanceEmpty -> ITEM_TYPE_EMPTY + else -> throw IllegalArgumentException("Incorrect viewType") + } + } + + private val onClickListener = View.OnClickListener { view -> + val model = view.getTag(R.string.tag_key_model) + if (model is AttendanceFull) { + onAttendanceClick?.invoke(model) + return@OnClickListener + } + if (model !is ExpandableItemModel<*>) + return@OnClickListener + expandModel(model, view) + } + + fun expandModel(model: ExpandableItemModel<*>?, view: View?, notifyAdapter: Boolean = true) { + model ?: return + val position = items.indexOf(model) + if (position == -1) + return + + view?.findViewById(R.id.dropdownIcon)?.let { dropdownIcon -> + ObjectAnimator.ofFloat( + dropdownIcon, + View.ROTATION, + if (model.state == STATE_CLOSED) 0f else 180f, + if (model.state == STATE_CLOSED) 180f else 0f + ).setDuration(200).start(); + } + + if (model is AttendanceDayRange || model is AttendanceMonth || model is AttendanceTypeGroup) { + // hide the preview, show summary + val preview = view?.findViewById(R.id.previewContainer) + val summary = view?.findViewById(R.id.summaryContainer) + val percentage = view?.findViewById(R.id.percentage) + preview?.isInvisible = model.state == STATE_CLOSED + summary?.isInvisible = model.state != STATE_CLOSED + percentage?.isVisible = model.state != STATE_CLOSED + } + + if (model.state == STATE_CLOSED) { + + val subItems = when { + model.items.isEmpty() -> listOf(AttendanceEmpty()) + else -> model.items + } + + model.state = STATE_OPENED + items.addAll(position + 1, subItems.filterNotNull()) + if (notifyAdapter) notifyItemRangeInserted(position + 1, subItems.size) + } + else { + val start = position + 1 + var end: Int = items.size + for (i in start until items.size) { + val model1 = items[i] + val level = (model1 as? ExpandableItemModel<*>)?.level ?: 3 + if (level <= model.level) { + end = i + break + } else { + if (model1 is ExpandableItemModel<*> && model1.state == STATE_OPENED) { + model1.state = STATE_CLOSED + } + } + } + + if (end != -1) { + items.subList(start, end).clear() + if (notifyAdapter) notifyItemRangeRemoved(start, end - start) + } + + model.state = STATE_CLOSED + } + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + val item = items[position] + if (holder !is BindableViewHolder<*, *>) + return + + val viewType = when (holder) { + is AttendanceViewHolder -> ITEM_TYPE_ATTENDANCE + is DayRangeViewHolder -> ITEM_TYPE_DAY_RANGE + is MonthViewHolder -> ITEM_TYPE_MONTH + is SubjectViewHolder -> ITEM_TYPE_SUBJECT + is TypeViewHolder -> ITEM_TYPE_TYPE + is EmptyViewHolder -> ITEM_TYPE_EMPTY + else -> throw IllegalArgumentException("Incorrect viewType") + } + holder.itemView.setTag(R.string.tag_key_view_type, viewType) + holder.itemView.setTag(R.string.tag_key_position, position) + holder.itemView.setTag(R.string.tag_key_model, item) + + when { + holder is AttendanceViewHolder && item is AttendanceFull -> holder.onBind(activity, app, item, position, this) + holder is DayRangeViewHolder && item is AttendanceDayRange -> holder.onBind(activity, app, item, position, this) + holder is MonthViewHolder && item is AttendanceMonth -> holder.onBind(activity, app, item, position, this) + holder is SubjectViewHolder && item is AttendanceSubject -> holder.onBind(activity, app, item, position, this) + holder is TypeViewHolder && item is AttendanceTypeGroup -> holder.onBind(activity, app, item, position, this) + holder is EmptyViewHolder && item is AttendanceEmpty -> holder.onBind(activity, app, item, position, this) + } + + holder.itemView.setOnClickListener(onClickListener) + } + + fun notifyItemChanged(model: Any) { + startCoroutineTimer(1000L, 0L) { + val index = items.indexOf(model) + if (index != -1) + notifyItemChanged(index) + } + } + + override fun getItemCount() = items.size +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceBar.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceBar.kt new file mode 100644 index 00000000..70be9256 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceBar.kt @@ -0,0 +1,106 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-5-1. + */ + +package pl.szczodrzynski.edziennik.ui.modules.attendance + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.* +import android.text.TextPaint +import android.util.AttributeSet +import android.view.View +import pl.szczodrzynski.edziennik.dp +import pl.szczodrzynski.edziennik.utils.Colors +import kotlin.math.roundToInt + +/* https://github.com/JakubekWeg/Mobishit/blob/master/app/src/main/java/jakubweg/mobishit/view/AttendanceBarView.kt */ +class AttendanceBar : View { + + constructor(context: Context) : super(context) + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) + + constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) + + private var attendancesList = listOf() + private val mainPaint = Paint(Paint.ANTI_ALIAS_FLAG) + private val textPaint = TextPaint(Paint.ANTI_ALIAS_FLAG).also { + it.textAlign = Paint.Align.CENTER + } + private var mPath = Path() + private var mCornerRadius: Float = 0.toFloat() + + init { + mCornerRadius = 4.dp.toFloat() + + if (isInEditMode) + setAttendanceData(listOf( + 0xff43a047.toInt() to 23, + 0xff009688.toInt() to 187, + 0xff3f51b5.toInt() to 46, + 0xff3f51b5.toInt() to 5, + 0xffffc107.toInt() to 5, + 0xff9e9e9e.toInt() to 26, + 0xff76ff03.toInt() to 34, + 0xffff3d00.toInt() to 8 + )) + } + + // color, count + private class AttendanceItem(var color: Int, var count: Int) + + fun setAttendanceData(list: List>) { + attendancesList = list.map { AttendanceItem(it.first, it.second) } + setWillNotDraw(false) + invalidate() + } + + override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { + super.onSizeChanged(w, h, oldw, oldh) + val r = RectF(0f, 0f, w.toFloat(), h.toFloat()) + mPath = Path().apply { + addRoundRect(r, mCornerRadius, mCornerRadius, Path.Direction.CW) + close() + } + } + + @SuppressLint("DrawAllocation", "CanvasSize") + override fun onDraw(canvas: Canvas?) { + canvas ?: return + + val sum = attendancesList.sumBy { it.count } + if (sum == 0) { + return + } + + canvas.clipPath(mPath) + + val top = paddingTop.toFloat() + val bottom = (height - paddingBottom).toFloat() + var left = paddingLeft.toFloat() + val unitWidth = (width - paddingRight - paddingLeft).toFloat() / sum.toFloat() + + textPaint.color = Color.BLACK + textPaint.textSize = 14.dp.toFloat() + + for (e in attendancesList) { + if (e.count == 0) + continue + + val width = unitWidth * e.count + mainPaint.color = e.color + canvas.drawRect(left, top, left + width, bottom, mainPaint) + + val percentage = (100f * e.count / sum).roundToInt().toString() + "%" + val textBounds = Rect() + textPaint.getTextBounds(percentage, 0, percentage.length, textBounds) + if (width > textBounds.width() + 8.dp && height > textBounds.height() + 2.dp) { + textPaint.color = Colors.legibleTextColor(e.color) + canvas.drawText(percentage, left + width / 2, bottom - height / 2 + textBounds.height()/2, textPaint) + } + + left += width + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceDetailsDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceDetailsDialog.kt new file mode 100644 index 00000000..ce49efbd --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceDetailsDialog.kt @@ -0,0 +1,64 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-5-9. + */ + +package pl.szczodrzynski.edziennik.ui.modules.attendance + +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity +import androidx.core.graphics.ColorUtils +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull +import pl.szczodrzynski.edziennik.databinding.AttendanceDetailsDialogBinding +import pl.szczodrzynski.edziennik.setTintColor +import kotlin.coroutines.CoroutineContext + +class AttendanceDetailsDialog( + val activity: AppCompatActivity, + val attendance: AttendanceFull, + val onShowListener: ((tag: String) -> Unit)? = null, + val onDismissListener: ((tag: String) -> Unit)? = null +) : CoroutineScope { + companion object { + private const val TAG = "AttendanceDetailsDialog" + } + + private lateinit var app: App + private lateinit var b: AttendanceDetailsDialogBinding + private lateinit var dialog: AlertDialog + + private val job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Main + + // local variables go here + + init { run { + if (activity.isFinishing) + return@run + onShowListener?.invoke(TAG) + app = activity.applicationContext as App + b = AttendanceDetailsDialogBinding.inflate(activity.layoutInflater) + dialog = MaterialAlertDialogBuilder(activity) + .setView(b.root) + .setPositiveButton(R.string.close, null) + .setOnDismissListener { + onDismissListener?.invoke(TAG) + } + .show() + val manager = app.attendanceManager + + val attendanceColor = manager.getAttendanceColor(attendance) + b.attendance = attendance + b.devMode = App.debugMode + b.attendanceName.setTextColor(if (ColorUtils.calculateLuminance(attendanceColor) > 0.3) 0xaa000000.toInt() else 0xccffffff.toInt()) + b.attendanceName.background.setTintColor(attendanceColor) + + b.attendanceIsCounted.setText(if (attendance.isCounted) R.string.yes else R.string.no) + }} +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceFragment.kt new file mode 100644 index 00000000..0af177f9 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceFragment.kt @@ -0,0 +1,117 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-30. + */ + +package pl.szczodrzynski.edziennik.ui.modules.attendance + +import android.os.AsyncTask +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Toast +import androidx.fragment.app.Fragment +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.* +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.databinding.AttendanceFragmentBinding +import pl.szczodrzynski.edziennik.ui.dialogs.settings.AttendanceConfigDialog +import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.FragmentLazyPagerAdapter +import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem +import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetSeparatorItem +import kotlin.coroutines.CoroutineContext + +class AttendanceFragment : Fragment(), CoroutineScope { + companion object { + private const val TAG = "AttendanceFragment" + const val VIEW_SUMMARY = 0 + const val VIEW_DAYS = 1 + const val VIEW_MONTHS = 2 + const val VIEW_TYPES = 3 + const val VIEW_LIST = 4 + var pageSelection = 1 + } + + private lateinit var app: App + private lateinit var activity: MainActivity + private lateinit var b: AttendanceFragmentBinding + + private val job: Job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Main + + // local/private variables go here + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + activity = (getActivity() as MainActivity?) ?: return null + context ?: return null + app = activity.application as App + b = AttendanceFragmentBinding.inflate(inflater) + b.refreshLayout.setParent(activity.swipeRefreshLayout) + return b.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + if (!isAdded) return + + activity.bottomSheet.prependItems( + BottomSheetPrimaryItem(true) + .withTitle(R.string.menu_attendance_config) + .withIcon(CommunityMaterial.Icon2.cmd_settings_outline) + .withOnClickListener(View.OnClickListener { + activity.bottomSheet.close() + AttendanceConfigDialog(activity, true, null, null) + }), + 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_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 + + val pagerAdapter = FragmentLazyPagerAdapter( + fragmentManager ?: return, + b.refreshLayout, + listOf( + AttendanceSummaryFragment() to getString(R.string.attendance_tab_summary), + + AttendanceListFragment().apply { + arguments = Bundle("viewType" to VIEW_DAYS) + } to getString(R.string.attendance_tab_days), + + AttendanceListFragment().apply { + arguments = Bundle("viewType" to VIEW_MONTHS) + } to getString(R.string.attendance_tab_months), + + AttendanceListFragment().apply { + arguments = Bundle("viewType" to VIEW_TYPES) + } to getString(R.string.attendance_tab_types), + + AttendanceListFragment().apply { + arguments = Bundle("viewType" to VIEW_LIST) + } to getString(R.string.attendance_tab_list) + ) + ) + b.viewPager.apply { + offscreenPageLimit = 1 + adapter = pagerAdapter + currentItem = pageSelection + addOnPageSelectedListener { + pageSelection = it + app.config.forProfile().attendance.attendancePageSelection = it + } + b.tabLayout.setupWithViewPager(this) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceFragment.java b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceFragment_.java similarity index 93% rename from app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceFragment.java rename to app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceFragment_.java index 2b5317a6..c46b3e09 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceFragment.java +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceFragment_.java @@ -51,7 +51,7 @@ import static pl.szczodrzynski.edziennik.data.db.entity.Attendance.TYPE_RELEASED import static pl.szczodrzynski.edziennik.data.db.entity.LoginStore.LOGIN_TYPE_VULCAN; import static pl.szczodrzynski.edziennik.data.db.entity.Metadata.TYPE_ATTENDANCE; -public class AttendanceFragment extends Fragment { +public class AttendanceFragment_ extends Fragment { private App app = null; private MainActivity activity = null; @@ -217,21 +217,21 @@ public class AttendanceFragment extends Fragment { subjectTotalCount = new LongSparseArray<>(); subjectAbsentCount = new LongSparseArray<>(); for (AttendanceFull attendance: attendanceList) { - if (app.getProfile().getLoginStoreType() == LOGIN_TYPE_VULCAN && attendance.type == TYPE_RELEASED) + if (app.getProfile().getLoginStoreType() == LOGIN_TYPE_VULCAN && attendance.getBaseType() == TYPE_RELEASED) continue; - int[] subjectTotal = subjectTotalCount.get(attendance.subjectId, new int[3]); - int[] subjectAbsent = subjectAbsentCount.get(attendance.subjectId, new int[3]); + int[] subjectTotal = subjectTotalCount.get(attendance.getSubjectId(), new int[3]); + int[] subjectAbsent = subjectAbsentCount.get(attendance.getSubjectId(), new int[3]); subjectTotal[0]++; - subjectTotal[attendance.semester]++; + subjectTotal[attendance.getSemester()]++; - if (attendance.type == TYPE_ABSENT || attendance.type == TYPE_ABSENT_EXCUSED) { + if (attendance.getBaseType() == TYPE_ABSENT || attendance.getBaseType() == TYPE_ABSENT_EXCUSED) { subjectAbsent[0]++; - subjectAbsent[attendance.semester]++; + subjectAbsent[attendance.getSemester()]++; } - subjectTotalCount.put(attendance.subjectId, subjectTotal); - subjectAbsentCount.put(attendance.subjectId, subjectAbsent); + subjectTotalCount.put(attendance.getSubjectId(), subjectTotal); + subjectAbsentCount.put(attendance.getSubjectId(), subjectAbsent); } } @@ -247,13 +247,13 @@ public class AttendanceFragment extends Fragment { List filteredList = new ArrayList<>(); for (AttendanceFull attendance: attendanceList) { - if (displayMode != MODE_YEAR && attendance.semester != displayMode) + if (displayMode != MODE_YEAR && attendance.getSemester() != displayMode) continue; - if (subjectIdFilter != -1 && attendance.subjectId != subjectIdFilter) + if (subjectIdFilter != -1 && attendance.getSubjectId() != subjectIdFilter) continue; - if (attendance.type != TYPE_PRESENT) + if (attendance.getBaseType() != TYPE_PRESENT) filteredList.add(attendance); - switch (attendance.type) { + switch (attendance.getBaseType()) { case TYPE_PRESENT: presentCount++; break; @@ -278,11 +278,12 @@ public class AttendanceFragment extends Fragment { b.attendanceView.setVisibility(View.VISIBLE); b.attendanceNoData.setVisibility(View.GONE); if ((adapter = (AttendanceAdapter) b.attendanceView.getAdapter()) != null) { - adapter.attendanceList = filteredList; + //adapter.setItems(filteredList); adapter.notifyDataSetChanged(); } else { - adapter = new AttendanceAdapter(getContext(), filteredList); + //adapter = new AttendanceAdapter(activity, true, null); + //adapter.setItems(filteredList); b.attendanceView.setAdapter(adapter); } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceListFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceListFragment.kt new file mode 100644 index 00000000..8e68cdbb --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceListFragment.kt @@ -0,0 +1,230 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-30. + */ + +package pl.szczodrzynski.edziennik.ui.modules.attendance + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.view.isVisible +import androidx.lifecycle.Observer +import androidx.recyclerview.widget.LinearLayoutManager +import kotlinx.coroutines.* +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.MainActivity +import pl.szczodrzynski.edziennik.data.db.entity.Attendance +import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull +import pl.szczodrzynski.edziennik.databinding.AttendanceListFragmentBinding +import pl.szczodrzynski.edziennik.isNotNullNorEmpty +import pl.szczodrzynski.edziennik.startCoroutineTimer +import pl.szczodrzynski.edziennik.ui.modules.attendance.models.AttendanceDayRange +import pl.szczodrzynski.edziennik.ui.modules.attendance.models.AttendanceMonth +import pl.szczodrzynski.edziennik.ui.modules.attendance.models.AttendanceTypeGroup +import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.LazyFragment +import pl.szczodrzynski.edziennik.ui.modules.grades.models.GradesSubject +import pl.szczodrzynski.edziennik.utils.models.Date +import kotlin.coroutines.CoroutineContext + +class AttendanceListFragment : LazyFragment(), CoroutineScope { + companion object { + private const val TAG = "AttendanceListFragment" + } + + private lateinit var app: App + private lateinit var activity: MainActivity + private lateinit var b: AttendanceListFragmentBinding + + private val job: Job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Main + + // local/private variables go here + private val manager by lazy { app.attendanceManager } + private var viewType = AttendanceFragment.VIEW_DAYS + private var expandSubjectId = 0L + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + activity = (getActivity() as MainActivity?) ?: return null + context ?: return null + app = activity.application as App + b = AttendanceListFragmentBinding.inflate(inflater) + return b.root + } + + override fun onPageCreated(): Boolean { startCoroutineTimer(100L) { + if (!isAdded) return@startCoroutineTimer + + viewType = arguments?.getInt("viewType") ?: AttendanceFragment.VIEW_DAYS + expandSubjectId = arguments?.getLong("gradesSubjectId") ?: 0L + + val adapter = AttendanceAdapter(activity, viewType) + var firstRun = true + + app.db.attendanceDao().getAll(App.profileId).observe(this@AttendanceListFragment, Observer { items -> this@AttendanceListFragment.launch { + if (!isAdded) return@launch + + // load & configure the adapter + adapter.items = withContext(Dispatchers.Default) { processAttendance(items) } + if (adapter.items.isNotNullNorEmpty() && b.list.adapter == null) { + b.list.adapter = adapter + b.list.apply { + setHasFixedSize(true) + layoutManager = LinearLayoutManager(context) + addOnScrollListener(onScrollListener) + } + } + adapter.notifyDataSetChanged() + setSwipeToRefresh(adapter.items.isNullOrEmpty()) + + if (firstRun) { + expandSubject(adapter) + firstRun = false + } + + // show/hide relevant views + b.progressBar.isVisible = false + if (adapter.items.isNullOrEmpty()) { + b.list.isVisible = false + b.noData.isVisible = true + } else { + b.list.isVisible = true + b.noData.isVisible = false + } + }}) + + adapter.onAttendanceClick = { + AttendanceDetailsDialog(activity, it) + } + }; return true} + + private fun expandSubject(adapter: AttendanceAdapter) { + var expandSubjectModel: GradesSubject? = null + if (expandSubjectId != 0L) { + expandSubjectModel = adapter.items.firstOrNull { it is GradesSubject && it.subjectId == expandSubjectId } as? GradesSubject + adapter.expandModel( + model = expandSubjectModel, + view = null, + notifyAdapter = false + ) + } + + startCoroutineTimer(500L) { + if (expandSubjectModel != null) { + b.list.smoothScrollToPosition( + adapter.items.indexOf(expandSubjectModel) + expandSubjectModel.semesters.size + (expandSubjectModel.semesters.firstOrNull()?.grades?.size ?: 0) + ) + } + } + } + + @Suppress("SuspendFunctionOnCoroutineScope") + private fun processAttendance(attendance: List): MutableList { + if (attendance.isEmpty()) + return mutableListOf() + + val groupConsecutiveDays = app.config.forProfile().attendance.groupConsecutiveDays + val showPresenceInMonth = app.config.forProfile().attendance.showPresenceInMonth + + if (viewType == AttendanceFragment.VIEW_DAYS) { + val items = attendance + .filter { it.baseType != Attendance.TYPE_PRESENT } + .groupBy { it.date } + .map { AttendanceDayRange( + rangeStart = it.key, + rangeEnd = null, + items = it.value.toMutableList() + ) } + .toMutableList() + + if (groupConsecutiveDays) { + items.sortByDescending { it.rangeStart } + val iterator = items.listIterator() + + if (!iterator.hasNext()) + return items.toMutableList() + var element = iterator.next() + while (iterator.hasNext()) { + var nextElement = iterator.next() + while (Date.diffDays(element.rangeStart, nextElement.rangeStart) <= 1 && iterator.hasNext()) { + if (element.rangeEnd == null) + element.rangeEnd = element.rangeStart + + element.items.addAll(nextElement.items) + element.rangeStart = nextElement.rangeStart + iterator.remove() + nextElement = iterator.next() + } + element = nextElement + } + } + + return items.toMutableList() + } + else if (viewType == AttendanceFragment.VIEW_MONTHS) { + val items = attendance + .groupBy { it.date.year to it.date.month } + .map { AttendanceMonth( + year = it.key.first, + month = it.key.second, + items = it.value.toMutableList() + ) } + + items.forEach { month -> + month.typeCountMap = month.items + .groupBy { it.typeObject } + .map { it.key to it.value.size } + .sortedBy { it.first } + .toMap() + + val totalCount = month.typeCountMap.entries.sumBy { + if (!it.key.isCounted || it.key.baseType == Attendance.TYPE_UNKNOWN) + 0 + else it.value + } + val presenceCount = month.typeCountMap.entries.sumBy { + when (it.key.baseType) { + Attendance.TYPE_PRESENT, + Attendance.TYPE_PRESENT_CUSTOM, + Attendance.TYPE_BELATED, + Attendance.TYPE_BELATED_EXCUSED, + Attendance.TYPE_RELEASED -> if (it.key.isCounted) it.value else 0 + else -> 0 + } + } + + month.percentage = if (totalCount == 0) + 0f + else + presenceCount.toFloat() / totalCount.toFloat() * 100f + + if (!showPresenceInMonth) + month.items.removeAll { it.baseType == Attendance.TYPE_PRESENT } + } + + return items.toMutableList() + } + else if (viewType == AttendanceFragment.VIEW_TYPES) { + val items = attendance + .groupBy { it.typeObject } + .map { AttendanceTypeGroup( + type = it.key, + items = it.value.toMutableList() + ) } + .sortedBy { it.items.size } + + items.forEach { type -> + type.percentage = if (attendance.isEmpty()) + 0f + else + type.items.size.toFloat() / attendance.size.toFloat() * 100f + + type.semesterCount = type.items.count { it.semester == app.profile.currentSemester } + } + + return items.toMutableList() + } + return attendance.filter { it.baseType != Attendance.TYPE_PRESENT }.toMutableList() + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceSummaryFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceSummaryFragment.kt new file mode 100644 index 00000000..5f61020b --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceSummaryFragment.kt @@ -0,0 +1,306 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-5-4. + */ + +package pl.szczodrzynski.edziennik.ui.modules.attendance + +import android.graphics.Color +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.animation.AccelerateDecelerateInterpolator +import android.view.animation.Animation +import android.view.animation.Transformation +import android.widget.LinearLayout +import android.widget.TextView +import androidx.core.graphics.ColorUtils +import androidx.core.view.isInvisible +import androidx.core.view.isVisible +import androidx.lifecycle.Observer +import androidx.recyclerview.widget.LinearLayoutManager +import kotlinx.coroutines.* +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.db.entity.Attendance +import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull +import pl.szczodrzynski.edziennik.databinding.AttendanceSummaryFragmentBinding +import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceFragment.Companion.VIEW_SUMMARY +import pl.szczodrzynski.edziennik.ui.modules.attendance.models.AttendanceSubject +import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.LazyFragment +import pl.szczodrzynski.edziennik.ui.modules.grades.models.GradesSubject +import pl.szczodrzynski.edziennik.utils.models.Date +import java.text.DecimalFormat +import kotlin.coroutines.CoroutineContext + +class AttendanceSummaryFragment : LazyFragment(), CoroutineScope { + companion object { + private const val TAG = "AttendanceSummaryFragment" + private var periodSelection = 0 + } + + private lateinit var app: App + private lateinit var activity: MainActivity + private lateinit var b: AttendanceSummaryFragmentBinding + + private val job: Job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Main + + // local/private variables go here + private val manager by lazy { app.attendanceManager } + private var expandSubjectId = 0L + private var attendance = listOf() + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + activity = (getActivity() as MainActivity?) ?: return null + context ?: return null + app = activity.application as App + b = AttendanceSummaryFragmentBinding.inflate(inflater) + return b.root + } + + override fun onPageCreated(): Boolean { startCoroutineTimer(100L) { + if (!isAdded) return@startCoroutineTimer + + expandSubjectId = arguments?.getLong("gradesSubjectId") ?: 0L + + val adapter = AttendanceAdapter(activity, VIEW_SUMMARY) + var firstRun = true + + app.db.attendanceDao().getAll(App.profileId).observe(this@AttendanceSummaryFragment, Observer { items -> this@AttendanceSummaryFragment.launch { + if (!isAdded) return@launch + + // load & configure the adapter + attendance = items + adapter.items = withContext(Dispatchers.Default) { processAttendance() } + if (adapter.items.isNotNullNorEmpty() && b.list.adapter == null) { + b.list.adapter = adapter + b.list.apply { + setHasFixedSize(false) + layoutManager = LinearLayoutManager(context) + isNestedScrollingEnabled = false + } + } + adapter.notifyDataSetChanged() + setSwipeToRefresh(adapter.items.isNullOrEmpty()) + + if (firstRun) { + expandSubject(adapter) + firstRun = false + } + + // show/hide relevant views + b.progressBar.isVisible = false + if (adapter.items.isNullOrEmpty()) { + b.statsLayout.isVisible = false + b.list.isVisible = false + b.noData.isVisible = true + } else { + b.statsLayout.isVisible = true + b.list.isVisible = true + b.noData.isVisible = false + } + }}) + + adapter.onAttendanceClick = { + AttendanceDetailsDialog(activity, it) + } + + b.toggleGroup.check(when (periodSelection) { + 0 -> R.id.allYear + 1 -> R.id.semester1 + 2 -> R.id.semester2 + else -> R.id.allYear + }) + b.toggleGroup.addOnButtonCheckedListener { _, checkedId, isChecked -> + if (!isChecked) + return@addOnButtonCheckedListener + periodSelection = when (checkedId) { + R.id.allYear -> 0 + R.id.semester1 -> 1 + R.id.semester2 -> 2 + else -> 0 + } + this@AttendanceSummaryFragment.launch { + adapter.items = withContext(Dispatchers.Default) { processAttendance() } + if (adapter.items.isNullOrEmpty()) { + b.statsLayout.isVisible = false + b.list.isVisible = false + b.noData.isVisible = true + } else { + b.statsLayout.isVisible = true + b.list.isVisible = true + b.noData.isVisible = false + } + adapter.notifyDataSetChanged() + } + } + }; return true} + + private fun expandSubject(adapter: AttendanceAdapter) { + var expandSubjectModel: GradesSubject? = null + if (expandSubjectId != 0L) { + expandSubjectModel = adapter.items.firstOrNull { it is GradesSubject && it.subjectId == expandSubjectId } as? GradesSubject + adapter.expandModel( + model = expandSubjectModel, + view = null, + notifyAdapter = false + ) + } + + startCoroutineTimer(500L) { + if (expandSubjectModel != null) { + b.list.smoothScrollToPosition( + adapter.items.indexOf(expandSubjectModel) + expandSubjectModel.semesters.size + (expandSubjectModel.semesters.firstOrNull()?.grades?.size ?: 0) + ) + } + } + } + + @Suppress("SuspendFunctionOnCoroutineScope") + private fun processAttendance(): MutableList { + val attendance = when (periodSelection) { + 0 -> attendance + 1 -> attendance.filter { it.semester == 1 } + 2 -> attendance.filter { it.semester == 2 } + else -> attendance + } + + if (attendance.isEmpty()) + return mutableListOf() + + val items = attendance + .groupBy { it.subjectId } + .map { AttendanceSubject( + subjectId = it.key, + subjectName = it.value.firstOrNull()?.subjectLongName ?: "", + items = it.value.toMutableList() + ) } + .sortedBy { it.subjectName.toLowerCase() } + + var totalCountSum = 0 + var presenceCountSum = 0 + + items.forEach { subject -> + subject.typeCountMap = subject.items + .groupBy { it.typeObject } + .map { it.key to it.value.size } + .sortedBy { it.first } + .toMap() + + val totalCount = subject.typeCountMap.entries.sumBy { + if (!it.key.isCounted || it.key.baseType == Attendance.TYPE_UNKNOWN) + 0 + else it.value + } + val presenceCount = subject.typeCountMap.entries.sumBy { + when (it.key.baseType) { + Attendance.TYPE_PRESENT, + Attendance.TYPE_PRESENT_CUSTOM, + Attendance.TYPE_BELATED, + Attendance.TYPE_BELATED_EXCUSED, + Attendance.TYPE_RELEASED -> if (it.key.isCounted) it.value else 0 + else -> 0 + } + } + totalCountSum += totalCount + presenceCountSum += presenceCount + + subject.percentage = if (totalCount == 0) + 0f + else + presenceCount.toFloat() / totalCount.toFloat() * 100f + + if (!false /* showPresenceInSubject */) + subject.items.removeAll { it.baseType == Attendance.TYPE_PRESENT } + } + + val typeCountMap = attendance + .groupBy { it.typeObject } + .map { it.key to it.value.size } + .sortedBy { it.first } + .toMap() + + val percentage = if (totalCountSum == 0) + 0f + else + presenceCountSum.toFloat() / totalCountSum.toFloat() * 100f + + launch { + b.attendanceBar.setAttendanceData(typeCountMap.map { manager.getAttendanceColor(it.key) to it.value }) + b.attendanceBar.isInvisible = typeCountMap.isEmpty() + + b.previewContainer.removeAllViews() + //val sum = typeCountMap.entries.sumBy { it.value }.toFloat() + typeCountMap.forEach { (type, count) -> + val layout = LinearLayout(activity) + val attendanceObject = Attendance( + profileId = 0, + id = 0, + baseType = type.baseType, + typeName = "", + typeShort = type.typeShort, + typeSymbol = type.typeSymbol, + typeColor = type.typeColor, + date = Date(0, 0, 0), + startTime = null, + semester = 0, + teacherId = 0, + subjectId = 0, + addedDate = 0 + ) + layout.addView(AttendanceView(activity, attendanceObject, manager)) + layout.addView(TextView(activity).also { + //it.setText(R.string.attendance_percentage_format, count/sum*100f) + it.text = count.toString() + it.setPadding(0, 0, 5.dp, 0) + }) + layout.setPadding(0, 8.dp, 0, 8.dp) + b.previewContainer.addView(layout) + } + + if (percentage == 0f) { + b.percentage.isInvisible = true + b.percentageCircle.isInvisible = true + } + else { + b.percentage.isVisible = true + b.percentageCircle.isVisible = true + b.percentage.setText(R.string.attendance_period_summary_format, percentage) + + val df = DecimalFormat("0.##") + b.percentageCircle.setProgressTextAdapter { value -> + df.format(value) + "%" + } + b.percentageCircle.maxProgress = 100.0 + animatePercentageIndicator(percentage.toDouble()) + } + } + + return items.toMutableList() + } + + private fun animatePercentageIndicator(targetProgress: Double) { + val startingProgress = b.percentageCircle.progress + val progressChange = targetProgress - startingProgress + + val a: Animation = object : Animation() { + override fun applyTransformation(interpolatedTime: Float, t: Transformation) { + val progress = startingProgress + (progressChange * interpolatedTime) + //if (interpolatedTime == 1f) + // progress = startingProgress + progressChange + + val color = ColorUtils.blendARGB(Color.RED, Color.GREEN, progress.toFloat() / 100.0f) + b.percentageCircle.progressColor = color + b.percentageCircle.setProgress(progress, 100.0) + } + + override fun willChangeBounds(): Boolean { + return false + } + } + a.duration = 1300 + a.interpolator = AccelerateDecelerateInterpolator() + b.percentageCircle.startAnimation(a) + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceView.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceView.kt new file mode 100644 index 00000000..dfe20eda --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceView.kt @@ -0,0 +1,84 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-29. + */ + +package pl.szczodrzynski.edziennik.ui.modules.attendance + +import android.annotation.SuppressLint +import android.content.Context +import android.text.TextUtils +import android.util.AttributeSet +import android.util.TypedValue +import android.view.Gravity +import android.view.View +import android.view.ViewGroup +import android.widget.LinearLayout +import androidx.appcompat.widget.AppCompatTextView +import androidx.core.graphics.ColorUtils +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.data.db.entity.Attendance +import pl.szczodrzynski.edziennik.dp +import pl.szczodrzynski.edziennik.setTintColor +import pl.szczodrzynski.edziennik.utils.managers.AttendanceManager + +class AttendanceView : AppCompatTextView { + + @JvmOverloads + constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : super(context, attrs, defStyleAttr) + + constructor(context: Context, attendance: Attendance, manager: AttendanceManager) : this(context, null) { + setAttendance(attendance, manager, false) + } + + @SuppressLint("RestrictedApi") + fun setAttendance(attendance: Attendance?, manager: AttendanceManager, bigView: Boolean = false) { + if (attendance == null) { + visibility = View.GONE + return + } + visibility = View.VISIBLE + + val attendanceName = if (manager.useSymbols) + attendance.typeSymbol + else + attendance.typeShort + + val attendanceColor = manager.getAttendanceColor(attendance) + + text = when { + attendanceName.isBlank() -> " " + else -> attendanceName + } + + setTextColor(if (ColorUtils.calculateLuminance(attendanceColor) > 0.3) + 0xaa000000.toInt() + else + 0xccffffff.toInt()) + + setBackgroundResource(if (bigView) R.drawable.bg_rounded_8dp else R.drawable.bg_rounded_4dp) + background.setTintColor(attendanceColor) + gravity = Gravity.CENTER + + if (bigView) { + setTextSize(TypedValue.COMPLEX_UNIT_SP, 22f) + setAutoSizeTextTypeUniformWithConfiguration( + 14, + 32, + 1, + TypedValue.COMPLEX_UNIT_SP + ) + setPadding(2.dp, 2.dp, 2.dp, 2.dp) + } + else { + setTextSize(TypedValue.COMPLEX_UNIT_SP, 14f) + setPadding(5.dp, 0, 5.dp, 0) + layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT).apply { + setMargins(0, 0, 5.dp, 0) + } + maxLines = 1 + ellipsize = TextUtils.TruncateAt.END + measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT) + } + } +} + diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/models/AttendanceCount.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/models/AttendanceCount.kt new file mode 100644 index 00000000..c2595885 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/models/AttendanceCount.kt @@ -0,0 +1,20 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-30. + */ + +package pl.szczodrzynski.edziennik.ui.modules.attendance.models + +class AttendanceCount { + var normalSum = 0f + var normalCount = 0 + var normalWeightedSum = 0f + var normalWeightedCount = 0f + + var pointSum = 0f + + var pointAvgSum = 0f + var pointAvgMax = 0f + + var normalAvg: Float? = null + var pointAvgPercent: Float? = null +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/models/AttendanceDayRange.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/models/AttendanceDayRange.kt new file mode 100644 index 00000000..e993d785 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/models/AttendanceDayRange.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-30. + */ + +package pl.szczodrzynski.edziennik.ui.modules.attendance.models + +import pl.szczodrzynski.edziennik.data.db.entity.Attendance +import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull +import pl.szczodrzynski.edziennik.ui.modules.grades.models.ExpandableItemModel +import pl.szczodrzynski.edziennik.utils.models.Date + +data class AttendanceDayRange( + var rangeStart: Date, + var rangeEnd: Date?, + override val items: MutableList = mutableListOf() +) : ExpandableItemModel(items) { + override var level = 1 + + var lastAddedDate = 0L + + var hasUnseen: Boolean = false + get() = field || items.any { it.baseType != Attendance.TYPE_PRESENT && !it.seen } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/models/AttendanceEmpty.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/models/AttendanceEmpty.kt new file mode 100644 index 00000000..46877f63 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/models/AttendanceEmpty.kt @@ -0,0 +1,7 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-5-4. + */ + +package pl.szczodrzynski.edziennik.ui.modules.attendance.models + +class AttendanceEmpty diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/models/AttendanceMonth.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/models/AttendanceMonth.kt new file mode 100644 index 00000000..c6d14914 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/models/AttendanceMonth.kt @@ -0,0 +1,26 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-30. + */ + +package pl.szczodrzynski.edziennik.ui.modules.attendance.models + +import pl.szczodrzynski.edziennik.data.db.entity.Attendance +import pl.szczodrzynski.edziennik.data.db.entity.AttendanceType +import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull +import pl.szczodrzynski.edziennik.ui.modules.grades.models.ExpandableItemModel + +data class AttendanceMonth( + val year: Int, + val month: Int, + override val items: MutableList = mutableListOf() +) : ExpandableItemModel(items) { + override var level = 1 + + var lastAddedDate = 0L + + var hasUnseen: Boolean = false + get() = field || items.any { it.baseType != Attendance.TYPE_PRESENT && !it.seen } + + var typeCountMap: Map = mapOf() + var percentage: Float = 0f +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/models/AttendanceSubject.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/models/AttendanceSubject.kt new file mode 100644 index 00000000..b56cd62a --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/models/AttendanceSubject.kt @@ -0,0 +1,26 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-5-4. + */ + +package pl.szczodrzynski.edziennik.ui.modules.attendance.models + +import pl.szczodrzynski.edziennik.data.db.entity.Attendance +import pl.szczodrzynski.edziennik.data.db.entity.AttendanceType +import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull +import pl.szczodrzynski.edziennik.ui.modules.grades.models.ExpandableItemModel + +data class AttendanceSubject( + val subjectId: Long, + val subjectName: String, + override val items: MutableList = mutableListOf() +) : ExpandableItemModel(items) { + override var level = 1 + + var lastAddedDate = 0L + + var hasUnseen: Boolean = false + get() = field || items.any { it.baseType != Attendance.TYPE_PRESENT && !it.seen } + + var typeCountMap: Map = mapOf() + var percentage: Float = 0f +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/models/AttendanceTypeGroup.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/models/AttendanceTypeGroup.kt new file mode 100644 index 00000000..d6b431ec --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/models/AttendanceTypeGroup.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-5-8. + */ + +package pl.szczodrzynski.edziennik.ui.modules.attendance.models + +import pl.szczodrzynski.edziennik.data.db.entity.Attendance +import pl.szczodrzynski.edziennik.data.db.entity.AttendanceType +import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull +import pl.szczodrzynski.edziennik.ui.modules.grades.models.ExpandableItemModel + +data class AttendanceTypeGroup( + val type: AttendanceType, + override val items: MutableList = mutableListOf() +) : ExpandableItemModel(items) { + override var level = 1 + + var lastAddedDate = 0L + + var hasUnseen: Boolean = false + get() = field || items.any { it.baseType != Attendance.TYPE_PRESENT && !it.seen } + + var percentage: Float = 0f + var semesterCount: Int = 0 +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/AttendanceViewHolder.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/AttendanceViewHolder.kt new file mode 100644 index 00000000..3e7a1d32 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/AttendanceViewHolder.kt @@ -0,0 +1,79 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-30. + */ + +package pl.szczodrzynski.edziennik.ui.modules.attendance.viewholder + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.view.ContextThemeWrapper +import androidx.core.view.isVisible +import androidx.recyclerview.widget.RecyclerView +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.concat +import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull +import pl.szczodrzynski.edziennik.databinding.AttendanceItemAttendanceBinding +import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceAdapter +import pl.szczodrzynski.edziennik.ui.modules.attendance.models.AttendanceDayRange +import pl.szczodrzynski.edziennik.ui.modules.attendance.models.AttendanceMonth +import pl.szczodrzynski.edziennik.ui.modules.grades.models.ExpandableItemModel +import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.BindableViewHolder +import pl.szczodrzynski.edziennik.utils.Themes +import pl.szczodrzynski.edziennik.utils.models.Week + +class AttendanceViewHolder( + inflater: LayoutInflater, + parent: ViewGroup, + val b: AttendanceItemAttendanceBinding = AttendanceItemAttendanceBinding.inflate(inflater, parent, false) +) : RecyclerView.ViewHolder(b.root), BindableViewHolder { + companion object { + private const val TAG = "AttendanceViewHolder" + } + + override fun onBind(activity: AppCompatActivity, app: App, item: AttendanceFull, position: Int, adapter: AttendanceAdapter) { + val manager = app.attendanceManager + val contextWrapper = ContextThemeWrapper(activity, Themes.appTheme) + + val bullet = " • " + + b.attendanceView.setAttendance(item, manager, bigView = true) + + b.type.text = item.typeName + b.subjectName.text = item.subjectLongName ?: item.lessonTopic + b.dateTime.text = listOf( + Week.getFullDayName(item.date.weekDay), + item.date.formattedStringShort, + item.startTime?.stringHM, + item.lessonNumber?.let { app.getString(R.string.attendance_lesson_number_format, it) } + ).concat(bullet) + + if (item.showAsUnseen == null) + item.showAsUnseen = !item.seen + + b.unread.isVisible = item.showAsUnseen == true + if (!item.seen) { + manager.markAsSeen(item) + + val container = adapter.items.firstOrNull { + it is ExpandableItemModel<*> && it.items.contains(item) + } as? ExpandableItemModel<*> ?: return + + var hasUnseen = true + if (container is AttendanceDayRange) { + hasUnseen = container.items.any { !it.seen } + container.hasUnseen = hasUnseen + } + if (container is AttendanceMonth) { + hasUnseen = container.items.any { !it.seen } + container.hasUnseen = hasUnseen + } + + // check if the unseen status has changed + if (!hasUnseen) { + adapter.notifyItemChanged(container) + } + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/DayRangeViewHolder.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/DayRangeViewHolder.kt new file mode 100644 index 00000000..b42d5e16 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/DayRangeViewHolder.kt @@ -0,0 +1,72 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-30. + */ + +package pl.szczodrzynski.edziennik.ui.modules.attendance.viewholder + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.view.ContextThemeWrapper +import androidx.core.view.isVisible +import androidx.recyclerview.widget.RecyclerView +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.concat +import pl.szczodrzynski.edziennik.data.db.entity.Attendance +import pl.szczodrzynski.edziennik.databinding.AttendanceItemDayRangeBinding +import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceAdapter +import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceAdapter.Companion.STATE_CLOSED +import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceView +import pl.szczodrzynski.edziennik.ui.modules.attendance.models.AttendanceDayRange +import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.BindableViewHolder +import pl.szczodrzynski.edziennik.utils.Themes + +class DayRangeViewHolder( + inflater: LayoutInflater, + parent: ViewGroup, + val b: AttendanceItemDayRangeBinding = AttendanceItemDayRangeBinding.inflate(inflater, parent, false) +) : RecyclerView.ViewHolder(b.root), BindableViewHolder { + companion object { + private const val TAG = "DayRangeViewHolder" + } + + override fun onBind(activity: AppCompatActivity, app: App, item: AttendanceDayRange, position: Int, adapter: AttendanceAdapter) { + val manager = app.attendanceManager + val contextWrapper = ContextThemeWrapper(activity, Themes.appTheme) + + b.title.text = listOf( + item.rangeStart.formattedString, + item.rangeEnd?.formattedString + ).concat(" - ") + + b.dropdownIcon.rotation = when (item.state) { + STATE_CLOSED -> 0f + else -> 180f + } + + b.unread.isVisible = item.hasUnseen + + b.previewContainer.visibility = if (item.state == STATE_CLOSED) View.VISIBLE else View.INVISIBLE + b.summaryContainer.visibility = if (item.state == STATE_CLOSED) View.INVISIBLE else View.VISIBLE + + b.previewContainer.removeAllViews() + + for (attendance in item.items) { + if (attendance.baseType == Attendance.TYPE_PRESENT_CUSTOM || attendance.baseType == Attendance.TYPE_UNKNOWN) + continue + b.previewContainer.addView(AttendanceView( + contextWrapper, + attendance, + manager + )) + } + if (item.items.isEmpty() || item.items.none { it.baseType != Attendance.TYPE_PRESENT_CUSTOM && it.baseType != Attendance.TYPE_UNKNOWN }) { + b.previewContainer.addView(TextView(contextWrapper).also { + it.setText(R.string.attendance_empty_text) + }) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/EmptyViewHolder.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/EmptyViewHolder.kt new file mode 100644 index 00000000..37fe635c --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/EmptyViewHolder.kt @@ -0,0 +1,29 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-5-4. + */ + +package pl.szczodrzynski.edziennik.ui.modules.attendance.viewholder + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.appcompat.app.AppCompatActivity +import androidx.recyclerview.widget.RecyclerView +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.databinding.AttendanceItemEmptyBinding +import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceAdapter +import pl.szczodrzynski.edziennik.ui.modules.attendance.models.AttendanceEmpty +import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.BindableViewHolder + +class EmptyViewHolder( + inflater: LayoutInflater, + parent: ViewGroup, + val b: AttendanceItemEmptyBinding = AttendanceItemEmptyBinding.inflate(inflater, parent, false) +) : RecyclerView.ViewHolder(b.root), BindableViewHolder { + companion object { + private const val TAG = "EmptyViewHolder" + } + + override fun onBind(activity: AppCompatActivity, app: App, item: AttendanceEmpty, position: Int, adapter: AttendanceAdapter) { + + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/MonthViewHolder.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/MonthViewHolder.kt new file mode 100644 index 00000000..71c139ac --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/MonthViewHolder.kt @@ -0,0 +1,99 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-30. + */ + +package pl.szczodrzynski.edziennik.ui.modules.attendance.viewholder + +import android.view.LayoutInflater +import android.view.ViewGroup +import android.widget.LinearLayout +import android.widget.TextView +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.view.ContextThemeWrapper +import androidx.core.view.isInvisible +import androidx.core.view.isVisible +import androidx.recyclerview.widget.RecyclerView +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.db.entity.Attendance +import pl.szczodrzynski.edziennik.databinding.AttendanceItemMonthBinding +import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceAdapter +import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceAdapter.Companion.STATE_CLOSED +import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceView +import pl.szczodrzynski.edziennik.ui.modules.attendance.models.AttendanceMonth +import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.BindableViewHolder +import pl.szczodrzynski.edziennik.utils.Themes +import pl.szczodrzynski.edziennik.utils.models.Date + +class MonthViewHolder( + inflater: LayoutInflater, + parent: ViewGroup, + val b: AttendanceItemMonthBinding = AttendanceItemMonthBinding.inflate(inflater, parent, false) +) : RecyclerView.ViewHolder(b.root), BindableViewHolder { + companion object { + private const val TAG = "MonthViewHolder" + } + + override fun onBind(activity: AppCompatActivity, app: App, item: AttendanceMonth, position: Int, adapter: AttendanceAdapter) { + val manager = app.attendanceManager + val contextWrapper = ContextThemeWrapper(activity, Themes.appTheme) + + b.title.text = listOf( + app.resources.getStringArray(R.array.material_calendar_months_array).getOrNull(item.month - 1)?.fixName(), + item.year.toString() + ).concat(" ") + + b.dropdownIcon.rotation = when (item.state) { + STATE_CLOSED -> 0f + else -> 180f + } + + b.unread.isVisible = item.hasUnseen + + b.attendanceBar.setAttendanceData(item.typeCountMap.map { manager.getAttendanceColor(it.key) to it.value }) + + b.previewContainer.isInvisible = item.state != STATE_CLOSED + b.summaryContainer.isInvisible = item.state == STATE_CLOSED + b.percentage.isVisible = item.state == STATE_CLOSED + + b.previewContainer.removeAllViews() + + val sum = item.typeCountMap.entries.sumBy { it.value }.toFloat() + item.typeCountMap.forEach { (type, count) -> + val layout = LinearLayout(contextWrapper) + val attendance = Attendance( + profileId = 0, + id = 0, + baseType = type.baseType, + typeName = "", + typeShort = type.typeShort, + typeSymbol = type.typeSymbol, + typeColor = type.typeColor, + date = Date(0, 0, 0), + startTime = null, + semester = 0, + teacherId = 0, + subjectId = 0, + addedDate = 0 + ) + layout.addView(AttendanceView(contextWrapper, attendance, manager)) + layout.addView(TextView(contextWrapper).also { + //it.setText(R.string.attendance_percentage_format, count/sum*100f) + it.text = count.toString() + it.setPadding(0, 0, 5.dp, 0) + }) + layout.setPadding(0, 8.dp, 0, 0) + b.previewContainer.addView(layout) + } + + if (item.percentage == 0f) { + b.percentage.isVisible = false + b.percentage.text = null + b.summaryContainer.isVisible = false + b.summaryContainer.text = null + } + else { + b.percentage.setText(R.string.attendance_percentage_format, item.percentage) + b.summaryContainer.setText(R.string.attendance_period_summary_format, item.percentage) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/SubjectViewHolder.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/SubjectViewHolder.kt new file mode 100644 index 00000000..e439111d --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/SubjectViewHolder.kt @@ -0,0 +1,57 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-5-4. + */ + +package pl.szczodrzynski.edziennik.ui.modules.attendance.viewholder + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.view.ContextThemeWrapper +import androidx.core.view.isVisible +import androidx.recyclerview.widget.RecyclerView +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.databinding.AttendanceItemSubjectBinding +import pl.szczodrzynski.edziennik.setText +import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceAdapter +import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceAdapter.Companion.STATE_CLOSED +import pl.szczodrzynski.edziennik.ui.modules.attendance.models.AttendanceSubject +import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.BindableViewHolder +import pl.szczodrzynski.edziennik.utils.Themes + +class SubjectViewHolder( + inflater: LayoutInflater, + parent: ViewGroup, + val b: AttendanceItemSubjectBinding = AttendanceItemSubjectBinding.inflate(inflater, parent, false) +) : RecyclerView.ViewHolder(b.root), BindableViewHolder { + companion object { + private const val TAG = "SubjectViewHolder" + } + + override fun onBind(activity: AppCompatActivity, app: App, item: AttendanceSubject, position: Int, adapter: AttendanceAdapter) { + val manager = app.attendanceManager + val contextWrapper = ContextThemeWrapper(activity, Themes.appTheme) + + b.title.text = item.subjectName + + b.dropdownIcon.rotation = when (item.state) { + STATE_CLOSED -> 0f + else -> 180f + } + + b.unread.isVisible = item.hasUnseen + + b.attendanceBar.setAttendanceData(item.typeCountMap.map { manager.getAttendanceColor(it.key) to it.value }) + + b.percentage.isVisible = true + + if (item.percentage == 0f) { + b.percentage.isVisible = false + b.percentage.text = null + } + else { + b.percentage.setText(R.string.attendance_percentage_format, item.percentage) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/TypeViewHolder.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/TypeViewHolder.kt new file mode 100644 index 00000000..a2f8b689 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/TypeViewHolder.kt @@ -0,0 +1,69 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-5-8. + */ + +package pl.szczodrzynski.edziennik.ui.modules.attendance.viewholder + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.view.ContextThemeWrapper +import androidx.core.view.isVisible +import androidx.recyclerview.widget.RecyclerView +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.concat +import pl.szczodrzynski.edziennik.data.db.entity.Attendance +import pl.szczodrzynski.edziennik.databinding.AttendanceItemTypeBinding +import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceAdapter +import pl.szczodrzynski.edziennik.ui.modules.attendance.models.AttendanceTypeGroup +import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.BindableViewHolder +import pl.szczodrzynski.edziennik.utils.Themes +import pl.szczodrzynski.edziennik.utils.models.Date + +class TypeViewHolder( + inflater: LayoutInflater, + parent: ViewGroup, + val b: AttendanceItemTypeBinding = AttendanceItemTypeBinding.inflate(inflater, parent, false) +) : RecyclerView.ViewHolder(b.root), BindableViewHolder { + companion object { + private const val TAG = "TypeViewHolder" + } + + override fun onBind(activity: AppCompatActivity, app: App, item: AttendanceTypeGroup, position: Int, adapter: AttendanceAdapter) { + val manager = app.attendanceManager + val contextWrapper = ContextThemeWrapper(activity, Themes.appTheme) + + val type = item.type + b.title.text = type.typeName + + b.dropdownIcon.rotation = when (item.state) { + AttendanceAdapter.STATE_CLOSED -> 0f + else -> 180f + } + + b.unread.isVisible = item.hasUnseen + + b.details.text = listOf( + app.getString(R.string.attendance_percentage_format, item.percentage), + app.getString(R.string.attendance_type_yearly_format, item.items.size), + app.getString(R.string.attendance_type_semester_format, item.semesterCount) + ).concat(" • ") + + b.type.setAttendance(Attendance( + profileId = 0, + id = 0, + baseType = type.baseType, + typeName = "", + typeShort = type.typeShort, + typeSymbol = type.typeSymbol, + typeColor = type.typeColor, + date = Date(0, 0, 0), + startTime = null, + semester = 0, + teacherId = 0, + subjectId = 0, + addedDate = 0 + ), manager, bigView = false) + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/behaviour/BehaviourFragment.java b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/behaviour/BehaviourFragment.java index 4ec8b4c6..d0b46f4a 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/behaviour/BehaviourFragment.java +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/behaviour/BehaviourFragment.java @@ -134,10 +134,10 @@ public class BehaviourFragment extends Fragment { List filteredList = new ArrayList<>(); for (NoticeFull notice: noticeList) { - if (displayMode != MODE_YEAR && notice.semester != displayMode) + if (displayMode != MODE_YEAR && notice.getSemester() != displayMode) continue; filteredList.add(notice); - switch (notice.type) { + switch (notice.getType()) { case Notice.TYPE_POSITIVE: praisesCount++; break; diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/behaviour/NoticesAdapter.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/behaviour/NoticesAdapter.kt index b1b85780..f495d61e 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/behaviour/NoticesAdapter.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/behaviour/NoticesAdapter.kt @@ -41,10 +41,10 @@ class NoticesAdapter//getting the context and product list with constructor if (app.profile.loginStoreType == LOGIN_TYPE_MOBIDZIENNIK && false) { holder.noticesItemReason.text = bs(null, notice.category, "\n") + notice.text - holder.noticesItemTeacherName.text = app.getString(R.string.notices_points_format, notice.teacherFullName, if (notice.points > 0) "+" + notice.points else notice.points) + holder.noticesItemTeacherName.text = app.getString(R.string.notices_points_format, notice.teacherName, if (notice.points ?: 0f > 0) "+" + notice.points else notice.points) } else { holder.noticesItemReason.text = notice.text - holder.noticesItemTeacherName.text = notice.teacherFullName + holder.noticesItemTeacherName.text = notice.teacherName } holder.noticesItemAddedDate.text = Date.fromMillis(notice.addedDate).formattedString diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/LabFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/LabFragment.kt index 45472cf9..60c30cf9 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/LabFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/LabFragment.kt @@ -9,26 +9,25 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment -import androidx.sqlite.db.SimpleSQLiteQuery 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.MainActivity -import pl.szczodrzynski.edziennik.data.db.entity.Event -import pl.szczodrzynski.edziennik.databinding.LabFragmentBinding -import pl.szczodrzynski.edziennik.onClick +import pl.szczodrzynski.edziennik.addOnPageSelectedListener +import pl.szczodrzynski.edziennik.databinding.TemplateFragmentBinding +import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.FragmentLazyPagerAdapter import kotlin.coroutines.CoroutineContext class LabFragment : Fragment(), CoroutineScope { companion object { private const val TAG = "LabFragment" + var pageSelection = 0 } private lateinit var app: App private lateinit var activity: MainActivity - private lateinit var b: LabFragmentBinding + private lateinit var b: TemplateFragmentBinding private val job: Job = Job() override val coroutineContext: CoroutineContext @@ -40,29 +39,30 @@ class LabFragment : Fragment(), CoroutineScope { activity = (getActivity() as MainActivity?) ?: return null context ?: return null app = activity.application as App - b = LabFragmentBinding.inflate(inflater) + b = TemplateFragmentBinding.inflate(inflater) + b.refreshLayout.setParent(activity.swipeRefreshLayout) return b.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { if (!isAdded) return - b.last10unseen.onClick { - launch(Dispatchers.Default) { - val events = app.db.eventDao().getAllNow(App.profileId) - val ids = events.sortedBy { it.date }.filter { it.type == Event.TYPE_HOMEWORK }.takeLast(10) - ids.forEach { - app.db.metadataDao().setSeen(App.profileId, it, false) - } + val pagerAdapter = FragmentLazyPagerAdapter( + fragmentManager ?: return, + b.refreshLayout, + listOf( + LabPageFragment() to "click me", + LabProfileFragment() to "JSON" + ) + ) + b.viewPager.apply { + offscreenPageLimit = 1 + adapter = pagerAdapter + currentItem = pageSelection + addOnPageSelectedListener { + pageSelection = it } - } - - b.rodo.onClick { - app.db.teacherDao().query(SimpleSQLiteQuery("UPDATE teachers SET teacherSurname = \"\" WHERE profileId = ${App.profileId}")) - } - - b.removeHomework.onClick { - app.db.eventDao().getRawNow("UPDATE events SET homeworkBody = NULL WHERE profileId = ${App.profileId}") + b.tabLayout.setupWithViewPager(this) } } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/LabJsonAdapter.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/LabJsonAdapter.kt new file mode 100644 index 00000000..d1543cfc --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/LabJsonAdapter.kt @@ -0,0 +1,191 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-5-12. + */ + +package pl.szczodrzynski.edziennik.ui.modules.debug + +import android.animation.ObjectAnimator +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.isInvisible +import androidx.recyclerview.widget.RecyclerView +import com.google.gson.JsonArray +import com.google.gson.JsonElement +import com.google.gson.JsonObject +import com.google.gson.JsonPrimitive +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.ui.modules.debug.models.LabJsonArray +import pl.szczodrzynski.edziennik.ui.modules.debug.models.LabJsonElement +import pl.szczodrzynski.edziennik.ui.modules.debug.models.LabJsonObject +import pl.szczodrzynski.edziennik.ui.modules.debug.viewholder.JsonArrayViewHolder +import pl.szczodrzynski.edziennik.ui.modules.debug.viewholder.JsonElementViewHolder +import pl.szczodrzynski.edziennik.ui.modules.debug.viewholder.JsonObjectViewHolder +import pl.szczodrzynski.edziennik.ui.modules.grades.models.ExpandableItemModel +import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.BindableViewHolder +import kotlin.coroutines.CoroutineContext + +class LabJsonAdapter( + val activity: AppCompatActivity, + var onJsonElementClick: ((item: LabJsonElement) -> Unit)? = null +) : RecyclerView.Adapter(), CoroutineScope { + companion object { + private const val TAG = "AttendanceAdapter" + private const val ITEM_TYPE_OBJECT = 0 + private const val ITEM_TYPE_ARRAY = 1 + private const val ITEM_TYPE_ELEMENT = 2 + const val STATE_CLOSED = 0 + const val STATE_OPENED = 1 + + fun expand(item: Any, level: Int): MutableList { + val json = when (item) { + is LabJsonObject -> item.jsonObject + is LabJsonArray -> item.jsonArray + is JsonObject -> item + is JsonArray -> item + is JsonPrimitive -> item + else -> return mutableListOf() + } + + return when (json) { + is JsonObject -> json.entrySet().mapNotNull { wrap(it.key, it.value, level) } + is JsonArray -> json.mapIndexedNotNull { index, jsonElement -> wrap(index.toString(), jsonElement, level) } + else -> listOf(LabJsonElement("?", json, level)) + }.toMutableList() + } + fun wrap(key: String, item: JsonElement, level: Int = 0): Any? { + return when (item) { + is JsonObject -> LabJsonObject(key, item, level + 1) + is JsonArray -> LabJsonArray(key, item, level + 1) + is JsonElement -> LabJsonElement(key, item, level + 1) + else -> null + } + } + } + + private val app = activity.applicationContext as App + + private val job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Main + + var items = mutableListOf() + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + val inflater = LayoutInflater.from(parent.context) + return when (viewType) { + ITEM_TYPE_OBJECT -> JsonObjectViewHolder(inflater, parent) + ITEM_TYPE_ARRAY -> JsonArrayViewHolder(inflater, parent) + ITEM_TYPE_ELEMENT -> JsonElementViewHolder(inflater, parent) + else -> throw IllegalArgumentException("Incorrect viewType") + } + } + + override fun getItemViewType(position: Int): Int { + return when (items[position]) { + is LabJsonObject -> ITEM_TYPE_OBJECT + is LabJsonArray -> ITEM_TYPE_ARRAY + is LabJsonElement -> ITEM_TYPE_ELEMENT + else -> throw IllegalArgumentException("Incorrect viewType") + } + } + + private val onClickListener = View.OnClickListener { view -> + val model = view.getTag(R.string.tag_key_model) + if (model is LabJsonElement) { + onJsonElementClick?.invoke(model) + return@OnClickListener + } + if (model !is ExpandableItemModel<*>) + return@OnClickListener + expandModel(model, view) + } + + fun expandModel(model: ExpandableItemModel<*>?, view: View?, notifyAdapter: Boolean = true) { + model ?: return + val position = items.indexOf(model) + if (position == -1) + return + + view?.findViewById(R.id.dropdownIcon)?.let { dropdownIcon -> + ObjectAnimator.ofFloat( + dropdownIcon, + View.ROTATION, + if (model.state == STATE_CLOSED) 0f else 180f, + if (model.state == STATE_CLOSED) 180f else 0f + ).setDuration(200).start(); + } + + // hide the preview, show summary + val preview = view?.findViewById(R.id.previewContainer) + val summary = view?.findViewById(R.id.summaryContainer) + preview?.isInvisible = model.state == STATE_CLOSED + summary?.isInvisible = model.state != STATE_CLOSED + + if (model.state == STATE_CLOSED) { + + val subItems = when { + //model.items.isEmpty() -> listOf(AttendanceEmpty()) + else -> expand(model, model.level) + } + + model.state = STATE_OPENED + items.addAll(position + 1, subItems) + if (notifyAdapter) notifyItemRangeInserted(position + 1, subItems.size) + } + else { + val start = position + 1 + var end: Int = items.size + for (i in start until items.size) { + val model1 = items[i] + val level = (model1 as? ExpandableItemModel<*>)?.level ?: 3 + if (level <= model.level) { + end = i + break + } else { + if (model1 is ExpandableItemModel<*> && model1.state == STATE_OPENED) { + model1.state = STATE_CLOSED + } + } + } + + if (end != -1) { + items.subList(start, end).clear() + if (notifyAdapter) notifyItemRangeRemoved(start, end - start) + } + + model.state = STATE_CLOSED + } + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + val item = items[position] + if (holder !is BindableViewHolder<*, *>) + return + + val viewType = when (holder) { + is JsonObjectViewHolder -> ITEM_TYPE_OBJECT + is JsonArrayViewHolder -> ITEM_TYPE_ARRAY + is JsonElementViewHolder -> ITEM_TYPE_ELEMENT + else -> throw IllegalArgumentException("Incorrect viewType") + } + holder.itemView.setTag(R.string.tag_key_view_type, viewType) + holder.itemView.setTag(R.string.tag_key_position, position) + holder.itemView.setTag(R.string.tag_key_model, item) + + when { + holder is JsonObjectViewHolder && item is LabJsonObject -> holder.onBind(activity, app, item, position, this) + holder is JsonArrayViewHolder && item is LabJsonArray -> holder.onBind(activity, app, item, position, this) + holder is JsonElementViewHolder && item is LabJsonElement -> holder.onBind(activity, app, item, position, this) + } + + holder.itemView.setOnClickListener(onClickListener) + } + + override fun getItemCount() = items.size +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/LabPageFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/LabPageFragment.kt new file mode 100644 index 00000000..bad9fd9f --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/LabPageFragment.kt @@ -0,0 +1,92 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-5-12. + */ + +package pl.szczodrzynski.edziennik.ui.modules.debug + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.sqlite.db.SimpleSQLiteQuery +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.db.entity.Event +import pl.szczodrzynski.edziennik.databinding.LabFragmentBinding +import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.LazyFragment +import pl.szczodrzynski.fslogin.decode +import kotlin.coroutines.CoroutineContext + +class LabPageFragment : LazyFragment(), CoroutineScope { + companion object { + private const val TAG = "LabPageFragment" + } + + private lateinit var app: App + private lateinit var activity: MainActivity + private lateinit var b: LabFragmentBinding + + private val job: Job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Main + + // local/private variables go here + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + activity = (getActivity() as MainActivity?) ?: return null + context ?: return null + app = activity.application as App + b = LabFragmentBinding.inflate(inflater) + return b.root + } + + override fun onPageCreated(): Boolean { + b.last10unseen.onClick { + launch(Dispatchers.Default) { + val events = app.db.eventDao().getAllNow(App.profileId) + val ids = events.sortedBy { it.date }.filter { it.type == Event.TYPE_HOMEWORK }.takeLast(10) + ids.forEach { + app.db.metadataDao().setSeen(App.profileId, it, false) + } + } + } + + b.rodo.onClick { + app.db.teacherDao().query(SimpleSQLiteQuery("UPDATE teachers SET teacherSurname = \"\" WHERE profileId = ${App.profileId}")) + } + + b.removeHomework.onClick { + app.db.eventDao().getRawNow("UPDATE events SET homeworkBody = NULL WHERE profileId = ${App.profileId}") + } + + 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") + b.cookies.text = text + } + + return true + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/LabProfileFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/LabProfileFragment.kt new file mode 100644 index 00000000..cea59402 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/LabProfileFragment.kt @@ -0,0 +1,82 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-5-12. + */ + +package pl.szczodrzynski.edziennik.ui.modules.debug + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.view.isVisible +import androidx.recyclerview.widget.LinearLayoutManager +import com.google.gson.JsonObject +import com.google.gson.JsonParser +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.MainActivity +import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask +import pl.szczodrzynski.edziennik.databinding.TemplateListPageFragmentBinding +import pl.szczodrzynski.edziennik.startCoroutineTimer +import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.LazyFragment +import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration +import kotlin.coroutines.CoroutineContext + +class LabProfileFragment : LazyFragment(), CoroutineScope { + companion object { + private const val TAG = "LabProfileFragment" + } + + private lateinit var app: App + private lateinit var activity: MainActivity + private lateinit var b: TemplateListPageFragmentBinding + + private val job: Job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Main + + // local/private variables go here + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + activity = (getActivity() as MainActivity?) ?: return null + context ?: return null + app = activity.application as App + b = TemplateListPageFragmentBinding.inflate(inflater) + return b.root + } + + override fun onPageCreated(): Boolean { startCoroutineTimer(100L) { + val adapter = LabJsonAdapter(activity) + val json = JsonObject().also { json -> + json.add("app.profile", app.profile.studentData) + json.add("app.config", JsonParser().parse(app.gson.toJson(app.config.values))) + EdziennikTask.profile?.let { + json.add("API.profile", it.studentData) + } ?: { + json.addProperty("API.profile", "null") + }() + EdziennikTask.loginStore?.let { + json.add("API.loginStore", it.data) + } ?: { + json.addProperty("API.loginStore", "null") + }() + } + adapter.items = LabJsonAdapter.expand(json, 0) + + b.list.adapter = adapter + b.list.apply { + setHasFixedSize(true) + layoutManager = LinearLayoutManager(context) + addItemDecoration(SimpleDividerItemDecoration(context)) + addOnScrollListener(onScrollListener) + } + + // show/hide relevant views + b.progressBar.isVisible = false + b.list.isVisible = true + b.noData.isVisible = false + + }; return true } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/models/LabJsonArray.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/models/LabJsonArray.kt new file mode 100644 index 00000000..a606e238 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/models/LabJsonArray.kt @@ -0,0 +1,15 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-5-12. + */ + +package pl.szczodrzynski.edziennik.ui.modules.debug.models + +import com.google.gson.JsonArray +import com.google.gson.JsonElement +import pl.szczodrzynski.edziennik.ui.modules.grades.models.ExpandableItemModel + +data class LabJsonArray( + val key: String, + val jsonArray: JsonArray, + override var level: Int +) : ExpandableItemModel(jsonArray.toMutableList()) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/models/LabJsonElement.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/models/LabJsonElement.kt new file mode 100644 index 00000000..e0e0e686 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/models/LabJsonElement.kt @@ -0,0 +1,13 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-5-12. + */ + +package pl.szczodrzynski.edziennik.ui.modules.debug.models + +import com.google.gson.JsonElement + +data class LabJsonElement( + val key: String, + val jsonElement: JsonElement, + var level: Int +) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/models/LabJsonObject.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/models/LabJsonObject.kt new file mode 100644 index 00000000..43a69201 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/models/LabJsonObject.kt @@ -0,0 +1,15 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-5-12. + */ + +package pl.szczodrzynski.edziennik.ui.modules.debug.models + +import com.google.gson.JsonElement +import com.google.gson.JsonObject +import pl.szczodrzynski.edziennik.ui.modules.grades.models.ExpandableItemModel + +data class LabJsonObject( + val key: String, + val jsonObject: JsonObject, + override var level: Int +) : ExpandableItemModel(jsonObject.entrySet().map { it.value }.toMutableList()) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/viewholder/JsonArrayViewHolder.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/viewholder/JsonArrayViewHolder.kt new file mode 100644 index 00000000..08d4cd3c --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/viewholder/JsonArrayViewHolder.kt @@ -0,0 +1,46 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-5-12. + */ + +package pl.szczodrzynski.edziennik.ui.modules.debug.viewholder + +import android.annotation.SuppressLint +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.view.ContextThemeWrapper +import androidx.core.view.isInvisible +import androidx.recyclerview.widget.RecyclerView +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.databinding.LabItemObjectBinding +import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceAdapter +import pl.szczodrzynski.edziennik.ui.modules.debug.LabJsonAdapter +import pl.szczodrzynski.edziennik.ui.modules.debug.models.LabJsonArray +import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.BindableViewHolder +import pl.szczodrzynski.edziennik.utils.Themes + +class JsonArrayViewHolder( + inflater: LayoutInflater, + parent: ViewGroup, + val b: LabItemObjectBinding = LabItemObjectBinding.inflate(inflater, parent, false) +) : RecyclerView.ViewHolder(b.root), BindableViewHolder { + companion object { + private const val TAG = "JsonArrayViewHolder" + } + + @SuppressLint("SetTextI18n") + override fun onBind(activity: AppCompatActivity, app: App, item: LabJsonArray, position: Int, adapter: LabJsonAdapter) { + val contextWrapper = ContextThemeWrapper(activity, Themes.appTheme) + + b.dropdownIcon.rotation = when (item.state) { + AttendanceAdapter.STATE_CLOSED -> 0f + else -> 180f + } + b.previewContainer.isInvisible = item.state != AttendanceAdapter.STATE_CLOSED + b.summaryContainer.isInvisible = item.state == AttendanceAdapter.STATE_CLOSED + + b.key.text = item.key + b.previewContainer.text = item.jsonArray.toString().take(200) + b.summaryContainer.text = item.jsonArray.size().toString() + " elements" + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/viewholder/JsonElementViewHolder.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/viewholder/JsonElementViewHolder.kt new file mode 100644 index 00000000..8ce6b461 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/viewholder/JsonElementViewHolder.kt @@ -0,0 +1,53 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-5-12. + */ + +package pl.szczodrzynski.edziennik.ui.modules.debug.viewholder + +import android.annotation.SuppressLint +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.view.ContextThemeWrapper +import androidx.recyclerview.widget.RecyclerView +import com.google.gson.JsonNull +import com.google.gson.JsonPrimitive +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.databinding.LabItemElementBinding +import pl.szczodrzynski.edziennik.ui.modules.debug.LabJsonAdapter +import pl.szczodrzynski.edziennik.ui.modules.debug.models.LabJsonElement +import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.BindableViewHolder +import pl.szczodrzynski.edziennik.utils.Themes + +class JsonElementViewHolder( + inflater: LayoutInflater, + parent: ViewGroup, + val b: LabItemElementBinding = LabItemElementBinding.inflate(inflater, parent, false) +) : RecyclerView.ViewHolder(b.root), BindableViewHolder { + companion object { + private const val TAG = "JsonObjectViewHolder" + } + + @SuppressLint("SetTextI18n") + override fun onBind(activity: AppCompatActivity, app: App, item: LabJsonElement, position: Int, adapter: LabJsonAdapter) { + val contextWrapper = ContextThemeWrapper(activity, Themes.appTheme) + + b.type.text = when (item.jsonElement) { + is JsonPrimitive -> when { + item.jsonElement.isNumber -> "Number" + item.jsonElement.isString -> "String" + item.jsonElement.isBoolean -> "Boolean" + else -> "Primitive" + } + is JsonNull -> "null" + else -> null + } + + val colorSecondary = android.R.attr.textColorSecondary.resolveAttr(activity) + b.key.text = listOf( + item.key.asColoredSpannable(colorSecondary), + ": ", + item.jsonElement.toString().asItalicSpannable() + ).concat("") + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/viewholder/JsonObjectViewHolder.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/viewholder/JsonObjectViewHolder.kt new file mode 100644 index 00000000..3131b0ea --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/viewholder/JsonObjectViewHolder.kt @@ -0,0 +1,46 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-5-12. + */ + +package pl.szczodrzynski.edziennik.ui.modules.debug.viewholder + +import android.annotation.SuppressLint +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.view.ContextThemeWrapper +import androidx.core.view.isInvisible +import androidx.recyclerview.widget.RecyclerView +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.databinding.LabItemObjectBinding +import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceAdapter +import pl.szczodrzynski.edziennik.ui.modules.debug.LabJsonAdapter +import pl.szczodrzynski.edziennik.ui.modules.debug.models.LabJsonObject +import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.BindableViewHolder +import pl.szczodrzynski.edziennik.utils.Themes + +class JsonObjectViewHolder( + inflater: LayoutInflater, + parent: ViewGroup, + val b: LabItemObjectBinding = LabItemObjectBinding.inflate(inflater, parent, false) +) : RecyclerView.ViewHolder(b.root), BindableViewHolder { + companion object { + private const val TAG = "JsonObjectViewHolder" + } + + @SuppressLint("SetTextI18n") + override fun onBind(activity: AppCompatActivity, app: App, item: LabJsonObject, position: Int, adapter: LabJsonAdapter) { + val contextWrapper = ContextThemeWrapper(activity, Themes.appTheme) + + b.dropdownIcon.rotation = when (item.state) { + AttendanceAdapter.STATE_CLOSED -> 0f + else -> 180f + } + b.previewContainer.isInvisible = item.state != AttendanceAdapter.STATE_CLOSED + b.summaryContainer.isInvisible = item.state == AttendanceAdapter.STATE_CLOSED + + b.key.text = item.key + b.previewContainer.text = item.jsonObject.toString().take(200) + b.summaryContainer.text = item.jsonObject.size().toString() + " elements" + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/grades/editor/GradesEditorFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/grades/editor/GradesEditorFragment.kt index 3e5978ee..6520ce54 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/grades/editor/GradesEditorFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/grades/editor/GradesEditorFragment.kt @@ -206,7 +206,7 @@ class GradesEditorFragment : Fragment() { gradeCountSemester = 0f averageSemester = 0f - app.db.gradeDao().getAllWhere(App.profileId, "subjectId = " + subject.id).observe(this, Observer { grades -> + app.db.gradeDao().getAllBySubject(App.profileId, subject.id).observe(this, Observer { grades -> for (grade in grades) { if (grade.type == Grade.TYPE_NORMAL) { if (grade.weight < 0) { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/grades/models/ExpandableItemModel.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/grades/models/ExpandableItemModel.kt index c52ba310..20cb3ffc 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/grades/models/ExpandableItemModel.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/grades/models/ExpandableItemModel.kt @@ -6,7 +6,7 @@ package pl.szczodrzynski.edziennik.ui.modules.grades.models import pl.szczodrzynski.edziennik.ui.modules.grades.GradesAdapter.Companion.STATE_CLOSED -abstract class ExpandableItemModel(val items: MutableList) { +abstract class ExpandableItemModel(open val items: MutableList) { open var level: Int = 3 var state: Int = STATE_CLOSED } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/grades/viewholder/GradeViewHolder.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/grades/viewholder/GradeViewHolder.kt index e71b7819..2af79ba3 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/grades/viewholder/GradeViewHolder.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/grades/viewholder/GradeViewHolder.kt @@ -52,7 +52,7 @@ class GradeViewHolder( b.gradeWeight.text = weightText b.gradeWeight.isVisible = weightText != null - b.gradeTeacherName.text = grade.teacherFullName + b.gradeTeacherName.text = grade.teacherName b.gradeAddedDate.text = Date.fromMillis(grade.addedDate).let { it.getRelativeString(app, 5) ?: it.formattedStringShort } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/home/CounterActivity.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/home/CounterActivity.kt index 36002444..e337f720 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/home/CounterActivity.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/home/CounterActivity.kt @@ -51,7 +51,7 @@ class CounterActivity : AppCompatActivity(), CoroutineScope { withContext(Dispatchers.Default) { lessonList.apply { clear() - addAll(app.db.timetableDao().getForDateNow(App.profileId, Date.getToday()) + addAll(app.db.timetableDao().getAllForDateNow(App.profileId, Date.getToday()) .filter { it.type != Lesson.TYPE_NO_LESSONS && it.type != Lesson.TYPE_CANCELLED && it.type != Lesson.TYPE_SHIFTED_SOURCE diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/home/cards/HomeGradesCard.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/home/cards/HomeGradesCard.kt index 0e26eb2c..f1c3373e 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/home/cards/HomeGradesCard.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/home/cards/HomeGradesCard.kt @@ -35,6 +35,7 @@ import pl.szczodrzynski.edziennik.ui.modules.home.HomeCard import pl.szczodrzynski.edziennik.ui.modules.home.HomeCardAdapter import pl.szczodrzynski.edziennik.ui.modules.home.HomeFragment import pl.szczodrzynski.edziennik.utils.Utils +import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.ItemGradesSubjectModel import kotlin.coroutines.CoroutineContext @@ -63,7 +64,7 @@ class HomeGradesCard( } holder.root += b.root - val sevenDaysAgo = System.currentTimeMillis() - 7 * 24 * 60 * 60 * 1000 + val sevenDaysAgo = Date.getToday().stepForward(0, 0, -7) app.db.gradeDao().getAllFromDate(profile.id, sevenDaysAgo).observe(fragment, Observer { grades.apply { @@ -120,7 +121,8 @@ class HomeGradesCard( val gradeName = GradeView( gradeItem.context, grade, - app.gradesManager + app.gradesManager, + periodGradesTextual = true ) totalWidth += gradeName.measuredWidth + 5.dp diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/home/cards/HomeLuckyNumberCard.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/home/cards/HomeLuckyNumberCard.kt index c5e4aede..31611e48 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/home/cards/HomeLuckyNumberCard.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/home/cards/HomeLuckyNumberCard.kt @@ -60,7 +60,7 @@ class HomeLuckyNumberCard( R.string.home_lucky_number_details b.subText.setText(subTextRes, profile.name ?: "", profile.studentNumber) - app.db.luckyNumberDao().getNearestFuture(profile.id, todayValue).observe(fragment, Observer { luckyNumber -> + app.db.luckyNumberDao().getNearestFuture(profile.id, today).observe(fragment, Observer { luckyNumber -> val isYours = luckyNumber?.number == profile.studentNumber val res: Pair> = when { luckyNumber == null -> R.string.home_lucky_number_no_info to emptyArray() diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginActivity.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginActivity.kt index 7f65279f..c686b857 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginActivity.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginActivity.kt @@ -1,3 +1,7 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-16. + */ + package pl.szczodrzynski.edziennik.ui.modules.login import android.app.Activity @@ -14,20 +18,19 @@ import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.data.api.models.ApiError import pl.szczodrzynski.edziennik.data.db.entity.LoginStore -import pl.szczodrzynski.edziennik.databinding.ActivityLoginBinding +import pl.szczodrzynski.edziennik.databinding.LoginActivityBinding import pl.szczodrzynski.edziennik.ui.modules.error.ErrorSnackbar import kotlin.coroutines.CoroutineContext class LoginActivity : AppCompatActivity(), CoroutineScope { companion object { private const val TAG = "LoginActivity" - @JvmField - var navOptions: NavOptions? = null var thisOneIsTricky = 0 } private val app: App by lazy { applicationContext as App } - private lateinit var b: ActivityLoginBinding + private lateinit var b: LoginActivityBinding + lateinit var navOptions: NavOptions val nav by lazy { Navigation.findNavController(this, R.id.nav_host_fragment) } val errorSnackbar: ErrorSnackbar by lazy { ErrorSnackbar(this) } @@ -36,7 +39,7 @@ class LoginActivity : AppCompatActivity(), CoroutineScope { get() = job + Dispatchers.Main var lastError: ApiError? = null - val profiles = mutableListOf() + val profiles = mutableListOf() val loginStores = mutableListOf() override fun onBackPressed() { @@ -50,7 +53,9 @@ class LoginActivity : AppCompatActivity(), CoroutineScope { return if (destination.id == R.id.loginSyncFragment) return - if (destination.id == R.id.loginChooserFragment) { + if (destination.id == R.id.loginFinishFragment) + return + if (destination.id == R.id.loginChooserFragment && loginStores.isEmpty()) { setResult(Activity.RESULT_CANCELED) finish() return @@ -83,7 +88,7 @@ class LoginActivity : AppCompatActivity(), CoroutineScope { .setPopExitAnim(R.anim.slide_out_right) .build() - b = ActivityLoginBinding.inflate(layoutInflater) + b = LoginActivityBinding.inflate(layoutInflater) setContentView(b.root) errorSnackbar.setCoordinator(b.coordinator, b.snackbarAnchor) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginChooserAdapter.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginChooserAdapter.kt new file mode 100644 index 00000000..c9ed2d6e --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginChooserAdapter.kt @@ -0,0 +1,135 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-16. + */ + +package pl.szczodrzynski.edziennik.ui.modules.login + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.appcompat.app.AppCompatActivity +import androidx.recyclerview.widget.RecyclerView +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.ui.modules.grades.models.ExpandableItemModel +import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.BindableViewHolder +import pl.szczodrzynski.edziennik.ui.modules.login.viewholder.ModeViewHolder +import pl.szczodrzynski.edziennik.ui.modules.login.viewholder.RegisterViewHolder +import kotlin.coroutines.CoroutineContext + +class LoginChooserAdapter( + val activity: AppCompatActivity, + val onModeClick: ((loginType: LoginInfo.Register, loginMode: LoginInfo.Mode) -> Unit)? = null +) : RecyclerView.Adapter(), CoroutineScope { + companion object { + private const val TAG = "LoginChooserAdapter" + private const val ITEM_TYPE_REGISTER = 0 + private const val ITEM_TYPE_MODE = 1 + const val STATE_CLOSED = 0 + const val STATE_OPENED = 1 + } + + private val app = activity.applicationContext as App + // optional: place the manager here + + private val job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Main + + var items = mutableListOf() + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + val inflater = LayoutInflater.from(parent.context) + return when (viewType) { + ITEM_TYPE_REGISTER -> RegisterViewHolder(inflater, parent) + ITEM_TYPE_MODE -> ModeViewHolder(inflater, parent) + else -> throw IllegalArgumentException("Incorrect viewType") + } + } + + override fun getItemViewType(position: Int): Int { + return when (items[position]) { + is LoginInfo.Register -> ITEM_TYPE_REGISTER + is LoginInfo.Mode -> ITEM_TYPE_MODE + else -> throw IllegalArgumentException("Incorrect viewType") + } + } + + private val onClickListener = View.OnClickListener { view -> + val model = view.getTag(R.string.tag_key_model) + if (model is LoginInfo.Register && model.loginModes.size == 1) { + onModeClick?.invoke(model, model.loginModes.first()) + return@OnClickListener + } + if (model is LoginInfo.Mode) { + val loginInfo = items.firstOrNull { + it is LoginInfo.Register && it.loginModes.contains(model) + } as? LoginInfo.Register + ?: return@OnClickListener + + onModeClick?.invoke(loginInfo, model) + return@OnClickListener + } + if (model !is LoginInfo.Register) + return@OnClickListener + expandModel(model, view) + } + + private fun expandModel(model: LoginInfo.Register, view: View?, notifyAdapter: Boolean = true) { + val position = items.indexOf(model) + if (position == -1) + return + + if (model.state == STATE_CLOSED) { + + val subItems = model.items + + model.state = STATE_OPENED + items.addAll(position + 1, subItems) + if (notifyAdapter) notifyItemRangeInserted(position + 1, subItems.size) + } + else { + val start = position + 1 + var end: Int = items.size + for (i in start until items.size) { + val model1 = items[i] + val level = (model1 as? ExpandableItemModel<*>)?.level ?: 3 + if (level <= model.level) { + end = i + break + } else { + if (model1 is ExpandableItemModel<*> && model1.state == STATE_OPENED) { + model1.state = STATE_CLOSED + } + } + } + + if (end != -1) { + items.subList(start, end).clear() + if (notifyAdapter) notifyItemRangeRemoved(start, end - start) + } + + model.state = STATE_CLOSED + } + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + val item = items[position] + if (holder !is BindableViewHolder<*, *>) + return + + holder.itemView.setTag(R.string.tag_key_model, item) + + when { + holder is RegisterViewHolder && item is LoginInfo.Register -> holder.onBind(activity, app, item, position, this) + holder is ModeViewHolder && item is LoginInfo.Mode -> holder.onBind(activity, app, item, position, this) + } + + holder.itemView.setOnClickListener(onClickListener) + } + + override fun getItemCount() = items.size +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginChooserFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginChooserFragment.kt index a522d427..c25b07d1 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginChooserFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginChooserFragment.kt @@ -1,78 +1,96 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-16. + */ + package pl.szczodrzynski.edziennik.ui.modules.login import android.app.Activity import android.content.Intent import android.os.Bundle -import android.os.Process import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.view.animation.AnimationUtils -import android.widget.CompoundButton +import androidx.core.view.isVisible import androidx.fragment.app.Fragment -import com.afollestad.materialdialogs.DialogAction -import com.afollestad.materialdialogs.MaterialDialog -import com.google.android.material.dialog.MaterialAlertDialogBuilder +import androidx.recyclerview.widget.LinearLayoutManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.Bundle import pl.szczodrzynski.edziennik.R -import pl.szczodrzynski.edziennik.databinding.FragmentLoginChooserBinding -import pl.szczodrzynski.edziennik.onChange +import pl.szczodrzynski.edziennik.databinding.LoginChooserFragmentBinding import pl.szczodrzynski.edziennik.onClick import pl.szczodrzynski.edziennik.ui.modules.feedback.FeedbackActivity -import pl.szczodrzynski.edziennik.utils.Anim -import kotlin.system.exitProcess +import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration +import kotlin.coroutines.CoroutineContext - -class LoginChooserFragment : Fragment() { +class LoginChooserFragment : Fragment(), CoroutineScope { companion object { private const val TAG = "LoginChooserFragment" - var fakeLogin = false } private lateinit var app: App private lateinit var activity: LoginActivity - private lateinit var b: FragmentLoginChooserBinding + private lateinit var b: LoginChooserFragmentBinding private val nav by lazy { activity.nav } + private val job: Job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Main + + // local/private variables go here + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { activity = (getActivity() as LoginActivity?) ?: return null context ?: return null app = activity.application as App - b = FragmentLoginChooserBinding.inflate(inflater) + b = LoginChooserFragmentBinding.inflate(inflater) return b.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - b.topLogo.onClick { - if (LoginActivity.thisOneIsTricky <= -1) { - LoginActivity.thisOneIsTricky = 999 - } - if (LoginActivity.thisOneIsTricky in 0..7) { - LoginActivity.thisOneIsTricky++ - if (LoginActivity.thisOneIsTricky == 7) { - b.topLogo.startAnimation(AnimationUtils.loadAnimation(activity, R.anim.shake)); - if (b.devMode.visibility != View.VISIBLE) - Anim.expand(b.devMode, 500, null); - LoginActivity.thisOneIsTricky = 3 - } + if (!isAdded) return + + val adapter = LoginChooserAdapter(activity) { loginType, loginMode -> + if (loginMode.isPlatformSelection) { + nav.navigate(R.id.loginPlatformListFragment, Bundle( + "loginType" to loginType.loginType, + "loginMode" to loginMode.loginMode + ), activity.navOptions) + return@LoginChooserAdapter } + + nav.navigate(R.id.loginFormFragment, Bundle( + "loginType" to loginType.loginType, + "loginMode" to loginMode.loginMode + ), activity.navOptions) + } + + LoginInfo.chooserList = LoginInfo.chooserList + ?: LoginInfo.list.toMutableList() + + adapter.items = LoginInfo.chooserList!! + b.list.adapter = adapter + b.list.apply { + setHasFixedSize(true) + layoutManager = LinearLayoutManager(context) + addItemDecoration(SimpleDividerItemDecoration(context)) + } + + b.helpButton.onClick { + startActivity(Intent(activity, FeedbackActivity::class.java)) } - b.loginMobidziennikLogo.onClick { nav.navigate(R.id.loginMobidziennikFragment, null, LoginActivity.navOptions) } - b.loginLibrusLogo.onClick { nav.navigate(R.id.loginLibrusFragment, null, LoginActivity.navOptions) } - b.loginVulcanLogo.onClick { nav.navigate(R.id.loginVulcanFragment, null, LoginActivity.navOptions) } - b.loginIuczniowieLogo.onClick { nav.navigate(R.id.loginIuczniowieFragment, null, LoginActivity.navOptions) } - b.loginLibrusJstLogo.onClick { nav.navigate(R.id.loginLibrusJstFragment, null, LoginActivity.navOptions) } - b.loginEdudziennikLogo.onClick { nav.navigate(R.id.loginEdudziennikFragment, null, LoginActivity.navOptions) } when { activity.loginStores.isNotEmpty() -> { // we are navigated here from LoginSummary - b.cancelButton.visibility = View.VISIBLE + b.cancelButton.isVisible = true b.cancelButton.onClick { nav.navigateUp() } } app.config.loginFinished -> { // we are navigated here from AppDrawer - b.cancelButton.visibility = View.VISIBLE + b.cancelButton.isVisible = true b.cancelButton.onClick { activity.setResult(Activity.RESULT_CANCELED) activity.finish() @@ -80,57 +98,8 @@ class LoginChooserFragment : Fragment() { } else -> { // there are no profiles - b.cancelButton.visibility = View.GONE + b.cancelButton.isVisible = false } } - - b.devMode.visibility = if (App.debugMode) View.VISIBLE else View.GONE - b.devMode.isChecked = app.config.debugMode - b.devMode.onChange { v, isChecked -> - if (isChecked) { - MaterialDialog.Builder(activity) - .title(R.string.are_you_sure) - .content(R.string.dev_mode_enable_warning) - .positiveText(R.string.yes) - .negativeText(R.string.no) - .onPositive { _: MaterialDialog?, _: DialogAction? -> - app.config.debugMode = true - App.devMode = true - MaterialAlertDialogBuilder(activity) - .setTitle("Restart") - .setMessage("Wymagany restart aplikacji") - .setPositiveButton("OK") { _, _ -> - Process.killProcess(Process.myPid()) - Runtime.getRuntime().exit(0) - exitProcess(0) - } - .setCancelable(false) - .show() - } - .onNegative { _: MaterialDialog?, _: DialogAction? -> - app.config.debugMode = false - App.devMode = false - b.devMode.isChecked = app.config.debugMode - b.devMode.jumpDrawablesToCurrentState() - Anim.collapse(b.devMode, 1000, null) - } - .show() - } else { - app.config.debugMode = false - App.devMode = false - /*if (b.devModeLayout.getVisibility() === View.VISIBLE) { - Anim.collapse(b.devModeTitle, 500, null) - Anim.collapse(b.devModeLayout, 500, null) - }*/ - } - } - - b.fakeLogin.visibility = if (App.devMode) View.VISIBLE else View.GONE - b.fakeLogin.isChecked = fakeLogin - b.fakeLogin.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> - fakeLogin = isChecked - } - - b.helpButton.onClick { startActivity(Intent(activity, FeedbackActivity::class.java)) } } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginEdudziennikFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginEdudziennikFragment.kt deleted file mode 100644 index be1a83a7..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginEdudziennikFragment.kt +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (c) Kuba Szczodrzyński 2020-1-3. - */ - -package pl.szczodrzynski.edziennik.ui.modules.login - -import android.animation.ArgbEvaluator -import android.animation.ObjectAnimator -import android.graphics.Color -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.Fragment -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import pl.szczodrzynski.edziennik.* -import pl.szczodrzynski.edziennik.data.api.ERROR_LOGIN_EDUDZIENNIK_WEB_INVALID_LOGIN -import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_EDUDZIENNIK -import pl.szczodrzynski.edziennik.databinding.FragmentLoginEdudziennikBinding -import java.util.* -import kotlin.coroutines.CoroutineContext - - -class LoginEdudziennikFragment : Fragment(), CoroutineScope { - companion object { - private const val TAG = "LoginEdudziennikFragment" - } - - private lateinit var app: App - private lateinit var activity: LoginActivity - private lateinit var b: FragmentLoginEdudziennikBinding - private val nav by lazy { activity.nav } - private var hehe = 0 - - private val job: Job = Job() - override val coroutineContext: CoroutineContext - get() = job + Dispatchers.Main - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - activity = (getActivity() as LoginActivity?) ?: return null - context ?: return null - app = activity.application as App - b = FragmentLoginEdudziennikBinding.inflate(inflater) - return b.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - activity.lastError?.let { error -> - activity.lastError = null - startCoroutineTimer(delayMillis = 100) { - when (error.errorCode) { - ERROR_LOGIN_EDUDZIENNIK_WEB_INVALID_LOGIN -> - b.loginPasswordLayout.error = getString(R.string.login_error_incorrect_login_or_password) - } - } - } - - b.topText.onClick { - if (LoginActivity.thisOneIsTricky != -1) - return@onClick - hehe++ - if (hehe >= 5) { - LoginActivity.thisOneIsTricky = 3 - val colorAnim = ObjectAnimator.ofInt( - b.topText, - "textColor", - Color.BLACK, - Color.RED, - Color.BLACK, - Color.RED, - Color.BLACK, - Color.RED, - Color.BLACK - ) - colorAnim.setEvaluator(ArgbEvaluator()) - colorAnim.duration = 1500L - colorAnim.start() - } - } - - b.backButton.onClick { nav.navigateUp() } - - b.loginButton.onClick { - var errors = false - - b.loginEmailLayout.error = null - b.loginPasswordLayout.error = null - - val email = b.loginEmail.text?.toString()?.toLowerCase(Locale.ROOT) ?: "" - val password = b.loginPassword.text?.toString() ?: "" - - if (email.isBlank()) { - b.loginEmailLayout.error = getString(R.string.login_error_no_email) - errors = true - } - if (password.isBlank()) { - b.loginPasswordLayout.error = getString(R.string.login_error_no_password) - errors = true - } - if (errors) return@onClick - - errors = false - - b.loginEmail.setText(email) - if (!"([\\w.\\-_+]+)?\\w+@[\\w-_]+(\\.\\w+)+".toRegex().matches(email)) { - b.loginEmailLayout.error = getString(R.string.login_error_incorrect_email) - errors = true - } - if (errors) return@onClick - - val args = Bundle( - "loginType" to LOGIN_TYPE_EDUDZIENNIK, - "email" to email, - "password" to password - ) - nav.navigate(R.id.loginProgressFragment, args, LoginActivity.navOptions) - } - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginFinishFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginFinishFragment.kt index 8f52b41a..7b2e66b8 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginFinishFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginFinishFragment.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) Kuba Szczodrzyński 2020-1-4. + * Copyright (c) Kuba Szczodrzyński 2020-4-16. */ package pl.szczodrzynski.edziennik.ui.modules.login @@ -14,7 +14,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import pl.szczodrzynski.edziennik.* -import pl.szczodrzynski.edziennik.databinding.FragmentLoginFinishBinding +import pl.szczodrzynski.edziennik.databinding.LoginFinishFragmentBinding import kotlin.coroutines.CoroutineContext class LoginFinishFragment : Fragment(), CoroutineScope { @@ -24,27 +24,29 @@ class LoginFinishFragment : Fragment(), CoroutineScope { private lateinit var app: App private lateinit var activity: LoginActivity - private lateinit var b: FragmentLoginFinishBinding + private lateinit var b: LoginFinishFragmentBinding private val nav by lazy { activity.nav } private val job: Job = Job() override val coroutineContext: CoroutineContext get() = job + Dispatchers.Main + // local/private variables go here + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { activity = (getActivity() as LoginActivity?) ?: return null context ?: return null app = activity.application as App - b = FragmentLoginFinishBinding.inflate(inflater) + b = LoginFinishFragmentBinding.inflate(inflater) return b.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - val firstRun = !App.config.loginFinished - App.config.loginFinished = true + val firstRun = !app.config.loginFinished + app.config.loginFinished = true if (!firstRun) { - b.loginFinishSubtitle.setText(R.string.login_finish_subtitle_not_first_run) + b.subTitle.setText(R.string.login_finish_subtitle_not_first_run) } b.finishButton.onClick { @@ -74,4 +76,4 @@ class LoginFinishFragment : Fragment(), CoroutineScope { } } } -} \ No newline at end of file +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginFormFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginFormFragment.kt new file mode 100644 index 00000000..d0676105 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginFormFragment.kt @@ -0,0 +1,177 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-16. + */ + +package pl.szczodrzynski.edziennik.ui.modules.login + +import android.annotation.SuppressLint +import android.os.Bundle +import android.text.InputType +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.view.isVisible +import androidx.core.widget.addTextChangedListener +import androidx.fragment.app.Fragment +import com.google.android.material.textfield.TextInputLayout +import com.google.gson.JsonParser +import com.mikepenz.iconics.IconicsDrawable +import com.mikepenz.iconics.utils.paddingDp +import com.mikepenz.iconics.utils.sizeDp +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.databinding.LoginFormFragmentBinding +import pl.szczodrzynski.edziennik.databinding.LoginFormItemBinding +import pl.szczodrzynski.navlib.colorAttr +import java.util.* +import kotlin.coroutines.CoroutineContext + +class LoginFormFragment : Fragment(), CoroutineScope { + companion object { + private const val TAG = "LoginFormFragment" + } + + private lateinit var app: App + private lateinit var activity: LoginActivity + private lateinit var b: LoginFormFragmentBinding + private val nav by lazy { activity.nav } + + private val job: Job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Main + + // local/private variables go here + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + activity = (getActivity() as LoginActivity?) ?: return null + context ?: return null + app = activity.application as App + b = LoginFormFragmentBinding.inflate(inflater) + return b.root + } + + @SuppressLint("ResourceType") + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + if (!isAdded) return + b.backButton.onClick { nav.navigateUp() } + + b.errorLayout.isVisible = false + b.errorLayout.background?.setTintColor(R.attr.colorError.resolveAttr(activity)) + + val loginType = arguments?.getInt("loginType") ?: return + val register = LoginInfo.list.firstOrNull { it.loginType == loginType } ?: return + val loginMode = arguments?.getInt("loginMode") ?: return + val mode = register.loginModes.firstOrNull { it.loginMode == loginMode } ?: return + + val platformName = arguments?.getString("platformName") + val platformGuideText = arguments?.getString("platformGuideText") + val platformDescription = arguments?.getString("platformDescription") + val platformFormFields = arguments?.getString("platformFormFields")?.split(";") + val platformApiData = arguments?.getString("platformApiData")?.let { JsonParser().parse(it)?.asJsonObject } + + 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) + + val credentials = mutableMapOf() + + for (credential in mode.credentials) { + if (platformFormFields?.contains(credential.keyName) == false) + continue + + val b = LoginFormItemBinding.inflate(layoutInflater) + b.textLayout.hint = app.getString(credential.name) + if (credential.hideText) { + b.textEdit.inputType = InputType.TYPE_TEXT_VARIATION_PASSWORD + b.textLayout.endIconMode = TextInputLayout.END_ICON_PASSWORD_TOGGLE + } + b.textEdit.addTextChangedListener { + b.textLayout.error = null + } + + b.textEdit.id = credential.name + + b.textEdit.setText(arguments?.getString(credential.keyName) ?: "") + b.textLayout.startIconDrawable = IconicsDrawable(activity) + .icon(credential.icon) + .sizeDp(24) + .paddingDp(2) + .colorAttr(activity, R.attr.colorOnBackground) + + this.b.formContainer.addView(b.root) + credentials[credential] = b + } + + activity.lastError?.let { error -> + activity.lastError = null + startCoroutineTimer(delayMillis = 200L) { + for (credential in credentials) { + credential.key.errorCodes[error.errorCode]?.let { + credential.value.textLayout.error = app.getString(it) + return@startCoroutineTimer + } + } + mode.errorCodes[error.errorCode]?.let { + b.errorText.text = app.getString(it) + b.errorLayout.isVisible = true + return@startCoroutineTimer + } + } + } + + b.loginButton.onClick { + val payload = Bundle( + "loginType" to loginType, + "loginMode" to loginMode + ) + + if (App.devMode && b.fakeLogin.isChecked) { + payload.putBoolean("fakeLogin", true) + } + + platformApiData?.entrySet()?.forEach { + payload.putString(it.key, it.value.asString) + } + + var hasErrors = false + credentials.forEach { (credential, b) -> + var text = b.textEdit.text?.toString() ?: return@forEach + if (!credential.hideText) + text = text.trim() + + if (credential.caseMode == LoginInfo.Credential.CaseMode.UPPER_CASE) + text = text.toUpperCase(Locale.getDefault()) + if (credential.caseMode == LoginInfo.Credential.CaseMode.LOWER_CASE) + text = text.toLowerCase(Locale.getDefault()) + + credential.stripTextRegex?.let { + text = text.replace(it.toRegex(), "") + } + + b.textEdit.setText(text) + + if (credential.isRequired && text.isBlank()) { + b.textLayout.error = app.getString(credential.emptyText) + hasErrors = true + return@forEach + } + + if (!text.matches(credential.validationRegex.toRegex())) { + b.textLayout.error = app.getString(credential.invalidText) + hasErrors = true + return@forEach + } + + payload.putString(credential.keyName, text) + arguments?.putString(credential.keyName, text) + } + + if (hasErrors) + return@onClick + + nav.navigate(R.id.loginProgressFragment, payload, activity.navOptions) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginInfo.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginInfo.kt new file mode 100644 index 00000000..146fd05b --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginInfo.kt @@ -0,0 +1,439 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-16. + */ + +package pl.szczodrzynski.edziennik.ui.modules.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.ui.modules.grades.models.ExpandableItemModel + +object LoginInfo { + + private fun getEmailCredential(keyName: String) = Credential( + keyName = keyName, + name = R.string.login_hint_email, + icon = CommunityMaterial.Icon.cmd_at, + emptyText = R.string.login_error_no_email, + invalidText = R.string.login_error_incorrect_email, + errorCodes = mapOf(), + isRequired = true, + validationRegex = "([\\w.\\-_+]+)?\\w+@[\\w-_]+(\\.\\w+)+", + caseMode = Credential.CaseMode.LOWER_CASE + ) + private fun getPasswordCredential(keyName: String) = Credential( + keyName = keyName, + name = R.string.login_hint_password, + icon = CommunityMaterial.Icon2.cmd_lock_outline, + emptyText = R.string.login_error_no_password, + invalidText = R.string.login_error_incorrect_login_or_password, + errorCodes = mapOf(), + isRequired = true, + validationRegex = ".*", + hideText = true + ) + + val list by lazy { listOf( + Register( + loginType = LOGIN_TYPE_LIBRUS, + internalName = "librus", + registerName = R.string.login_register_librus, + registerLogo = R.drawable.login_logo_librus, + loginModes = listOf( + Mode( + loginMode = LOGIN_MODE_LIBRUS_EMAIL, + name = R.string.login_mode_librus_email, + icon = R.drawable.login_mode_librus_email, + hintText = R.string.login_mode_librus_email_hint, + guideText = R.string.login_mode_librus_email_guide, + isRecommended = true, + credentials = listOf( + getEmailCredential("email"), + getPasswordCredential("password") + ), + 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, + name = R.string.login_mode_librus_synergia, + icon = R.drawable.login_mode_librus_synergia, + hintText = R.string.login_mode_librus_synergia_hint, + guideText = R.string.login_mode_librus_synergia_guide, + credentials = listOf( + Credential( + keyName = "accountLogin", + name = R.string.login_hint_login, + icon = CommunityMaterial.Icon.cmd_account_outline, + emptyText = R.string.login_error_no_login, + invalidText = R.string.login_error_incorrect_login, + errorCodes = mapOf(), + isRequired = true, + validationRegex = "[A-z0-9._\\-+]+", + caseMode = Credential.CaseMode.LOWER_CASE + ), + getPasswordCredential("accountPassword") + ), + errorCodes = mapOf( + ERROR_LOGIN_LIBRUS_API_INVALID_LOGIN to R.string.login_error_incorrect_login_or_password, + ERROR_LOGIN_LIBRUS_API_INVALID_REQUEST to R.string.login_error_incorrect_login_or_password + ) + ),*/ + Mode( + loginMode = LOGIN_MODE_LIBRUS_JST, + name = R.string.login_mode_librus_jst, + icon = R.drawable.login_mode_librus_jst, + hintText = R.string.login_mode_librus_jst_hint, + guideText = R.string.login_mode_librus_jst_guide, + credentials = listOf( + Credential( + keyName = "accountCode", + name = R.string.login_hint_token, + icon = CommunityMaterial.Icon.cmd_code_braces, + emptyText = R.string.login_error_no_token, + invalidText = R.string.login_error_incorrect_token, + errorCodes = mapOf(), + isRequired = true, + validationRegex = "[A-Z0-9_]+", + caseMode = Credential.CaseMode.UPPER_CASE + ), + Credential( + keyName = "accountPin", + name = R.string.login_hint_pin, + icon = CommunityMaterial.Icon2.cmd_lock, + emptyText = R.string.login_error_no_pin, + invalidText = R.string.login_error_incorrect_pin, + errorCodes = mapOf(), + isRequired = true, + validationRegex = "[a-z0-9_]+", + caseMode = Credential.CaseMode.LOWER_CASE + ) + ), + errorCodes = mapOf( + ERROR_LOGIN_LIBRUS_API_INVALID_LOGIN to R.string.login_error_incorrect_code_or_pin, + ERROR_LOGIN_LIBRUS_API_INVALID_REQUEST to R.string.login_error_incorrect_code_or_pin + ) + ) + ) + ), + Register( + loginType = LOGIN_TYPE_VULCAN, + internalName = "vulcan", + registerName = R.string.login_type_vulcan, + registerLogo = R.drawable.login_logo_vulcan, + loginModes = listOf( + Mode( + loginMode = LOGIN_MODE_VULCAN_API, + name = R.string.login_mode_vulcan_api, + icon = R.drawable.login_mode_vulcan_api, + hintText = R.string.login_mode_vulcan_api_hint, + guideText = R.string.login_mode_vulcan_api_guide, + isRecommended = true, + credentials = listOf( + Credential( + keyName = "deviceToken", + name = R.string.login_hint_token, + icon = CommunityMaterial.Icon.cmd_code_braces, + emptyText = R.string.login_error_no_token, + invalidText = R.string.login_error_incorrect_token, + errorCodes = mapOf( + ERROR_LOGIN_VULCAN_INVALID_TOKEN to R.string.login_error_incorrect_token + ), + isRequired = true, + validationRegex = "[A-Z0-9]{5,12}", + caseMode = Credential.CaseMode.UPPER_CASE + ), + Credential( + keyName = "symbol", + name = R.string.login_hint_symbol, + icon = CommunityMaterial.Icon2.cmd_school, + emptyText = R.string.login_error_no_symbol, + invalidText = R.string.login_error_incorrect_symbol, + errorCodes = mapOf( + ERROR_LOGIN_VULCAN_INVALID_SYMBOL to R.string.login_error_incorrect_symbol + ), + isRequired = true, + validationRegex = "[a-z0-9_-]+", + caseMode = Credential.CaseMode.LOWER_CASE + ), + Credential( + keyName = "devicePin", + name = R.string.login_hint_pin, + icon = CommunityMaterial.Icon2.cmd_lock, + emptyText = R.string.login_error_no_pin, + invalidText = R.string.login_error_incorrect_pin, + errorCodes = mapOf( + ERROR_LOGIN_VULCAN_INVALID_PIN to R.string.login_error_incorrect_pin + ), + isRequired = true, + validationRegex = "[0-9]+", + caseMode = Credential.CaseMode.LOWER_CASE + ) + ), + errorCodes = mapOf( + ERROR_LOGIN_VULCAN_EXPIRED_TOKEN to R.string.login_error_expired_token + ) + )/*, + Mode( + loginMode = LOGIN_MODE_VULCAN_WEB, + name = R.string.login_mode_vulcan_web, + icon = R.drawable.login_mode_vulcan_web, + hintText = R.string.login_mode_vulcan_web_hint, + guideText = R.string.login_mode_vulcan_web_guide, + isTesting = true, + isPlatformSelection = true, + credentials = listOf( + getEmailCredential("webEmail"), + Credential( + keyName = "webUsername", + name = R.string.login_hint_username, + icon = CommunityMaterial.Icon.cmd_account_outline, + emptyText = R.string.login_error_no_username, + invalidText = R.string.login_error_incorrect_username, + errorCodes = mapOf(), + isRequired = true, + validationRegex = "[A-Z]{7}[0-9]+", + caseMode = Credential.CaseMode.UPPER_CASE + ), + getPasswordCredential("webPassword") + ), + errorCodes = mapOf() + )*/ + ) + ), + Register( + loginType = LOGIN_TYPE_MOBIDZIENNIK, + internalName = "mobidziennik", + registerName = R.string.login_type_mobidziennik, + registerLogo = R.drawable.login_logo_mobidziennik, + loginModes = listOf( + Mode( + loginMode = LOGIN_MODE_MOBIDZIENNIK_WEB, + name = R.string.login_mode_mobidziennik_web, + icon = R.drawable.login_mode_mobidziennik_web, + hintText = R.string.login_mode_mobidziennik_web_hint, + guideText = R.string.login_mode_mobidziennik_web_guide, + credentials = listOf( + Credential( + keyName = "username", + name = R.string.login_hint_login_email, + icon = CommunityMaterial.Icon.cmd_account_outline, + emptyText = R.string.login_error_no_login, + invalidText = R.string.login_error_incorrect_login, + errorCodes = mapOf(), + isRequired = true, + validationRegex = "^[a-z0-9_\\-@+.]+$", + caseMode = Credential.CaseMode.LOWER_CASE + ), + Credential( + keyName = "password", + name = R.string.login_hint_password, + icon = CommunityMaterial.Icon2.cmd_lock_outline, + emptyText = R.string.login_error_no_password, + invalidText = R.string.login_error_incorrect_login_or_password, + errorCodes = mapOf( + ERROR_LOGIN_MOBIDZIENNIK_WEB_OLD_PASSWORD to R.string.login_error_old_password + ), + isRequired = true, + validationRegex = ".*", + hideText = true + ), + Credential( + keyName = "serverName", + name = R.string.login_hint_address, + icon = CommunityMaterial.Icon2.cmd_web, + emptyText = R.string.login_error_no_address, + invalidText = R.string.login_error_incorrect_address, + errorCodes = mapOf( + ERROR_LOGIN_MOBIDZIENNIK_WEB_INVALID_ADDRESS to R.string.login_error_incorrect_address + ), + isRequired = true, + validationRegex = "^[a-z0-9_\\-]+\$", + caseMode = Credential.CaseMode.LOWER_CASE + ) + ), + errorCodes = mapOf( + ERROR_LOGIN_MOBIDZIENNIK_WEB_INVALID_LOGIN to R.string.login_error_incorrect_login_or_password, + ERROR_LOGIN_MOBIDZIENNIK_WEB_ARCHIVED to R.string.sync_error_archived + ) + ) + ) + ), + Register( + loginType = LOGIN_TYPE_IDZIENNIK, + internalName = "idziennik", + registerName = R.string.login_type_idziennik, + registerLogo = R.drawable.login_logo_iuczniowie, + loginModes = listOf( + Mode( + loginMode = LOGIN_MODE_IDZIENNIK_WEB, + name = R.string.login_mode_idziennik_web, + icon = R.drawable.login_mode_idziennik_web, + hintText = R.string.login_mode_idziennik_web_hint, + guideText = R.string.login_mode_idziennik_web_guide, + credentials = listOf( + Credential( + keyName = "schoolName", + name = R.string.login_hint_school_name, + icon = CommunityMaterial.Icon2.cmd_school, + emptyText = R.string.login_error_no_school_name, + invalidText = R.string.login_error_incorrect_school_name, + errorCodes = mapOf( + ERROR_LOGIN_IDZIENNIK_WEB_INVALID_SCHOOL_NAME to R.string.login_error_incorrect_school_name + ), + isRequired = true, + validationRegex = "^[a-z0-9_\\-.]+$", + caseMode = Credential.CaseMode.LOWER_CASE + ), + Credential( + keyName = "username", + name = R.string.login_hint_username, + icon = CommunityMaterial.Icon.cmd_account_outline, + emptyText = R.string.login_error_no_username, + invalidText = R.string.login_error_incorrect_username, + errorCodes = mapOf(), + isRequired = true, + validationRegex = "^[a-z0-9_\\-.]+$", + caseMode = Credential.CaseMode.LOWER_CASE + ), + getPasswordCredential("password") + ), + errorCodes = mapOf( + ERROR_LOGIN_IDZIENNIK_WEB_INVALID_LOGIN to R.string.login_error_incorrect_login_or_password + ) + ) + ) + ), + Register( + loginType = LOGIN_TYPE_EDUDZIENNIK, + internalName = "edudziennik", + registerName = R.string.login_type_edudziennik, + registerLogo = R.drawable.login_logo_edudziennik, + loginModes = listOf( + Mode( + loginMode = LOGIN_MODE_EDUDZIENNIK_WEB, + name = R.string.login_mode_edudziennik_web, + icon = R.drawable.login_mode_edudziennik_web, + hintText = R.string.login_mode_edudziennik_web_hint, + guideText = R.string.login_mode_edudziennik_web_guide, + credentials = listOf( + getEmailCredential("email"), + getPasswordCredential("password") + ), + errorCodes = mapOf( + ERROR_LOGIN_EDUDZIENNIK_WEB_INVALID_LOGIN to R.string.login_error_incorrect_login_or_password + ) + ) + ) + ), + Register( + loginType = LOGIN_TYPE_PODLASIE, + internalName = "podlasie", + registerName = R.string.login_type_podlasie, + registerLogo = R.drawable.login_logo_podlasie, + loginModes = listOf( + Mode( + loginMode = LOGIN_MODE_PODLASIE_API, + name = R.string.login_mode_podlasie_api, + icon = R.drawable.login_mode_podlasie_api, + guideText = R.string.login_mode_podlasie_api_guide, + credentials = listOf( + Credential( + keyName = "apiToken", + name = R.string.login_hint_token, + icon = CommunityMaterial.Icon2.cmd_lock_outline, + emptyText = R.string.login_error_no_token, + invalidText = R.string.login_error_incorrect_token, + errorCodes = mapOf(), + isRequired = true, + validationRegex = "[a-zA-Z0-9]{10}", + caseMode = Credential.CaseMode.UNCHANGED + ) + ), + errorCodes = mapOf() + ) + ) + ) + ) } + + data class Register( + val loginType: Int, + val internalName: String, + val registerName: Int, + @DrawableRes + val registerLogo: Int, + + val loginModes: List + ) : ExpandableItemModel(loginModes.toMutableList()) { + override var level = 1 + } + + data class Mode( + val loginMode: Int, + + @StringRes + val name: Int, + @DrawableRes + val icon: Int, + @StringRes + val hintText: Int? = null, + @StringRes + val guideText: Int, + + val isRecommended: Boolean = false, + val isTesting: Boolean = false, + val isPlatformSelection: Boolean = false, + + val credentials: List, + val errorCodes: Map + ) + + data class Platform( + val id: Int, + val loginType: Int, + val loginMode: Int, + val name: String, + val description: String?, + val guideText: String?, + val icon: String, + val screenshot: String?, + val formFields: List, + val apiData: JsonObject + ) + + data class Credential( + val keyName: String, + + @StringRes + val name: Int, + val icon: IIcon, + @StringRes + val placeholder: Int? = null, + @StringRes + val emptyText: Int, + @StringRes + val invalidText: Int, + val errorCodes: Map, + @StringRes + val hintText: Int? = null, + + val isRequired: Boolean = true, + val validationRegex: String, + val caseMode: CaseMode = CaseMode.UNCHANGED, + val hideText: Boolean = false, + val stripTextRegex: String? = null + ) { + enum class CaseMode { UNCHANGED, UPPER_CASE, LOWER_CASE } + } + + var chooserList: MutableList? = null + var platformList: MutableMap> = mutableMapOf() +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginIuczniowieFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginIuczniowieFragment.kt deleted file mode 100644 index 379eee1a..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginIuczniowieFragment.kt +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright (c) Kuba Szczodrzyński 2020-1-3. - */ - -package pl.szczodrzynski.edziennik.ui.modules.login - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.Fragment -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import pl.szczodrzynski.edziennik.* -import pl.szczodrzynski.edziennik.data.api.ERROR_LOGIN_IDZIENNIK_WEB_INVALID_SCHOOL_NAME -import pl.szczodrzynski.edziennik.data.api.ERROR_LOGIN_LIBRUS_PORTAL_NOT_ACTIVATED -import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_IDZIENNIK -import pl.szczodrzynski.edziennik.databinding.FragmentLoginIuczniowieBinding -import java.util.* -import kotlin.coroutines.CoroutineContext - -class LoginIuczniowieFragment : Fragment(), CoroutineScope { - companion object { - private const val TAG = "LoginIuczniowieFragment" - } - - private lateinit var app: App - private lateinit var activity: LoginActivity - private lateinit var b: FragmentLoginIuczniowieBinding - private val nav by lazy { activity.nav } - - private val job: Job = Job() - override val coroutineContext: CoroutineContext - get() = job + Dispatchers.Main - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - activity = (getActivity() as LoginActivity?) ?: return null - context ?: return null - app = activity.application as App - b = FragmentLoginIuczniowieBinding.inflate(inflater) - return b.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - activity.lastError?.let { error -> - activity.lastError = null - startCoroutineTimer(delayMillis = 100) { - when (error.errorCode) { - ERROR_LOGIN_IDZIENNIK_WEB_INVALID_SCHOOL_NAME -> - b.loginSchoolNameLayout.error = getString(R.string.login_error_incorrect_school_name) - ERROR_LOGIN_LIBRUS_PORTAL_NOT_ACTIVATED -> - b.loginPasswordLayout.error = getString(R.string.login_error_incorrect_login_or_password) - } - } - } - - b.helpButton.onClick { nav.navigate(R.id.loginIuczniowieHelpFragment, null, LoginActivity.navOptions) } - b.backButton.onClick { nav.navigateUp() } - - b.loginButton.onClick { - var errors = false - - b.loginSchoolNameLayout.error = null - b.loginUsernameLayout.error = null - b.loginPasswordLayout.error = null - - val schoolName = b.loginSchoolName.text?.toString()?.toLowerCase(Locale.ROOT) ?: "" - val username = b.loginUsername.text?.toString()?.toLowerCase(Locale.ROOT) ?: "" - val password = b.loginPassword.text?.toString() ?: "" - - if (schoolName.isBlank()) { - b.loginSchoolNameLayout.error = getString(R.string.login_error_no_school_name) - errors = true - } - if (username.isBlank()) { - b.loginUsernameLayout.error = getString(R.string.login_error_no_username) - errors = true - } - if (password.isBlank()) { - b.loginPasswordLayout.error = getString(R.string.login_error_no_password) - errors = true - } - if (errors) return@onClick - - errors = false - - b.loginSchoolName.setText(schoolName) - b.loginUsername.setText(username) - if (!"[a-z0-9_\\-]+".toRegex().matches(schoolName)) { - b.loginSchoolNameLayout.error = getString(R.string.login_error_incorrect_school_name) - errors = true - } - if (!"[a-z0-9_\\-]+".toRegex().matches(username)) { - b.loginUsernameLayout.error = getString(R.string.login_error_incorrect_username) - errors = true - } - if (errors) return@onClick - - val args = Bundle( - "loginType" to LOGIN_TYPE_IDZIENNIK, - "schoolName" to schoolName, - "username" to username, - "password" to password - ) - nav.navigate(R.id.loginProgressFragment, args, LoginActivity.navOptions) - } - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginIuczniowieHelpFragment.java b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginIuczniowieHelpFragment.java deleted file mode 100644 index 7b5c9d96..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginIuczniowieHelpFragment.java +++ /dev/null @@ -1,48 +0,0 @@ -package pl.szczodrzynski.edziennik.ui.modules.login; - -import androidx.databinding.DataBindingUtil; -import android.os.Bundle; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.fragment.app.Fragment; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import androidx.navigation.NavController; -import androidx.navigation.Navigation; -import pl.szczodrzynski.edziennik.App; -import pl.szczodrzynski.edziennik.R; -import pl.szczodrzynski.edziennik.databinding.FragmentLoginIuczniowieHelpBinding; - -public class LoginIuczniowieHelpFragment extends Fragment { - - private App app; - private NavController nav; - private FragmentLoginIuczniowieHelpBinding b; - private static final String TAG = "LoginIuczniowieHelp"; - - public LoginIuczniowieHelpFragment() { } - - @Override - public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - // Inflate the layout for this fragment - if (getActivity() != null) { - app = (App) getActivity().getApplicationContext(); - nav = Navigation.findNavController(getActivity(), R.id.nav_host_fragment); - } - else { - return null; - } - b = DataBindingUtil.inflate(inflater, R.layout.fragment_login_iuczniowie_help, container, false); - return b.getRoot(); - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - assert getContext() != null; - assert getActivity() != null; - - b.backButton.setOnClickListener((v) -> nav.navigateUp()); - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginLibrusCaptchaActivity.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginLibrusCaptchaActivity.kt deleted file mode 100644 index 3cbf29ed..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginLibrusCaptchaActivity.kt +++ /dev/null @@ -1,116 +0,0 @@ -package pl.szczodrzynski.edziennik.ui.modules.login - -import android.annotation.SuppressLint -import android.graphics.Color -import android.os.Build -import android.os.Bundle -import android.util.Base64 -import android.webkit.JavascriptInterface -import android.webkit.WebView -import androidx.appcompat.app.AlertDialog -import androidx.appcompat.app.AppCompatActivity -import com.afollestad.materialdialogs.MaterialDialog -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import pl.szczodrzynski.edziennik.R -import pl.szczodrzynski.edziennik.data.api.LIBRUS_USER_AGENT -import pl.szczodrzynski.edziennik.utils.Themes -import pl.szczodrzynski.edziennik.utils.Utils.hexFromColorInt -import java.nio.charset.Charset - -class LoginLibrusCaptchaActivity : AppCompatActivity() { - companion object { - private const val TAG = "LoginLibrusCaptchaActivity" - } - - private lateinit var webView: WebView - private lateinit var dialog: AlertDialog - private lateinit var jsInterface: CaptchaCallbackInterface - - @SuppressLint("AddJavascriptInterface", "SetJavaScriptEnabled") - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setTheme(Themes.appThemeNoDisplay) - setFinishOnTouchOutside(false) - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - WebView.setWebContentsDebuggingEnabled(true) - } - - val base64Content = """ -PCFET0NUWVBFIGh0bWw+PGh0bWw+PGhlYWQ+PHNjcmlwdCBzcmM9Imh0dHBzOi8vd3d3Lmdvb2ds -ZS5jb20vcmVjYXB0Y2hhL2FwaS5qcz9vbmxvYWQ9cmVhZHkmcmVuZGVyPWV4cGxpY2l0Ij48L3Nj -cmlwdD48L2hlYWQ+PGJvZHk+PGJyPjxjZW50ZXIgaWQ9ImdyIj48L2NlbnRlcj48YnI+PHNjcmlw -dD5mdW5jdGlvbiByZWFkeSgpe2dyZWNhcHRjaGEucmVuZGVyKCdncicse3NpdGVrZXk6JzZMZjQ4 -bW9VQUFBQUFCOUNsaGR2SHI0NmdSV1ItQ04zMUNYUVBHMlUnLHRoZW1lOidUSEVNRScsY2FsbGJh -Y2s6ZnVuY3Rpb24oZSl7d2luZG93LmlmLmNhbGxiYWNrKGUpO30sImV4cGlyZWQtY2FsbGJhY2si -OmZ1bmN0aW9uKCl7d2luZG93LmlmLmV4cGlyZWRDYWxsYmFjayhlKTt9LCJlcnJvci1jYWxsYmFj -ayI6ZnVuY3Rpb24oKXt3aW5kb3cuaWYuZXJyb3JDYWxsYmFjayhlKTt9fSk7fTwvc2NyaXB0Pjwv -Ym9keT48L2h0bWw+""" - - val backgroundColor = if (Themes.isDark) 0x424242 else 0xffffff - val backgroundColorString = hexFromColorInt(backgroundColor) - - val htmlContent = Base64.decode(base64Content, Base64.DEFAULT) - .toString(Charset.defaultCharset()) - .replace("COLOR", backgroundColorString, true) - .replace("THEME", if (Themes.isDark) "dark" else "light") - - jsInterface = object : CaptchaCallbackInterface { - @JavascriptInterface - override fun callback(recaptchaResponse: String) { - MaterialDialog.Builder(this@LoginLibrusCaptchaActivity) - .title("Captcha checked") - .content("Response: $recaptchaResponse") - .positiveText("OK") - .show() - } - - @JavascriptInterface - override fun expiredCallback() { - MaterialDialog.Builder(this@LoginLibrusCaptchaActivity) - .title("Captcha expired") - .content("Captcha expired") - .positiveText("OK") - .show() - } - - @JavascriptInterface - override fun errorCallback() { - MaterialDialog.Builder(this@LoginLibrusCaptchaActivity) - .title("Captcha error") - .content("Captcha error") - .positiveText("OK") - .show() - } - } - - webView = WebView(this).apply { - //setBackgroundColor((backgroundColor.toLong() or 0xff000000).toInt()) - setBackgroundColor(Color.TRANSPARENT) - settings.javaScriptEnabled = true - settings.userAgentString = LIBRUS_USER_AGENT - addJavascriptInterface(jsInterface, "if") - loadDataWithBaseURL("https://portal.librus.pl/rodzina/login/", htmlContent, "text/html", "UTF-8", null) - setLayerType(WebView.LAYER_TYPE_SOFTWARE, null) - } - - dialog = MaterialAlertDialogBuilder(this) - .setTitle(R.string.login_librus_captcha_title) - .setView(webView) - .setNegativeButton(R.string.cancel) { dialog, _ -> - dialog.dismiss() - finish() - } - .setCancelable(false) - .show() - } - - interface CaptchaCallbackInterface { - @JavascriptInterface - fun callback(recaptchaResponse: String) - @JavascriptInterface - fun expiredCallback() - @JavascriptInterface - fun errorCallback() - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginLibrusFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginLibrusFragment.kt deleted file mode 100644 index 11c2bdd2..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginLibrusFragment.kt +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (c) Kuba Szczodrzyński 2020-1-3. - */ - -package pl.szczodrzynski.edziennik.ui.modules.login - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.Fragment -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import pl.szczodrzynski.edziennik.* -import pl.szczodrzynski.edziennik.data.api.ERROR_LOGIN_LIBRUS_PORTAL_INVALID_LOGIN -import pl.szczodrzynski.edziennik.data.api.ERROR_LOGIN_LIBRUS_PORTAL_NOT_ACTIVATED -import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_LIBRUS -import pl.szczodrzynski.edziennik.databinding.FragmentLoginLibrusBinding -import java.util.* -import kotlin.coroutines.CoroutineContext - -class LoginLibrusFragment : Fragment(), CoroutineScope { - companion object { - private const val TAG = "LoginLibrusFragment" - } - - private lateinit var app: App - private lateinit var activity: LoginActivity - private lateinit var b: FragmentLoginLibrusBinding - private val nav by lazy { activity.nav } - - private val job: Job = Job() - override val coroutineContext: CoroutineContext - get() = job + Dispatchers.Main - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - activity = (getActivity() as LoginActivity?) ?: return null - context ?: return null - app = activity.application as App - b = FragmentLoginLibrusBinding.inflate(inflater) - return b.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - activity.lastError?.let { error -> - activity.lastError = null - startCoroutineTimer(delayMillis = 100) { - when (error.errorCode) { - ERROR_LOGIN_LIBRUS_PORTAL_INVALID_LOGIN -> - b.loginPasswordLayout.error = getString(R.string.login_error_incorrect_login_or_password) - ERROR_LOGIN_LIBRUS_PORTAL_NOT_ACTIVATED -> - b.loginEmailLayout.error = getString(R.string.login_error_account_not_activated) - } - } - } - - b.helpButton.onClick { nav.navigate(R.id.loginLibrusHelpFragment, null, LoginActivity.navOptions) } - b.backButton.onClick { nav.navigateUp() } - - b.loginButton.onClick { - var errors = false - - b.loginEmailLayout.error = null - b.loginPasswordLayout.error = null - - val email = b.loginEmail.text?.toString()?.toLowerCase(Locale.ROOT) ?: "" - val password = b.loginPassword.text?.toString() ?: "" - - if (email.isBlank()) { - b.loginEmailLayout.error = getString(R.string.login_error_no_email) - errors = true - } - if (password.isBlank()) { - b.loginPasswordLayout.error = getString(R.string.login_error_no_password) - errors = true - } - if (errors) return@onClick - - errors = false - - b.loginEmail.setText(email) - if (!"([\\w.\\-_+]+)?\\w+@[\\w-_]+(\\.\\w+)+".toRegex().matches(email)) { - b.loginEmailLayout.error = getString(R.string.login_error_incorrect_email) - errors = true - } - if (errors) return@onClick - - val args = Bundle( - "loginType" to LOGIN_TYPE_LIBRUS, - "email" to email, - "password" to password - ) - nav.navigate(R.id.loginProgressFragment, args, LoginActivity.navOptions) - } - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginLibrusHelpFragment.java b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginLibrusHelpFragment.java deleted file mode 100644 index 6984498a..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginLibrusHelpFragment.java +++ /dev/null @@ -1,48 +0,0 @@ -package pl.szczodrzynski.edziennik.ui.modules.login; - -import androidx.databinding.DataBindingUtil; -import android.os.Bundle; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.fragment.app.Fragment; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import androidx.navigation.NavController; -import androidx.navigation.Navigation; -import pl.szczodrzynski.edziennik.App; -import pl.szczodrzynski.edziennik.R; -import pl.szczodrzynski.edziennik.databinding.FragmentLoginLibrusHelpBinding; - -public class LoginLibrusHelpFragment extends Fragment { - - private App app; - private NavController nav; - private FragmentLoginLibrusHelpBinding b; - private static final String TAG = "LoginLibrusHelp"; - - public LoginLibrusHelpFragment() { } - - @Override - public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - // Inflate the layout for this fragment - if (getActivity() != null) { - app = (App) getActivity().getApplicationContext(); - nav = Navigation.findNavController(getActivity(), R.id.nav_host_fragment); - } - else { - return null; - } - b = DataBindingUtil.inflate(inflater, R.layout.fragment_login_librus_help, container, false); - return b.getRoot(); - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - assert getContext() != null; - assert getActivity() != null; - - b.backButton.setOnClickListener((v) -> nav.navigateUp()); - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginLibrusJstFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginLibrusJstFragment.kt deleted file mode 100644 index d4e3d445..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginLibrusJstFragment.kt +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (c) Kuba Szczodrzyński 2020-1-3. - */ - -package pl.szczodrzynski.edziennik.ui.modules.login - -import android.graphics.Color -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.view.WindowManager -import androidx.fragment.app.Fragment -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 kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import pl.szczodrzynski.edziennik.* -import pl.szczodrzynski.edziennik.data.api.ERROR_LOGIN_LIBRUS_API_INVALID_LOGIN -import pl.szczodrzynski.edziennik.data.api.ERROR_LOGIN_LIBRUS_API_INVALID_REQUEST -import pl.szczodrzynski.edziennik.data.api.LOGIN_MODE_LIBRUS_JST -import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_LIBRUS -import pl.szczodrzynski.edziennik.databinding.FragmentLoginLibrusJstBinding -import pl.szczodrzynski.edziennik.ui.dialogs.QrScannerDialog -import java.util.* -import kotlin.coroutines.CoroutineContext - -class LoginLibrusJstFragment : Fragment(), CoroutineScope { - companion object { - private const val TAG = "LoginLibrusJstFragment" - } - - private lateinit var app: App - private lateinit var activity: LoginActivity - private lateinit var b: FragmentLoginLibrusJstBinding - private val nav by lazy { activity.nav } - - private val job: Job = Job() - override val coroutineContext: CoroutineContext - get() = job + Dispatchers.Main - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - activity = (getActivity() as LoginActivity?) ?: return null - context ?: return null - app = activity.application as App - b = FragmentLoginLibrusJstBinding.inflate(inflater) - return b.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - activity.lastError?.let { error -> - activity.lastError = null - startCoroutineTimer(delayMillis = 100) { - when (error.errorCode) { - ERROR_LOGIN_LIBRUS_API_INVALID_LOGIN, - ERROR_LOGIN_LIBRUS_API_INVALID_REQUEST -> - b.loginCodeLayout.error = getString(R.string.login_error_incorrect_code_or_pin) - } - } - } - - b.loginQrScan.setImageDrawable(IconicsDrawable(activity) - .icon(CommunityMaterial.Icon2.cmd_qrcode_scan) - .colorInt(Color.BLACK) - .sizeDp(72)) - b.loginQrScan.onClick { - QrScannerDialog(activity, { code -> - b.loginCode.setText(code) - if (b.loginPin.requestFocus()) { - activity.window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); - } - }) - } - - b.helpButton.onClick { nav.navigate(R.id.loginLibrusHelpFragment, null, LoginActivity.navOptions) } - b.backButton.onClick { nav.navigateUp() } - - b.loginButton.onClick { - var errors = false - - b.loginCodeLayout.error = null - b.loginPinLayout.error = null - - val code = b.loginCode.text?.toString()?.toUpperCase(Locale.ROOT) ?: "" - val pin = b.loginPin.text?.toString() ?: "" - - if (code.isBlank()) { - b.loginCodeLayout.error = getString(R.string.login_error_no_code) - errors = true - } - if (pin.isBlank()) { - b.loginPinLayout.error = getString(R.string.login_error_no_pin) - errors = true - } - if (errors) return@onClick - - errors = false - - b.loginCode.setText(code) - if (!"[A-Z0-9_]+".toRegex().matches(code)) { - b.loginCodeLayout.error = getString(R.string.login_error_incorrect_code) - errors = true - } - if (!"[a-z0-9_]+".toRegex().matches(pin)) { - b.loginPinLayout.error = getString(R.string.login_error_incorrect_pin) - errors = true - } - if (errors) return@onClick - - val args = Bundle( - "loginType" to LOGIN_TYPE_LIBRUS, - "loginMode" to LOGIN_MODE_LIBRUS_JST, - "accountCode" to code, - "accountPin" to pin - ) - nav.navigate(R.id.loginProgressFragment, args, LoginActivity.navOptions) - } - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginMobidziennikFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginMobidziennikFragment.kt deleted file mode 100644 index de0deb3d..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginMobidziennikFragment.kt +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (c) Kuba Szczodrzyński 2020-1-3. - */ - -package pl.szczodrzynski.edziennik.ui.modules.login - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.Fragment -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import pl.szczodrzynski.edziennik.* -import pl.szczodrzynski.edziennik.data.api.* -import pl.szczodrzynski.edziennik.databinding.FragmentLoginMobidziennikBinding -import java.util.* -import kotlin.coroutines.CoroutineContext - -class LoginMobidziennikFragment : Fragment(), CoroutineScope { - companion object { - private const val TAG = "LoginMobidziennikFragment" - } - - private lateinit var app: App - private lateinit var activity: LoginActivity - private lateinit var b: FragmentLoginMobidziennikBinding - private val nav by lazy { activity.nav } - - private val job: Job = Job() - override val coroutineContext: CoroutineContext - get() = job + Dispatchers.Main - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - activity = (getActivity() as LoginActivity?) ?: return null - context ?: return null - app = activity.application as App - b = FragmentLoginMobidziennikBinding.inflate(inflater) - return b.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - activity.lastError?.let { error -> - activity.lastError = null - startCoroutineTimer(delayMillis = 100) { - when (error.errorCode) { - ERROR_LOGIN_MOBIDZIENNIK_WEB_INVALID_LOGIN -> - b.loginPasswordLayout.error = getString(R.string.login_error_incorrect_login_or_password) - ERROR_LOGIN_MOBIDZIENNIK_WEB_OLD_PASSWORD -> - b.loginPasswordLayout.error = getString(R.string.login_error_old_password) - ERROR_LOGIN_MOBIDZIENNIK_WEB_ARCHIVED -> - b.loginUsernameLayout.error = getString(R.string.sync_error_archived) - ERROR_LOGIN_MOBIDZIENNIK_WEB_INVALID_ADDRESS -> - b.loginServerAddressLayout.error = getString(R.string.login_error_incorrect_address) - } - } - } - - b.helpButton.onClick { nav.navigate(R.id.loginMobidziennikHelpFragment, null, LoginActivity.navOptions) } - b.backButton.onClick { nav.navigateUp() } - - b.loginButton.onClick { - var errors = false - - b.loginServerAddressLayout.error = null - b.loginUsernameLayout.error = null - b.loginPasswordLayout.error = null - - val serverName = b.loginServerAddress.text - ?.toString() - ?.toLowerCase(Locale.ROOT) - ?.replace("(?:http://|www.|mobidziennik\\.pl|wizja\\.net|\\.)".toRegex(), "") ?: "" - val username = b.loginUsername.text?.toString()?.toLowerCase(Locale.ROOT) ?: "" - val password = b.loginPassword.text?.toString() ?: "" - - if (serverName.isBlank()) { - b.loginServerAddressLayout.error = getString(R.string.login_error_no_address) - errors = true - } - if (username.isBlank()) { - b.loginUsernameLayout.error = getString(R.string.login_error_no_login) - errors = true - } - if (password.isBlank()) { - b.loginPasswordLayout.error = getString(R.string.login_error_no_password) - errors = true - } - if (errors) return@onClick - - errors = false - - b.loginServerAddress.setText(serverName) - b.loginUsername.setText(username) - if (!"^[a-z0-9_\\-]+$".toRegex().matches(serverName)) { - b.loginServerAddressLayout.error = getString(R.string.login_error_incorrect_address) - errors = true - } - if (!"^[a-z0-9_\\-@+.]+$".toRegex().matches(username)) { - b.loginUsernameLayout.error = getString(R.string.login_error_incorrect_login) - errors = true - } - if (errors) return@onClick - - val args = Bundle( - "loginType" to LOGIN_TYPE_MOBIDZIENNIK, - "serverName" to serverName, - "username" to username, - "password" to password - ) - nav.navigate(R.id.loginProgressFragment, args, LoginActivity.navOptions) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginMobidziennikHelpFragment.java b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginMobidziennikHelpFragment.java deleted file mode 100644 index 511f6682..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginMobidziennikHelpFragment.java +++ /dev/null @@ -1,48 +0,0 @@ -package pl.szczodrzynski.edziennik.ui.modules.login; - -import androidx.databinding.DataBindingUtil; -import android.os.Bundle; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.fragment.app.Fragment; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import androidx.navigation.NavController; -import androidx.navigation.Navigation; -import pl.szczodrzynski.edziennik.App; -import pl.szczodrzynski.edziennik.R; -import pl.szczodrzynski.edziennik.databinding.FragmentLoginMobidziennikHelpBinding; - -public class LoginMobidziennikHelpFragment extends Fragment { - - private App app; - private NavController nav; - private FragmentLoginMobidziennikHelpBinding b; - private static final String TAG = "LoginMobidziennikHelp"; - - public LoginMobidziennikHelpFragment() { } - - @Override - public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - // Inflate the layout for this fragment - if (getActivity() != null) { - app = (App) getActivity().getApplicationContext(); - nav = Navigation.findNavController(getActivity(), R.id.nav_host_fragment); - } - else { - return null; - } - b = DataBindingUtil.inflate(inflater, R.layout.fragment_login_mobidziennik_help, container, false); - return b.getRoot(); - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - assert getContext() != null; - assert getActivity() != null; - - b.backButton.setOnClickListener((v) -> nav.navigateUp()); - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginPlatformAdapter.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginPlatformAdapter.kt new file mode 100644 index 00000000..5bc80f36 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginPlatformAdapter.kt @@ -0,0 +1,50 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-16. + */ + +package pl.szczodrzynski.edziennik.ui.modules.login + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.appcompat.app.AppCompatActivity +import androidx.recyclerview.widget.RecyclerView +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.onClick +import pl.szczodrzynski.edziennik.ui.modules.login.viewholder.PlatformViewHolder +import kotlin.coroutines.CoroutineContext + +class LoginPlatformAdapter( + val activity: AppCompatActivity, + val onPlatformClick: ((platform: LoginInfo.Platform) -> Unit)? = null +) : RecyclerView.Adapter(), CoroutineScope { + companion object { + private const val TAG = "LoginPlatformAdapter" + } + + private val app = activity.applicationContext as App + // optional: place the manager here + + private val job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Main + + var items = listOf() + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PlatformViewHolder { + val inflater = LayoutInflater.from(parent.context) + return PlatformViewHolder(inflater, parent) + } + + override fun onBindViewHolder(holder: PlatformViewHolder, position: Int) { + val item = items[position] + holder.onBind(activity, app, item, position, this) + onPlatformClick?.let { + holder.b.root.onClick { _ -> it(item) } + } + } + + override fun getItemCount() = items.size +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginPlatformListFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginPlatformListFragment.kt new file mode 100644 index 00000000..9beb0860 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginPlatformListFragment.kt @@ -0,0 +1,100 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-16. + */ + +package pl.szczodrzynski.edziennik.ui.modules.login + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.view.isVisible +import androidx.fragment.app.Fragment +import androidx.recyclerview.widget.LinearLayoutManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi +import pl.szczodrzynski.edziennik.databinding.LoginPlatformListFragmentBinding +import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration +import kotlin.coroutines.CoroutineContext + +class LoginPlatformListFragment : Fragment(), CoroutineScope { + companion object { + private const val TAG = "LoginPlatformListFragment" + } + + private lateinit var app: App + private lateinit var activity: LoginActivity + private lateinit var b: LoginPlatformListFragmentBinding + private val nav by lazy { activity.nav } + + private val job: Job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Main + + // local/private variables go here + private val api by lazy { SzkolnyApi(app) } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + activity = (getActivity() as LoginActivity?) ?: return null + context ?: return null + app = activity.application as App + b = LoginPlatformListFragmentBinding.inflate(inflater) + return b.root + } + + private lateinit var timeoutJob: Job + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + if (!isAdded) return + b.backButton.onClick { nav.navigateUp() } + + val loginType = arguments?.getInt("loginType") ?: return + val register = LoginInfo.list.firstOrNull { it.loginType == loginType } ?: return + val loginMode = arguments?.getInt("loginMode") ?: return + val mode = register.loginModes.firstOrNull { it.loginMode == loginMode } ?: return + + timeoutJob = startCoroutineTimer(5000L) { + b.timeoutText.isVisible = true + timeoutJob.cancel() + } + + val adapter = LoginPlatformAdapter(activity) { platform -> + nav.navigate(R.id.loginFormFragment, Bundle( + "loginType" to platform.loginType, + "loginMode" to platform.loginMode, + "platformName" to platform.name, + "platformDescription" to platform.description, + "platformFormFields" to platform.formFields.joinToString(";"), + "platformApiData" to platform.apiData.toString() + ), activity.navOptions) + } + + launch { + val platforms = LoginInfo.platformList[mode.name] + ?: run { + api.runCatching(activity) { + getPlatforms(register.internalName) + } ?: run { + nav.navigateUp() + return@launch + } + } + LoginInfo.platformList[mode.name] = platforms + + adapter.items = platforms + b.list.adapter = adapter + b.list.apply { + setHasFixedSize(true) + layoutManager = LinearLayoutManager(context) + addItemDecoration(SimpleDividerItemDecoration(context)) + } + timeoutJob.cancel() + b.loadingLayout.isVisible = false + b.list.isVisible = true + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginProfileObject.java b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginProfileObject.java deleted file mode 100644 index a86cfa86..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginProfileObject.java +++ /dev/null @@ -1,29 +0,0 @@ -package pl.szczodrzynski.edziennik.ui.modules.login; - - -import java.util.ArrayList; -import java.util.List; - -import androidx.annotation.NonNull; -import pl.szczodrzynski.edziennik.data.db.entity.LoginStore; -import pl.szczodrzynski.edziennik.data.db.entity.Profile; - -public class LoginProfileObject { - LoginStore loginStore = null; - List profileList = new ArrayList<>(); - List selectedList = new ArrayList<>(); - - public LoginProfileObject(@NonNull LoginStore loginStore, @NonNull List profileList) { - this.loginStore = loginStore; - this.profileList = profileList; - for (Profile ignored : profileList) { - selectedList.add(true); - } - } - - public LoginProfileObject addProfile(Profile profile) { - profileList.add(profile); - selectedList.add(true); - return this; - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginProgressFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginProgressFragment.kt index 929d9551..7550b08c 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginProgressFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginProgressFragment.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) Kuba Szczodrzyński 2020-1-3. + * Copyright (c) Kuba Szczodrzyński 2020-4-16. */ package pl.szczodrzynski.edziennik.ui.modules.login @@ -27,8 +27,10 @@ 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.databinding.FragmentLoginProgressBinding +import pl.szczodrzynski.edziennik.databinding.LoginProgressFragmentBinding +import pl.szczodrzynski.edziennik.joinNotNullStrings import kotlin.coroutines.CoroutineContext +import kotlin.math.max class LoginProgressFragment : Fragment(), CoroutineScope { companion object { @@ -37,22 +39,28 @@ class LoginProgressFragment : Fragment(), CoroutineScope { private lateinit var app: App private lateinit var activity: LoginActivity - private lateinit var b: FragmentLoginProgressBinding + private lateinit var b: LoginProgressFragmentBinding private val nav by lazy { activity.nav } private val job: Job = Job() override val coroutineContext: CoroutineContext get() = job + Dispatchers.Main + // local/private variables go here + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { activity = (getActivity() as LoginActivity?) ?: return null context ?: return null app = activity.application as App - b = FragmentLoginProgressBinding.inflate(inflater) + b = LoginProgressFragmentBinding.inflate(inflater) return b.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + if (!isAdded) return + + EventBus.getDefault().removeStickyEvent(FirstLoginFinishedEvent::class.java) + val args = arguments ?: run { activity.error(ApiError(TAG, LOGIN_NO_ARGUMENTS)) nav.navigateUp() @@ -66,25 +74,28 @@ class LoginProgressFragment : Fragment(), CoroutineScope { launch { activity.errorSnackbar.dismiss() - val firstProfileId = (app.db.profileDao().lastId ?: 0) + 1 + val maxProfileId = max( + app.db.profileDao().lastId ?: 0, + activity.profiles.maxBy { it.profile.id }?.profile?.id ?: 0 + ) val loginType = args.getInt("loginType", -1) val loginMode = args.getInt("loginMode", 0) val loginStore = LoginStore( - id = firstProfileId, + id = maxProfileId + 1, type = loginType, mode = loginMode ) loginStore.copyFrom(args) - if (App.devMode && LoginChooserFragment.fakeLogin) { - loginStore.putLoginData("fakeLogin", true) - } + loginStore.removeLoginData("loginType") + loginStore.removeLoginData("loginMode") EdziennikTask.firstLogin(loginStore).enqueue(activity) } } - @Subscribe(threadMode = ThreadMode.MAIN) + @Subscribe(threadMode = ThreadMode.MAIN, sticky = true) fun onFirstLoginFinishedEvent(event: FirstLoginFinishedEvent) { + EventBus.getDefault().removeStickyEvent(event) if (event.profileList.isEmpty()) { MaterialAlertDialogBuilder(activity) .setTitle(R.string.login_account_no_students) @@ -94,10 +105,21 @@ class LoginProgressFragment : Fragment(), CoroutineScope { .show() return } + + // update subnames with school years and class name + for (profile in event.profileList) { + val schoolYearName = "${profile.studentSchoolYearStart}/${profile.studentSchoolYearStart + 1}" + profile.subname = joinNotNullStrings( + " - ", + profile.studentClassName, + schoolYearName + ) + } + activity.loginStores += event.loginStore - activity.profiles += event.profileList.map { LoginSummaryProfileAdapter.Item(it) } + activity.profiles += event.profileList.map { LoginSummaryAdapter.Item(it) } activity.errorSnackbar.dismiss() - nav.navigate(R.id.loginSummaryFragment, null, LoginActivity.navOptions) + nav.navigate(R.id.loginSummaryFragment, null, activity.navOptions) } @Subscribe(threadMode = ThreadMode.MAIN, sticky = true) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginSummaryAdapter.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginSummaryAdapter.kt new file mode 100644 index 00000000..bc9cafbf --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginSummaryAdapter.kt @@ -0,0 +1,80 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-16. + */ + +package pl.szczodrzynski.edziennik.ui.modules.login + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.data.db.entity.Profile +import pl.szczodrzynski.edziennik.databinding.LoginSummaryItemBinding +import pl.szczodrzynski.edziennik.onClick +import pl.szczodrzynski.edziennik.trigger +import kotlin.coroutines.CoroutineContext + +class LoginSummaryAdapter( + val activity: LoginActivity, + val onSelectionChanged: ((item: Item) -> Unit)? = null +) : RecyclerView.Adapter(), CoroutineScope { + companion object { + private const val TAG = "LoginSummaryAdapter" + } + + private val app = activity.applicationContext as App + // optional: place the manager here + + private val job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Main + + var items = listOf() + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val inflater = LayoutInflater.from(parent.context) + return ViewHolder(LoginSummaryItemBinding.inflate(inflater, parent, false)) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val item = items[position] + val b = holder.b + val profile = item.profile + val loginStore = activity.loginStores.firstOrNull { it.id == profile.loginStoreId } + ?: return + + val loginType = loginStore.type + val register = LoginInfo.list.firstOrNull { it.loginType == loginType } ?: return + val loginMode = loginStore.mode + val mode = register.loginModes.firstOrNull { it.loginMode == loginMode } ?: return + + b.profileName.text = profile.name + b.profileDetails.text = profile.subname + b.checkBox.isChecked = item.isSelected + b.modeIcon.setImageResource(mode.icon) + + if (profile.isParent) { + b.accountType.setText(R.string.account_type_parent) + } else { + b.accountType.setText(R.string.account_type_child) + } + + b.root.onClick { + b.checkBox.trigger() + } + b.checkBox.setOnCheckedChangeListener { _, isChecked -> + item.isSelected = isChecked + onSelectionChanged?.invoke(item) + } + } + + override fun getItemCount() = items.size + + class ViewHolder(val b: LoginSummaryItemBinding) : RecyclerView.ViewHolder(b.root) + + class Item(val profile: Profile, var isSelected: Boolean = true) +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginSummaryFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginSummaryFragment.kt index b16c0256..63525c45 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginSummaryFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginSummaryFragment.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) Kuba Szczodrzyński 2020-1-3. + * Copyright (c) Kuba Szczodrzyński 2020-4-16. */ package pl.szczodrzynski.edziennik.ui.modules.login @@ -16,7 +16,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import pl.szczodrzynski.edziennik.* -import pl.szczodrzynski.edziennik.databinding.FragmentLoginSummaryBinding +import pl.szczodrzynski.edziennik.databinding.LoginSummaryFragmentBinding import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration import kotlin.coroutines.CoroutineContext @@ -27,27 +27,32 @@ class LoginSummaryFragment : Fragment(), CoroutineScope { private lateinit var app: App private lateinit var activity: LoginActivity - private lateinit var b: FragmentLoginSummaryBinding + private lateinit var b: LoginSummaryFragmentBinding private val nav by lazy { activity.nav } private val job: Job = Job() override val coroutineContext: CoroutineContext get() = job + Dispatchers.Main + // local/private variables go here + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { activity = (getActivity() as LoginActivity?) ?: return null context ?: return null app = activity.application as App - b = FragmentLoginSummaryBinding.inflate(inflater) + b = LoginSummaryFragmentBinding.inflate(inflater) return b.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - b.profileListView.apply { - adapter = LoginSummaryProfileAdapter(activity, activity.profiles) { item -> - b.finishButton.isEnabled = activity.profiles.any { it.isSelected } - } + val adapter = LoginSummaryAdapter(activity) { _ -> + b.finishButton.isEnabled = activity.profiles.any { it.isSelected } + } + + adapter.items = activity.profiles + b.list.adapter = adapter + b.list.apply { isNestedScrollingEnabled = false setHasFixedSize(true) layoutManager = LinearLayoutManager(context) @@ -66,7 +71,7 @@ class LoginSummaryFragment : Fragment(), CoroutineScope { } b.anotherButton.onClick { - nav.navigate(R.id.loginChooserFragment, null, LoginActivity.navOptions) + nav.navigate(R.id.loginChooserFragment, null, activity.navOptions) } b.finishButton.onClick { @@ -86,7 +91,7 @@ class LoginSummaryFragment : Fragment(), CoroutineScope { val args = Bundle( "registrationAllowed" to b.registerMeSwitch.isChecked ) - nav.navigate(R.id.loginSyncFragment, args, LoginActivity.navOptions) + nav.navigate(R.id.loginSyncFragment, args, activity.navOptions) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginSummaryProfileAdapter.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginSummaryProfileAdapter.kt deleted file mode 100644 index 7a74dd76..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginSummaryProfileAdapter.kt +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (c) Kuba Szczodrzyński 2020-1-3. - */ - -package pl.szczodrzynski.edziennik.ui.modules.login - -import android.content.Context -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.recyclerview.widget.RecyclerView -import pl.szczodrzynski.edziennik.* -import pl.szczodrzynski.edziennik.data.api.* -import pl.szczodrzynski.edziennik.data.db.entity.Profile -import pl.szczodrzynski.edziennik.databinding.RowLoginProfileListItemBinding - -class LoginSummaryProfileAdapter( - val context: Context, - val items: List, - val onSelectionChanged: ((item: Item) -> Unit)? = null -) : RecyclerView.Adapter() { - - private val app by lazy { context.applicationContext as App } - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { - val inflater = LayoutInflater.from(parent.context) - val view = RowLoginProfileListItemBinding.inflate(inflater, parent, false) - return ViewHolder(view) - } - - override fun onBindViewHolder(holder: ViewHolder, position: Int) { - val item = items[position] - val profile = item.profile - val b = holder.b - - b.textView.text = profile.name - b.checkBox.isChecked = item.isSelected - - val registerIcon = when (profile.loginStoreType) { - LOGIN_TYPE_MOBIDZIENNIK -> R.drawable.logo_mobidziennik - LOGIN_TYPE_LIBRUS -> R.drawable.logo_librus - LOGIN_TYPE_IDZIENNIK -> R.drawable.logo_idziennik - LOGIN_TYPE_VULCAN -> R.drawable.logo_vulcan - LOGIN_TYPE_EDUDZIENNIK -> R.drawable.logo_edudziennik - else -> null - } - if (registerIcon == null) - b.registerIcon.visibility = View.GONE - else { - b.registerIcon.visibility = View.VISIBLE - b.registerIcon.setImageResource(registerIcon) - } - - if (profile.isParent) { - b.accountType.setText(R.string.login_summary_account_parent) - } else { - b.accountType.setText(R.string.login_summary_account_child) - } - - val schoolYearName = "${profile.studentSchoolYearStart}/${profile.studentSchoolYearStart+1}" - b.textDetails.text = joinNotNullStrings( - " - ", - profile.studentClassName, - schoolYearName - ) - - b.root.onClick { - b.checkBox.trigger() - } - b.checkBox.setOnCheckedChangeListener { _, isChecked -> - item.isSelected = isChecked - onSelectionChanged?.invoke(item) - } - } - - override fun getItemCount() = items.size - - class ViewHolder(val b: RowLoginProfileListItemBinding) : RecyclerView.ViewHolder(b.root) - - class Item(val profile: Profile, var isSelected: Boolean = true) -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginSyncErrorFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginSyncErrorFragment.kt index 49977dee..e13130a5 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginSyncErrorFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginSyncErrorFragment.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) Kuba Szczodrzyński 2020-1-3. + * Copyright (c) Kuba Szczodrzyński 2020-4-14. */ package pl.szczodrzynski.edziennik.ui.modules.login @@ -14,7 +14,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.R -import pl.szczodrzynski.edziennik.databinding.FragmentLoginSyncErrorBinding +import pl.szczodrzynski.edziennik.databinding.LoginSyncErrorFragmentBinding import pl.szczodrzynski.edziennik.onClick import kotlin.coroutines.CoroutineContext @@ -25,18 +25,20 @@ class LoginSyncErrorFragment : Fragment(), CoroutineScope { private lateinit var app: App private lateinit var activity: LoginActivity - private lateinit var b: FragmentLoginSyncErrorBinding + private lateinit var b: LoginSyncErrorFragmentBinding private val nav by lazy { activity.nav } private val job: Job = Job() override val coroutineContext: CoroutineContext get() = job + Dispatchers.Main + // local/private variables go here + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { activity = (getActivity() as LoginActivity?) ?: return null context ?: return null app = activity.application as App - b = FragmentLoginSyncErrorBinding.inflate(inflater) + b = LoginSyncErrorFragmentBinding.inflate(inflater) return b.root } @@ -44,7 +46,7 @@ class LoginSyncErrorFragment : Fragment(), CoroutineScope { b.errorDetails.text = activity.lastError?.getStringReason(activity) activity.lastError = null b.nextButton.onClick { - nav.navigate(R.id.loginFinishFragment, arguments, LoginActivity.navOptions) + nav.navigate(R.id.loginFinishFragment, arguments, activity.navOptions) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginSyncFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginSyncFragment.kt index 789bcb07..3736e20f 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginSyncFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginSyncFragment.kt @@ -1,3 +1,7 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-16. + */ + package pl.szczodrzynski.edziennik.ui.modules.login import android.os.Bundle @@ -19,9 +23,8 @@ import pl.szczodrzynski.edziennik.data.api.events.ApiTaskAllFinishedEvent import pl.szczodrzynski.edziennik.data.api.events.ApiTaskErrorEvent import pl.szczodrzynski.edziennik.data.api.events.ApiTaskProgressEvent import pl.szczodrzynski.edziennik.data.api.events.ApiTaskStartedEvent -import pl.szczodrzynski.edziennik.data.db.entity.Profile.Companion.REGISTRATION_DISABLED -import pl.szczodrzynski.edziennik.data.db.entity.Profile.Companion.REGISTRATION_ENABLED -import pl.szczodrzynski.edziennik.databinding.FragmentLoginSyncBinding +import pl.szczodrzynski.edziennik.data.db.entity.Profile +import pl.szczodrzynski.edziennik.databinding.LoginSyncFragmentBinding import kotlin.coroutines.CoroutineContext import kotlin.math.roundToInt @@ -32,7 +35,7 @@ class LoginSyncFragment : Fragment(), CoroutineScope { private lateinit var app: App private lateinit var activity: LoginActivity - private lateinit var b: FragmentLoginSyncBinding + private lateinit var b: LoginSyncFragmentBinding private val nav: NavController by lazy { Navigation.findNavController(activity, R.id.nav_host_fragment) } private val job: Job = Job() @@ -45,20 +48,24 @@ class LoginSyncFragment : Fragment(), CoroutineScope { activity = (getActivity() as LoginActivity?) ?: return null context ?: return null app = activity.application as App - b = FragmentLoginSyncBinding.inflate(inflater) + b = LoginSyncFragmentBinding.inflate(inflater) return b.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + + EventBus.getDefault().removeStickyEvent(ApiTaskAllFinishedEvent::class.java) + EventBus.getDefault().removeStickyEvent(ApiTaskErrorEvent::class.java) + 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) - REGISTRATION_ENABLED + Profile.REGISTRATION_ENABLED else - REGISTRATION_DISABLED + Profile.REGISTRATION_DISABLED app.db.eventTypeDao().addDefaultTypes(activity, it.id) } @@ -82,15 +89,16 @@ class LoginSyncFragment : Fragment(), CoroutineScope { ).concat(" ") } - @Subscribe(threadMode = ThreadMode.MAIN) + @Subscribe(threadMode = ThreadMode.MAIN, sticky = true) fun onSyncFinishedEvent(event: ApiTaskAllFinishedEvent) { - nav.navigate(R.id.loginFinishFragment, finishArguments, LoginActivity.navOptions) + EventBus.getDefault().removeStickyEvent(event) + nav.navigate(R.id.loginFinishFragment, finishArguments, activity.navOptions) } @Subscribe(threadMode = ThreadMode.MAIN) fun onSyncProgressEvent(event: ApiTaskProgressEvent) { b.loginSyncProgressBar.progress = event.progress.roundToInt() - b.loginSyncProgressBar.isIndeterminate = event.progress < 0f + b.loginSyncProgressBar.isIndeterminate = event.progress <= 0f b.loginSyncSubtitle2.text = event.progressText } @@ -98,7 +106,7 @@ class LoginSyncFragment : Fragment(), CoroutineScope { fun onSyncErrorEvent(event: ApiTaskErrorEvent) { EventBus.getDefault().removeStickyEvent(event) activity.error(event.error) - nav.navigate(R.id.loginSyncErrorFragment, finishArguments, LoginActivity.navOptions) + nav.navigate(R.id.loginSyncErrorFragment, finishArguments, activity.navOptions) } override fun onStart() { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginTemplateFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginTemplateFragment.kt deleted file mode 100644 index 4264416a..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginTemplateFragment.kt +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (c) Kuba Szczodrzyński 2020-1-3. - */ - -package pl.szczodrzynski.edziennik.ui.modules.login - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.Fragment -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import pl.szczodrzynski.edziennik.* -import pl.szczodrzynski.edziennik.data.api.ERROR_LOGIN_LIBRUS_PORTAL_INVALID_LOGIN -import pl.szczodrzynski.edziennik.data.api.ERROR_LOGIN_LIBRUS_PORTAL_NOT_ACTIVATED -import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_TEMPLATE -import pl.szczodrzynski.edziennik.databinding.FragmentLoginTemplateBinding -import java.util.* -import kotlin.coroutines.CoroutineContext - -class LoginTemplateFragment : Fragment(), CoroutineScope { - companion object { - private const val TAG = "LoginTemplateFragment" - } - - private lateinit var app: App - private lateinit var activity: LoginActivity - private lateinit var b: FragmentLoginTemplateBinding - private val nav by lazy { activity.nav } - - private val job: Job = Job() - override val coroutineContext: CoroutineContext - get() = job + Dispatchers.Main - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - activity = (getActivity() as LoginActivity?) ?: return null - context ?: return null - app = activity.application as App - b = FragmentLoginTemplateBinding.inflate(inflater) - return b.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - activity.lastError?.let { error -> - activity.lastError = null - startCoroutineTimer(delayMillis = 100) { - when (error.errorCode) { - ERROR_LOGIN_LIBRUS_PORTAL_INVALID_LOGIN -> - b.loginPasswordLayout.error = getString(R.string.login_error_incorrect_login_or_password) - ERROR_LOGIN_LIBRUS_PORTAL_NOT_ACTIVATED -> - b.loginEmailLayout.error = getString(R.string.login_error_account_not_activated) - } - } - } - - b.helpButton.onClick { nav.navigate(R.id.loginLibrusHelpFragment, null, LoginActivity.navOptions) } - b.backButton.onClick { nav.navigateUp() } - - b.loginButton.onClick { - var errors = false - - b.loginEmailLayout.error = null - b.loginPasswordLayout.error = null - - val email = b.loginEmail.text?.toString()?.toLowerCase(Locale.ROOT) ?: "" - val password = b.loginPassword.text?.toString() ?: "" - - if (email.isBlank()) { - b.loginEmailLayout.error = getString(R.string.login_error_no_email) - errors = true - } - if (password.isBlank()) { - b.loginPasswordLayout.error = getString(R.string.login_error_no_password) - errors = true - } - if (errors) return@onClick - - errors = false - - b.loginEmail.setText(email) - if (!"([\\w.\\-_+]+)?\\w+@[\\w-_]+(\\.\\w+)+".toRegex().matches(email)) { - b.loginEmailLayout.error = getString(R.string.login_error_incorrect_email) - errors = true - } - if (errors) return@onClick - - val args = Bundle( - "loginType" to LOGIN_TYPE_TEMPLATE, - "email" to email, - "password" to password - ) - nav.navigate(R.id.loginProgressFragment, args, LoginActivity.navOptions) - } - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginVulcanFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginVulcanFragment.kt deleted file mode 100644 index 63ea9834..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginVulcanFragment.kt +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright (c) Kuba Szczodrzyński 2020-1-3. - */ - -package pl.szczodrzynski.edziennik.ui.modules.login - -import android.graphics.Color -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.view.WindowManager -import androidx.fragment.app.Fragment -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 kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import pl.szczodrzynski.edziennik.* -import pl.szczodrzynski.edziennik.data.api.* -import pl.szczodrzynski.edziennik.databinding.FragmentLoginVulcanBinding -import pl.szczodrzynski.edziennik.ui.dialogs.QrScannerDialog -import pl.szczodrzynski.edziennik.utils.Utils -import java.util.* -import kotlin.coroutines.CoroutineContext - -class LoginVulcanFragment : Fragment(), CoroutineScope { - companion object { - private const val TAG = "LoginVulcanFragment" - } - - private lateinit var app: App - private lateinit var activity: LoginActivity - private lateinit var b: FragmentLoginVulcanBinding - private val nav by lazy { activity.nav } - - private val job: Job = Job() - override val coroutineContext: CoroutineContext - get() = job + Dispatchers.Main - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - activity = (getActivity() as LoginActivity?) ?: return null - context ?: return null - app = activity.application as App - b = FragmentLoginVulcanBinding.inflate(inflater) - return b.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - activity.lastError?.let { error -> - activity.lastError = null - startCoroutineTimer(delayMillis = 100) { - when (error.errorCode) { - ERROR_LOGIN_VULCAN_INVALID_TOKEN -> - b.loginTokenLayout.error = getString(R.string.login_error_incorrect_token) - ERROR_LOGIN_VULCAN_EXPIRED_TOKEN -> - b.loginTokenLayout.error = getString(R.string.login_error_expired_token) - ERROR_LOGIN_VULCAN_INVALID_SYMBOL -> - b.loginSymbolLayout.error = getString(R.string.login_error_incorrect_symbol) - ERROR_LOGIN_VULCAN_INVALID_PIN -> - b.loginPinLayout.error = getString(R.string.login_error_incorrect_pin) - } - } - } - - b.loginQrScan.setImageDrawable(IconicsDrawable(activity) - .icon(CommunityMaterial.Icon2.cmd_qrcode_scan) - .colorInt(Color.BLACK) - .sizeDp(72)) - b.loginQrScan.onClick { - QrScannerDialog(activity, { code -> - try { - val data = Utils.VulcanQrEncryptionUtils.decode(code) - "CERT#https?://.+?/([A-z]+)/mobile-api#([A-z0-9]+)#ENDCERT".toRegex().find(data)?.let { - b.loginToken.setText(it[2]) - b.loginSymbol.setText(it[1]) - if (b.loginPin.requestFocus()) { - activity.window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); - } - } - } - catch (_: Exception) {} - }) - } - - b.helpButton.onClick { nav.navigate(R.id.loginVulcanHelpFragment, null, LoginActivity.navOptions) } - b.backButton.onClick { nav.navigateUp() } - - b.loginButton.onClick { - var errors = false - - b.loginTokenLayout.error = null - b.loginSymbolLayout.error = null - b.loginPinLayout.error = null - - val token = b.loginToken.text?.toString()?.toUpperCase(Locale.ROOT) ?: "" - val symbol = b.loginSymbol.text?.toString()?.toLowerCase(Locale.ROOT) ?: "" - val pin = b.loginPin.text?.toString() ?: "" - - if (token.isBlank()) { - b.loginTokenLayout.error = getString(R.string.login_error_no_token) - errors = true - } - if (symbol.isBlank()) { - b.loginSymbolLayout.error = getString(R.string.login_error_no_symbol) - errors = true - } - if (pin.isBlank()) { - b.loginPinLayout.error = getString(R.string.login_error_no_pin) - errors = true - } - if (errors) return@onClick - - errors = false - - b.loginToken.setText(token) - b.loginSymbol.setText(symbol) - b.loginPin.setText(pin) - if (!"[A-Z0-9]{5,12}".toRegex().matches(token)) { - b.loginTokenLayout.error = getString(R.string.login_error_incorrect_token) - errors = true - } - if (!"[a-z0-9_-]+".toRegex().matches(symbol)) { - b.loginSymbolLayout.error = getString(R.string.login_error_incorrect_symbol) - errors = true - } - if (!"[a-z0-9_]+".toRegex().matches(pin)) { - b.loginPinLayout.error = getString(R.string.login_error_incorrect_pin) - errors = true - } - if (errors) return@onClick - - val args = Bundle( - "loginType" to LOGIN_TYPE_VULCAN, - "deviceToken" to token, - "deviceSymbol" to symbol, - "devicePin" to pin - ) - nav.navigate(R.id.loginProgressFragment, args, LoginActivity.navOptions) - } - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginVulcanHelpFragment.java b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginVulcanHelpFragment.java deleted file mode 100644 index 0aa899ae..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginVulcanHelpFragment.java +++ /dev/null @@ -1,48 +0,0 @@ -package pl.szczodrzynski.edziennik.ui.modules.login; - -import androidx.databinding.DataBindingUtil; -import android.os.Bundle; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.fragment.app.Fragment; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import androidx.navigation.NavController; -import androidx.navigation.Navigation; -import pl.szczodrzynski.edziennik.App; -import pl.szczodrzynski.edziennik.R; -import pl.szczodrzynski.edziennik.databinding.FragmentLoginVulcanHelpBinding; - -public class LoginVulcanHelpFragment extends Fragment { - - private App app; - private NavController nav; - private FragmentLoginVulcanHelpBinding b; - private static final String TAG = "LoginVulcanHelp"; - - public LoginVulcanHelpFragment() { } - - @Override - public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - // Inflate the layout for this fragment - if (getActivity() != null) { - app = (App) getActivity().getApplicationContext(); - nav = Navigation.findNavController(getActivity(), R.id.nav_host_fragment); - } - else { - return null; - } - b = DataBindingUtil.inflate(inflater, R.layout.fragment_login_vulcan_help, container, false); - return b.getRoot(); - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - assert getContext() != null; - assert getActivity() != null; - - b.backButton.setOnClickListener((v) -> nav.navigateUp()); - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/viewholder/ModeViewHolder.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/viewholder/ModeViewHolder.kt new file mode 100644 index 00000000..cdb0e7b8 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/viewholder/ModeViewHolder.kt @@ -0,0 +1,39 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-10. + */ + +package pl.szczodrzynski.edziennik.ui.modules.login.viewholder + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.isVisible +import androidx.recyclerview.widget.RecyclerView +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.databinding.LoginChooserModeItemBinding +import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.BindableViewHolder +import pl.szczodrzynski.edziennik.ui.modules.login.LoginChooserAdapter +import pl.szczodrzynski.edziennik.ui.modules.login.LoginInfo + +class ModeViewHolder( + inflater: LayoutInflater, + parent: ViewGroup, + val b: LoginChooserModeItemBinding = LoginChooserModeItemBinding.inflate(inflater, parent, false) +) : RecyclerView.ViewHolder(b.root), BindableViewHolder { + companion object { + private const val TAG = "ModeViewHolder" + } + + override fun onBind(activity: AppCompatActivity, app: App, item: LoginInfo.Mode, position: Int, adapter: LoginChooserAdapter) { + b.logo.setImageResource(item.icon) + b.name.setText(item.name) + if (item.hintText == null) { + b.description.isVisible = false + } + else { + b.description.isVisible = true + b.description.setText(item.hintText) + } + b.hint.isVisible = false + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/viewholder/PlatformViewHolder.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/viewholder/PlatformViewHolder.kt new file mode 100644 index 00000000..ea8a063d --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/viewholder/PlatformViewHolder.kt @@ -0,0 +1,35 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-10. + */ + +package pl.szczodrzynski.edziennik.ui.modules.login.viewholder + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.isVisible +import androidx.recyclerview.widget.RecyclerView +import coil.api.load +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.databinding.LoginPlatformItemBinding +import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.BindableViewHolder +import pl.szczodrzynski.edziennik.ui.modules.login.LoginInfo +import pl.szczodrzynski.edziennik.ui.modules.login.LoginPlatformAdapter + +class PlatformViewHolder( + inflater: LayoutInflater, + parent: ViewGroup, + val b: LoginPlatformItemBinding = LoginPlatformItemBinding.inflate(inflater, parent, false) +) : RecyclerView.ViewHolder(b.root), BindableViewHolder { + companion object { + private const val TAG = "PlatformViewHolder" + } + + override fun onBind(activity: AppCompatActivity, app: App, item: LoginInfo.Platform, position: Int, adapter: LoginPlatformAdapter) { + b.logo.load(item.icon) + b.name.text = item.name + b.description.text = item.description + b.description.isVisible = item.description != null + b.screenshotButton.isVisible = false + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/viewholder/RegisterViewHolder.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/viewholder/RegisterViewHolder.kt new file mode 100644 index 00000000..921041c8 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/viewholder/RegisterViewHolder.kt @@ -0,0 +1,32 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-10. + */ + +package pl.szczodrzynski.edziennik.ui.modules.login.viewholder + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.isVisible +import androidx.recyclerview.widget.RecyclerView +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.databinding.LoginChooserItemBinding +import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.BindableViewHolder +import pl.szczodrzynski.edziennik.ui.modules.login.LoginChooserAdapter +import pl.szczodrzynski.edziennik.ui.modules.login.LoginInfo + +class RegisterViewHolder( + inflater: LayoutInflater, + parent: ViewGroup, + val b: LoginChooserItemBinding = LoginChooserItemBinding.inflate(inflater, parent, false) +) : RecyclerView.ViewHolder(b.root), BindableViewHolder { + companion object { + private const val TAG = "RegisterViewHolder" + } + + override fun onBind(activity: AppCompatActivity, app: App, item: LoginInfo.Register, position: Int, adapter: LoginChooserAdapter) { + b.logo.setImageResource(item.registerLogo) + b.name.setText(item.registerName) + b.description.isVisible = false + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/settings/SettingsNewFragment.java b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/settings/SettingsNewFragment.java index 3e3eac0e..2c84c878 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/settings/SettingsNewFragment.java +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/settings/SettingsNewFragment.java @@ -52,6 +52,7 @@ import pl.szczodrzynski.edziennik.network.NetworkUtils; import pl.szczodrzynski.edziennik.sync.SyncWorker; import pl.szczodrzynski.edziennik.sync.UpdateWorker; import pl.szczodrzynski.edziennik.ui.dialogs.changelog.ChangelogDialog; +import pl.szczodrzynski.edziennik.ui.dialogs.settings.AttendanceConfigDialog; import pl.szczodrzynski.edziennik.ui.dialogs.settings.GradesConfigDialog; import pl.szczodrzynski.edziennik.ui.dialogs.settings.ProfileRemoveDialog; import pl.szczodrzynski.edziennik.ui.dialogs.sync.NotificationFilterDialog; @@ -885,6 +886,15 @@ public class SettingsNewFragment extends MaterialAboutFragment { .color(IconicsColor.colorInt(iconColor)) ).setOnClickAction(() -> new GradesConfigDialog(activity, false, null, null))); + items.add(new MaterialAboutActionItem( + getString(R.string.menu_attendance_config), + null, + new IconicsDrawable(activity) + .icon(CommunityMaterial.Icon.cmd_calendar_remove_outline) + .size(IconicsSize.dp(iconSizeDp)) + .color(IconicsColor.colorInt(iconColor)) + ).setOnClickAction(() -> new AttendanceConfigDialog(activity, false, null, null))); + registerCardAllowRegistrationItem = new MaterialAboutSwitchItem( getString(R.string.settings_register_allow_registration_text), getString(R.string.settings_register_allow_registration_subtext), @@ -1135,8 +1145,8 @@ public class SettingsNewFragment extends MaterialAboutFragment { new MaterialDialog.Builder(activity) .title(getString(R.string.settings_about_language_dialog_title)) .content(getString(R.string.settings_about_language_dialog_text)) - .items(getString(R.string.language_system), getString(R.string.language_polish), getString(R.string.language_english)) - .itemsCallbackSingleChoice(app.getConfig().getUi().getLanguage() == null ? 0 : app.getConfig().getUi().getLanguage().equals("pl") ? 1 : 2, (dialog, itemView, which, text) -> { + .items(getString(R.string.language_system), getString(R.string.language_polish), getString(R.string.language_english), getString(R.string.language_german)) + .itemsCallbackSingleChoice(app.getConfig().getUi().getLanguage() == null ? 0 : app.getConfig().getUi().getLanguage().equals("pl") ? 1 : app.getConfig().getUi().getLanguage().equals("en") ? 2 : 3, (dialog, itemView, which, text) -> { switch (which) { case 0: app.getConfig().getUi().setLanguage(null); @@ -1148,6 +1158,9 @@ public class SettingsNewFragment extends MaterialAboutFragment { case 2: app.getConfig().getUi().setLanguage("en"); break; + case 3: + app.getConfig().getUi().setLanguage("de"); + break; } activity.recreate(MainActivity.DRAWER_ITEM_SETTINGS); return true; diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/TimetableDayFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/TimetableDayFragment.kt index 848d0801..e6464d7d 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/TimetableDayFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/TimetableDayFragment.kt @@ -105,7 +105,7 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope { override fun onPageCreated(): Boolean { // observe lesson database - app.db.timetableDao().getForDate(App.profileId, date).observe(this, Observer { lessons -> + app.db.timetableDao().getAllForDate(App.profileId, date).observe(this, Observer { lessons -> launch { val events = withContext(Dispatchers.Default) { app.db.eventDao().getAllByDateNow(App.profileId, date) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/views/TimeDropdown.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/views/TimeDropdown.kt index 08eda003..e60f24f8 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/views/TimeDropdown.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/views/TimeDropdown.kt @@ -87,7 +87,7 @@ class TimeDropdown : TextInputDropDown { ) } } else if (displayMode == DISPLAY_LESSONS && lessonsDate != null) { - val lessons = db.timetableDao().getForDateNow(profileId, lessonsDate!!) + val lessons = db.timetableDao().getAllForDateNow(profileId, lessonsDate!!) if (lessons.isEmpty()) { hours += Item( 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 014f5bbe..3dfa12f6 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 @@ -48,7 +48,7 @@ class WidgetLuckyNumberProvider : AppWidgetProvider() { val tomorrowValue = tomorrow.value val profile = app.db.profileDao().getByIdNow(config.profileId) - val luckyNumber = app.db.luckyNumberDao().getNearestFutureNow(config.profileId, todayValue) + val luckyNumber = app.db.luckyNumberDao().getNearestFutureNow(config.profileId, today) val isYours = luckyNumber?.number == profile?.studentNumber var noNumberText = false 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 new file mode 100644 index 00000000..cd726797 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/AttendanceManager.kt @@ -0,0 +1,80 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-28. + */ + +package pl.szczodrzynski.edziennik.utils.managers + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.data.db.entity.Attendance +import pl.szczodrzynski.edziennik.data.db.entity.AttendanceType +import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull +import pl.szczodrzynski.edziennik.startCoroutineTimer +import kotlin.coroutines.CoroutineContext + +class AttendanceManager(val app: App) : CoroutineScope { + + private val job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Default + + val useSymbols + get() = app.config.forProfile().attendance.useSymbols + + fun getTypeShort(baseType: Int): String { + return when (baseType) { + Attendance.TYPE_PRESENT -> "ob" + Attendance.TYPE_PRESENT_CUSTOM -> " " + Attendance.TYPE_ABSENT -> "nb" + Attendance.TYPE_ABSENT_EXCUSED -> "u" + Attendance.TYPE_RELEASED -> "zw" + Attendance.TYPE_BELATED -> "sp" + Attendance.TYPE_BELATED_EXCUSED -> "su" + Attendance.TYPE_DAY_FREE -> "w" + else -> "?" + } + } + + fun getAttendanceColor(baseType: Int): Int { + return when (baseType) { + Attendance.TYPE_PRESENT -> 0xff009688.toInt() + Attendance.TYPE_PRESENT_CUSTOM -> 0xff64b5f6.toInt() + Attendance.TYPE_ABSENT -> 0xffff3d00.toInt() + Attendance.TYPE_ABSENT_EXCUSED -> 0xff76ff03.toInt() + Attendance.TYPE_RELEASED -> 0xff9e9e9e.toInt() + Attendance.TYPE_BELATED -> 0xffffc107.toInt() + Attendance.TYPE_BELATED_EXCUSED -> 0xffffc107.toInt() + Attendance.TYPE_DAY_FREE -> 0xff43a047.toInt() + else -> 0xff64b5f6.toInt() + } + } + fun getAttendanceColor(typeObject: AttendanceType): Int { + return (if (useSymbols) typeObject.typeColor else null) + ?: if (typeObject.baseType == Attendance.TYPE_PRESENT_CUSTOM || !typeObject.isCounted) + typeObject.typeColor ?: 0xff64b5f6.toInt() + else getAttendanceColor(typeObject.baseType) + } + fun getAttendanceColor(attendance: Attendance): Int { + return (if (useSymbols) attendance.typeColor else null) + ?: if (attendance.baseType == Attendance.TYPE_PRESENT_CUSTOM || !attendance.isCounted) + attendance.typeColor ?: 0xff64b5f6.toInt() + else getAttendanceColor(attendance.baseType) + } + + /* _ _ _____ _____ _ __ _ + | | | |_ _| / ____| (_)/ _(_) + | | | | | | | (___ _ __ ___ ___ _| |_ _ ___ + | | | | | | \___ \| '_ \ / _ \/ __| | _| |/ __| + | |__| |_| |_ ____) | |_) | __/ (__| | | | | (__ + \____/|_____| |_____/| .__/ \___|\___|_|_| |_|\___| + | | + |*/ + fun markAsSeen(attendance: AttendanceFull) { + attendance.seen = true + startCoroutineTimer(500L, 0L) { + app.db.metadataDao().setSeen(attendance.profileId, attendance, true) + } + } +} 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 8a09df3d..61ea5ad2 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 @@ -208,6 +208,30 @@ class GradesManager(val app: App) : CoroutineScope { } } + fun getGradeNumberName(name: String): String { + return when(name.toLowerCase()){ + "niedostateczny", "f" -> "1" + "niedostateczny plus", "f+" -> "1+" + "niedostateczny minus", "f-" -> "1-" + "dopuszczający", "e" -> "2" + "dopuszczający plus", "e+" -> "2+" + "dopuszczający minus", "e-" -> "2-" + "dostateczny", "d" -> "3" + "dostateczny plus", "d+" -> "3+" + "dostateczny minus", "d-" -> "3-" + "dobry", "c" -> "4" + "dobry plus", "c+" -> "4+" + "dobry minus", "c-" -> "4-" + "bardzo dobry", "b" -> "5" + "bardzo dobry plus", "b+" -> "5+" + "bardzo dobry minus", "b-" -> "5-" + "celujący", "a" -> "6" + "celujący plus", "a+" -> "6+" + "celujący minus", "a-" -> "6-" + else -> name + } + } + /* _ _ _____ _____ _ __ _ | | | |_ _| / ____| (_)/ _(_) | | | | | | | (___ _ __ ___ ___ _| |_ _ ___ 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 c1d32538..88323bc1 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 @@ -8,6 +8,7 @@ import androidx.annotation.Nullable; import java.text.DateFormat; import java.util.Calendar; import java.util.Locale; +import java.util.TimeZone; import pl.szczodrzynski.edziennik.ExtensionsKt; import pl.szczodrzynski.edziennik.R; @@ -108,7 +109,11 @@ public class Date implements Comparable { public static long fromIso(String dateTime) { try { - return Date.fromY_m_d(dateTime).combineWith(new Time(Integer.parseInt(dateTime.substring(11, 13)), Integer.parseInt(dateTime.substring(14, 16)), Integer.parseInt(dateTime.substring(17, 19)))); + Calendar c = Calendar.getInstance(); + c.set(Integer.parseInt(dateTime.substring(0, 4)), Integer.parseInt(dateTime.substring(5, 7)) - 1, Integer.parseInt(dateTime.substring(8, 10)), Integer.parseInt(dateTime.substring(11, 13)), Integer.parseInt(dateTime.substring(14, 16)), Integer.parseInt(dateTime.substring(17, 19))); + c.set(Calendar.MILLISECOND, 0); + c.setTimeZone(TimeZone.getTimeZone("UTC")); + return c.getTimeInMillis(); } catch (Exception e) { return System.currentTimeMillis(); diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/utils/models/Time.java b/app/src/main/java/pl/szczodrzynski/edziennik/utils/models/Time.java index d19e317f..04b0c8a3 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/utils/models/Time.java +++ b/app/src/main/java/pl/szczodrzynski/edziennik/utils/models/Time.java @@ -49,6 +49,16 @@ public class Time implements Comparable