From a98e8398fd0a3783af729d6d61965f214c636f48 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Miko=C5=82aj=20Pich?=
Date: Fri, 12 Jan 2024 18:34:43 +0100
Subject: [PATCH 01/50] Add webview to obtain cloudflare captcha cookies for
okhttp (#2392)
---
app/build.gradle | 3 +-
.../io/github/wulkanowy/data/DataModule.kt | 2 +
.../github/wulkanowy/ui/base/BaseActivity.kt | 5 ++
.../wulkanowy/ui/base/BaseDialogFragment.kt | 4 ++
.../github/wulkanowy/ui/base/BaseFragment.kt | 4 ++
.../github/wulkanowy/ui/base/BasePresenter.kt | 1 +
.../io/github/wulkanowy/ui/base/BaseView.kt | 2 +
.../github/wulkanowy/ui/base/ErrorHandler.kt | 4 ++
.../ui/modules/captcha/CaptchaDialog.kt | 72 +++++++++++++++++++
.../ui/modules/settings/SettingsFragment.kt | 3 +
.../settings/advanced/AdvancedFragment.kt | 4 ++
.../settings/appearance/AppearanceFragment.kt | 4 ++
.../notifications/NotificationsFragment.kt | 4 ++
.../ui/modules/settings/sync/SyncFragment.kt | 4 ++
.../utils/WebkitCookieManagerProxy.kt | 39 ++++++++++
app/src/main/res/layout/dialog_captcha.xml | 12 ++++
.../ui/modules/settings/ads/AdsFragment.kt | 4 ++
17 files changed, 170 insertions(+), 1 deletion(-)
create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/captcha/CaptchaDialog.kt
create mode 100644 app/src/main/java/io/github/wulkanowy/utils/WebkitCookieManagerProxy.kt
create mode 100644 app/src/main/res/layout/dialog_captcha.xml
diff --git a/app/build.gradle b/app/build.gradle
index 180df1a6a..7069672ad 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -193,7 +193,7 @@ ext {
}
dependencies {
- implementation 'io.github.wulkanowy:sdk:2.3.5'
+ implementation 'io.github.wulkanowy:sdk:2.3.6-SNAPSHOT'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4'
@@ -238,6 +238,7 @@ dependencies {
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0"
implementation "com.squareup.okhttp3:logging-interceptor:4.12.0"
+ implementation "com.squareup.okhttp3:okhttp-urlconnection:4.12.0"
implementation "com.jakewharton.timber:timber:5.0.1"
implementation 'com.github.Faierbel:slf4j-timber:2.0'
diff --git a/app/src/main/java/io/github/wulkanowy/data/DataModule.kt b/app/src/main/java/io/github/wulkanowy/data/DataModule.kt
index bea3f7064..950e817bb 100644
--- a/app/src/main/java/io/github/wulkanowy/data/DataModule.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/DataModule.kt
@@ -21,6 +21,7 @@ import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.RemoteConfigHelper
+import io.github.wulkanowy.utils.WebkitCookieManagerProxy
import kotlinx.serialization.json.Json
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
@@ -43,6 +44,7 @@ internal class DataModule {
buildTag = android.os.Build.MODEL
userAgentTemplate = remoteConfig.userAgentTemplate
setSimpleHttpLogger { Timber.d(it) }
+ setAdditionalCookieManager(WebkitCookieManagerProxy())
// for debug only
addInterceptor(chuckerInterceptor, network = true)
diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BaseActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/base/BaseActivity.kt
index 026d38ded..29996db7c 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/base/BaseActivity.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/base/BaseActivity.kt
@@ -11,6 +11,7 @@ import com.google.android.material.snackbar.Snackbar
import com.google.android.material.snackbar.Snackbar.LENGTH_LONG
import io.github.wulkanowy.R
import io.github.wulkanowy.ui.modules.auth.AuthDialog
+import io.github.wulkanowy.ui.modules.captcha.CaptchaDialog
import io.github.wulkanowy.ui.modules.login.LoginActivity
import io.github.wulkanowy.utils.FragmentLifecycleLogger
import io.github.wulkanowy.utils.getThemeAttrColor
@@ -77,6 +78,10 @@ abstract class BaseActivity, VB : ViewBinding> :
.show()
}
+ override fun onCaptchaVerificationRequired(url: String?) {
+ CaptchaDialog.newInstance(url).show(supportFragmentManager, "captcha_dialog")
+ }
+
override fun showDecryptionFailedDialog() {
MaterialAlertDialogBuilder(this)
.setTitle(R.string.main_session_expired)
diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BaseDialogFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/base/BaseDialogFragment.kt
index 50e4b05d4..cb85fd8aa 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/base/BaseDialogFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/base/BaseDialogFragment.kt
@@ -32,6 +32,10 @@ abstract class BaseDialogFragment : DialogFragment(), BaseView
(activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog()
}
+ override fun onCaptchaVerificationRequired(url: String?) {
+ (activity as? BaseActivity<*, *>)?.onCaptchaVerificationRequired(url)
+ }
+
override fun showDecryptionFailedDialog() {
(activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog()
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BaseFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/base/BaseFragment.kt
index cec2670b2..4f919f456 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/base/BaseFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/base/BaseFragment.kt
@@ -43,6 +43,10 @@ abstract class BaseFragment(@LayoutRes layoutId: Int) : Fragme
(activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog()
}
+ override fun onCaptchaVerificationRequired(url: String?) {
+ (activity as? BaseActivity<*, *>)?.onCaptchaVerificationRequired(url)
+ }
+
override fun showDecryptionFailedDialog() {
(activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog()
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BasePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/base/BasePresenter.kt
index ee92e4fc1..d4cb20cac 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/base/BasePresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/base/BasePresenter.kt
@@ -29,6 +29,7 @@ open class BasePresenter(
errorHandler.apply {
showErrorMessage = view::showError
onExpiredCredentials = view::showExpiredCredentialsDialog
+ onCaptchaVerificationRequired = view::onCaptchaVerificationRequired
onDecryptionFailed = view::showDecryptionFailedDialog
onNoCurrentStudent = view::openClearLoginView
onPasswordChangeRequired = view::showChangePasswordSnackbar
diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BaseView.kt b/app/src/main/java/io/github/wulkanowy/ui/base/BaseView.kt
index e97a6ab90..88d5754f8 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/base/BaseView.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/base/BaseView.kt
@@ -8,6 +8,8 @@ interface BaseView {
fun showExpiredCredentialsDialog()
+ fun onCaptchaVerificationRequired(url: String?)
+
fun showDecryptionFailedDialog()
fun showAuthDialog()
diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/ErrorHandler.kt b/app/src/main/java/io/github/wulkanowy/ui/base/ErrorHandler.kt
index 56905709d..e17c0c9ec 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/base/ErrorHandler.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/base/ErrorHandler.kt
@@ -4,6 +4,7 @@ import android.content.Context
import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.data.exceptions.NoCurrentStudentException
import io.github.wulkanowy.sdk.scrapper.exception.AuthorizationRequiredException
+import io.github.wulkanowy.sdk.scrapper.exception.CloudflareVerificationException
import io.github.wulkanowy.sdk.scrapper.login.BadCredentialsException
import io.github.wulkanowy.sdk.scrapper.login.PasswordChangeRequiredException
import io.github.wulkanowy.utils.getErrorString
@@ -25,6 +26,8 @@ open class ErrorHandler @Inject constructor(@ApplicationContext protected val co
var onAuthorizationRequired: () -> Unit = {}
+ var onCaptchaVerificationRequired: (url: String?) -> Unit = {}
+
fun dispatch(error: Throwable) {
Timber.e(error, "An exception occurred while the Wulkanowy was running")
proceed(error)
@@ -38,6 +41,7 @@ open class ErrorHandler @Inject constructor(@ApplicationContext protected val co
is BadCredentialsException -> onExpiredCredentials()
is NoCurrentStudentException -> onNoCurrentStudent()
is AuthorizationRequiredException -> onAuthorizationRequired()
+ is CloudflareVerificationException -> onCaptchaVerificationRequired(error.originalUrl)
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/captcha/CaptchaDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/captcha/CaptchaDialog.kt
new file mode 100644
index 000000000..6c4d6420f
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/captcha/CaptchaDialog.kt
@@ -0,0 +1,72 @@
+package io.github.wulkanowy.ui.modules.captcha
+
+import android.annotation.SuppressLint
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.webkit.WebResourceError
+import android.webkit.WebResourceRequest
+import android.webkit.WebView
+import android.webkit.WebViewClient
+import androidx.core.os.bundleOf
+import dagger.hilt.android.AndroidEntryPoint
+import io.github.wulkanowy.databinding.DialogCaptchaBinding
+import io.github.wulkanowy.sdk.Sdk
+import io.github.wulkanowy.ui.base.BaseDialogFragment
+import timber.log.Timber
+import javax.inject.Inject
+
+@AndroidEntryPoint
+class CaptchaDialog : BaseDialogFragment() {
+
+ @Inject
+ lateinit var sdk: Sdk
+
+ companion object {
+ private const val CAPTCHA_URL = "captcha_url"
+ fun newInstance(url: String?): CaptchaDialog {
+ return CaptchaDialog().apply {
+ arguments = bundleOf(CAPTCHA_URL to url)
+ }
+ }
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View = DialogCaptchaBinding.inflate(inflater).apply { binding = this }.root
+
+ @SuppressLint("SetJavaScriptEnabled")
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ with(binding.captchaWebview) {
+ with(settings) {
+ javaScriptEnabled = true
+ userAgentString = sdk.userAgent
+ }
+
+ webViewClient = object : WebViewClient() {
+ override fun onPageFinished(view: WebView?, url: String?) {
+ view?.evaluateJavascript("document.getElementById('challenge-running') == undefined") {
+ if (it == "true") {
+ dismiss()
+ } else Timber.e("JS result: $it")
+ }
+ }
+
+ override fun onReceivedError(
+ view: WebView?,
+ request: WebResourceRequest?,
+ error: WebResourceError?
+ ) {
+ super.onReceivedError(view, request, error)
+ }
+ }
+
+ loadUrl(arguments?.getString(CAPTCHA_URL).orEmpty())
+ }
+ }
+}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsFragment.kt
index 19c4ef6b7..f8d1323c6 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsFragment.kt
@@ -3,6 +3,7 @@ package io.github.wulkanowy.ui.modules.settings
import android.os.Bundle
import androidx.preference.PreferenceFragmentCompat
import io.github.wulkanowy.R
+import io.github.wulkanowy.ui.base.BaseActivity
import io.github.wulkanowy.ui.modules.main.MainView
import timber.log.Timber
@@ -26,6 +27,8 @@ class SettingsFragment : PreferenceFragmentCompat(), MainView.TitledView, Settin
override fun showExpiredCredentialsDialog() {}
+ override fun onCaptchaVerificationRequired(url: String?) = Unit
+
override fun showDecryptionFailedDialog() {}
override fun openClearLoginView() {}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/advanced/AdvancedFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/advanced/AdvancedFragment.kt
index 256b13375..a1d00227f 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/advanced/AdvancedFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/advanced/AdvancedFragment.kt
@@ -51,6 +51,10 @@ class AdvancedFragment : PreferenceFragmentCompat(),
(activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog()
}
+ override fun onCaptchaVerificationRequired(url: String?) {
+ (activity as? BaseActivity<*, *>)?.onCaptchaVerificationRequired(url)
+ }
+
override fun showDecryptionFailedDialog() {
(activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog()
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/AppearanceFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/AppearanceFragment.kt
index 20423eb91..b9b35019a 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/AppearanceFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/AppearanceFragment.kt
@@ -67,6 +67,10 @@ class AppearanceFragment : PreferenceFragmentCompat(),
(activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog()
}
+ override fun onCaptchaVerificationRequired(url: String?) {
+ (activity as? BaseActivity<*, *>)?.onCaptchaVerificationRequired(url)
+ }
+
override fun showDecryptionFailedDialog() {
(activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog()
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsFragment.kt
index 2ae983c26..fdc4a24d9 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsFragment.kt
@@ -137,6 +137,10 @@ class NotificationsFragment : PreferenceFragmentCompat(),
(activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog()
}
+ override fun onCaptchaVerificationRequired(url: String?) {
+ (activity as? BaseActivity<*, *>)?.onCaptchaVerificationRequired(url)
+ }
+
override fun showDecryptionFailedDialog() {
(activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog()
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncFragment.kt
index 133b1ff44..1e81e58ac 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncFragment.kt
@@ -88,6 +88,10 @@ class SyncFragment : PreferenceFragmentCompat(),
(activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog()
}
+ override fun onCaptchaVerificationRequired(url: String?) {
+ (activity as? BaseActivity<*, *>)?.onCaptchaVerificationRequired(url)
+ }
+
override fun showDecryptionFailedDialog() {
(activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog()
}
diff --git a/app/src/main/java/io/github/wulkanowy/utils/WebkitCookieManagerProxy.kt b/app/src/main/java/io/github/wulkanowy/utils/WebkitCookieManagerProxy.kt
new file mode 100644
index 000000000..509f39f58
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/utils/WebkitCookieManagerProxy.kt
@@ -0,0 +1,39 @@
+package io.github.wulkanowy.utils
+
+import java.net.CookiePolicy
+import java.net.URI
+import android.webkit.CookieManager as WebkitCookieManager
+import java.net.CookieManager as JavaCookieManager
+
+class WebkitCookieManagerProxy : JavaCookieManager(null, CookiePolicy.ACCEPT_ALL) {
+
+ private val webkitCookieManager: WebkitCookieManager = WebkitCookieManager.getInstance()
+
+ override fun put(uri: URI?, responseHeaders: Map>?) {
+ if (uri == null || responseHeaders == null) return
+ val url = uri.toString()
+ for (headerKey in responseHeaders.keys) {
+ if (headerKey == null || !(
+ headerKey.equals("Set-Cookie2", ignoreCase = true) ||
+ headerKey.equals("Set-Cookie", ignoreCase = true)
+ )
+ ) continue
+
+ // process each of the headers
+ for (headerValue in responseHeaders[headerKey].orEmpty()) {
+ webkitCookieManager.setCookie(url, headerValue)
+ }
+ }
+ }
+
+ override operator fun get(
+ uri: URI?,
+ requestHeaders: Map?>?
+ ): Map> {
+ require(!(uri == null || requestHeaders == null)) { "Argument is null" }
+ val res = mutableMapOf>()
+ val cookie = webkitCookieManager.getCookie(uri.toString())
+ if (cookie != null) res["Cookie"] = listOf(cookie)
+ return res
+ }
+}
diff --git a/app/src/main/res/layout/dialog_captcha.xml b/app/src/main/res/layout/dialog_captcha.xml
new file mode 100644
index 000000000..2df18066d
--- /dev/null
+++ b/app/src/main/res/layout/dialog_captcha.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
diff --git a/app/src/play/java/io/github/wulkanowy/ui/modules/settings/ads/AdsFragment.kt b/app/src/play/java/io/github/wulkanowy/ui/modules/settings/ads/AdsFragment.kt
index d7d83e6c9..30b9e6b77 100644
--- a/app/src/play/java/io/github/wulkanowy/ui/modules/settings/ads/AdsFragment.kt
+++ b/app/src/play/java/io/github/wulkanowy/ui/modules/settings/ads/AdsFragment.kt
@@ -105,6 +105,10 @@ class AdsFragment : PreferenceFragmentCompat(), MainView.TitledView, AdsView {
(activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog()
}
+ override fun onCaptchaVerificationRequired(url: String?) {
+ (activity as? BaseActivity<*, *>)?.onCaptchaVerificationRequired(url)
+ }
+
override fun showDecryptionFailedDialog() {
(activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog()
}
From 096fe359e72abb67dc3cd7608428c5f91d29f334 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Miko=C5=82aj=20Pich?=
Date: Sun, 14 Jan 2024 14:09:04 +0100
Subject: [PATCH 02/50] Make some improvements in captcha dialog (#2393)
* Add improvements retrying after captcha solved
* Add showAuthDialog from BaseActivity instead of displaying this dialog manually
* Add getCookieStore() with removeAll impl in WebkitCookieManagerProxy
* Add debounce to captcha dialog showing logic
* Add refresh button to captcha dialog
* Destroy webview along with captcha dialog
* Add clear webkit cookies button to debug menu
* Add captcha error message
* Update captcha verified message
---
.../TimetableNotificationSchedulerHelper.kt | 2 -
.../wulkanowy/ui/base/BaseDialogFragment.kt | 3 +-
.../github/wulkanowy/ui/base/BaseFragment.kt | 3 +-
.../ui/modules/captcha/CaptchaDialog.kt | 40 ++++++++++++------
.../ui/modules/dashboard/DashboardFragment.kt | 12 ++++++
.../modules/dashboard/DashboardPresenter.kt | 8 ++++
.../ui/modules/dashboard/DashboardView.kt | 3 +-
.../ui/modules/debug/DebugFragment.kt | 5 +++
.../ui/modules/debug/DebugPresenter.kt | 2 +
.../wulkanowy/ui/modules/debug/DebugView.kt | 2 +
.../modules/login/form/LoginFormFragment.kt | 9 ++++
.../modules/login/form/LoginFormPresenter.kt | 4 ++
.../wulkanowy/ui/modules/main/MainActivity.kt | 34 +++++++++++++++
.../settings/advanced/AdvancedFragment.kt | 3 +-
.../settings/appearance/AppearanceFragment.kt | 3 +-
.../notifications/NotificationsFragment.kt | 3 +-
.../ui/modules/settings/sync/SyncFragment.kt | 3 +-
.../wulkanowy/utils/ExceptionExtension.kt | 2 +
.../utils/WebkitCookieManagerProxy.kt | 19 +++++++++
app/src/main/res/layout/dialog_captcha.xml | 41 ++++++++++++++++++-
app/src/main/res/values/strings.xml | 7 ++++
21 files changed, 179 insertions(+), 29 deletions(-)
diff --git a/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationSchedulerHelper.kt b/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationSchedulerHelper.kt
index 42078d03f..aae7882f1 100644
--- a/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationSchedulerHelper.kt
+++ b/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationSchedulerHelper.kt
@@ -65,8 +65,6 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
range = lesson.start..lesson.end,
requestCode = getRequestCode(lesson.start, studentId)
)
-
- Timber.d("TimetableNotification canceled: type 1 & 2, subject: ${lesson.subject}, start: ${lesson.start}, student: $studentId")
}
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BaseDialogFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/base/BaseDialogFragment.kt
index cb85fd8aa..e63887b8f 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/base/BaseDialogFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/base/BaseDialogFragment.kt
@@ -8,7 +8,6 @@ import android.widget.Toast
import androidx.fragment.app.DialogFragment
import androidx.viewbinding.ViewBinding
import com.google.android.material.elevation.SurfaceColors
-import io.github.wulkanowy.ui.modules.auth.AuthDialog
import io.github.wulkanowy.utils.AnalyticsHelper
import io.github.wulkanowy.utils.lifecycleAwareVariable
import javax.inject.Inject
@@ -49,7 +48,7 @@ abstract class BaseDialogFragment : DialogFragment(), BaseView
}
override fun showAuthDialog() {
- AuthDialog.newInstance().show(childFragmentManager, "auth_dialog")
+ (activity as? BaseActivity<*, *>)?.showAuthDialog()
}
override fun showErrorDetailsDialog(error: Throwable) {
diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BaseFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/base/BaseFragment.kt
index 4f919f456..ba346131c 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/base/BaseFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/base/BaseFragment.kt
@@ -7,7 +7,6 @@ import androidx.viewbinding.ViewBinding
import com.google.android.material.snackbar.Snackbar
import com.google.android.material.snackbar.Snackbar.LENGTH_LONG
import io.github.wulkanowy.R
-import io.github.wulkanowy.ui.modules.auth.AuthDialog
import io.github.wulkanowy.utils.lifecycleAwareVariable
abstract class BaseFragment(@LayoutRes layoutId: Int) : Fragment(layoutId),
@@ -52,7 +51,7 @@ abstract class BaseFragment(@LayoutRes layoutId: Int) : Fragme
}
override fun showAuthDialog() {
- AuthDialog.newInstance().show(childFragmentManager, "auth_dialog")
+ (activity as? BaseActivity<*, *>)?.showAuthDialog()
}
override fun openClearLoginView() {
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/captcha/CaptchaDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/captcha/CaptchaDialog.kt
index 6c4d6420f..098d08ed9 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/captcha/CaptchaDialog.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/captcha/CaptchaDialog.kt
@@ -5,12 +5,11 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
-import android.webkit.WebResourceError
-import android.webkit.WebResourceRequest
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.core.os.bundleOf
import dagger.hilt.android.AndroidEntryPoint
+import io.github.wulkanowy.R
import io.github.wulkanowy.databinding.DialogCaptchaBinding
import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.ui.base.BaseDialogFragment
@@ -23,8 +22,13 @@ class CaptchaDialog : BaseDialogFragment() {
@Inject
lateinit var sdk: Sdk
+ private var webView: WebView? = null
+
companion object {
+ const val CAPTCHA_SUCCESS = "captcha_success"
private const val CAPTCHA_URL = "captcha_url"
+ private const val CAPTCHA_CHECK_JS = "document.getElementById('challenge-running') == null"
+
fun newInstance(url: String?): CaptchaDialog {
return CaptchaDialog().apply {
arguments = bundleOf(CAPTCHA_URL to url)
@@ -41,8 +45,14 @@ class CaptchaDialog : BaseDialogFragment() {
@SuppressLint("SetJavaScriptEnabled")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
+ isCancelable = false
+ binding.captchaRefresh.setOnClickListener {
+ binding.captchaWebview.loadUrl(arguments?.getString(CAPTCHA_URL).orEmpty())
+ }
+ binding.captchaClose.setOnClickListener { dismiss() }
with(binding.captchaWebview) {
+ webView = this
with(settings) {
javaScriptEnabled = true
userAgentString = sdk.userAgent
@@ -50,23 +60,27 @@ class CaptchaDialog : BaseDialogFragment() {
webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView?, url: String?) {
- view?.evaluateJavascript("document.getElementById('challenge-running') == undefined") {
+ view?.evaluateJavascript(CAPTCHA_CHECK_JS) {
if (it == "true") {
- dismiss()
- } else Timber.e("JS result: $it")
+ onChallengeAccepted()
+ }
}
}
-
- override fun onReceivedError(
- view: WebView?,
- request: WebResourceRequest?,
- error: WebResourceError?
- ) {
- super.onReceivedError(view, request, error)
- }
}
loadUrl(arguments?.getString(CAPTCHA_URL).orEmpty())
}
}
+
+ private fun onChallengeAccepted() {
+ runCatching { parentFragmentManager.setFragmentResult(CAPTCHA_SUCCESS, bundleOf()) }
+ .onFailure { Timber.e(it) }
+ showMessage(getString(R.string.captcha_verified_message))
+ dismiss()
+ }
+
+ override fun onDestroy() {
+ webView?.destroy()
+ super.onDestroy()
+ }
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardFragment.kt
index 301262a04..bedbce231 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardFragment.kt
@@ -18,6 +18,7 @@ import io.github.wulkanowy.databinding.FragmentDashboardBinding
import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.account.accountdetails.AccountDetailsFragment
import io.github.wulkanowy.ui.modules.attendance.summary.AttendanceSummaryFragment
+import io.github.wulkanowy.ui.modules.captcha.CaptchaDialog.Companion.CAPTCHA_SUCCESS
import io.github.wulkanowy.ui.modules.conference.ConferenceFragment
import io.github.wulkanowy.ui.modules.dashboard.adapters.DashboardAdapter
import io.github.wulkanowy.ui.modules.exam.ExamFragment
@@ -36,6 +37,7 @@ import io.github.wulkanowy.utils.getErrorString
import io.github.wulkanowy.utils.getThemeAttrColor
import io.github.wulkanowy.utils.openInternetBrowser
import io.github.wulkanowy.utils.toFormattedString
+import timber.log.Timber
import java.time.LocalDate
import javax.inject.Inject
@@ -62,6 +64,9 @@ class DashboardFragment : BaseFragment(R.layout.fragme
return ((recyclerWidth - margin) / resources.displayMetrics.density).toInt()
}
+ override val isViewEmpty
+ get() = dashboardAdapter.itemCount == 0
+
companion object {
fun newInstance() = DashboardFragment()
@@ -77,6 +82,13 @@ class DashboardFragment : BaseFragment(R.layout.fragme
super.onViewCreated(view, savedInstanceState)
binding = FragmentDashboardBinding.bind(view)
presenter.onAttachView(this)
+ initializeCaptchaResultObserver()
+ }
+
+ private fun initializeCaptchaResultObserver() {
+ childFragmentManager.setFragmentResultListener(CAPTCHA_SUCCESS, this) { _, _ ->
+ presenter.onRetryAfterCaptcha()
+ }
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardPresenter.kt
index c93dd9e78..d7add2c05 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardPresenter.kt
@@ -239,6 +239,14 @@ class DashboardPresenter @Inject constructor(
loadData(selectedDashboardTiles, forceRefresh = true)
}
+ fun onRetryAfterCaptcha() {
+ view?.run {
+ showErrorView(false)
+ showProgress(true)
+ }
+ loadData(selectedDashboardTiles, forceRefresh = true)
+ }
+
fun onViewReselected() {
Timber.i("Dashboard view is reselected")
view?.run {
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardView.kt
index 767885434..fe011c929 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardView.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardView.kt
@@ -6,6 +6,8 @@ interface DashboardView : BaseView {
val tileWidth: Int
+ val isViewEmpty: Boolean
+
fun initView()
fun updateData(data: List)
@@ -27,6 +29,5 @@ interface DashboardView : BaseView {
fun popViewToRoot()
fun openNotificationsCenterView()
-
fun openInternetBrowser(url: String)
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/DebugFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/DebugFragment.kt
index 000916b17..9db01a307 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/DebugFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/DebugFragment.kt
@@ -2,6 +2,7 @@ package io.github.wulkanowy.ui.modules.debug
import android.os.Bundle
import android.view.View
+import android.webkit.CookieManager
import androidx.recyclerview.widget.LinearLayoutManager
import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R
@@ -58,6 +59,10 @@ class DebugFragment : BaseFragment(R.layout.fragment_debug
(activity as? MainActivity)?.pushView(NotificationDebugFragment.newInstance())
}
+ override fun clearWebkitCookies() {
+ CookieManager.getInstance().removeAllCookies(null)
+ }
+
override fun onDestroyView() {
presenter.onDetachView()
super.onDestroyView()
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/DebugPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/DebugPresenter.kt
index 67ac88861..816b59858 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/DebugPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/DebugPresenter.kt
@@ -15,6 +15,7 @@ class DebugPresenter @Inject constructor(
val items = listOf(
DebugItem(R.string.logviewer_title),
DebugItem(R.string.notification_debug_title),
+ DebugItem(R.string.debug_cookies_clear),
)
override fun onAttachView(view: DebugView) {
@@ -31,6 +32,7 @@ class DebugPresenter @Inject constructor(
when (item.title) {
R.string.logviewer_title -> view?.openLogViewer()
R.string.notification_debug_title -> view?.openNotificationsDebug()
+ R.string.debug_cookies_clear -> view?.clearWebkitCookies()
else -> Timber.d("Unknown debug item: $item")
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/DebugView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/DebugView.kt
index 9396ec6ac..792d63d9e 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/DebugView.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/DebugView.kt
@@ -11,4 +11,6 @@ interface DebugView : BaseView {
fun openLogViewer()
fun openNotificationsDebug()
+
+ fun clearWebkitCookies()
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormFragment.kt
index 8e9b86fa3..975cad185 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormFragment.kt
@@ -7,6 +7,7 @@ import android.view.View.GONE
import android.view.View.VISIBLE
import androidx.core.view.isVisible
import androidx.core.widget.doOnTextChanged
+import androidx.fragment.app.setFragmentResultListener
import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.AdminMessage
@@ -14,6 +15,7 @@ import io.github.wulkanowy.data.pojos.RegisterUser
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.databinding.FragmentLoginFormBinding
import io.github.wulkanowy.ui.base.BaseFragment
+import io.github.wulkanowy.ui.modules.captcha.CaptchaDialog
import io.github.wulkanowy.ui.modules.dashboard.viewholders.AdminMessageViewHolder
import io.github.wulkanowy.ui.modules.login.LoginActivity
import io.github.wulkanowy.ui.modules.login.LoginData
@@ -72,6 +74,13 @@ class LoginFormFragment : BaseFragment(R.layout.fragme
super.onViewCreated(view, savedInstanceState)
binding = FragmentLoginFormBinding.bind(view)
presenter.onAttachView(this)
+ initializeCaptchaResultObserver()
+ }
+
+ private fun initializeCaptchaResultObserver() {
+ setFragmentResultListener(CaptchaDialog.CAPTCHA_SUCCESS) { _, _ ->
+ presenter.onRetryAfterCaptcha()
+ }
}
override fun initView() {
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenter.kt
index ad535c382..c9ae4f27f 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenter.kt
@@ -152,6 +152,10 @@ class LoginFormPresenter @Inject constructor(
)
}
+ fun onRetryAfterCaptcha() {
+ onSignInClick()
+ }
+
fun onSignInClick() {
val loginData = getLoginData()
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt
index ba0ef4050..62c16257e 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt
@@ -16,6 +16,7 @@ import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
+import androidx.lifecycle.lifecycleScope
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import com.google.android.material.dialog.MaterialAlertDialogBuilder
@@ -30,6 +31,8 @@ import io.github.wulkanowy.databinding.ActivityMainBinding
import io.github.wulkanowy.ui.base.BaseActivity
import io.github.wulkanowy.ui.modules.Destination
import io.github.wulkanowy.ui.modules.account.accountquick.AccountQuickDialog
+import io.github.wulkanowy.ui.modules.auth.AuthDialog
+import io.github.wulkanowy.ui.modules.captcha.CaptchaDialog
import io.github.wulkanowy.ui.modules.settings.appearance.menuorder.AppMenuItem
import io.github.wulkanowy.utils.AnalyticsHelper
import io.github.wulkanowy.utils.AppInfo
@@ -40,10 +43,17 @@ import io.github.wulkanowy.utils.dpToPx
import io.github.wulkanowy.utils.nickOrName
import io.github.wulkanowy.utils.safelyPopFragments
import io.github.wulkanowy.utils.setOnViewChangeListener
+import kotlinx.coroutines.FlowPreview
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.debounce
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import timber.log.Timber
import javax.inject.Inject
+import kotlin.time.Duration.Companion.seconds
@AndroidEntryPoint
class MainActivity : BaseActivity(), MainView,
@@ -73,6 +83,8 @@ class MainActivity : BaseActivity(), MainVie
private val navController =
FragNavController(supportFragmentManager, R.id.main_fragment_container)
+ private val captchaVerificationEvent = MutableSharedFlow()
+
companion object {
private const val EXTRA_START_DESTINATION = "start_destination_json"
@@ -144,6 +156,7 @@ class MainActivity : BaseActivity(), MainVie
initializeToolbar()
initializeBottomNavigation(startMenuIndex, rootAppMenuItems)
initializeNavController(startMenuIndex, rootUpdatedDestinations)
+ initializeCaptchaVerificationEvent()
}
private fun initializeNavController(
@@ -323,6 +336,27 @@ class MainActivity : BaseActivity(), MainVie
.show()
}
+ @OptIn(FlowPreview::class)
+ private fun initializeCaptchaVerificationEvent() {
+ captchaVerificationEvent
+ .debounce(1.seconds)
+ .onEach { url ->
+ Timber.d("Showing captcha dialog for: $url")
+ showDialogFragment(CaptchaDialog.newInstance(url))
+ }
+ .launchIn(lifecycleScope)
+ }
+
+ override fun onCaptchaVerificationRequired(url: String?) {
+ lifecycleScope.launch {
+ captchaVerificationEvent.emit(url)
+ }
+ }
+
+ override fun showAuthDialog() {
+ showDialogFragment(AuthDialog.newInstance())
+ }
+
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
navController.onSaveInstanceState(outState)
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/advanced/AdvancedFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/advanced/AdvancedFragment.kt
index a1d00227f..3ef1a80a3 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/advanced/AdvancedFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/advanced/AdvancedFragment.kt
@@ -8,7 +8,6 @@ import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R
import io.github.wulkanowy.ui.base.BaseActivity
import io.github.wulkanowy.ui.base.ErrorDialog
-import io.github.wulkanowy.ui.modules.auth.AuthDialog
import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.AppInfo
import javax.inject.Inject
@@ -72,7 +71,7 @@ class AdvancedFragment : PreferenceFragmentCompat(),
}
override fun showAuthDialog() {
- AuthDialog.newInstance().show(childFragmentManager, "auth_dialog")
+ (activity as? BaseActivity<*, *>)?.showAuthDialog()
}
override fun onResume() {
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/AppearanceFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/AppearanceFragment.kt
index b9b35019a..3d0c8052b 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/AppearanceFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/AppearanceFragment.kt
@@ -9,7 +9,6 @@ import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R
import io.github.wulkanowy.ui.base.BaseActivity
import io.github.wulkanowy.ui.base.ErrorDialog
-import io.github.wulkanowy.ui.modules.auth.AuthDialog
import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.AppInfo
import javax.inject.Inject
@@ -88,7 +87,7 @@ class AppearanceFragment : PreferenceFragmentCompat(),
}
override fun showAuthDialog() {
- AuthDialog.newInstance().show(childFragmentManager, "auth_dialog")
+ (activity as? BaseActivity<*, *>)?.showAuthDialog()
}
override fun onResume() {
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsFragment.kt
index fdc4a24d9..0bf9ddadd 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsFragment.kt
@@ -21,7 +21,6 @@ import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R
import io.github.wulkanowy.ui.base.BaseActivity
import io.github.wulkanowy.ui.base.ErrorDialog
-import io.github.wulkanowy.ui.modules.auth.AuthDialog
import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.openInternetBrowser
@@ -158,7 +157,7 @@ class NotificationsFragment : PreferenceFragmentCompat(),
}
override fun showAuthDialog() {
- AuthDialog.newInstance().show(childFragmentManager, "auth_dialog")
+ (activity as? BaseActivity<*, *>)?.showAuthDialog()
}
override fun showFixSyncDialog() {
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncFragment.kt
index 1e81e58ac..d57144832 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncFragment.kt
@@ -10,7 +10,6 @@ import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R
import io.github.wulkanowy.ui.base.BaseActivity
import io.github.wulkanowy.ui.base.ErrorDialog
-import io.github.wulkanowy.ui.modules.auth.AuthDialog
import io.github.wulkanowy.ui.modules.main.MainView
import javax.inject.Inject
@@ -109,7 +108,7 @@ class SyncFragment : PreferenceFragmentCompat(),
}
override fun showAuthDialog() {
- AuthDialog.newInstance().show(childFragmentManager, "auth_dialog")
+ (activity as? BaseActivity<*, *>)?.showAuthDialog()
}
override fun onResume() {
diff --git a/app/src/main/java/io/github/wulkanowy/utils/ExceptionExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/ExceptionExtension.kt
index a4c2537ac..18fc10bba 100644
--- a/app/src/main/java/io/github/wulkanowy/utils/ExceptionExtension.kt
+++ b/app/src/main/java/io/github/wulkanowy/utils/ExceptionExtension.kt
@@ -3,6 +3,7 @@ package io.github.wulkanowy.utils
import android.content.res.Resources
import io.github.wulkanowy.R
import io.github.wulkanowy.sdk.exception.FeatureNotAvailableException
+import io.github.wulkanowy.sdk.scrapper.exception.CloudflareVerificationException
import io.github.wulkanowy.sdk.scrapper.exception.FeatureDisabledException
import io.github.wulkanowy.sdk.scrapper.exception.ScrapperException
import io.github.wulkanowy.sdk.scrapper.exception.ServiceUnavailableException
@@ -34,6 +35,7 @@ fun Resources.getErrorString(error: Throwable): String = when (error) {
is FeatureNotAvailableException -> R.string.error_feature_not_available
is VulcanException -> R.string.error_unknown_uonet
is ScrapperException -> R.string.error_unknown_app
+ is CloudflareVerificationException -> R.string.error_cloudflare_captcha
is SSLHandshakeException -> when {
error.isCausedByCertificateNotValidNow() -> R.string.error_invalid_device_datetime
else -> R.string.error_timeout
diff --git a/app/src/main/java/io/github/wulkanowy/utils/WebkitCookieManagerProxy.kt b/app/src/main/java/io/github/wulkanowy/utils/WebkitCookieManagerProxy.kt
index 509f39f58..a54978717 100644
--- a/app/src/main/java/io/github/wulkanowy/utils/WebkitCookieManagerProxy.kt
+++ b/app/src/main/java/io/github/wulkanowy/utils/WebkitCookieManagerProxy.kt
@@ -1,6 +1,8 @@
package io.github.wulkanowy.utils
import java.net.CookiePolicy
+import java.net.CookieStore
+import java.net.HttpCookie
import java.net.URI
import android.webkit.CookieManager as WebkitCookieManager
import java.net.CookieManager as JavaCookieManager
@@ -36,4 +38,21 @@ class WebkitCookieManagerProxy : JavaCookieManager(null, CookiePolicy.ACCEPT_ALL
if (cookie != null) res["Cookie"] = listOf(cookie)
return res
}
+
+ override fun getCookieStore(): CookieStore {
+ val cookies = super.getCookieStore()
+ return object : CookieStore {
+ override fun add(uri: URI?, cookie: HttpCookie?) = cookies.add(uri, cookie)
+ override fun get(uri: URI?): List = cookies.get(uri)
+ override fun getCookies(): List = cookies.cookies
+ override fun getURIs(): List = cookies.urIs
+ override fun remove(uri: URI?, cookie: HttpCookie?): Boolean =
+ cookies.remove(uri, cookie)
+
+ override fun removeAll(): Boolean {
+ webkitCookieManager.removeAllCookies(null)
+ return true
+ }
+ }
+ }
}
diff --git a/app/src/main/res/layout/dialog_captcha.xml b/app/src/main/res/layout/dialog_captcha.xml
index 2df18066d..539aa0cc9 100644
--- a/app/src/main/res/layout/dialog_captcha.xml
+++ b/app/src/main/res/layout/dialog_captcha.xml
@@ -1,12 +1,51 @@
+
+
+
+
+
+
+ android:layout_height="match_parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/captcha_close" />
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 72910b85c..60d85606d 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -14,6 +14,7 @@
Log viewer
Debug
Notification debug
+ Clear webview cookies
Contributors
Licenses
Messages
@@ -833,6 +834,11 @@
Skip for now
+
+ Verification is in progress. Wait…
+ Verified successfully
+
+
No internet connection
An error occurred. Check your device clock
@@ -842,6 +848,7 @@
Maintenance underway UONET + register. Try again later
Unknown UONET + register error. Try again later
Unknown application error. Please try again later
+ Captcha verification required
An unexpected error occurred
Feature disabled by your school
Feature not available. Login in a mode other than Mobile API
From 9ececeb4e92b3adb2ac801097159d1bedd1a2de9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Miko=C5=82aj=20Pich?=
Date: Sun, 14 Jan 2024 16:41:57 +0100
Subject: [PATCH 03/50] New Crowdin updates (#2394)
---
app/src/main/res/values-cs/strings.xml | 5 +++++
app/src/main/res/values-da-rDK/strings.xml | 5 +++++
app/src/main/res/values-de/strings.xml | 5 +++++
app/src/main/res/values-es-rES/strings.xml | 5 +++++
app/src/main/res/values-it-rIT/strings.xml | 5 +++++
app/src/main/res/values-pl/strings.xml | 5 +++++
app/src/main/res/values-ru/strings.xml | 5 +++++
app/src/main/res/values-sk/strings.xml | 5 +++++
app/src/main/res/values-uk/strings.xml | 5 +++++
9 files changed, 45 insertions(+)
diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml
index 8e60b7a65..b4f1f878a 100644
--- a/app/src/main/res/values-cs/strings.xml
+++ b/app/src/main/res/values-cs/strings.xml
@@ -13,6 +13,7 @@
Prohlížeč protokolů
Ladění
Ladění oznámení
+ Vymazat soubory cookie webview
Tvůrci
Licence
Zprávy
@@ -833,6 +834,9 @@
Autorizace
Pro provoz aplikace potřebujeme potvrdit vaši identitu. Zadejte PESEL žáka <b>%1$s</b> v níže uvedeném poli
Zatím přeskočit
+
+ Probíhá ověřování. Počkejte…
+ Úspěšně ověřeno
Žádné internetové připojení
Vyskytla se chyba. Zkontrolujte hodiny svého zařízení
@@ -842,6 +846,7 @@
Probíhá údržba deníku UONET+. Zkuste to později znovu
Neznámá chyba deniku UONET+. Prosím zkuste to znovu později
Neznámá chyba aplikace. Prosím zkuste to znovu později
+ Vyžadováno ověření Captcha
Vyskytla se neočekávaná chyba
Funkce je deaktivována přes vaší školou
Funkce není k dispozici. Přihlaste se v jiném režimu než Mobile API
diff --git a/app/src/main/res/values-da-rDK/strings.xml b/app/src/main/res/values-da-rDK/strings.xml
index 013066629..ac616418c 100644
--- a/app/src/main/res/values-da-rDK/strings.xml
+++ b/app/src/main/res/values-da-rDK/strings.xml
@@ -13,6 +13,7 @@
Log viewer
Debug
Notification debug
+ Clear webview cookies
Contributors
Licenses
Messages
@@ -743,6 +744,9 @@
Authorization
To operate the application, we need to confirm your identity. Please enter the student\'s PESEL <b>%1$s</b> in the field below
Skip for now
+
+ Verification is in progress. Wait…
+ Verified successfully
No internet connection
An error occurred. Check your device clock
@@ -752,6 +756,7 @@
Maintenance underway UONET + register. Try again later
Unknown UONET + register error. Try again later
Unknown application error. Please try again later
+ Captcha verification required
An unexpected error occurred
Feature disabled by your school
Feature not available. Login in a mode other than Mobile API
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 09173d38b..ec6aa655f 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -13,6 +13,7 @@
Log Viewer
Debuggen
Benachrichtigungen debuggen
+ Clear webview cookies
Mitarbeiter
Lizenzen
Nachrichten
@@ -743,6 +744,9 @@
Authorization
To operate the application, we need to confirm your identity. Please enter the student\'s PESEL <b>%1$s</b> in the field below
Skip for now
+
+ Verification is in progress. Wait…
+ Verified successfully
Keine Internetverbindung
Es ist ein Fehler aufgetreten. Überprüfen Sie Ihre Geräteuhr
@@ -752,6 +756,7 @@
Wartung im Gange UONET + Klassenbuch. Versuchen Sie es später noch einmal
Unbekannter UONET + Registerfehler. Versuchen Sie es später erneut
Unbekannter Anwendungsfehler. Bitte versuchen Sie es später noch einmal
+ Captcha verification required
Ein unerwarteter Fehler ist aufgetreten
Funktion, die von Ihrer Schule deaktiviert wurde
Feature in diesem Modus nicht verfügbar
diff --git a/app/src/main/res/values-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml
index 013066629..ac616418c 100644
--- a/app/src/main/res/values-es-rES/strings.xml
+++ b/app/src/main/res/values-es-rES/strings.xml
@@ -13,6 +13,7 @@
Log viewer
Debug
Notification debug
+ Clear webview cookies
Contributors
Licenses
Messages
@@ -743,6 +744,9 @@
Authorization
To operate the application, we need to confirm your identity. Please enter the student\'s PESEL <b>%1$s</b> in the field below
Skip for now
+
+ Verification is in progress. Wait…
+ Verified successfully
No internet connection
An error occurred. Check your device clock
@@ -752,6 +756,7 @@
Maintenance underway UONET + register. Try again later
Unknown UONET + register error. Try again later
Unknown application error. Please try again later
+ Captcha verification required
An unexpected error occurred
Feature disabled by your school
Feature not available. Login in a mode other than Mobile API
diff --git a/app/src/main/res/values-it-rIT/strings.xml b/app/src/main/res/values-it-rIT/strings.xml
index 013066629..ac616418c 100644
--- a/app/src/main/res/values-it-rIT/strings.xml
+++ b/app/src/main/res/values-it-rIT/strings.xml
@@ -13,6 +13,7 @@
Log viewer
Debug
Notification debug
+ Clear webview cookies
Contributors
Licenses
Messages
@@ -743,6 +744,9 @@
Authorization
To operate the application, we need to confirm your identity. Please enter the student\'s PESEL <b>%1$s</b> in the field below
Skip for now
+
+ Verification is in progress. Wait…
+ Verified successfully
No internet connection
An error occurred. Check your device clock
@@ -752,6 +756,7 @@
Maintenance underway UONET + register. Try again later
Unknown UONET + register error. Try again later
Unknown application error. Please try again later
+ Captcha verification required
An unexpected error occurred
Feature disabled by your school
Feature not available. Login in a mode other than Mobile API
diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml
index fb9d170a3..1b4fbe664 100644
--- a/app/src/main/res/values-pl/strings.xml
+++ b/app/src/main/res/values-pl/strings.xml
@@ -13,6 +13,7 @@
Przeglądarka logów
Debugowanie
Debugowanie powiadomień
+ Wyczyść ciasteczka webview
Twórcy
Licencje
Wiadomości
@@ -833,6 +834,9 @@
Autoryzacja
Rodzicu, musimy mieć pewność, że Twój adres e-mail został powiązany z prawidłowym kontem ucznia. W celu autoryzacji konta podaj numer PESEL ucznia <b>%1$s</b> w polu poniżej
Na razie pomiń
+
+ Trwa weryfikacja. Czekaj…
+ Pomyślnie zweryfikowano
Brak połączenia z internetem
Wystąpił błąd. Sprawdź poprawność daty w urządzeniu
@@ -842,6 +846,7 @@
Trwa przerwa techniczna dziennika UONET+. Spróbuj ponownie później
Nieznany błąd dziennika UONET+. Spróbuj ponownie później
Nieznany błąd aplikacji. Spróbuj ponownie później
+ Wymagana weryfikacja captcha
Wystąpił nieoczekiwany błąd
Funkcja wyłączona przez szkołę
Funkcja niedostępna. Zaloguj się w trybie innym niż Mobilne API
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index c604cd8b3..feb08a03b 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -13,6 +13,7 @@
Просмотр журнала
Отладка
Отладка уведомлений
+ Clear webview cookies
Разработчики
Лицензии
Сообщения
@@ -833,6 +834,9 @@
Авторизация
Для работы приложения нам необходимо подтвердить вашу личность. Введите PESEL учащегося <b>%1$s</b> в поле ниже
Пропустить сейчас
+
+ Verification is in progress. Wait…
+ Verified successfully
Интернет-соединение отсутствует
Произошла ошибка. Проверьте время на вашем устройстве
@@ -842,6 +846,7 @@
UONET+ проводит техническое обслуживание, повторите попытку позже
Неизвестная ошибка дневника UONET+, повторите попытку позже
Неизвестная ошибка приложения, повторите попытку позже
+ Captcha verification required
Произошла непредвиденная ошибка
Функция отключена вашей школой
Функция недоступна в режиме Mobile API. Воспользуйтесь другим режимом
diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml
index e02b1542a..aaf04bc85 100644
--- a/app/src/main/res/values-sk/strings.xml
+++ b/app/src/main/res/values-sk/strings.xml
@@ -13,6 +13,7 @@
Prehliadač protokolov
Ladenie
Ladenie oznámení
+ Vymazať súbory cookie webview
Tvorcovia
Licencie
Správy
@@ -833,6 +834,9 @@
Autorizácia
Na prevádzku aplikácie potrebujeme potvrdiť vašu identitu. Zadajte PESEL žiaka <b>%1$s</b> v nižšie uvedenom poli
Zatiaľ preskočiť
+
+ Overovanie prebieha. Počkajte…
+ Úspešne overené
Žiadne internetové pripojenie
Vyskytla sa chyba. Skontrolujte hodiny svojho zariadenia
@@ -842,6 +846,7 @@
Prebieha údržba denníka UONET+. Skúste to neskôr znova
Neznáma chyba dennika UONET+. Prosím skúste to znova neskôr
Neznáma chyba aplikácie. Prosím skúste to znova neskôr
+ Vyžaduje sa overenie Captcha
Vyskytla sa neočakávaná chyba
Funkcia je deaktivovaná cez vašou školou
Funkcia nie je k dispozícii. Prihláste sa v inom režime než Mobile API
diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml
index 32617f429..fffae003b 100644
--- a/app/src/main/res/values-uk/strings.xml
+++ b/app/src/main/res/values-uk/strings.xml
@@ -13,6 +13,7 @@
Переглядач логів
Відладка
Відладка сповіщень
+ Очистити кукі веб - перегляду
Розробники
Ліцензії
Листи
@@ -833,6 +834,9 @@
Авторизувати
Для роботи програми нам потрібно підтвердити вашу особу. Будь ласка, введіть число PESEL <b>%1$s</b> студента в поле нижче
Поки що пропустити
+
+ Верифікація в процесі. Чекайте…
+ Верифікація завершена
Немає з\'єднання з інтернетом
Сталася помилка. Перевірте годинник пристрою
@@ -842,6 +846,7 @@
UONET+ проводить технічне осблуговування, спробуйте пізніше
Невідома помилка щоденника UONET+, спробуйте пізніше
Невідома помилка програми, спробуйте пізніше
+ Необхідна перевірка Captcha
Відбулася несподівана помилка
Функція вимкнена вашою школою
Функція недоступна в режимі Mobile API. Увійдіть в інший режим
From 976eb5a7720fdc2e74356015016f19783ca53e92 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Miko=C5=82aj=20Pich?=
Date: Sun, 14 Jan 2024 16:45:30 +0100
Subject: [PATCH 04/50] Fix cancelling dashboard jobs (#2395)
---
.../java/io/github/wulkanowy/data/Resource.kt | 16 ++++++++--
.../ui/modules/captcha/CaptchaDialog.kt | 2 +-
.../modules/dashboard/DashboardPresenter.kt | 30 ++++++++++++++++---
3 files changed, 40 insertions(+), 8 deletions(-)
diff --git a/app/src/main/java/io/github/wulkanowy/data/Resource.kt b/app/src/main/java/io/github/wulkanowy/data/Resource.kt
index 2c5bf0ea9..d7c2aeed9 100644
--- a/app/src/main/java/io/github/wulkanowy/data/Resource.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/Resource.kt
@@ -1,6 +1,16 @@
package io.github.wulkanowy.data
-import kotlinx.coroutines.flow.*
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.catch
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.emitAll
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.takeWhile
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import timber.log.Timber
@@ -131,7 +141,7 @@ inline fun networkBoundResource(
query().map { Resource.Success(filterResult(it)) }
} catch (throwable: Throwable) {
onFetchFailed(throwable)
- query().map { Resource.Error(throwable) }
+ flowOf(Resource.Error(throwable))
}
} else {
query().map { Resource.Success(filterResult(it)) }
@@ -165,7 +175,7 @@ inline fun networkBoundResource(
query().map { Resource.Success(mapResult(it)) }
} catch (throwable: Throwable) {
onFetchFailed(throwable)
- query().map { Resource.Error(throwable) }
+ flowOf(Resource.Error(throwable))
}
} else {
query().map { Resource.Success(mapResult(it)) }
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/captcha/CaptchaDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/captcha/CaptchaDialog.kt
index 098d08ed9..ed8293a9f 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/captcha/CaptchaDialog.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/captcha/CaptchaDialog.kt
@@ -76,7 +76,7 @@ class CaptchaDialog : BaseDialogFragment() {
runCatching { parentFragmentManager.setFragmentResult(CAPTCHA_SUCCESS, bundleOf()) }
.onFailure { Timber.e(it) }
showMessage(getString(R.string.captcha_verified_message))
- dismiss()
+ dismissAllowingStateLoss()
}
override fun onDestroy() {
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardPresenter.kt
index d7add2c05..74b427e78 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardPresenter.kt
@@ -324,7 +324,7 @@ class DashboardPresenter @Inject constructor(
) { luckyNumberResource, messageResource, attendanceResource ->
val resList = listOf(luckyNumberResource, messageResource, attendanceResource)
- DashboardItem.HorizontalGroup(
+ resList to DashboardItem.HorizontalGroup(
isLoading = resList.any { it is Resource.Loading },
error = resList.map { it.errorOrNull }.let { errors ->
if (errors.all { it != null }) {
@@ -349,9 +349,9 @@ class DashboardPresenter @Inject constructor(
)
})
}
- .filterNot { it.isLoading && forceRefresh }
+ .filterNot { (_, it) -> it.isLoading && forceRefresh }
.distinctUntilChanged()
- .onEach {
+ .onEach { (_, it) ->
updateData(it, forceRefresh)
if (it.isLoading) {
@@ -369,7 +369,7 @@ class DashboardPresenter @Inject constructor(
)
errorHandler.dispatch(it)
}
- .launch("horizontal_group ${if (forceRefresh) "-forceRefresh" else ""}")
+ .launchWithUniqueRefreshJob("horizontal_group", forceRefresh)
}
private fun loadGrades(student: Student, forceRefresh: Boolean) {
@@ -862,6 +862,28 @@ class DashboardPresenter @Inject constructor(
onEach {
if (it is Resource.Success) {
cancelJobs(jobName)
+ } else if (it is Resource.Error) {
+ cancelJobs(jobName)
+ }
+ }.launch(jobName)
+ } else {
+ launch(jobName)
+ }
+ }
+
+ @JvmName("launchWithUniqueRefreshJobHorizontalGroup")
+ private fun Flow>, *>>.launchWithUniqueRefreshJob(
+ name: String,
+ forceRefresh: Boolean
+ ) {
+ val jobName = if (forceRefresh) "$name-forceRefresh" else name
+
+ if (forceRefresh) {
+ onEach { (resources, _) ->
+ if (resources.all { it is Resource.Success<*> }) {
+ cancelJobs(jobName)
+ } else if (resources.any { it is Resource.Error<*> }) {
+ cancelJobs(jobName)
}
}.launch(jobName)
} else {
From 497acf9d685102064b670a07038ecae6b2f47098 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Miko=C5=82aj=20Pich?=
Date: Sun, 14 Jan 2024 17:32:41 +0100
Subject: [PATCH 05/50] Version 2.3.4
---
app/build.gradle | 8 ++++----
app/src/main/play/release-notes/pl-PL/default.txt | 5 +++--
2 files changed, 7 insertions(+), 6 deletions(-)
diff --git a/app/build.gradle b/app/build.gradle
index 7069672ad..8d10ce926 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -27,8 +27,8 @@ android {
testApplicationId "io.github.tests.wulkanowy"
minSdkVersion 21
targetSdkVersion 34
- versionCode 143
- versionName "2.3.3"
+ versionCode 144
+ versionName "2.3.4"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
resValue "string", "app_name", "Wulkanowy"
@@ -163,7 +163,7 @@ play {
track = 'production'
releaseStatus = ReleaseStatus.IN_PROGRESS
userFraction = 0.99d
- updatePriority = 3
+ updatePriority = 1
enabled.set(false)
}
@@ -193,7 +193,7 @@ ext {
}
dependencies {
- implementation 'io.github.wulkanowy:sdk:2.3.6-SNAPSHOT'
+ implementation 'io.github.wulkanowy:sdk:2.3.6'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4'
diff --git a/app/src/main/play/release-notes/pl-PL/default.txt b/app/src/main/play/release-notes/pl-PL/default.txt
index 0a2eb68f4..c2c30883e 100644
--- a/app/src/main/play/release-notes/pl-PL/default.txt
+++ b/app/src/main/play/release-notes/pl-PL/default.txt
@@ -1,5 +1,6 @@
-Wersja 2.3.3
+Wersja 2.3.4
-— poprawiliśmy kolejne usterki przy odświeżaniu danych (teraz to powinno działać już dużo lepiej)
+— dodaliśmy obsługę captchy, co umożliwi używanie apki np. na odmianie ResMan Rzeszów
+— naprawiliśmy wyświetlanie frekwencji w szkołach używających eduOne (piszcie, jeśli nadal nie działa)
Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases
From e58c15596109075691dfcb7a7cee974ff037d1ab Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rafa=C5=82=20Borcz?=
Date: Sun, 14 Jan 2024 18:02:15 +0100
Subject: [PATCH 06/50] New Crowdin updates (#2396)
---
app/src/main/res/values-da-rDK/strings.xml | 764 ---------------------
app/src/main/res/values-de/strings.xml | 2 +-
app/src/main/res/values-es-rES/strings.xml | 764 ---------------------
app/src/main/res/values-it-rIT/strings.xml | 764 ---------------------
app/src/main/res/values-ru/strings.xml | 2 +-
5 files changed, 2 insertions(+), 2294 deletions(-)
delete mode 100644 app/src/main/res/values-da-rDK/strings.xml
delete mode 100644 app/src/main/res/values-es-rES/strings.xml
delete mode 100644 app/src/main/res/values-it-rIT/strings.xml
diff --git a/app/src/main/res/values-da-rDK/strings.xml b/app/src/main/res/values-da-rDK/strings.xml
deleted file mode 100644
index ac616418c..000000000
--- a/app/src/main/res/values-da-rDK/strings.xml
+++ /dev/null
@@ -1,764 +0,0 @@
-
-
-
- Login
- Wulkanowy
- Grades
- Attendance
- Exams
- Timetable
- Settings
- More
- About
- Log viewer
- Debug
- Notification debug
- Clear webview cookies
- Contributors
- Licenses
- Messages
- New message
- New homework
- Notes and achievements
- Homework
- Accounts manager
- Select account
- Account details
- Student info
- Dashboard
- Notifications center
- Menu configuartion
-
- Semester %1$d, %2$d/%3$d
-
- Sign in with the student or parent account
- Enter the symbol from the register page for account: <b>%1$s</b>
- Username
- Email
- Login, PESEL or e-mail
- Password
- UONET+ register variant
- Custom domain suffix
- Mobile API
- Scraper
- Hybrid
- Token
- PIN
- Symbol
- E.g. \"lodz\" or \"powiatjaroslawski\"
- Sign in
- Password too short
- Login details are incorrect
- %1$s. Make sure the correct UONET+ register variation is selected below
- Invalid PIN
- Invalid token
- Token expired
- Invalid email
- Use the assigned login instead of email
- Use the assigned login or email in @%1$s
- Invalid symbol. If you cannot find it, please contact the school
- Don\'t make this up! If you cannot find it, please contact the school
- Student not found. Validate the symbol and the chosen variation of the UONET+ register
- Selected student is already logged in
- The symbol can be found on the register page in Uczeń → Dostęp Mobilny → Wygeneruj kod dostępu.\n\nMake sure that you have set the appropriate register variant in the UONET+ register variant field on the first login screen
- Select students to log in to the application
- Other options
- In this mode, a lucky number does not work, a class grade stats, summary of attendance, excuse for absence, completed lessons, school information and preview of the list of registered devices
- This mode displays the same data as it appears on the register website
- The combination of the best features of the other two modes. It works faster than scraper and provides features not available in the Mobile API mode. It is in the experimental phase
- Privacy policy
- Trouble signing in? Contact us!
- Email
- Discord
- Send email
- Make sure you select the correct UONET+ register variation!
- Reset password
- Recover your account
- Recover
- Student is already signed in
- Standard
- Other search locations
- No active students found
- Enter a different symbol
- Get help
- Full school name with the town (required)
- Np. ZSTiO Jarosław lub SP nr 99 w Łodzi
- Enter correct name of the school
- Additional information in Polish (optional)
- Np. \"Ostatnio zmieniłem szkołę i…\" albo \"Jestem rodzicem i nie widzę drugiego dziecka…\"
- Submit
-
- Enable notifications
- Enable notifications so you don\'t miss message from teacher or new grade
- Skip
- Enable
-
- Account manager
- Log in
- Session expired
- Session expired, log in again
- Your account password has been changed. You need to log in to Wulkanowy again
- Password changed
- Application support
- Do you like this app? Support its development by enabling non-invasive ads that you can disable at any time
- Enable ads
-
- Grade
- Semester %d
- Change semester
- No grades
- Weight
- Weight: %s
- Comment
- Number of new ratings: %1$d
- Average: %1$.2f
- Points: %s
- No average
- Total points
- Final grade
- Predicted grade
- Calculated average
- How does Calculated Average work?
- The Calculated Average is the arithmetic average calculated from the subjects averages. It allows you to know the approximate final average. It is calculated in a way selected by the user in the application settings. It is recommended that you choose the appropriate option. This is because the calculation of school averages differs. Additionally, if your school reports the average of the subjects on the Vulcan page, the application downloads them and does not calculate these averages. This can be changed by forcing the calculation of the average in the application settings.\n\nAverage of grades only from selected semester:\n1. Calculating the weighted average for each subject in a given semester\n2.Adding calculated averages\n3. Calculation of the arithmetic average of the summed averages\n\nAverage of averages from both semesters:\n1.Calculating the weighted average for each subject in semester 1 and 2\n2. Calculating the arithmetic average of the calculated averages for semesters 1 and 2 for each subject.\n3. Adding calculated averages\n4. Calculation of the arithmetic average of the summed averages\n\nAverage of grades from the whole year:\n1. Calculating weighted average over the year for each subject. The final average in the 1st semester is irrelevant.\n2. Adding calculated averages\n3. Calculating the arithmetic average of summed averages
- How does the Final Average work?
- The Final Average is the arithmetic average calculated from all currently available final grades in the given semester.\n\nThe calculation scheme consists of the following steps:\n1. Summing up the final grades given by teachers\n2. Divide by the number of subjects that have already been graded
- Final average
- from %1$d of %2$d subjects
- Summary
- Class
- Mark as read
- Partial
- Semester
- Points
- Legend
- Class average: %1$s
- Your average: %1$s
- Your grade: %1$s
- Class
- Student
-
- - %d grade
- - %d grades
-
-
- - New grade
- - New grades
-
-
- - New predicted grade
- - New predicted grades
-
-
- - New final grade
- - New final grades
-
-
- - You received %1$d grade
- - You received %1$d grades
-
-
- - You received %1$d predicted grade
- - You received %1$d predicted grades
-
-
- - You received %1$d final grade
- - You received %1$d final grades
-
-
- Lesson
- Room
- Group
- Hours
- Changes
- No lessons this day
- %s min
- %s sec
- %1$s left
- in %1$s
- Finished
- Now: %s
- Next: %s
- Later: %s
- %1$s lesson %2$d - %3$s
- Change of room from %1$s to %2$s
- Change of teacher from %1$s to %2$s
- Change of subject from %1$s to %2$s
-
- - No lesson
- - No lessons
-
-
- - Timetable change
- - Timetable changes
-
-
- - %1$s - %2$d change in timetable
- - %1$s - %2$d changes in timetable
-
-
- - %1$d change in timetable
- - %1$d changes in timetable
-
-
- - %d change
- - %d changes
-
-
- Completed lessons
- Show completed lessons
- No info about completed lessons
- Topic
- Absence
- Resources
-
- Additional lessons
- Show additional lessons
- No info about additional lessons
- New lesson
- New additional lesson
- Additional lesson added successfully
- Additional lesson deleted successfully
- Repeat weekly
- Delete additional lesson
- Just this lesson
- All in the series
- Start time
- End time
- End time must be greater than start time
-
- Attendance summary
- Absent for school reasons
- Excused absence
- Unexcused absence
- Exemption
- Excused lateness
- Unexcused lateness
- Present
- Deleted
- Unknown
- Number of lesson
- No entries
- Absence reason (optional)
- Send
- Absence excuse request sent successfully!
- You must select at least one absence!
- Excuse
-
- - New attendance
- - New attendance
-
-
- - %1$d new attendance
- - %1$d attendance
-
-
- - %d attendance
- - %d attendance
-
-
- Total
-
- No exams this week
- Type
- Entry date
-
- - New exam
- - New exams
-
-
- - %d new exam
- - %d new exams
-
-
- - %d exam
- - %d exams
-
-
- Inbox
- Sent
- Trash
- (no subject)
- No messages
- From:
- To:
- Date: %1$s
- Reply
- Forward
- Select all
- Unselect all
- Move to trash
- Delete permanently
- Message deleted successfully
- student
- parent
- guardian
- employee
- Share
- Print
- Subject
- Content
- Message sent successfully
- Message does not exist
- You need to choose at least 1 recipient
- The message content must be at least 3 characters
- All mailboxes
- Only unread
- Only with attachments
- Read: %s
- Read by: %1$d of %2$d people
-
- - %1$d message
- - %1$d messages
-
-
- - New message
- - New messages
-
- Do you want to restore draft message?
- Do you want to restore draft message with recipients: %s?
-
- - You received %1$d message
- - You received %1$d messages
-
-
- - %1$d selected
- - %1$d selected
-
- Messages deleted
- Choose mailbox
- Incognito mode is on
- Thanks to incognito mode sender is not notified when you read the message
-
- No info about notes
- Points
-
- - %d note
- - %d notes
-
-
- - New note
- - New notes
-
-
- - You received %1$d note
- - You received %1$d notes
-
-
-
- - %d praise
- - %d praises
-
-
- - New praise
- - New praises
-
-
- - You received %1$d praise
- - You received %1$d praises
-
-
-
- - %d neutral note
- - %d neutral notes
-
-
- - New neutral note
- - New neutral notes
-
-
- - You received %1$d neutral note
- - You received %1$d neutral notes
-
-
- No info about homework
- Mark as done
- Mark as undone
- Add homework
- Homework added successfully
- Homework deleted successfully
- Attachments
-
- - New homework
- - New homework
-
-
- - You received %d new homework
- - You received %d new homework
-
-
- - %d homework
- - %d homework
-
-
- Lucky number
- Today\'s lucky number is
- No info about the lucky number
- Lucky number for today
- Today\'s lucky number is: %s
- Show history
-
- Lucky number history
- No info about lucky numbers
-
- Mobile devices
- No devices
- Deregister
- Device removed
- QR code
- Token
- Symbol
- PIN
-
- School and teachers
-
- School
- No info about school
- School name
- School address
- Telephone
- Name of headmaster
- Name of pedagogue
- Show on map
- Call
-
- Teachers
- No info about teachers
- No subject
-
- Conferences
- No info about conferences
-
- - %d conference
- - %d conferences
-
-
- - New conference
- - New conferences
-
-
- - You have %1$d new conference
- - You have %1$d new conferences
-
- Present at conference
- Agenda
- Place
- Topic
-
- School announcements
- No school announcements
-
- - %d school announcement
- - %d school announcements
-
-
- - New school announcement
- - New school announcements
-
-
- - You have %1$d new school announcement
- - You have %1$d new school announcements
-
-
- Add account
- Logout
- Do you want to log out this student?
- Student logout
- Student account
- Parent account
- Edit data
- Accounts manager
- Select student
- Family
- Contact
- Residence details
- Personal information
-
- App version
- Contributors
- List of Wulkanowy developers
- Report a bug
- Send a bug report via e-mail
- FAQ
- Read Frequently Asked Questions
- Discord server
- Join the Wulkanowy community
- Facebook fanpage
- Twitter page
- Follow us on twitter
- Like our facebook fanpage
- Privacy policy
- Rules for collecting personal data
- System settings
- Open system settings
- Homepage
- Visit the website and help develop the application
- Licenses
- Licenses of libraries used in the application
-
- License
-
- Avatar
- See more on GitHub
-
- No info about student or student family
- Name
- Second name
- Gender
- Polish citizenship
- Family name
- Mother\'s and father\'s names
- Phone
- Cellphone
- E-mail
- Address of residence
- Address of registration
- Correspondence address
- Surname and first name
- Degree of kinship
- Address
- Phones
- Male
- Female
- Last name
- Guardian
-
- Nick
- Add nick
- Choose avatar color
-
- Share logs
- Refresh
-
- Lessons
- (Tomorrow)
- (Today and tomorrow)
- In a moment:
- Soon:
- First:
- Now:
- End of lessons
- Next:
- Later:
-
- - %1$d more lesson
- - %1$d more lessons
-
- until %1$s
- No upcoming lessons
- An error occurred while loading the lessons
- Homework
- No homework to do
- An error occurred while loading the homework
-
- - %1$d more homework
- - %1$d more homework
-
- due %1$s
- Last grades
- No new grades
- An error occurred while loading the grades
- School announcements
- No current announcements
- An error occurred while loading the announcements
-
- - %1$d more announcement
- - %1$d more announcements
-
- Exams
- No upcoming exams
- An error occurred while loading the exams
-
- - %1$d more exam
- - %1$d more exams
-
- Conferences
- No upcoming conferences
- An error occurred while loading the conferences
-
- - %1$d more conference
- - %1$d more conferences
-
- An error occurred while loading data
- None
-
- Check for updates
- Before reporting a bug, check first if an update with the bug fix is available
-
- Content
- Retry
- Description
- No description
- Teacher
- Date
- Entry date
- Color
- Details
- Category
- Close
- No data
- Subject
- Prev
- Next
- Search
- Search…
- Yes
- No
- Save
- Title
- Add
- Copied
- Undo
- Change
- Add to calendar
- Cancel
-
- No lessons
- Synchronized on %1$s at %2$s
- Choose theme
- Light
- Dark
- System Theme
-
- App
- Default view
- Calculated average options
- Force average calculation by app
- Show presence
- Theme
- Grades expanding
- Show groups next to subjects
- Show empty tiles where there\'s no lesson
- Show chart list in class grades
- Show subjects without grades
- Grades color scheme
- Subjects sorting
- Language
- Menu configuration
- Set the order of functions in the menu
- Notifications
- Other
- Show notifications
- Show upcoming lesson notifications
- Make upcoming lesson notification persistent
- Turn off when notification is not showing in your watch/band
- Open system notification settings
- Fix synchronization & notifications issues
- Your device may have data synchronization issues and with notifications.\n\nTo fix them, you need to add Wulkanowy to the autostart and turn off battery optimization/saving in the phone settings.
- Show debug notifications
- Synchronization is disabled
- Official app notifications
- Capture official app notifications
- Remove official app notifications after capture
- Capture notifications
- With this feature you can gain a substitute of push notifications like in the official app. All you need to do is allow Wulkanowy to receive all notifications in your system settings.\n\nHow it works?\nWhen you get a notification in Dziennik VULCAN, Wulkanowy will be notified (that\'s what these extra permissions are for) and will trigger a sync so that can send its own notification.\n\nFOR ADVANCED USERS ONLY
- Upcoming lesson notifications
- You must allow the Wulkanowy app to set alarms and reminders in your system settings to use this feature.
- Go to settings
- Synchronization
- Automatic update
- Suspended on holidays
- Updates interval
- Wi-Fi only
- Sync now
- Synced!
- Sync failed
- Sync in progress
- Last full sync: %s
- Value of the plus
- Value of the minus
- Reply with message history
- Show arithmetic average when no weights provided
- Incognito mode
- Do not inform about reading the message
- Support
- Privacy Policy
- Agreements
- Show consent to data processing
- Show ads in app
- Watch single ad to support project
- Consent to data processing
- To view an advertisement you must agree to the data processing terms of our Privacy Policy
- Agree
- Privacy policy
- Ad is loading
- Thank you for your support, come back later for more ads
- Advanced
- Appearance & Behavior
- Notifications
- Synchronization
- Advertisements
- Grades
- Dashboard
- Tiles visibility
- Attendance
- Timetable
- Grades
- Calculated average
- Messages
- Appearance & Behavior
- Languages, themes, subjects sorting
- App notifications, fix problems
- Notifications
- Synchronization
- Automatic update, synchronization interval
- Plus and minus values, average calculation
- Advanced
- App version, contributors, social portals
- Displaying advertisements, project support
-
- New grades
- New homework
- New conferences
- New exams
- Lucky number
- New messages
- New notes
- New school announcements
- Push notifications
- Upcoming lessons
- Debug
- Timetable change
- New attendance
-
- Black
- Red
- Blue
- Green
- Purple
- No color
-
- Download of updates has started…
- An update has just been downloaded.
- Restart
- Update failed! Wulkanowy may not function properly. Consider updating
-
- Application restart
- The application must restart for the changes to be saved
- Restart
-
- Authorization has been rejected. The data provided does not match the records in the secretary\'s office.
- Invalid PESEL
- PESEL
- Authorize
- Authorization completed successfully
- Authorization
- To operate the application, we need to confirm your identity. Please enter the student\'s PESEL <b>%1$s</b> in the field below
- Skip for now
-
- Verification is in progress. Wait…
- Verified successfully
-
- No internet connection
- An error occurred. Check your device clock
- Connection to register failed. Servers can be overloaded. Please try again later
- Loading data failed. Please try again later
- Register password change required
- Maintenance underway UONET + register. Try again later
- Unknown UONET + register error. Try again later
- Unknown application error. Please try again later
- Captcha verification required
- An unexpected error occurred
- Feature disabled by your school
- Feature not available. Login in a mode other than Mobile API
- This field is required
-
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index ec6aa655f..7e0ce8689 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -10,7 +10,7 @@
Einstellungen
Mehr
Über die Applikation
- Log Viewer
+ Log viewer
Debuggen
Benachrichtigungen debuggen
Clear webview cookies
diff --git a/app/src/main/res/values-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml
deleted file mode 100644
index ac616418c..000000000
--- a/app/src/main/res/values-es-rES/strings.xml
+++ /dev/null
@@ -1,764 +0,0 @@
-
-
-
- Login
- Wulkanowy
- Grades
- Attendance
- Exams
- Timetable
- Settings
- More
- About
- Log viewer
- Debug
- Notification debug
- Clear webview cookies
- Contributors
- Licenses
- Messages
- New message
- New homework
- Notes and achievements
- Homework
- Accounts manager
- Select account
- Account details
- Student info
- Dashboard
- Notifications center
- Menu configuartion
-
- Semester %1$d, %2$d/%3$d
-
- Sign in with the student or parent account
- Enter the symbol from the register page for account: <b>%1$s</b>
- Username
- Email
- Login, PESEL or e-mail
- Password
- UONET+ register variant
- Custom domain suffix
- Mobile API
- Scraper
- Hybrid
- Token
- PIN
- Symbol
- E.g. \"lodz\" or \"powiatjaroslawski\"
- Sign in
- Password too short
- Login details are incorrect
- %1$s. Make sure the correct UONET+ register variation is selected below
- Invalid PIN
- Invalid token
- Token expired
- Invalid email
- Use the assigned login instead of email
- Use the assigned login or email in @%1$s
- Invalid symbol. If you cannot find it, please contact the school
- Don\'t make this up! If you cannot find it, please contact the school
- Student not found. Validate the symbol and the chosen variation of the UONET+ register
- Selected student is already logged in
- The symbol can be found on the register page in Uczeń → Dostęp Mobilny → Wygeneruj kod dostępu.\n\nMake sure that you have set the appropriate register variant in the UONET+ register variant field on the first login screen
- Select students to log in to the application
- Other options
- In this mode, a lucky number does not work, a class grade stats, summary of attendance, excuse for absence, completed lessons, school information and preview of the list of registered devices
- This mode displays the same data as it appears on the register website
- The combination of the best features of the other two modes. It works faster than scraper and provides features not available in the Mobile API mode. It is in the experimental phase
- Privacy policy
- Trouble signing in? Contact us!
- Email
- Discord
- Send email
- Make sure you select the correct UONET+ register variation!
- Reset password
- Recover your account
- Recover
- Student is already signed in
- Standard
- Other search locations
- No active students found
- Enter a different symbol
- Get help
- Full school name with the town (required)
- Np. ZSTiO Jarosław lub SP nr 99 w Łodzi
- Enter correct name of the school
- Additional information in Polish (optional)
- Np. \"Ostatnio zmieniłem szkołę i…\" albo \"Jestem rodzicem i nie widzę drugiego dziecka…\"
- Submit
-
- Enable notifications
- Enable notifications so you don\'t miss message from teacher or new grade
- Skip
- Enable
-
- Account manager
- Log in
- Session expired
- Session expired, log in again
- Your account password has been changed. You need to log in to Wulkanowy again
- Password changed
- Application support
- Do you like this app? Support its development by enabling non-invasive ads that you can disable at any time
- Enable ads
-
- Grade
- Semester %d
- Change semester
- No grades
- Weight
- Weight: %s
- Comment
- Number of new ratings: %1$d
- Average: %1$.2f
- Points: %s
- No average
- Total points
- Final grade
- Predicted grade
- Calculated average
- How does Calculated Average work?
- The Calculated Average is the arithmetic average calculated from the subjects averages. It allows you to know the approximate final average. It is calculated in a way selected by the user in the application settings. It is recommended that you choose the appropriate option. This is because the calculation of school averages differs. Additionally, if your school reports the average of the subjects on the Vulcan page, the application downloads them and does not calculate these averages. This can be changed by forcing the calculation of the average in the application settings.\n\nAverage of grades only from selected semester:\n1. Calculating the weighted average for each subject in a given semester\n2.Adding calculated averages\n3. Calculation of the arithmetic average of the summed averages\n\nAverage of averages from both semesters:\n1.Calculating the weighted average for each subject in semester 1 and 2\n2. Calculating the arithmetic average of the calculated averages for semesters 1 and 2 for each subject.\n3. Adding calculated averages\n4. Calculation of the arithmetic average of the summed averages\n\nAverage of grades from the whole year:\n1. Calculating weighted average over the year for each subject. The final average in the 1st semester is irrelevant.\n2. Adding calculated averages\n3. Calculating the arithmetic average of summed averages
- How does the Final Average work?
- The Final Average is the arithmetic average calculated from all currently available final grades in the given semester.\n\nThe calculation scheme consists of the following steps:\n1. Summing up the final grades given by teachers\n2. Divide by the number of subjects that have already been graded
- Final average
- from %1$d of %2$d subjects
- Summary
- Class
- Mark as read
- Partial
- Semester
- Points
- Legend
- Class average: %1$s
- Your average: %1$s
- Your grade: %1$s
- Class
- Student
-
- - %d grade
- - %d grades
-
-
- - New grade
- - New grades
-
-
- - New predicted grade
- - New predicted grades
-
-
- - New final grade
- - New final grades
-
-
- - You received %1$d grade
- - You received %1$d grades
-
-
- - You received %1$d predicted grade
- - You received %1$d predicted grades
-
-
- - You received %1$d final grade
- - You received %1$d final grades
-
-
- Lesson
- Room
- Group
- Hours
- Changes
- No lessons this day
- %s min
- %s sec
- %1$s left
- in %1$s
- Finished
- Now: %s
- Next: %s
- Later: %s
- %1$s lesson %2$d - %3$s
- Change of room from %1$s to %2$s
- Change of teacher from %1$s to %2$s
- Change of subject from %1$s to %2$s
-
- - No lesson
- - No lessons
-
-
- - Timetable change
- - Timetable changes
-
-
- - %1$s - %2$d change in timetable
- - %1$s - %2$d changes in timetable
-
-
- - %1$d change in timetable
- - %1$d changes in timetable
-
-
- - %d change
- - %d changes
-
-
- Completed lessons
- Show completed lessons
- No info about completed lessons
- Topic
- Absence
- Resources
-
- Additional lessons
- Show additional lessons
- No info about additional lessons
- New lesson
- New additional lesson
- Additional lesson added successfully
- Additional lesson deleted successfully
- Repeat weekly
- Delete additional lesson
- Just this lesson
- All in the series
- Start time
- End time
- End time must be greater than start time
-
- Attendance summary
- Absent for school reasons
- Excused absence
- Unexcused absence
- Exemption
- Excused lateness
- Unexcused lateness
- Present
- Deleted
- Unknown
- Number of lesson
- No entries
- Absence reason (optional)
- Send
- Absence excuse request sent successfully!
- You must select at least one absence!
- Excuse
-
- - New attendance
- - New attendance
-
-
- - %1$d new attendance
- - %1$d attendance
-
-
- - %d attendance
- - %d attendance
-
-
- Total
-
- No exams this week
- Type
- Entry date
-
- - New exam
- - New exams
-
-
- - %d new exam
- - %d new exams
-
-
- - %d exam
- - %d exams
-
-
- Inbox
- Sent
- Trash
- (no subject)
- No messages
- From:
- To:
- Date: %1$s
- Reply
- Forward
- Select all
- Unselect all
- Move to trash
- Delete permanently
- Message deleted successfully
- student
- parent
- guardian
- employee
- Share
- Print
- Subject
- Content
- Message sent successfully
- Message does not exist
- You need to choose at least 1 recipient
- The message content must be at least 3 characters
- All mailboxes
- Only unread
- Only with attachments
- Read: %s
- Read by: %1$d of %2$d people
-
- - %1$d message
- - %1$d messages
-
-
- - New message
- - New messages
-
- Do you want to restore draft message?
- Do you want to restore draft message with recipients: %s?
-
- - You received %1$d message
- - You received %1$d messages
-
-
- - %1$d selected
- - %1$d selected
-
- Messages deleted
- Choose mailbox
- Incognito mode is on
- Thanks to incognito mode sender is not notified when you read the message
-
- No info about notes
- Points
-
- - %d note
- - %d notes
-
-
- - New note
- - New notes
-
-
- - You received %1$d note
- - You received %1$d notes
-
-
-
- - %d praise
- - %d praises
-
-
- - New praise
- - New praises
-
-
- - You received %1$d praise
- - You received %1$d praises
-
-
-
- - %d neutral note
- - %d neutral notes
-
-
- - New neutral note
- - New neutral notes
-
-
- - You received %1$d neutral note
- - You received %1$d neutral notes
-
-
- No info about homework
- Mark as done
- Mark as undone
- Add homework
- Homework added successfully
- Homework deleted successfully
- Attachments
-
- - New homework
- - New homework
-
-
- - You received %d new homework
- - You received %d new homework
-
-
- - %d homework
- - %d homework
-
-
- Lucky number
- Today\'s lucky number is
- No info about the lucky number
- Lucky number for today
- Today\'s lucky number is: %s
- Show history
-
- Lucky number history
- No info about lucky numbers
-
- Mobile devices
- No devices
- Deregister
- Device removed
- QR code
- Token
- Symbol
- PIN
-
- School and teachers
-
- School
- No info about school
- School name
- School address
- Telephone
- Name of headmaster
- Name of pedagogue
- Show on map
- Call
-
- Teachers
- No info about teachers
- No subject
-
- Conferences
- No info about conferences
-
- - %d conference
- - %d conferences
-
-
- - New conference
- - New conferences
-
-
- - You have %1$d new conference
- - You have %1$d new conferences
-
- Present at conference
- Agenda
- Place
- Topic
-
- School announcements
- No school announcements
-
- - %d school announcement
- - %d school announcements
-
-
- - New school announcement
- - New school announcements
-
-
- - You have %1$d new school announcement
- - You have %1$d new school announcements
-
-
- Add account
- Logout
- Do you want to log out this student?
- Student logout
- Student account
- Parent account
- Edit data
- Accounts manager
- Select student
- Family
- Contact
- Residence details
- Personal information
-
- App version
- Contributors
- List of Wulkanowy developers
- Report a bug
- Send a bug report via e-mail
- FAQ
- Read Frequently Asked Questions
- Discord server
- Join the Wulkanowy community
- Facebook fanpage
- Twitter page
- Follow us on twitter
- Like our facebook fanpage
- Privacy policy
- Rules for collecting personal data
- System settings
- Open system settings
- Homepage
- Visit the website and help develop the application
- Licenses
- Licenses of libraries used in the application
-
- License
-
- Avatar
- See more on GitHub
-
- No info about student or student family
- Name
- Second name
- Gender
- Polish citizenship
- Family name
- Mother\'s and father\'s names
- Phone
- Cellphone
- E-mail
- Address of residence
- Address of registration
- Correspondence address
- Surname and first name
- Degree of kinship
- Address
- Phones
- Male
- Female
- Last name
- Guardian
-
- Nick
- Add nick
- Choose avatar color
-
- Share logs
- Refresh
-
- Lessons
- (Tomorrow)
- (Today and tomorrow)
- In a moment:
- Soon:
- First:
- Now:
- End of lessons
- Next:
- Later:
-
- - %1$d more lesson
- - %1$d more lessons
-
- until %1$s
- No upcoming lessons
- An error occurred while loading the lessons
- Homework
- No homework to do
- An error occurred while loading the homework
-
- - %1$d more homework
- - %1$d more homework
-
- due %1$s
- Last grades
- No new grades
- An error occurred while loading the grades
- School announcements
- No current announcements
- An error occurred while loading the announcements
-
- - %1$d more announcement
- - %1$d more announcements
-
- Exams
- No upcoming exams
- An error occurred while loading the exams
-
- - %1$d more exam
- - %1$d more exams
-
- Conferences
- No upcoming conferences
- An error occurred while loading the conferences
-
- - %1$d more conference
- - %1$d more conferences
-
- An error occurred while loading data
- None
-
- Check for updates
- Before reporting a bug, check first if an update with the bug fix is available
-
- Content
- Retry
- Description
- No description
- Teacher
- Date
- Entry date
- Color
- Details
- Category
- Close
- No data
- Subject
- Prev
- Next
- Search
- Search…
- Yes
- No
- Save
- Title
- Add
- Copied
- Undo
- Change
- Add to calendar
- Cancel
-
- No lessons
- Synchronized on %1$s at %2$s
- Choose theme
- Light
- Dark
- System Theme
-
- App
- Default view
- Calculated average options
- Force average calculation by app
- Show presence
- Theme
- Grades expanding
- Show groups next to subjects
- Show empty tiles where there\'s no lesson
- Show chart list in class grades
- Show subjects without grades
- Grades color scheme
- Subjects sorting
- Language
- Menu configuration
- Set the order of functions in the menu
- Notifications
- Other
- Show notifications
- Show upcoming lesson notifications
- Make upcoming lesson notification persistent
- Turn off when notification is not showing in your watch/band
- Open system notification settings
- Fix synchronization & notifications issues
- Your device may have data synchronization issues and with notifications.\n\nTo fix them, you need to add Wulkanowy to the autostart and turn off battery optimization/saving in the phone settings.
- Show debug notifications
- Synchronization is disabled
- Official app notifications
- Capture official app notifications
- Remove official app notifications after capture
- Capture notifications
- With this feature you can gain a substitute of push notifications like in the official app. All you need to do is allow Wulkanowy to receive all notifications in your system settings.\n\nHow it works?\nWhen you get a notification in Dziennik VULCAN, Wulkanowy will be notified (that\'s what these extra permissions are for) and will trigger a sync so that can send its own notification.\n\nFOR ADVANCED USERS ONLY
- Upcoming lesson notifications
- You must allow the Wulkanowy app to set alarms and reminders in your system settings to use this feature.
- Go to settings
- Synchronization
- Automatic update
- Suspended on holidays
- Updates interval
- Wi-Fi only
- Sync now
- Synced!
- Sync failed
- Sync in progress
- Last full sync: %s
- Value of the plus
- Value of the minus
- Reply with message history
- Show arithmetic average when no weights provided
- Incognito mode
- Do not inform about reading the message
- Support
- Privacy Policy
- Agreements
- Show consent to data processing
- Show ads in app
- Watch single ad to support project
- Consent to data processing
- To view an advertisement you must agree to the data processing terms of our Privacy Policy
- Agree
- Privacy policy
- Ad is loading
- Thank you for your support, come back later for more ads
- Advanced
- Appearance & Behavior
- Notifications
- Synchronization
- Advertisements
- Grades
- Dashboard
- Tiles visibility
- Attendance
- Timetable
- Grades
- Calculated average
- Messages
- Appearance & Behavior
- Languages, themes, subjects sorting
- App notifications, fix problems
- Notifications
- Synchronization
- Automatic update, synchronization interval
- Plus and minus values, average calculation
- Advanced
- App version, contributors, social portals
- Displaying advertisements, project support
-
- New grades
- New homework
- New conferences
- New exams
- Lucky number
- New messages
- New notes
- New school announcements
- Push notifications
- Upcoming lessons
- Debug
- Timetable change
- New attendance
-
- Black
- Red
- Blue
- Green
- Purple
- No color
-
- Download of updates has started…
- An update has just been downloaded.
- Restart
- Update failed! Wulkanowy may not function properly. Consider updating
-
- Application restart
- The application must restart for the changes to be saved
- Restart
-
- Authorization has been rejected. The data provided does not match the records in the secretary\'s office.
- Invalid PESEL
- PESEL
- Authorize
- Authorization completed successfully
- Authorization
- To operate the application, we need to confirm your identity. Please enter the student\'s PESEL <b>%1$s</b> in the field below
- Skip for now
-
- Verification is in progress. Wait…
- Verified successfully
-
- No internet connection
- An error occurred. Check your device clock
- Connection to register failed. Servers can be overloaded. Please try again later
- Loading data failed. Please try again later
- Register password change required
- Maintenance underway UONET + register. Try again later
- Unknown UONET + register error. Try again later
- Unknown application error. Please try again later
- Captcha verification required
- An unexpected error occurred
- Feature disabled by your school
- Feature not available. Login in a mode other than Mobile API
- This field is required
-
diff --git a/app/src/main/res/values-it-rIT/strings.xml b/app/src/main/res/values-it-rIT/strings.xml
deleted file mode 100644
index ac616418c..000000000
--- a/app/src/main/res/values-it-rIT/strings.xml
+++ /dev/null
@@ -1,764 +0,0 @@
-
-
-
- Login
- Wulkanowy
- Grades
- Attendance
- Exams
- Timetable
- Settings
- More
- About
- Log viewer
- Debug
- Notification debug
- Clear webview cookies
- Contributors
- Licenses
- Messages
- New message
- New homework
- Notes and achievements
- Homework
- Accounts manager
- Select account
- Account details
- Student info
- Dashboard
- Notifications center
- Menu configuartion
-
- Semester %1$d, %2$d/%3$d
-
- Sign in with the student or parent account
- Enter the symbol from the register page for account: <b>%1$s</b>
- Username
- Email
- Login, PESEL or e-mail
- Password
- UONET+ register variant
- Custom domain suffix
- Mobile API
- Scraper
- Hybrid
- Token
- PIN
- Symbol
- E.g. \"lodz\" or \"powiatjaroslawski\"
- Sign in
- Password too short
- Login details are incorrect
- %1$s. Make sure the correct UONET+ register variation is selected below
- Invalid PIN
- Invalid token
- Token expired
- Invalid email
- Use the assigned login instead of email
- Use the assigned login or email in @%1$s
- Invalid symbol. If you cannot find it, please contact the school
- Don\'t make this up! If you cannot find it, please contact the school
- Student not found. Validate the symbol and the chosen variation of the UONET+ register
- Selected student is already logged in
- The symbol can be found on the register page in Uczeń → Dostęp Mobilny → Wygeneruj kod dostępu.\n\nMake sure that you have set the appropriate register variant in the UONET+ register variant field on the first login screen
- Select students to log in to the application
- Other options
- In this mode, a lucky number does not work, a class grade stats, summary of attendance, excuse for absence, completed lessons, school information and preview of the list of registered devices
- This mode displays the same data as it appears on the register website
- The combination of the best features of the other two modes. It works faster than scraper and provides features not available in the Mobile API mode. It is in the experimental phase
- Privacy policy
- Trouble signing in? Contact us!
- Email
- Discord
- Send email
- Make sure you select the correct UONET+ register variation!
- Reset password
- Recover your account
- Recover
- Student is already signed in
- Standard
- Other search locations
- No active students found
- Enter a different symbol
- Get help
- Full school name with the town (required)
- Np. ZSTiO Jarosław lub SP nr 99 w Łodzi
- Enter correct name of the school
- Additional information in Polish (optional)
- Np. \"Ostatnio zmieniłem szkołę i…\" albo \"Jestem rodzicem i nie widzę drugiego dziecka…\"
- Submit
-
- Enable notifications
- Enable notifications so you don\'t miss message from teacher or new grade
- Skip
- Enable
-
- Account manager
- Log in
- Session expired
- Session expired, log in again
- Your account password has been changed. You need to log in to Wulkanowy again
- Password changed
- Application support
- Do you like this app? Support its development by enabling non-invasive ads that you can disable at any time
- Enable ads
-
- Grade
- Semester %d
- Change semester
- No grades
- Weight
- Weight: %s
- Comment
- Number of new ratings: %1$d
- Average: %1$.2f
- Points: %s
- No average
- Total points
- Final grade
- Predicted grade
- Calculated average
- How does Calculated Average work?
- The Calculated Average is the arithmetic average calculated from the subjects averages. It allows you to know the approximate final average. It is calculated in a way selected by the user in the application settings. It is recommended that you choose the appropriate option. This is because the calculation of school averages differs. Additionally, if your school reports the average of the subjects on the Vulcan page, the application downloads them and does not calculate these averages. This can be changed by forcing the calculation of the average in the application settings.\n\nAverage of grades only from selected semester:\n1. Calculating the weighted average for each subject in a given semester\n2.Adding calculated averages\n3. Calculation of the arithmetic average of the summed averages\n\nAverage of averages from both semesters:\n1.Calculating the weighted average for each subject in semester 1 and 2\n2. Calculating the arithmetic average of the calculated averages for semesters 1 and 2 for each subject.\n3. Adding calculated averages\n4. Calculation of the arithmetic average of the summed averages\n\nAverage of grades from the whole year:\n1. Calculating weighted average over the year for each subject. The final average in the 1st semester is irrelevant.\n2. Adding calculated averages\n3. Calculating the arithmetic average of summed averages
- How does the Final Average work?
- The Final Average is the arithmetic average calculated from all currently available final grades in the given semester.\n\nThe calculation scheme consists of the following steps:\n1. Summing up the final grades given by teachers\n2. Divide by the number of subjects that have already been graded
- Final average
- from %1$d of %2$d subjects
- Summary
- Class
- Mark as read
- Partial
- Semester
- Points
- Legend
- Class average: %1$s
- Your average: %1$s
- Your grade: %1$s
- Class
- Student
-
- - %d grade
- - %d grades
-
-
- - New grade
- - New grades
-
-
- - New predicted grade
- - New predicted grades
-
-
- - New final grade
- - New final grades
-
-
- - You received %1$d grade
- - You received %1$d grades
-
-
- - You received %1$d predicted grade
- - You received %1$d predicted grades
-
-
- - You received %1$d final grade
- - You received %1$d final grades
-
-
- Lesson
- Room
- Group
- Hours
- Changes
- No lessons this day
- %s min
- %s sec
- %1$s left
- in %1$s
- Finished
- Now: %s
- Next: %s
- Later: %s
- %1$s lesson %2$d - %3$s
- Change of room from %1$s to %2$s
- Change of teacher from %1$s to %2$s
- Change of subject from %1$s to %2$s
-
- - No lesson
- - No lessons
-
-
- - Timetable change
- - Timetable changes
-
-
- - %1$s - %2$d change in timetable
- - %1$s - %2$d changes in timetable
-
-
- - %1$d change in timetable
- - %1$d changes in timetable
-
-
- - %d change
- - %d changes
-
-
- Completed lessons
- Show completed lessons
- No info about completed lessons
- Topic
- Absence
- Resources
-
- Additional lessons
- Show additional lessons
- No info about additional lessons
- New lesson
- New additional lesson
- Additional lesson added successfully
- Additional lesson deleted successfully
- Repeat weekly
- Delete additional lesson
- Just this lesson
- All in the series
- Start time
- End time
- End time must be greater than start time
-
- Attendance summary
- Absent for school reasons
- Excused absence
- Unexcused absence
- Exemption
- Excused lateness
- Unexcused lateness
- Present
- Deleted
- Unknown
- Number of lesson
- No entries
- Absence reason (optional)
- Send
- Absence excuse request sent successfully!
- You must select at least one absence!
- Excuse
-
- - New attendance
- - New attendance
-
-
- - %1$d new attendance
- - %1$d attendance
-
-
- - %d attendance
- - %d attendance
-
-
- Total
-
- No exams this week
- Type
- Entry date
-
- - New exam
- - New exams
-
-
- - %d new exam
- - %d new exams
-
-
- - %d exam
- - %d exams
-
-
- Inbox
- Sent
- Trash
- (no subject)
- No messages
- From:
- To:
- Date: %1$s
- Reply
- Forward
- Select all
- Unselect all
- Move to trash
- Delete permanently
- Message deleted successfully
- student
- parent
- guardian
- employee
- Share
- Print
- Subject
- Content
- Message sent successfully
- Message does not exist
- You need to choose at least 1 recipient
- The message content must be at least 3 characters
- All mailboxes
- Only unread
- Only with attachments
- Read: %s
- Read by: %1$d of %2$d people
-
- - %1$d message
- - %1$d messages
-
-
- - New message
- - New messages
-
- Do you want to restore draft message?
- Do you want to restore draft message with recipients: %s?
-
- - You received %1$d message
- - You received %1$d messages
-
-
- - %1$d selected
- - %1$d selected
-
- Messages deleted
- Choose mailbox
- Incognito mode is on
- Thanks to incognito mode sender is not notified when you read the message
-
- No info about notes
- Points
-
- - %d note
- - %d notes
-
-
- - New note
- - New notes
-
-
- - You received %1$d note
- - You received %1$d notes
-
-
-
- - %d praise
- - %d praises
-
-
- - New praise
- - New praises
-
-
- - You received %1$d praise
- - You received %1$d praises
-
-
-
- - %d neutral note
- - %d neutral notes
-
-
- - New neutral note
- - New neutral notes
-
-
- - You received %1$d neutral note
- - You received %1$d neutral notes
-
-
- No info about homework
- Mark as done
- Mark as undone
- Add homework
- Homework added successfully
- Homework deleted successfully
- Attachments
-
- - New homework
- - New homework
-
-
- - You received %d new homework
- - You received %d new homework
-
-
- - %d homework
- - %d homework
-
-
- Lucky number
- Today\'s lucky number is
- No info about the lucky number
- Lucky number for today
- Today\'s lucky number is: %s
- Show history
-
- Lucky number history
- No info about lucky numbers
-
- Mobile devices
- No devices
- Deregister
- Device removed
- QR code
- Token
- Symbol
- PIN
-
- School and teachers
-
- School
- No info about school
- School name
- School address
- Telephone
- Name of headmaster
- Name of pedagogue
- Show on map
- Call
-
- Teachers
- No info about teachers
- No subject
-
- Conferences
- No info about conferences
-
- - %d conference
- - %d conferences
-
-
- - New conference
- - New conferences
-
-
- - You have %1$d new conference
- - You have %1$d new conferences
-
- Present at conference
- Agenda
- Place
- Topic
-
- School announcements
- No school announcements
-
- - %d school announcement
- - %d school announcements
-
-
- - New school announcement
- - New school announcements
-
-
- - You have %1$d new school announcement
- - You have %1$d new school announcements
-
-
- Add account
- Logout
- Do you want to log out this student?
- Student logout
- Student account
- Parent account
- Edit data
- Accounts manager
- Select student
- Family
- Contact
- Residence details
- Personal information
-
- App version
- Contributors
- List of Wulkanowy developers
- Report a bug
- Send a bug report via e-mail
- FAQ
- Read Frequently Asked Questions
- Discord server
- Join the Wulkanowy community
- Facebook fanpage
- Twitter page
- Follow us on twitter
- Like our facebook fanpage
- Privacy policy
- Rules for collecting personal data
- System settings
- Open system settings
- Homepage
- Visit the website and help develop the application
- Licenses
- Licenses of libraries used in the application
-
- License
-
- Avatar
- See more on GitHub
-
- No info about student or student family
- Name
- Second name
- Gender
- Polish citizenship
- Family name
- Mother\'s and father\'s names
- Phone
- Cellphone
- E-mail
- Address of residence
- Address of registration
- Correspondence address
- Surname and first name
- Degree of kinship
- Address
- Phones
- Male
- Female
- Last name
- Guardian
-
- Nick
- Add nick
- Choose avatar color
-
- Share logs
- Refresh
-
- Lessons
- (Tomorrow)
- (Today and tomorrow)
- In a moment:
- Soon:
- First:
- Now:
- End of lessons
- Next:
- Later:
-
- - %1$d more lesson
- - %1$d more lessons
-
- until %1$s
- No upcoming lessons
- An error occurred while loading the lessons
- Homework
- No homework to do
- An error occurred while loading the homework
-
- - %1$d more homework
- - %1$d more homework
-
- due %1$s
- Last grades
- No new grades
- An error occurred while loading the grades
- School announcements
- No current announcements
- An error occurred while loading the announcements
-
- - %1$d more announcement
- - %1$d more announcements
-
- Exams
- No upcoming exams
- An error occurred while loading the exams
-
- - %1$d more exam
- - %1$d more exams
-
- Conferences
- No upcoming conferences
- An error occurred while loading the conferences
-
- - %1$d more conference
- - %1$d more conferences
-
- An error occurred while loading data
- None
-
- Check for updates
- Before reporting a bug, check first if an update with the bug fix is available
-
- Content
- Retry
- Description
- No description
- Teacher
- Date
- Entry date
- Color
- Details
- Category
- Close
- No data
- Subject
- Prev
- Next
- Search
- Search…
- Yes
- No
- Save
- Title
- Add
- Copied
- Undo
- Change
- Add to calendar
- Cancel
-
- No lessons
- Synchronized on %1$s at %2$s
- Choose theme
- Light
- Dark
- System Theme
-
- App
- Default view
- Calculated average options
- Force average calculation by app
- Show presence
- Theme
- Grades expanding
- Show groups next to subjects
- Show empty tiles where there\'s no lesson
- Show chart list in class grades
- Show subjects without grades
- Grades color scheme
- Subjects sorting
- Language
- Menu configuration
- Set the order of functions in the menu
- Notifications
- Other
- Show notifications
- Show upcoming lesson notifications
- Make upcoming lesson notification persistent
- Turn off when notification is not showing in your watch/band
- Open system notification settings
- Fix synchronization & notifications issues
- Your device may have data synchronization issues and with notifications.\n\nTo fix them, you need to add Wulkanowy to the autostart and turn off battery optimization/saving in the phone settings.
- Show debug notifications
- Synchronization is disabled
- Official app notifications
- Capture official app notifications
- Remove official app notifications after capture
- Capture notifications
- With this feature you can gain a substitute of push notifications like in the official app. All you need to do is allow Wulkanowy to receive all notifications in your system settings.\n\nHow it works?\nWhen you get a notification in Dziennik VULCAN, Wulkanowy will be notified (that\'s what these extra permissions are for) and will trigger a sync so that can send its own notification.\n\nFOR ADVANCED USERS ONLY
- Upcoming lesson notifications
- You must allow the Wulkanowy app to set alarms and reminders in your system settings to use this feature.
- Go to settings
- Synchronization
- Automatic update
- Suspended on holidays
- Updates interval
- Wi-Fi only
- Sync now
- Synced!
- Sync failed
- Sync in progress
- Last full sync: %s
- Value of the plus
- Value of the minus
- Reply with message history
- Show arithmetic average when no weights provided
- Incognito mode
- Do not inform about reading the message
- Support
- Privacy Policy
- Agreements
- Show consent to data processing
- Show ads in app
- Watch single ad to support project
- Consent to data processing
- To view an advertisement you must agree to the data processing terms of our Privacy Policy
- Agree
- Privacy policy
- Ad is loading
- Thank you for your support, come back later for more ads
- Advanced
- Appearance & Behavior
- Notifications
- Synchronization
- Advertisements
- Grades
- Dashboard
- Tiles visibility
- Attendance
- Timetable
- Grades
- Calculated average
- Messages
- Appearance & Behavior
- Languages, themes, subjects sorting
- App notifications, fix problems
- Notifications
- Synchronization
- Automatic update, synchronization interval
- Plus and minus values, average calculation
- Advanced
- App version, contributors, social portals
- Displaying advertisements, project support
-
- New grades
- New homework
- New conferences
- New exams
- Lucky number
- New messages
- New notes
- New school announcements
- Push notifications
- Upcoming lessons
- Debug
- Timetable change
- New attendance
-
- Black
- Red
- Blue
- Green
- Purple
- No color
-
- Download of updates has started…
- An update has just been downloaded.
- Restart
- Update failed! Wulkanowy may not function properly. Consider updating
-
- Application restart
- The application must restart for the changes to be saved
- Restart
-
- Authorization has been rejected. The data provided does not match the records in the secretary\'s office.
- Invalid PESEL
- PESEL
- Authorize
- Authorization completed successfully
- Authorization
- To operate the application, we need to confirm your identity. Please enter the student\'s PESEL <b>%1$s</b> in the field below
- Skip for now
-
- Verification is in progress. Wait…
- Verified successfully
-
- No internet connection
- An error occurred. Check your device clock
- Connection to register failed. Servers can be overloaded. Please try again later
- Loading data failed. Please try again later
- Register password change required
- Maintenance underway UONET + register. Try again later
- Unknown UONET + register error. Try again later
- Unknown application error. Please try again later
- Captcha verification required
- An unexpected error occurred
- Feature disabled by your school
- Feature not available. Login in a mode other than Mobile API
- This field is required
-
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index feb08a03b..2ca669287 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -829,7 +829,7 @@
Авторизация отклонена. Предоставленные данные не соответствуют записям в кабинете секретаря.
Неправильный номер PESEL
Номер PESEL
- Authorize
+ Авторизовать
Авторизация прошла успешно
Авторизация
Для работы приложения нам необходимо подтвердить вашу личность. Введите PESEL учащегося <b>%1$s</b> в поле ниже
From 725668f855eb0ea41217e1a57d2b72547634ceed Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Fri, 19 Jan 2024 09:22:34 +0000
Subject: [PATCH 07/50] Bump androidx.lifecycle:lifecycle-livedata-ktx from
2.6.2 to 2.7.0 (#2398)
---
app/build.gradle | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/build.gradle b/app/build.gradle
index 8d10ce926..cd8a1df20 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -221,7 +221,7 @@ dependencies {
implementation "androidx.work:work-runtime:$work_manager"
playImplementation "androidx.work:work-gcm:$work_manager"
- implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.6.2"
+ implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.7.0"
implementation "androidx.room:room-runtime:$room"
implementation "androidx.room:room-ktx:$room"
From 9dfb282e881aa0dc6278777993352740d84b43f4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rafa=C5=82=20Borcz?=
Date: Sun, 21 Jan 2024 11:39:55 +0100
Subject: [PATCH 08/50] Add X to close admin message (#2401)
---
.../58.json | 2451 +++++++++++++++++
.../github/wulkanowy/data/db/AppDatabase.kt | 124 +-
.../data/db/entities/AdminMessage.kt | 7 +-
.../data/db/migrations/Migration58.kt | 10 +
.../viewholders/AdminMessageViewHolder.kt | 10 +-
app/src/main/res/drawable/ic_close.xml | 10 +
.../layout/item_dashboard_admin_message.xml | 15 +-
7 files changed, 2617 insertions(+), 10 deletions(-)
create mode 100644 app/schemas/io.github.wulkanowy.data.db.AppDatabase/58.json
create mode 100644 app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration58.kt
create mode 100644 app/src/main/res/drawable/ic_close.xml
diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/58.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/58.json
new file mode 100644
index 000000000..e6e71229c
--- /dev/null
+++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/58.json
@@ -0,0 +1,2451 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 58,
+ "identityHash": "cd1d4f8f2b6e3860fbc1de93d4f5ca42",
+ "entities": [
+ {
+ "tableName": "Students",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`scrapper_base_url` TEXT NOT NULL, `scrapper_domain_suffix` TEXT NOT NULL DEFAULT '', `mobile_base_url` TEXT NOT NULL, `login_type` TEXT NOT NULL, `login_mode` TEXT NOT NULL, `certificate_key` TEXT NOT NULL, `private_key` TEXT NOT NULL, `is_parent` INTEGER NOT NULL, `email` TEXT NOT NULL, `password` TEXT NOT NULL, `symbol` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `user_login_id` INTEGER NOT NULL, `user_name` TEXT NOT NULL, `student_name` TEXT NOT NULL, `school_id` TEXT NOT NULL, `school_short` TEXT NOT NULL, `school_name` TEXT NOT NULL, `class_name` TEXT NOT NULL, `class_id` INTEGER NOT NULL, `is_current` INTEGER NOT NULL, `registration_date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `nick` TEXT NOT NULL, `avatar_color` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "scrapperBaseUrl",
+ "columnName": "scrapper_base_url",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "scrapperDomainSuffix",
+ "columnName": "scrapper_domain_suffix",
+ "affinity": "TEXT",
+ "notNull": true,
+ "defaultValue": "''"
+ },
+ {
+ "fieldPath": "mobileBaseUrl",
+ "columnName": "mobile_base_url",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "loginType",
+ "columnName": "login_type",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "loginMode",
+ "columnName": "login_mode",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "certificateKey",
+ "columnName": "certificate_key",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "privateKey",
+ "columnName": "private_key",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isParent",
+ "columnName": "is_parent",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "email",
+ "columnName": "email",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "password",
+ "columnName": "password",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "symbol",
+ "columnName": "symbol",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "userLoginId",
+ "columnName": "user_login_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "userName",
+ "columnName": "user_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "studentName",
+ "columnName": "student_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "schoolSymbol",
+ "columnName": "school_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "schoolShortName",
+ "columnName": "school_short",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "schoolName",
+ "columnName": "school_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "className",
+ "columnName": "class_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "classId",
+ "columnName": "class_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isCurrent",
+ "columnName": "is_current",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "registrationDate",
+ "columnName": "registration_date",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "nick",
+ "columnName": "nick",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "avatarColor",
+ "columnName": "avatar_color",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [
+ {
+ "name": "index_Students_email_symbol_student_id_school_id_class_id",
+ "unique": true,
+ "columnNames": [
+ "email",
+ "symbol",
+ "student_id",
+ "school_id",
+ "class_id"
+ ],
+ "orders": [],
+ "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Students_email_symbol_student_id_school_id_class_id` ON `${TABLE_NAME}` (`email`, `symbol`, `student_id`, `school_id`, `class_id`)"
+ }
+ ],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Semesters",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `kindergarten_diary_id` INTEGER NOT NULL DEFAULT 0, `diary_name` TEXT NOT NULL, `school_year` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `semester_name` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_current` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "diaryId",
+ "columnName": "diary_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "kindergartenDiaryId",
+ "columnName": "kindergarten_diary_id",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "0"
+ },
+ {
+ "fieldPath": "diaryName",
+ "columnName": "diary_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "schoolYear",
+ "columnName": "school_year",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "semesterId",
+ "columnName": "semester_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "semesterName",
+ "columnName": "semester_name",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "start",
+ "columnName": "start",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "end",
+ "columnName": "end",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "classId",
+ "columnName": "class_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "unitId",
+ "columnName": "unit_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "current",
+ "columnName": "is_current",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [
+ {
+ "name": "index_Semesters_student_id_diary_id_kindergarten_diary_id_semester_id",
+ "unique": true,
+ "columnNames": [
+ "student_id",
+ "diary_id",
+ "kindergarten_diary_id",
+ "semester_id"
+ ],
+ "orders": [],
+ "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Semesters_student_id_diary_id_kindergarten_diary_id_semester_id` ON `${TABLE_NAME}` (`student_id`, `diary_id`, `kindergarten_diary_id`, `semester_id`)"
+ }
+ ],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Exams",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `group` TEXT NOT NULL, `type` TEXT NOT NULL, `description` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "diaryId",
+ "columnName": "diary_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "date",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "entryDate",
+ "columnName": "entry_date",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subject",
+ "columnName": "subject",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "group",
+ "columnName": "group",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "description",
+ "columnName": "description",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "teacher",
+ "columnName": "teacher",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "teacherSymbol",
+ "columnName": "teacher_symbol",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isNotified",
+ "columnName": "is_notified",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Timetable",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `number` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `subjectOld` TEXT NOT NULL, `group` TEXT NOT NULL, `room` TEXT NOT NULL, `roomOld` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacherOld` TEXT NOT NULL, `info` TEXT NOT NULL, `student_plan` INTEGER NOT NULL, `changes` INTEGER NOT NULL, `canceled` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "diaryId",
+ "columnName": "diary_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "number",
+ "columnName": "number",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "start",
+ "columnName": "start",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "end",
+ "columnName": "end",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "date",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subject",
+ "columnName": "subject",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subjectOld",
+ "columnName": "subjectOld",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "group",
+ "columnName": "group",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "room",
+ "columnName": "room",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "roomOld",
+ "columnName": "roomOld",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "teacher",
+ "columnName": "teacher",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "teacherOld",
+ "columnName": "teacherOld",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "info",
+ "columnName": "info",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isStudentPlan",
+ "columnName": "student_plan",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "changes",
+ "columnName": "changes",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "canceled",
+ "columnName": "canceled",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isNotified",
+ "columnName": "is_notified",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Attendance",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `time_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `excused` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `excusable` INTEGER NOT NULL, `excuse_status` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "diaryId",
+ "columnName": "diary_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "timeId",
+ "columnName": "time_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "date",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "number",
+ "columnName": "number",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subject",
+ "columnName": "subject",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "presence",
+ "columnName": "presence",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "absence",
+ "columnName": "absence",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "exemption",
+ "columnName": "exemption",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lateness",
+ "columnName": "lateness",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "excused",
+ "columnName": "excused",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "deleted",
+ "columnName": "deleted",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "excusable",
+ "columnName": "excusable",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "excuseStatus",
+ "columnName": "excuse_status",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isNotified",
+ "columnName": "is_notified",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "AttendanceSummary",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `subject_id` INTEGER NOT NULL, `month` INTEGER NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `absence_excused` INTEGER NOT NULL, `absence_for_school_reasons` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `lateness_excused` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "diaryId",
+ "columnName": "diary_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subjectId",
+ "columnName": "subject_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "month",
+ "columnName": "month",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "presence",
+ "columnName": "presence",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "absence",
+ "columnName": "absence",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "absenceExcused",
+ "columnName": "absence_excused",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "absenceForSchoolReasons",
+ "columnName": "absence_for_school_reasons",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lateness",
+ "columnName": "lateness",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "latenessExcused",
+ "columnName": "lateness_excused",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "exemption",
+ "columnName": "exemption",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Grades",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `entry` TEXT NOT NULL, `value` REAL NOT NULL, `modifier` REAL NOT NULL, `comment` TEXT NOT NULL, `color` TEXT NOT NULL, `grade_symbol` TEXT NOT NULL, `description` TEXT NOT NULL, `weight` TEXT NOT NULL, `weightValue` REAL NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "semesterId",
+ "columnName": "semester_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subject",
+ "columnName": "subject",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "entry",
+ "columnName": "entry",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "value",
+ "columnName": "value",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "modifier",
+ "columnName": "modifier",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "comment",
+ "columnName": "comment",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "color",
+ "columnName": "color",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "gradeSymbol",
+ "columnName": "grade_symbol",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "description",
+ "columnName": "description",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "weight",
+ "columnName": "weight",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "weightValue",
+ "columnName": "weightValue",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "date",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "teacher",
+ "columnName": "teacher",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isRead",
+ "columnName": "is_read",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isNotified",
+ "columnName": "is_notified",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "GradesSummary",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `position` INTEGER NOT NULL, `subject` TEXT NOT NULL, `predicted_grade` TEXT NOT NULL, `final_grade` TEXT NOT NULL, `proposed_points` TEXT NOT NULL, `final_points` TEXT NOT NULL, `points_sum` TEXT NOT NULL, `average` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_predicted_grade_notified` INTEGER NOT NULL, `is_final_grade_notified` INTEGER NOT NULL, `predicted_grade_last_change` INTEGER NOT NULL, `final_grade_last_change` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "semesterId",
+ "columnName": "semester_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "position",
+ "columnName": "position",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subject",
+ "columnName": "subject",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "predictedGrade",
+ "columnName": "predicted_grade",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "finalGrade",
+ "columnName": "final_grade",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "proposedPoints",
+ "columnName": "proposed_points",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "finalPoints",
+ "columnName": "final_points",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "pointsSum",
+ "columnName": "points_sum",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "average",
+ "columnName": "average",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isPredictedGradeNotified",
+ "columnName": "is_predicted_grade_notified",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isFinalGradeNotified",
+ "columnName": "is_final_grade_notified",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "predictedGradeLastChange",
+ "columnName": "predicted_grade_last_change",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "finalGradeLastChange",
+ "columnName": "final_grade_last_change",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "GradePartialStatistics",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `class_average` TEXT NOT NULL, `student_average` TEXT NOT NULL, `class_amounts` TEXT NOT NULL, `student_amounts` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "semesterId",
+ "columnName": "semester_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subject",
+ "columnName": "subject",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "classAverage",
+ "columnName": "class_average",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "studentAverage",
+ "columnName": "student_average",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "classAmounts",
+ "columnName": "class_amounts",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "studentAmounts",
+ "columnName": "student_amounts",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "GradesPointsStatistics",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `others` REAL NOT NULL, `student` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "semesterId",
+ "columnName": "semester_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subject",
+ "columnName": "subject",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "others",
+ "columnName": "others",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "student",
+ "columnName": "student",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "GradeSemesterStatistics",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `amounts` TEXT NOT NULL, `student_grade` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "semesterId",
+ "columnName": "semester_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subject",
+ "columnName": "subject",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "amounts",
+ "columnName": "amounts",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "studentGrade",
+ "columnName": "student_grade",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Messages",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`email` TEXT NOT NULL, `message_global_key` TEXT NOT NULL, `mailbox_key` TEXT NOT NULL, `message_id` INTEGER NOT NULL, `correspondents` TEXT NOT NULL, `subject` TEXT NOT NULL, `date` INTEGER NOT NULL, `folder_id` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `read_by` INTEGER, `unread_by` INTEGER, `has_attachments` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `content` TEXT NOT NULL, `sender` TEXT, `recipients` TEXT)",
+ "fields": [
+ {
+ "fieldPath": "email",
+ "columnName": "email",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "messageGlobalKey",
+ "columnName": "message_global_key",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "mailboxKey",
+ "columnName": "mailbox_key",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "messageId",
+ "columnName": "message_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "correspondents",
+ "columnName": "correspondents",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subject",
+ "columnName": "subject",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "date",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "folderId",
+ "columnName": "folder_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "unread",
+ "columnName": "unread",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "readBy",
+ "columnName": "read_by",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "unreadBy",
+ "columnName": "unread_by",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "hasAttachments",
+ "columnName": "has_attachments",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isNotified",
+ "columnName": "is_notified",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "content",
+ "columnName": "content",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "sender",
+ "columnName": "sender",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "recipients",
+ "columnName": "recipients",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "MessageAttachments",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`message_global_key` TEXT NOT NULL, `url` TEXT NOT NULL, `filename` TEXT NOT NULL, PRIMARY KEY(`message_global_key`, `url`, `filename`))",
+ "fields": [
+ {
+ "fieldPath": "messageGlobalKey",
+ "columnName": "message_global_key",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "url",
+ "columnName": "url",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "filename",
+ "columnName": "filename",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "message_global_key",
+ "url",
+ "filename"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Notes",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `category` TEXT NOT NULL, `category_type` INTEGER NOT NULL, `is_points_show` INTEGER NOT NULL, `points` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "date",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "teacher",
+ "columnName": "teacher",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "teacherSymbol",
+ "columnName": "teacher_symbol",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "category",
+ "columnName": "category",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "categoryType",
+ "columnName": "category_type",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isPointsShow",
+ "columnName": "is_points_show",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "points",
+ "columnName": "points",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "content",
+ "columnName": "content",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isRead",
+ "columnName": "is_read",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isNotified",
+ "columnName": "is_notified",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Homework",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `attachments` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_done` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `is_added_by_user` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "semesterId",
+ "columnName": "semester_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "date",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "entryDate",
+ "columnName": "entry_date",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subject",
+ "columnName": "subject",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "content",
+ "columnName": "content",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "teacher",
+ "columnName": "teacher",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "teacherSymbol",
+ "columnName": "teacher_symbol",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "attachments",
+ "columnName": "attachments",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isDone",
+ "columnName": "is_done",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isNotified",
+ "columnName": "is_notified",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isAddedByUser",
+ "columnName": "is_added_by_user",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Subjects",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "diaryId",
+ "columnName": "diary_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "realId",
+ "columnName": "real_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "LuckyNumbers",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `lucky_number` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "date",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "luckyNumber",
+ "columnName": "lucky_number",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isNotified",
+ "columnName": "is_notified",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "CompletedLesson",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `topic` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `substitution` TEXT NOT NULL, `absence` TEXT NOT NULL, `resources` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "diaryId",
+ "columnName": "diary_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "date",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "number",
+ "columnName": "number",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subject",
+ "columnName": "subject",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "topic",
+ "columnName": "topic",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "teacher",
+ "columnName": "teacher",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "teacherSymbol",
+ "columnName": "teacher_symbol",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "substitution",
+ "columnName": "substitution",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "absence",
+ "columnName": "absence",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "resources",
+ "columnName": "resources",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Mailboxes",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`globalKey` TEXT NOT NULL, `email` TEXT NOT NULL, `symbol` TEXT NOT NULL, `schoolId` TEXT NOT NULL, `fullName` TEXT NOT NULL, `userName` TEXT NOT NULL, `studentName` TEXT NOT NULL, `schoolNameShort` TEXT NOT NULL, `type` TEXT NOT NULL, PRIMARY KEY(`globalKey`))",
+ "fields": [
+ {
+ "fieldPath": "globalKey",
+ "columnName": "globalKey",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "email",
+ "columnName": "email",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "symbol",
+ "columnName": "symbol",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "schoolId",
+ "columnName": "schoolId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "fullName",
+ "columnName": "fullName",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "userName",
+ "columnName": "userName",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "studentName",
+ "columnName": "studentName",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "schoolNameShort",
+ "columnName": "schoolNameShort",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "globalKey"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Recipients",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`mailboxGlobalKey` TEXT NOT NULL, `studentMailboxGlobalKey` TEXT NOT NULL, `fullName` TEXT NOT NULL, `userName` TEXT NOT NULL, `schoolShortName` TEXT NOT NULL, `type` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "mailboxGlobalKey",
+ "columnName": "mailboxGlobalKey",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "studentMailboxGlobalKey",
+ "columnName": "studentMailboxGlobalKey",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "fullName",
+ "columnName": "fullName",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "userName",
+ "columnName": "userName",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "schoolShortName",
+ "columnName": "schoolShortName",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "MobileDevices",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`user_login_id` INTEGER NOT NULL, `device_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "userLoginId",
+ "columnName": "user_login_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "deviceId",
+ "columnName": "device_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "date",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Teachers",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `short_name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "classId",
+ "columnName": "class_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subject",
+ "columnName": "subject",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "shortName",
+ "columnName": "short_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "School",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `address` TEXT NOT NULL, `contact` TEXT NOT NULL, `headmaster` TEXT NOT NULL, `pedagogue` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "classId",
+ "columnName": "class_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "address",
+ "columnName": "address",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "contact",
+ "columnName": "contact",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "headmaster",
+ "columnName": "headmaster",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "pedagogue",
+ "columnName": "pedagogue",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Conferences",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `subject` TEXT NOT NULL, `agenda` TEXT NOT NULL, `present_on_conference` TEXT NOT NULL, `conference_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "diaryId",
+ "columnName": "diary_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "title",
+ "columnName": "title",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subject",
+ "columnName": "subject",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "agenda",
+ "columnName": "agenda",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "presentOnConference",
+ "columnName": "present_on_conference",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "conferenceId",
+ "columnName": "conference_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "date",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isNotified",
+ "columnName": "is_notified",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "TimetableAdditional",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `repeat_id` BLOB DEFAULT NULL, `is_added_by_user` INTEGER NOT NULL DEFAULT 0)",
+ "fields": [
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "diaryId",
+ "columnName": "diary_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "start",
+ "columnName": "start",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "end",
+ "columnName": "end",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "date",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subject",
+ "columnName": "subject",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "repeatId",
+ "columnName": "repeat_id",
+ "affinity": "BLOB",
+ "notNull": false,
+ "defaultValue": "NULL"
+ },
+ {
+ "fieldPath": "isAddedByUser",
+ "columnName": "is_added_by_user",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "0"
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "StudentInfo",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `full_name` TEXT NOT NULL, `first_name` TEXT NOT NULL, `second_name` TEXT NOT NULL, `surname` TEXT NOT NULL, `birth_date` INTEGER NOT NULL, `birth_place` TEXT NOT NULL, `gender` TEXT NOT NULL, `has_polish_citizenship` INTEGER NOT NULL, `family_name` TEXT NOT NULL, `parents_names` TEXT NOT NULL, `address` TEXT NOT NULL, `registered_address` TEXT NOT NULL, `correspondence_address` TEXT NOT NULL, `phone_number` TEXT NOT NULL, `cell_phone_number` TEXT NOT NULL, `email` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `first_guardian_full_name` TEXT, `first_guardian_kinship` TEXT, `first_guardian_address` TEXT, `first_guardian_phones` TEXT, `first_guardian_email` TEXT, `second_guardian_full_name` TEXT, `second_guardian_kinship` TEXT, `second_guardian_address` TEXT, `second_guardian_phones` TEXT, `second_guardian_email` TEXT)",
+ "fields": [
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "fullName",
+ "columnName": "full_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "firstName",
+ "columnName": "first_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "secondName",
+ "columnName": "second_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "surname",
+ "columnName": "surname",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "birthDate",
+ "columnName": "birth_date",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "birthPlace",
+ "columnName": "birth_place",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "gender",
+ "columnName": "gender",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "hasPolishCitizenship",
+ "columnName": "has_polish_citizenship",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "familyName",
+ "columnName": "family_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "parentsNames",
+ "columnName": "parents_names",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "address",
+ "columnName": "address",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "registeredAddress",
+ "columnName": "registered_address",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "correspondenceAddress",
+ "columnName": "correspondence_address",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "phoneNumber",
+ "columnName": "phone_number",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "cellPhoneNumber",
+ "columnName": "cell_phone_number",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "email",
+ "columnName": "email",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "firstGuardian.fullName",
+ "columnName": "first_guardian_full_name",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "firstGuardian.kinship",
+ "columnName": "first_guardian_kinship",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "firstGuardian.address",
+ "columnName": "first_guardian_address",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "firstGuardian.phones",
+ "columnName": "first_guardian_phones",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "firstGuardian.email",
+ "columnName": "first_guardian_email",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "secondGuardian.fullName",
+ "columnName": "second_guardian_full_name",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "secondGuardian.kinship",
+ "columnName": "second_guardian_kinship",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "secondGuardian.address",
+ "columnName": "second_guardian_address",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "secondGuardian.phones",
+ "columnName": "second_guardian_phones",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "secondGuardian.email",
+ "columnName": "second_guardian_email",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "TimetableHeaders",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "diaryId",
+ "columnName": "diary_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "date",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "content",
+ "columnName": "content",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "SchoolAnnouncements",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`user_login_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "userLoginId",
+ "columnName": "user_login_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "date",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subject",
+ "columnName": "subject",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "content",
+ "columnName": "content",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isNotified",
+ "columnName": "is_notified",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Notifications",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `type` TEXT NOT NULL, `destination` TEXT NOT NULL DEFAULT '{\"type\":\"io.github.wulkanowy.ui.modules.Destination.Dashboard\"}', `date` INTEGER NOT NULL, `data` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "title",
+ "columnName": "title",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "content",
+ "columnName": "content",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "destination",
+ "columnName": "destination",
+ "affinity": "TEXT",
+ "notNull": true,
+ "defaultValue": "'{\"type\":\"io.github.wulkanowy.ui.modules.Destination.Dashboard\"}'"
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "date",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "data",
+ "columnName": "data",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "AdminMessages",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `version_name` INTEGER, `version_max` INTEGER, `target_register_host` TEXT, `target_flavor` TEXT, `destination_url` TEXT, `priority` TEXT NOT NULL, `types` TEXT NOT NULL DEFAULT '[]', `is_ok_visible` INTEGER NOT NULL DEFAULT 0, `is_x_visible` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`id`))",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "title",
+ "columnName": "title",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "content",
+ "columnName": "content",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "versionMin",
+ "columnName": "version_name",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "versionMax",
+ "columnName": "version_max",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "targetRegisterHost",
+ "columnName": "target_register_host",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "targetFlavor",
+ "columnName": "target_flavor",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "destinationUrl",
+ "columnName": "destination_url",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "priority",
+ "columnName": "priority",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "types",
+ "columnName": "types",
+ "affinity": "TEXT",
+ "notNull": true,
+ "defaultValue": "'[]'"
+ },
+ {
+ "fieldPath": "isOkVisible",
+ "columnName": "is_ok_visible",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "0"
+ },
+ {
+ "fieldPath": "isXVisible",
+ "columnName": "is_x_visible",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "0"
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ }
+ ],
+ "views": [],
+ "setupQueries": [
+ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'cd1d4f8f2b6e3860fbc1de93d4f5ca42')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt b/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt
index 48a2942c9..a2f230f4c 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt
@@ -1,11 +1,124 @@
package io.github.wulkanowy.data.db
import android.content.Context
-import androidx.room.*
+import androidx.room.AutoMigration
+import androidx.room.Database
+import androidx.room.Room
+import androidx.room.RoomDatabase
import androidx.room.RoomDatabase.JournalMode.TRUNCATE
-import io.github.wulkanowy.data.db.dao.*
-import io.github.wulkanowy.data.db.entities.*
-import io.github.wulkanowy.data.db.migrations.*
+import androidx.room.TypeConverters
+import io.github.wulkanowy.data.db.dao.AdminMessageDao
+import io.github.wulkanowy.data.db.dao.AttendanceDao
+import io.github.wulkanowy.data.db.dao.AttendanceSummaryDao
+import io.github.wulkanowy.data.db.dao.CompletedLessonsDao
+import io.github.wulkanowy.data.db.dao.ConferenceDao
+import io.github.wulkanowy.data.db.dao.ExamDao
+import io.github.wulkanowy.data.db.dao.GradeDao
+import io.github.wulkanowy.data.db.dao.GradePartialStatisticsDao
+import io.github.wulkanowy.data.db.dao.GradePointsStatisticsDao
+import io.github.wulkanowy.data.db.dao.GradeSemesterStatisticsDao
+import io.github.wulkanowy.data.db.dao.GradeSummaryDao
+import io.github.wulkanowy.data.db.dao.HomeworkDao
+import io.github.wulkanowy.data.db.dao.LuckyNumberDao
+import io.github.wulkanowy.data.db.dao.MailboxDao
+import io.github.wulkanowy.data.db.dao.MessageAttachmentDao
+import io.github.wulkanowy.data.db.dao.MessagesDao
+import io.github.wulkanowy.data.db.dao.MobileDeviceDao
+import io.github.wulkanowy.data.db.dao.NoteDao
+import io.github.wulkanowy.data.db.dao.NotificationDao
+import io.github.wulkanowy.data.db.dao.RecipientDao
+import io.github.wulkanowy.data.db.dao.SchoolAnnouncementDao
+import io.github.wulkanowy.data.db.dao.SchoolDao
+import io.github.wulkanowy.data.db.dao.SemesterDao
+import io.github.wulkanowy.data.db.dao.StudentDao
+import io.github.wulkanowy.data.db.dao.StudentInfoDao
+import io.github.wulkanowy.data.db.dao.SubjectDao
+import io.github.wulkanowy.data.db.dao.TeacherDao
+import io.github.wulkanowy.data.db.dao.TimetableAdditionalDao
+import io.github.wulkanowy.data.db.dao.TimetableDao
+import io.github.wulkanowy.data.db.dao.TimetableHeaderDao
+import io.github.wulkanowy.data.db.entities.AdminMessage
+import io.github.wulkanowy.data.db.entities.Attendance
+import io.github.wulkanowy.data.db.entities.AttendanceSummary
+import io.github.wulkanowy.data.db.entities.CompletedLesson
+import io.github.wulkanowy.data.db.entities.Conference
+import io.github.wulkanowy.data.db.entities.Exam
+import io.github.wulkanowy.data.db.entities.Grade
+import io.github.wulkanowy.data.db.entities.GradePartialStatistics
+import io.github.wulkanowy.data.db.entities.GradePointsStatistics
+import io.github.wulkanowy.data.db.entities.GradeSemesterStatistics
+import io.github.wulkanowy.data.db.entities.GradeSummary
+import io.github.wulkanowy.data.db.entities.Homework
+import io.github.wulkanowy.data.db.entities.LuckyNumber
+import io.github.wulkanowy.data.db.entities.Mailbox
+import io.github.wulkanowy.data.db.entities.Message
+import io.github.wulkanowy.data.db.entities.MessageAttachment
+import io.github.wulkanowy.data.db.entities.MobileDevice
+import io.github.wulkanowy.data.db.entities.Note
+import io.github.wulkanowy.data.db.entities.Notification
+import io.github.wulkanowy.data.db.entities.Recipient
+import io.github.wulkanowy.data.db.entities.School
+import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
+import io.github.wulkanowy.data.db.entities.Semester
+import io.github.wulkanowy.data.db.entities.Student
+import io.github.wulkanowy.data.db.entities.StudentInfo
+import io.github.wulkanowy.data.db.entities.Subject
+import io.github.wulkanowy.data.db.entities.Teacher
+import io.github.wulkanowy.data.db.entities.Timetable
+import io.github.wulkanowy.data.db.entities.TimetableAdditional
+import io.github.wulkanowy.data.db.entities.TimetableHeader
+import io.github.wulkanowy.data.db.migrations.Migration10
+import io.github.wulkanowy.data.db.migrations.Migration11
+import io.github.wulkanowy.data.db.migrations.Migration12
+import io.github.wulkanowy.data.db.migrations.Migration13
+import io.github.wulkanowy.data.db.migrations.Migration14
+import io.github.wulkanowy.data.db.migrations.Migration15
+import io.github.wulkanowy.data.db.migrations.Migration16
+import io.github.wulkanowy.data.db.migrations.Migration17
+import io.github.wulkanowy.data.db.migrations.Migration18
+import io.github.wulkanowy.data.db.migrations.Migration19
+import io.github.wulkanowy.data.db.migrations.Migration2
+import io.github.wulkanowy.data.db.migrations.Migration20
+import io.github.wulkanowy.data.db.migrations.Migration21
+import io.github.wulkanowy.data.db.migrations.Migration22
+import io.github.wulkanowy.data.db.migrations.Migration23
+import io.github.wulkanowy.data.db.migrations.Migration24
+import io.github.wulkanowy.data.db.migrations.Migration25
+import io.github.wulkanowy.data.db.migrations.Migration26
+import io.github.wulkanowy.data.db.migrations.Migration27
+import io.github.wulkanowy.data.db.migrations.Migration28
+import io.github.wulkanowy.data.db.migrations.Migration29
+import io.github.wulkanowy.data.db.migrations.Migration3
+import io.github.wulkanowy.data.db.migrations.Migration30
+import io.github.wulkanowy.data.db.migrations.Migration31
+import io.github.wulkanowy.data.db.migrations.Migration32
+import io.github.wulkanowy.data.db.migrations.Migration33
+import io.github.wulkanowy.data.db.migrations.Migration34
+import io.github.wulkanowy.data.db.migrations.Migration35
+import io.github.wulkanowy.data.db.migrations.Migration36
+import io.github.wulkanowy.data.db.migrations.Migration37
+import io.github.wulkanowy.data.db.migrations.Migration38
+import io.github.wulkanowy.data.db.migrations.Migration39
+import io.github.wulkanowy.data.db.migrations.Migration4
+import io.github.wulkanowy.data.db.migrations.Migration40
+import io.github.wulkanowy.data.db.migrations.Migration41
+import io.github.wulkanowy.data.db.migrations.Migration42
+import io.github.wulkanowy.data.db.migrations.Migration43
+import io.github.wulkanowy.data.db.migrations.Migration44
+import io.github.wulkanowy.data.db.migrations.Migration46
+import io.github.wulkanowy.data.db.migrations.Migration49
+import io.github.wulkanowy.data.db.migrations.Migration5
+import io.github.wulkanowy.data.db.migrations.Migration50
+import io.github.wulkanowy.data.db.migrations.Migration51
+import io.github.wulkanowy.data.db.migrations.Migration53
+import io.github.wulkanowy.data.db.migrations.Migration54
+import io.github.wulkanowy.data.db.migrations.Migration55
+import io.github.wulkanowy.data.db.migrations.Migration57
+import io.github.wulkanowy.data.db.migrations.Migration58
+import io.github.wulkanowy.data.db.migrations.Migration6
+import io.github.wulkanowy.data.db.migrations.Migration7
+import io.github.wulkanowy.data.db.migrations.Migration8
+import io.github.wulkanowy.data.db.migrations.Migration9
import io.github.wulkanowy.utils.AppInfo
import javax.inject.Singleton
@@ -51,6 +164,7 @@ import javax.inject.Singleton
AutoMigration(from = 54, to = 55, spec = Migration55::class),
AutoMigration(from = 55, to = 56),
AutoMigration(from = 56, to = 57, spec = Migration57::class),
+ AutoMigration(from = 57, to = 58, spec = Migration58::class),
],
version = AppDatabase.VERSION_SCHEMA,
exportSchema = true
@@ -59,7 +173,7 @@ import javax.inject.Singleton
abstract class AppDatabase : RoomDatabase() {
companion object {
- const val VERSION_SCHEMA = 57
+ const val VERSION_SCHEMA = 58
fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf(
Migration2(),
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/AdminMessage.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/AdminMessage.kt
index 875c2a3a5..0c8f1a5d1 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/entities/AdminMessage.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/AdminMessage.kt
@@ -37,6 +37,9 @@ data class AdminMessage(
@ColumnInfo(name = "types", defaultValue = "[]")
val types: List = emptyList(),
- @ColumnInfo(name = "is_dismissible")
- val isDismissible: Boolean = false
+ @ColumnInfo(name = "is_ok_visible", defaultValue = "0")
+ val isOkVisible: Boolean = false,
+
+ @ColumnInfo(name = "is_x_visible", defaultValue = "0")
+ val isXVisible: Boolean = false
)
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration58.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration58.kt
new file mode 100644
index 000000000..c440d58d6
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration58.kt
@@ -0,0 +1,10 @@
+package io.github.wulkanowy.data.db.migrations
+
+import androidx.room.DeleteColumn
+import androidx.room.migration.AutoMigrationSpec
+
+@DeleteColumn(
+ tableName = "AdminMessages",
+ columnName = "is_dismissible",
+)
+class Migration58 : AutoMigrationSpec
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/viewholders/AdminMessageViewHolder.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/viewholders/AdminMessageViewHolder.kt
index 81099801a..1e0f0bdbf 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/viewholders/AdminMessageViewHolder.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/viewholders/AdminMessageViewHolder.kt
@@ -7,7 +7,6 @@ import androidx.recyclerview.widget.RecyclerView
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.AdminMessage
import io.github.wulkanowy.databinding.ItemDashboardAdminMessageBinding
-import io.github.wulkanowy.ui.modules.dashboard.DashboardItem
import io.github.wulkanowy.utils.getThemeAttrColor
class AdminMessageViewHolder(
@@ -25,9 +24,11 @@ class AdminMessageViewHolder(
context.getThemeAttrColor(R.attr.colorMessageHigh) to
context.getThemeAttrColor(R.attr.colorOnMessageHigh)
}
+
"MEDIUM" -> {
context.getThemeAttrColor(R.attr.colorMessageMedium) to Color.BLACK
}
+
else -> null to context.getThemeAttrColor(R.attr.colorOnSurface)
}
@@ -37,11 +38,16 @@ class AdminMessageViewHolder(
dashboardAdminMessageItemDescription.text = item.content
dashboardAdminMessageItemDescription.setTextColor(textColor)
dashboardAdminMessageItemIcon.setColorFilter(textColor)
- dashboardAdminMessageItemDismiss.isVisible = item.isDismissible
+ dashboardAdminMessageItemDismiss.isVisible = item.isOkVisible
+ dashboardAdminMessageItemClose.isVisible = item.isXVisible
dashboardAdminMessageItemDismiss.setTextColor(textColor)
+ dashboardAdminMessageItemClose.imageTintList = ColorStateList.valueOf(textColor)
dashboardAdminMessageItemDismiss.setOnClickListener {
onAdminMessageDismissClickListener(item)
}
+ dashboardAdminMessageItemClose.setOnClickListener {
+ onAdminMessageDismissClickListener(item)
+ }
root.setCardBackgroundColor(backgroundColor?.let { ColorStateList.valueOf(it) })
item.destinationUrl?.let { url ->
diff --git a/app/src/main/res/drawable/ic_close.xml b/app/src/main/res/drawable/ic_close.xml
new file mode 100644
index 000000000..1d6c00461
--- /dev/null
+++ b/app/src/main/res/drawable/ic_close.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/layout/item_dashboard_admin_message.xml b/app/src/main/res/layout/item_dashboard_admin_message.xml
index e12241df5..407e12921 100644
--- a/app/src/main/res/layout/item_dashboard_admin_message.xml
+++ b/app/src/main/res/layout/item_dashboard_admin_message.xml
@@ -34,11 +34,24 @@
android:layout_marginEnd="16dp"
android:textSize="18sp"
android:textStyle="bold"
- app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintEnd_toStartOf="@id/dashboard_admin_message_item_close"
app:layout_constraintStart_toEndOf="@id/dashboard_admin_message_item_icon"
app:layout_constraintTop_toTopOf="parent"
tools:text="@tools:sample/lorem" />
+
+
Date: Sun, 21 Jan 2024 12:39:23 +0100
Subject: [PATCH 09/50] Add admin message to error view in dashboard (#2400)
---
app/build.gradle | 2 +-
.../ui/modules/dashboard/DashboardFragment.kt | 13 +++++--
.../modules/dashboard/DashboardPresenter.kt | 20 ++++++-----
.../ui/modules/dashboard/DashboardView.kt | 3 +-
.../main/res/layout/fragment_dashboard.xml | 36 +++++++++++++++----
5 files changed, 54 insertions(+), 20 deletions(-)
diff --git a/app/build.gradle b/app/build.gradle
index cd8a1df20..a67adb6fa 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -193,7 +193,7 @@ ext {
}
dependencies {
- implementation 'io.github.wulkanowy:sdk:2.3.6'
+ implementation 'io.github.wulkanowy:sdk:2.3.7-SNAPSHOT'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4'
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardFragment.kt
index bedbce231..b7a0796c5 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardFragment.kt
@@ -21,6 +21,7 @@ import io.github.wulkanowy.ui.modules.attendance.summary.AttendanceSummaryFragme
import io.github.wulkanowy.ui.modules.captcha.CaptchaDialog.Companion.CAPTCHA_SUCCESS
import io.github.wulkanowy.ui.modules.conference.ConferenceFragment
import io.github.wulkanowy.ui.modules.dashboard.adapters.DashboardAdapter
+import io.github.wulkanowy.ui.modules.dashboard.viewholders.AdminMessageViewHolder
import io.github.wulkanowy.ui.modules.exam.ExamFragment
import io.github.wulkanowy.ui.modules.grade.GradeFragment
import io.github.wulkanowy.ui.modules.homework.HomeworkFragment
@@ -37,7 +38,6 @@ import io.github.wulkanowy.utils.getErrorString
import io.github.wulkanowy.utils.getThemeAttrColor
import io.github.wulkanowy.utils.openInternetBrowser
import io.github.wulkanowy.utils.toFormattedString
-import timber.log.Timber
import java.time.LocalDate
import javax.inject.Inject
@@ -199,8 +199,17 @@ class DashboardFragment : BaseFragment(R.layout.fragme
binding.dashboardRecycler.isVisible = show
}
- override fun showErrorView(show: Boolean) {
+ override fun showErrorView(show: Boolean, adminMessageItem: DashboardItem.AdminMessages?) {
binding.dashboardErrorContainer.isVisible = show
+ binding.dashboardErrorAdminMessage.root.isVisible = adminMessageItem != null
+
+ if (adminMessageItem != null) {
+ AdminMessageViewHolder(
+ binding = binding.dashboardErrorAdminMessage,
+ onAdminMessageDismissClickListener = presenter::onAdminMessageDismissed,
+ onAdminMessageClickListener = presenter::onAdminMessageSelected,
+ ).bind(adminMessageItem.adminMessage)
+ }
}
override fun setErrorDetails(error: Throwable) {
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardPresenter.kt
index 74b427e78..4e1587439 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardPresenter.kt
@@ -403,7 +403,7 @@ class DashboardPresenter @Inject constructor(
subjectWithGrades = it.dataOrNull,
gradeTheme = preferencesRepository.gradeColorTheme,
isLoading = true
- ), forceRefresh
+ ), false
)
if (!it.dataOrNull.isNullOrEmpty()) {
@@ -452,7 +452,7 @@ class DashboardPresenter @Inject constructor(
if (forceRefresh) return@onEach
updateData(
DashboardItem.Lessons(it.dataOrNull, isLoading = true),
- forceRefresh
+ false
)
if (!it.dataOrNull?.lessons.isNullOrEmpty()) {
@@ -509,7 +509,7 @@ class DashboardPresenter @Inject constructor(
val data = it.dataOrNull.orEmpty()
updateData(
DashboardItem.Homework(data, isLoading = true),
- forceRefresh
+ false
)
if (data.isNotEmpty()) {
@@ -543,7 +543,7 @@ class DashboardPresenter @Inject constructor(
if (forceRefresh) return@onEach
updateData(
DashboardItem.Announcements(it.dataOrNull.orEmpty(), isLoading = true),
- forceRefresh
+ false
)
if (!it.dataOrNull.isNullOrEmpty()) {
@@ -586,7 +586,7 @@ class DashboardPresenter @Inject constructor(
if (forceRefresh) return@onEach
updateData(
DashboardItem.Exams(it.dataOrNull.orEmpty(), isLoading = true),
- forceRefresh
+ false
)
if (!it.dataOrNull.isNullOrEmpty()) {
@@ -627,7 +627,7 @@ class DashboardPresenter @Inject constructor(
if (forceRefresh) return@onEach
updateData(
DashboardItem.Conferences(it.dataOrNull.orEmpty(), isLoading = true),
- forceRefresh
+ false
)
if (!it.dataOrNull.isNullOrEmpty()) {
@@ -662,7 +662,7 @@ class DashboardPresenter @Inject constructor(
is Resource.Loading -> {
Timber.i("Loading dashboard admin message data started")
if (forceRefresh) return@onEach
- updateData(DashboardItem.AdminMessages(), forceRefresh)
+ updateData(DashboardItem.AdminMessages(), false)
}
is Resource.Success -> {
@@ -692,7 +692,7 @@ class DashboardPresenter @Inject constructor(
private fun loadAds(forceRefresh: Boolean) {
presenterScope.launch {
if (!forceRefresh) {
- updateData(DashboardItem.Ads(), forceRefresh)
+ updateData(DashboardItem.Ads(), false)
}
val dashboardAdItem =
@@ -813,6 +813,8 @@ class DashboardPresenter @Inject constructor(
val filteredItems = itemsLoadedList.filterNot {
it.type == DashboardItem.Type.ACCOUNT || it.type == DashboardItem.Type.ADMIN_MESSAGE
}
+ val dataLoadedAdminMessageItem =
+ itemsLoadedList.find { it.type == DashboardItem.Type.ADMIN_MESSAGE && it.isDataLoaded } as DashboardItem.AdminMessages?
val isAccountItemError =
itemsLoadedList.find { it.type == DashboardItem.Type.ACCOUNT }?.error != null
val isGeneralError =
@@ -834,7 +836,7 @@ class DashboardPresenter @Inject constructor(
showRefresh(false)
if ((forceRefresh && wasGeneralError) || !forceRefresh) {
showContent(false)
- showErrorView(true)
+ showErrorView(true, dataLoadedAdminMessageItem)
setErrorDetails(lastError)
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardView.kt
index fe011c929..56a0a773a 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardView.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardView.kt
@@ -20,7 +20,7 @@ interface DashboardView : BaseView {
fun showRefresh(show: Boolean)
- fun showErrorView(show: Boolean)
+ fun showErrorView(show: Boolean, adminMessageItem: DashboardItem.AdminMessages? = null)
fun setErrorDetails(error: Throwable)
@@ -29,5 +29,6 @@ interface DashboardView : BaseView {
fun popViewToRoot()
fun openNotificationsCenterView()
+
fun openInternetBrowser(url: String)
}
diff --git a/app/src/main/res/layout/fragment_dashboard.xml b/app/src/main/res/layout/fragment_dashboard.xml
index 348602d77..9de44a70d 100644
--- a/app/src/main/res/layout/fragment_dashboard.xml
+++ b/app/src/main/res/layout/fragment_dashboard.xml
@@ -18,7 +18,8 @@
+ android:layout_height="match_parent"
+ tools:visibility="gone">
-
+
+
@@ -55,14 +70,21 @@
android:gravity="center"
android:padding="8dp"
android:text="@string/error_unknown"
- android:textSize="20sp" />
+ android:textSize="20sp"
+ app:layout_constraintBottom_toTopOf="@id/dashboard_error_buttons"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/dashboard_error_image" />
+ android:orientation="horizontal"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/dashboard_error_message">
-
+
From a51a54dc7ac444f73bb525c5a54c26d9c281ce46 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Miko=C5=82aj=20Pich?=
Date: Sun, 21 Jan 2024 18:59:54 +0100
Subject: [PATCH 10/50] Normalize synchronization date ranges to fix weird
notification issues (#2403)
---
.../data/repositories/ExamRepository.kt | 18 ++++++++++--------
.../data/repositories/HomeworkRepository.kt | 7 ++++++-
.../services/sync/works/AttendanceWork.kt | 13 ++++++++++---
.../wulkanowy/services/sync/works/ExamWork.kt | 16 +++++++++++++---
.../services/sync/works/HomeworkWork.kt | 16 +++++++++++++---
.../services/sync/works/TimetableWork.kt | 13 ++++++++++---
.../ui/modules/dashboard/DashboardPresenter.kt | 4 ++--
7 files changed, 64 insertions(+), 23 deletions(-)
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/ExamRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/ExamRepository.kt
index 013c0951d..59f422428 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/ExamRepository.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/ExamRepository.kt
@@ -67,14 +67,16 @@ class ExamRepository @Inject constructor(
filterResult = { it.filter { item -> item.date in start..end } }
)
- fun getExamsFromDatabase(semester: Semester, start: LocalDate): Flow> {
- return examDb.loadAll(
- diaryId = semester.diaryId,
- studentId = semester.studentId,
- from = start.startExamsDay,
- end = start.endExamsDay
- )
- }
+ fun getExamsFromDatabase(
+ semester: Semester,
+ start: LocalDate,
+ end: LocalDate
+ ): Flow> = examDb.loadAll(
+ diaryId = semester.diaryId,
+ studentId = semester.studentId,
+ from = start,
+ end = end,
+ )
suspend fun updateExam(exam: List) = examDb.updateAll(exam)
}
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/HomeworkRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/HomeworkRepository.kt
index f564824de..048c95a38 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/HomeworkRepository.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/HomeworkRepository.kt
@@ -7,7 +7,12 @@ import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk
-import io.github.wulkanowy.utils.*
+import io.github.wulkanowy.utils.AutoRefreshHelper
+import io.github.wulkanowy.utils.getRefreshKey
+import io.github.wulkanowy.utils.init
+import io.github.wulkanowy.utils.monday
+import io.github.wulkanowy.utils.sunday
+import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.sync.Mutex
import java.time.LocalDate
import javax.inject.Inject
diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/AttendanceWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/AttendanceWork.kt
index 657f69638..4fc097492 100644
--- a/app/src/main/java/io/github/wulkanowy/services/sync/works/AttendanceWork.kt
+++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/AttendanceWork.kt
@@ -16,17 +16,24 @@ class AttendanceWork @Inject constructor(
) : Work {
override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) {
+ val startDate = now().previousOrSameSchoolDay
+ val endDate = startDate.plusDays(7)
+
attendanceRepository.getAttendance(
student = student,
semester = semester,
- start = now().previousOrSameSchoolDay,
- end = now().previousOrSameSchoolDay,
+ start = startDate,
+ end = endDate,
forceRefresh = true,
notify = notify,
)
.waitForResult()
- attendanceRepository.getAttendanceFromDatabase(semester, now().minusDays(7), now())
+ attendanceRepository.getAttendanceFromDatabase(
+ semester = semester,
+ start = startDate,
+ end = endDate,
+ )
.first()
.filterNot { it.isNotified }
.let {
diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/ExamWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/ExamWork.kt
index 7071bce20..4b0b1bdb3 100644
--- a/app/src/main/java/io/github/wulkanowy/services/sync/works/ExamWork.kt
+++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/ExamWork.kt
@@ -5,6 +5,8 @@ import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.ExamRepository
import io.github.wulkanowy.data.waitForResult
import io.github.wulkanowy.services.sync.notifications.NewExamNotification
+import io.github.wulkanowy.utils.endExamsDay
+import io.github.wulkanowy.utils.startExamsDay
import kotlinx.coroutines.flow.first
import java.time.LocalDate.now
import javax.inject.Inject
@@ -15,16 +17,24 @@ class ExamWork @Inject constructor(
) : Work {
override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) {
+ val startDate = now().startExamsDay
+ val endDate = startDate.endExamsDay
+
examRepository.getExams(
student = student,
semester = semester,
- start = now(),
- end = now(),
+ start = startDate,
+ end = endDate,
forceRefresh = true,
notify = notify,
).waitForResult()
- examRepository.getExamsFromDatabase(semester, now()).first()
+ examRepository.getExamsFromDatabase(
+ semester = semester,
+ start = startDate,
+ end = endDate,
+ )
+ .first()
.filter { !it.isNotified }.let {
if (it.isNotEmpty()) newExamNotification.notify(it, student)
diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/HomeworkWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/HomeworkWork.kt
index 4cfe27d0d..ddff3af7c 100644
--- a/app/src/main/java/io/github/wulkanowy/services/sync/works/HomeworkWork.kt
+++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/HomeworkWork.kt
@@ -5,7 +5,9 @@ import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.HomeworkRepository
import io.github.wulkanowy.data.waitForResult
import io.github.wulkanowy.services.sync.notifications.NewHomeworkNotification
+import io.github.wulkanowy.utils.monday
import io.github.wulkanowy.utils.nextOrSameSchoolDay
+import io.github.wulkanowy.utils.sunday
import kotlinx.coroutines.flow.first
import java.time.LocalDate.now
import javax.inject.Inject
@@ -16,16 +18,24 @@ class HomeworkWork @Inject constructor(
) : Work {
override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) {
+ val startDate = now().nextOrSameSchoolDay.monday
+ val endDate = startDate.sunday
+
homeworkRepository.getHomework(
student = student,
semester = semester,
- start = now().nextOrSameSchoolDay,
- end = now().nextOrSameSchoolDay,
+ start = startDate,
+ end = endDate,
forceRefresh = true,
notify = notify,
).waitForResult()
- homeworkRepository.getHomeworkFromDatabase(semester, now(), now().plusDays(7)).first()
+ homeworkRepository.getHomeworkFromDatabase(
+ semester = semester,
+ start = startDate,
+ end = endDate
+ )
+ .first()
.filter { !it.isNotified }.let {
if (it.isNotEmpty()) newHomeworkNotification.notify(it, student)
diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/TimetableWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/TimetableWork.kt
index 29b1f13c7..ac9a8eb4c 100644
--- a/app/src/main/java/io/github/wulkanowy/services/sync/works/TimetableWork.kt
+++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/TimetableWork.kt
@@ -16,17 +16,24 @@ class TimetableWork @Inject constructor(
) : Work {
override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) {
+ val startDate = now().nextOrSameSchoolDay
+ val endDate = startDate.plusDays(7)
+
timetableRepository.getTimetable(
student = student,
semester = semester,
- start = now().nextOrSameSchoolDay,
- end = now().nextOrSameSchoolDay,
+ start = startDate,
+ end = endDate,
forceRefresh = true,
notify = notify,
)
.waitForResult()
- timetableRepository.getTimetableFromDatabase(semester, now(), now().plusDays(7))
+ timetableRepository.getTimetableFromDatabase(
+ semester = semester,
+ from = startDate,
+ end = endDate,
+ )
.first()
.filterNot { it.isNotified }
.let {
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardPresenter.kt
index 4e1587439..ad604499b 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardPresenter.kt
@@ -435,13 +435,13 @@ class DashboardPresenter @Inject constructor(
private fun loadLessons(student: Student, forceRefresh: Boolean) {
flatResourceFlow {
val semester = semesterRepository.getCurrentSemester(student)
- val date = LocalDate.now()
+ val date = LocalDate.now().nextOrSameSchoolDay
timetableRepository.getTimetable(
student = student,
semester = semester,
start = date,
- end = date.plusDays(1),
+ end = date,
forceRefresh = forceRefresh
)
}
From e0f4cad7fb4e35cc38772b02e7b9a8a62de51996 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Miko=C5=82aj=20Pich?=
Date: Sun, 21 Jan 2024 20:01:00 +0100
Subject: [PATCH 11/50] Add missing unitId to sdk switchSemester call (#2402)
---
.../data/repositories/AttendanceRepository.kt | 12 +++++++++---
.../data/repositories/AttendanceSummaryRepository.kt | 3 ++-
.../data/repositories/CompletedLessonsRepository.kt | 2 +-
.../data/repositories/ConferenceRepository.kt | 3 ++-
.../wulkanowy/data/repositories/ExamRepository.kt | 10 ++++++++--
.../wulkanowy/data/repositories/GradeRepository.kt | 2 +-
.../data/repositories/GradeStatisticsRepository.kt | 7 ++++---
.../data/repositories/HomeworkRepository.kt | 2 +-
.../data/repositories/MobileDeviceRepository.kt | 7 ++++---
.../wulkanowy/data/repositories/NoteRepository.kt | 2 +-
.../wulkanowy/data/repositories/SchoolRepository.kt | 3 ++-
.../wulkanowy/data/repositories/SchoolsRepository.kt | 7 ++-----
.../data/repositories/StudentInfoRepository.kt | 3 ++-
.../wulkanowy/data/repositories/StudentRepository.kt | 5 +++--
.../wulkanowy/data/repositories/SubjectRepository.kt | 6 ++++--
.../wulkanowy/data/repositories/TeacherRepository.kt | 3 ++-
.../data/repositories/TimetableRepository.kt | 2 +-
.../java/io/github/wulkanowy/utils/SdkExtension.kt | 10 ++++++++++
18 files changed, 59 insertions(+), 30 deletions(-)
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt
index 3afb99077..6d782047b 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt
@@ -9,7 +9,13 @@ import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.sdk.pojo.Absent
-import io.github.wulkanowy.utils.*
+import io.github.wulkanowy.utils.AutoRefreshHelper
+import io.github.wulkanowy.utils.getRefreshKey
+import io.github.wulkanowy.utils.init
+import io.github.wulkanowy.utils.monday
+import io.github.wulkanowy.utils.sunday
+import io.github.wulkanowy.utils.switchSemester
+import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.sync.Mutex
@@ -58,7 +64,7 @@ class AttendanceRepository @Inject constructor(
)
}
sdk.init(student)
- .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
+ .switchSemester(semester)
.getAttendance(start.monday, end.sunday)
.mapToEntities(semester, lessons)
},
@@ -97,7 +103,7 @@ class AttendanceRepository @Inject constructor(
)
}
sdk.init(student)
- .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
+ .switchSemester(semester)
.excuseForAbsence(items, reason)
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceSummaryRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceSummaryRepository.kt
index 8e0709135..6bdcf9d7f 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceSummaryRepository.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceSummaryRepository.kt
@@ -9,6 +9,7 @@ import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.AutoRefreshHelper
import io.github.wulkanowy.utils.getRefreshKey
import io.github.wulkanowy.utils.init
+import io.github.wulkanowy.utils.switchSemester
import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.sync.Mutex
import javax.inject.Inject
@@ -40,7 +41,7 @@ class AttendanceSummaryRepository @Inject constructor(
query = { attendanceDb.loadAll(semester.diaryId, semester.studentId, subjectId) },
fetch = {
sdk.init(student)
- .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
+ .switchSemester(semester)
.getAttendanceSummary(subjectId)
.mapToEntities(semester, subjectId)
},
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/CompletedLessonsRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/CompletedLessonsRepository.kt
index 8f393cadb..1579ae62b 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/CompletedLessonsRepository.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/CompletedLessonsRepository.kt
@@ -48,7 +48,7 @@ class CompletedLessonsRepository @Inject constructor(
},
fetch = {
sdk.init(student)
- .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
+ .switchSemester(semester)
.getCompletedLessons(start.monday, end.sunday)
.mapToEntities(semester)
},
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/ConferenceRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/ConferenceRepository.kt
index 83204cab0..7eb37f0b7 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/ConferenceRepository.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/ConferenceRepository.kt
@@ -10,6 +10,7 @@ import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.AutoRefreshHelper
import io.github.wulkanowy.utils.getRefreshKey
import io.github.wulkanowy.utils.init
+import io.github.wulkanowy.utils.switchSemester
import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.sync.Mutex
@@ -46,7 +47,7 @@ class ConferenceRepository @Inject constructor(
},
fetch = {
sdk.init(student)
- .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
+ .switchSemester(semester)
.getConferences()
.mapToEntities(semester)
.filter { it.date >= startDate }
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/ExamRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/ExamRepository.kt
index 59f422428..96026a55b 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/ExamRepository.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/ExamRepository.kt
@@ -7,7 +7,13 @@ import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk
-import io.github.wulkanowy.utils.*
+import io.github.wulkanowy.utils.AutoRefreshHelper
+import io.github.wulkanowy.utils.endExamsDay
+import io.github.wulkanowy.utils.getRefreshKey
+import io.github.wulkanowy.utils.init
+import io.github.wulkanowy.utils.startExamsDay
+import io.github.wulkanowy.utils.switchSemester
+import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.sync.Mutex
import java.time.LocalDate
@@ -51,7 +57,7 @@ class ExamRepository @Inject constructor(
},
fetch = {
sdk.init(student)
- .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
+ .switchSemester(semester)
.getExams(start.startExamsDay, start.endExamsDay)
.mapToEntities(semester)
},
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/GradeRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/GradeRepository.kt
index f5f895d82..b2bb7b906 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/GradeRepository.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/GradeRepository.kt
@@ -52,7 +52,7 @@ class GradeRepository @Inject constructor(
},
fetch = {
val (details, summary) = sdk.init(student)
- .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
+ .switchSemester(semester)
.getGrades(semester.semesterId)
details.mapToEntities(semester) to summary.mapToEntities(semester)
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepository.kt
index 9fa06c497..23d7b8582 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepository.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepository.kt
@@ -16,6 +16,7 @@ import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.AutoRefreshHelper
import io.github.wulkanowy.utils.getRefreshKey
import io.github.wulkanowy.utils.init
+import io.github.wulkanowy.utils.switchSemester
import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.sync.Mutex
import java.util.*
@@ -56,7 +57,7 @@ class GradeStatisticsRepository @Inject constructor(
query = { gradePartialStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
fetch = {
sdk.init(student)
- .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
+ .switchSemester(semester)
.getGradesPartialStatistics(semester.semesterId)
.mapToEntities(semester)
},
@@ -101,7 +102,7 @@ class GradeStatisticsRepository @Inject constructor(
query = { gradeSemesterStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
fetch = {
sdk.init(student)
- .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
+ .switchSemester(semester)
.getGradesSemesterStatistics(semester.semesterId)
.mapToEntities(semester)
},
@@ -157,7 +158,7 @@ class GradeStatisticsRepository @Inject constructor(
query = { gradePointsStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
fetch = {
sdk.init(student)
- .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
+ .switchSemester(semester)
.getGradesPointsStatistics(semester.semesterId)
.mapToEntities(semester)
},
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/HomeworkRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/HomeworkRepository.kt
index 048c95a38..d4899b19e 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/HomeworkRepository.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/HomeworkRepository.kt
@@ -55,7 +55,7 @@ class HomeworkRepository @Inject constructor(
},
fetch = {
sdk.init(student)
- .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
+ .switchSemester(semester)
.getHomework(start.monday, end.sunday)
.mapToEntities(semester)
},
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/MobileDeviceRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/MobileDeviceRepository.kt
index 07c6959e3..412f9e7f0 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/MobileDeviceRepository.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/MobileDeviceRepository.kt
@@ -12,6 +12,7 @@ import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.AutoRefreshHelper
import io.github.wulkanowy.utils.getRefreshKey
import io.github.wulkanowy.utils.init
+import io.github.wulkanowy.utils.switchSemester
import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.sync.Mutex
import javax.inject.Inject
@@ -42,7 +43,7 @@ class MobileDeviceRepository @Inject constructor(
query = { mobileDb.loadAll(student.userLoginId) },
fetch = {
sdk.init(student)
- .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
+ .switchSemester(semester)
.getRegisteredDevices()
.mapToEntities(student)
},
@@ -56,7 +57,7 @@ class MobileDeviceRepository @Inject constructor(
suspend fun unregisterDevice(student: Student, semester: Semester, device: MobileDevice) {
sdk.init(student)
- .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
+ .switchSemester(semester)
.unregisterDevice(device.deviceId)
mobileDb.deleteAll(listOf(device))
@@ -64,7 +65,7 @@ class MobileDeviceRepository @Inject constructor(
suspend fun getToken(student: Student, semester: Semester): MobileDeviceToken {
return sdk.init(student)
- .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
+ .switchSemester(semester)
.getToken()
.mapToMobileDeviceToken()
}
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/NoteRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/NoteRepository.kt
index 4101803f3..eeb1d53ef 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/NoteRepository.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/NoteRepository.kt
@@ -41,7 +41,7 @@ class NoteRepository @Inject constructor(
query = { noteDb.loadAll(student.studentId) },
fetch = {
sdk.init(student)
- .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
+ .switchSemester(semester)
.getNotes()
.mapToEntities(semester)
},
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolRepository.kt
index 7972ed084..f757ef047 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolRepository.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolRepository.kt
@@ -9,6 +9,7 @@ import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.AutoRefreshHelper
import io.github.wulkanowy.utils.getRefreshKey
import io.github.wulkanowy.utils.init
+import io.github.wulkanowy.utils.switchSemester
import kotlinx.coroutines.sync.Mutex
import javax.inject.Inject
import javax.inject.Singleton
@@ -40,7 +41,7 @@ class SchoolRepository @Inject constructor(
query = { schoolDb.load(semester.studentId, semester.classId) },
fetch = {
sdk.init(student)
- .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
+ .switchSemester(semester)
.getSchool()
.mapToEntity(semester)
},
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolsRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolsRepository.kt
index 9c6429343..216a8c112 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolsRepository.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolsRepository.kt
@@ -11,6 +11,7 @@ import io.github.wulkanowy.ui.modules.login.LoginData
import io.github.wulkanowy.utils.IntegrityHelper
import io.github.wulkanowy.utils.getCurrentOrLast
import io.github.wulkanowy.utils.init
+import io.github.wulkanowy.utils.switchSemester
import kotlinx.coroutines.withTimeout
import timber.log.Timber
import java.util.UUID
@@ -42,11 +43,7 @@ class SchoolsRepository @Inject constructor(
val schoolInfo = sdk
.init(student.copy(password = loginData.password))
- .switchDiary(
- diaryId = semester.diaryId,
- kindergartenDiaryId = semester.kindergartenDiaryId,
- schoolYear = semester.schoolYear
- )
+ .switchSemester(semester)
.getSchool()
schoolsService.logLoginEvent(
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/StudentInfoRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/StudentInfoRepository.kt
index efc82a772..d6cd25c82 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/StudentInfoRepository.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/StudentInfoRepository.kt
@@ -7,6 +7,7 @@ import io.github.wulkanowy.data.mappers.mapToEntity
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.init
+import io.github.wulkanowy.utils.switchSemester
import kotlinx.coroutines.sync.Mutex
import javax.inject.Inject
import javax.inject.Singleton
@@ -30,7 +31,7 @@ class StudentInfoRepository @Inject constructor(
query = { studentInfoDao.loadStudentInfo(student.studentId) },
fetch = {
sdk.init(student)
- .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
+ .switchSemester(semester)
.getStudentInfo().mapToEntity(semester)
},
saveFetchResult = { old, new ->
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/StudentRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/StudentRepository.kt
index bfad12a8f..e063840cb 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/StudentRepository.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/StudentRepository.kt
@@ -16,6 +16,7 @@ import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.DispatchersProvider
import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.security.Scrambler
+import io.github.wulkanowy.utils.switchSemester
import kotlinx.coroutines.withContext
import javax.inject.Inject
import javax.inject.Singleton
@@ -149,12 +150,12 @@ class StudentRepository @Inject constructor(
suspend fun authorizePermission(student: Student, semester: Semester, pesel: String) =
sdk.init(student)
- .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
+ .switchSemester(semester)
.authorizePermission(pesel)
suspend fun refreshStudentName(student: Student, semester: Semester) {
val newCurrentApiStudent = sdk.init(student)
- .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
+ .switchSemester(semester)
.getCurrentStudent() ?: return
val studentName = StudentName(
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/SubjectRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/SubjectRepository.kt
index 3926122b3..98cb181af 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/SubjectRepository.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/SubjectRepository.kt
@@ -9,6 +9,7 @@ import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.AutoRefreshHelper
import io.github.wulkanowy.utils.getRefreshKey
import io.github.wulkanowy.utils.init
+import io.github.wulkanowy.utils.switchSemester
import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.sync.Mutex
import javax.inject.Inject
@@ -39,8 +40,9 @@ class SubjectRepository @Inject constructor(
query = { subjectDao.loadAll(semester.diaryId, semester.studentId) },
fetch = {
sdk.init(student)
- .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
- .getSubjects().mapToEntities(semester)
+ .switchSemester(semester)
+ .getSubjects()
+ .mapToEntities(semester)
},
saveFetchResult = { old, new ->
subjectDao.deleteAll(old uniqueSubtract new)
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/TeacherRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/TeacherRepository.kt
index 4e3b40f96..42698f922 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/TeacherRepository.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/TeacherRepository.kt
@@ -9,6 +9,7 @@ import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.AutoRefreshHelper
import io.github.wulkanowy.utils.getRefreshKey
import io.github.wulkanowy.utils.init
+import io.github.wulkanowy.utils.switchSemester
import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.sync.Mutex
import javax.inject.Inject
@@ -39,7 +40,7 @@ class TeacherRepository @Inject constructor(
query = { teacherDb.loadAll(semester.studentId, semester.classId) },
fetch = {
sdk.init(student)
- .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
+ .switchSemester(semester)
.getTeachers()
.mapToEntities(semester)
},
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt
index 136fb8d5b..9305d3b31 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt
@@ -65,7 +65,7 @@ class TimetableRepository @Inject constructor(
query = { getFullTimetableFromDatabase(student, semester, start, end) },
fetch = {
val timetableFull = sdk.init(student)
- .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
+ .switchSemester(semester)
.getTimetable(start.monday, end.sunday)
timetableFull.mapToEntities(semester)
diff --git a/app/src/main/java/io/github/wulkanowy/utils/SdkExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/SdkExtension.kt
index df99be98b..9b6ca7060 100644
--- a/app/src/main/java/io/github/wulkanowy/utils/SdkExtension.kt
+++ b/app/src/main/java/io/github/wulkanowy/utils/SdkExtension.kt
@@ -1,5 +1,6 @@
package io.github.wulkanowy.utils
+import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.sdk.Sdk
import timber.log.Timber
@@ -30,3 +31,12 @@ fun Sdk.init(student: Student): Sdk {
return this
}
+
+fun Sdk.switchSemester(semester: Semester): Sdk {
+ return switchDiary(
+ diaryId = semester.diaryId,
+ kindergartenDiaryId = semester.kindergartenDiaryId,
+ schoolYear = semester.schoolYear,
+ unitId = semester.unitId,
+ )
+}
From dc59f4ffa35c85bea6db3d0eef81b1b2e484f94f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Miko=C5=82aj=20Pich?=
Date: Sun, 21 Jan 2024 21:04:23 +0100
Subject: [PATCH 12/50] Version 2.3.5
---
app/build.gradle | 8 ++++----
.../wulkanowy/data/repositories/HomeworkRepository.kt | 1 +
app/src/main/play/release-notes/pl-PL/default.txt | 6 +++---
3 files changed, 8 insertions(+), 7 deletions(-)
diff --git a/app/build.gradle b/app/build.gradle
index a67adb6fa..7b290e638 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -27,8 +27,8 @@ android {
testApplicationId "io.github.tests.wulkanowy"
minSdkVersion 21
targetSdkVersion 34
- versionCode 144
- versionName "2.3.4"
+ versionCode 145
+ versionName "2.3.5"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
resValue "string", "app_name", "Wulkanowy"
@@ -162,7 +162,7 @@ play {
defaultToAppBundles = false
track = 'production'
releaseStatus = ReleaseStatus.IN_PROGRESS
- userFraction = 0.99d
+ userFraction = 0.15d
updatePriority = 1
enabled.set(false)
}
@@ -193,7 +193,7 @@ ext {
}
dependencies {
- implementation 'io.github.wulkanowy:sdk:2.3.7-SNAPSHOT'
+ implementation 'io.github.wulkanowy:sdk:2.3.7'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4'
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/HomeworkRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/HomeworkRepository.kt
index d4899b19e..010cf8458 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/HomeworkRepository.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/HomeworkRepository.kt
@@ -12,6 +12,7 @@ import io.github.wulkanowy.utils.getRefreshKey
import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.monday
import io.github.wulkanowy.utils.sunday
+import io.github.wulkanowy.utils.switchSemester
import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.sync.Mutex
import java.time.LocalDate
diff --git a/app/src/main/play/release-notes/pl-PL/default.txt b/app/src/main/play/release-notes/pl-PL/default.txt
index c2c30883e..04f3ba463 100644
--- a/app/src/main/play/release-notes/pl-PL/default.txt
+++ b/app/src/main/play/release-notes/pl-PL/default.txt
@@ -1,6 +1,6 @@
-Wersja 2.3.4
+Wersja 2.3.5
-— dodaliśmy obsługę captchy, co umożliwi używanie apki np. na odmianie ResMan Rzeszów
-— naprawiliśmy wyświetlanie frekwencji w szkołach używających eduOne (piszcie, jeśli nadal nie działa)
+— naprawiliśmy ładowanie frekwencji dla szkół używających eduOne
+— naprawiliśmy wielokrotne wysyłanie powiadomień o zmianach w planie lekcji
Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases
From 098af9884a4179e2ea44a22330b068b58ae3c2da Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 23 Jan 2024 19:17:29 +0000
Subject: [PATCH 13/50] Bump com.google.firebase:firebase-bom from 32.7.0 to
32.7.1 (#2404)
---
app/build.gradle | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/build.gradle b/app/build.gradle
index 7b290e638..54659cdd7 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -250,7 +250,7 @@ dependencies {
implementation 'com.fredporciuncula:flow-preferences:1.9.1'
implementation 'org.apache.commons:commons-text:1.11.0'
- playImplementation platform('com.google.firebase:firebase-bom:32.7.0')
+ playImplementation platform('com.google.firebase:firebase-bom:32.7.1')
playImplementation 'com.google.firebase:firebase-analytics'
playImplementation 'com.google.firebase:firebase-messaging'
playImplementation 'com.google.firebase:firebase-crashlytics:'
From 10add8a70ed105a214579d6ee019dc770765f42c Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Fri, 26 Jan 2024 10:21:30 +0000
Subject: [PATCH 14/50] Bump com.android.tools.build:gradle from 8.2.1 to 8.2.2
(#2406)
---
build.gradle | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/build.gradle b/build.gradle
index 095d1b72f..e6e090b80 100644
--- a/build.gradle
+++ b/build.gradle
@@ -14,7 +14,7 @@ buildscript {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
classpath "com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin:$kotlin_version-1.0.16"
- classpath 'com.android.tools.build:gradle:8.2.1'
+ classpath 'com.android.tools.build:gradle:8.2.2'
classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
classpath 'com.google.gms:google-services:4.4.0'
classpath 'com.huawei.agconnect:agcp:1.9.1.303'
From 88043569ac8cb7a6fe1b418ece97aaec1b29e6f6 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Fri, 26 Jan 2024 10:23:01 +0000
Subject: [PATCH 15/50] Bump com.huawei.hms:hianalytics from 6.12.0.300 to
6.12.0.301 (#2407)
---
app/build.gradle | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/build.gradle b/app/build.gradle
index 54659cdd7..c40bef8eb 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -262,7 +262,7 @@ dependencies {
playImplementation 'com.google.android.play:review-ktx:2.0.1'
playImplementation "com.google.android.ump:user-messaging-platform:2.1.0"
- hmsImplementation 'com.huawei.hms:hianalytics:6.12.0.300'
+ hmsImplementation 'com.huawei.hms:hianalytics:6.12.0.301'
hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.9.1.303'
releaseImplementation "com.github.chuckerteam.chucker:library-no-op:$chucker"
From fc91936884715cc1d46d5af17086364961f8631d Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Fri, 26 Jan 2024 10:30:36 +0000
Subject: [PATCH 16/50] Bump com.google.android.ump:user-messaging-platform
from 2.1.0 to 2.2.0 (#2408)
---
app/build.gradle | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/build.gradle b/app/build.gradle
index c40bef8eb..c0caf1dd8 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -260,7 +260,7 @@ dependencies {
playImplementation "com.google.android.play:integrity:1.3.0"
playImplementation 'com.google.android.play:app-update-ktx:2.1.0'
playImplementation 'com.google.android.play:review-ktx:2.0.1'
- playImplementation "com.google.android.ump:user-messaging-platform:2.1.0"
+ playImplementation "com.google.android.ump:user-messaging-platform:2.2.0"
hmsImplementation 'com.huawei.hms:hianalytics:6.12.0.301'
hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.9.1.303'
From e58a60410c3a6ee0dda506d2422905fe9cc8ee4d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rafa=C5=82=20Borcz?=
Date: Sun, 4 Feb 2024 10:48:59 +0100
Subject: [PATCH 17/50] Fix android tests (#2410)
---
.gitignore | 2 ++
.idea/migrations.xml | 10 ----------
app/build.gradle | 4 +++-
.../wulkanowy/utils/security/ScramblerTest.kt | 15 +++++++++------
4 files changed, 14 insertions(+), 17 deletions(-)
delete mode 100644 .idea/migrations.xml
diff --git a/.gitignore b/.gitignore
index 921bd0a9a..980085e38 100644
--- a/.gitignore
+++ b/.gitignore
@@ -65,6 +65,8 @@ captures/
.idea/uiDesigner.xml
.idea/runConfigurations.xml
.idea/discord.xml
+.idea/migrations.xml
+.idea/androidTestResultsUserPreferences.xml
# Keystore files
*.jks
diff --git a/.idea/migrations.xml b/.idea/migrations.xml
deleted file mode 100644
index f8051a6f9..000000000
--- a/.idea/migrations.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
index c0caf1dd8..b60dc3b4b 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -142,7 +142,9 @@ android {
packagingOptions {
resources {
excludes += ['META-INF/library_release.kotlin_module',
- 'META-INF/library-core_release.kotlin_module']
+ 'META-INF/library-core_release.kotlin_module',
+ 'META-INF/LICENSE.md',
+ 'META-INF/LICENSE-notice.md']
}
}
diff --git a/app/src/androidTest/java/io/github/wulkanowy/utils/security/ScramblerTest.kt b/app/src/androidTest/java/io/github/wulkanowy/utils/security/ScramblerTest.kt
index 0c47e6bb6..1b0319f69 100644
--- a/app/src/androidTest/java/io/github/wulkanowy/utils/security/ScramblerTest.kt
+++ b/app/src/androidTest/java/io/github/wulkanowy/utils/security/ScramblerTest.kt
@@ -14,34 +14,37 @@ import kotlin.test.assertFailsWith
@RunWith(AndroidJUnit4::class)
class ScramblerTest {
+ private val scrambler = Scrambler(ApplicationProvider.getApplicationContext())
+
@Test
fun encryptDecryptTest() {
- assertEquals("TEST", decrypt(encrypt("TEST",
- ApplicationProvider.getApplicationContext())))
+ assertEquals(
+ "TEST", scrambler.decrypt(scrambler.encrypt("TEST"))
+ )
}
@Test
fun emptyTextEncryptTest() {
assertFailsWith {
- decrypt("")
+ scrambler.decrypt("")
}
assertFailsWith {
- encrypt("", ApplicationProvider.getApplicationContext())
+ scrambler.encrypt("")
}
}
@Test
@SdkSuppress(minSdkVersion = 18)
fun emptyKeyStoreTest() {
- val text = encrypt("test", ApplicationProvider.getApplicationContext())
+ val text = scrambler.encrypt("test")
val keyStore = KeyStore.getInstance("AndroidKeyStore")
keyStore.load(null)
keyStore.deleteEntry("wulkanowy_password")
assertFailsWith {
- decrypt(text)
+ scrambler.decrypt(text)
}
}
}
From a05f1f70f7f8c7b7dbcc0aefa6f98cd021522943 Mon Sep 17 00:00:00 2001
From: JestemKamil <84380834+JestemKamil@users.noreply.github.com>
Date: Tue, 6 Feb 2024 18:21:56 +0100
Subject: [PATCH 18/50] Add colors to attendance and ! to unexcused lateness
(#2412)
---
.../modules/attendance/AttendanceAdapter.kt | 32 ++++++++++++++++++-
.../ui/modules/attendance/AttendanceDialog.kt | 12 +++++++
app/src/main/res/values-night-v31/styles.xml | 3 ++
app/src/main/res/values-night/styles.xml | 2 ++
app/src/main/res/values-v31/styles.xml | 3 ++
app/src/main/res/values/attrs.xml | 2 ++
app/src/main/res/values/colors.xml | 6 ++++
app/src/main/res/values/styles.xml | 2 ++
8 files changed, 61 insertions(+), 1 deletion(-)
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceAdapter.kt
index 39f376f65..4e9baac3a 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceAdapter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceAdapter.kt
@@ -1,5 +1,6 @@
package io.github.wulkanowy.ui.modules.attendance
+import android.graphics.Typeface
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@@ -10,6 +11,7 @@ import io.github.wulkanowy.data.db.entities.Attendance
import io.github.wulkanowy.data.enums.SentExcuseStatus
import io.github.wulkanowy.databinding.ItemAttendanceBinding
import io.github.wulkanowy.utils.descriptionRes
+import io.github.wulkanowy.utils.getThemeAttrColor
import io.github.wulkanowy.utils.isExcusableOrNotExcused
import javax.inject.Inject
@@ -39,7 +41,33 @@ class AttendanceAdapter @Inject constructor() :
root.context.getString(R.string.all_no_data)
}
attendanceItemDescription.setText(item.descriptionRes)
- attendanceItemAlert.isVisible = item.let { it.absence && !it.excused }
+
+ attendanceItemDescription.setTextColor(
+ root.context.getThemeAttrColor(
+ when {
+ item.absence && !item.excused -> R.attr.colorAttendanceAbsence
+ item.lateness && !item.excused -> R.attr.colorAttendanceLateness
+ else -> android.R.attr.textColorSecondary
+ }
+ )
+ )
+
+ if (item.exemption || item.excused) {
+ attendanceItemDescription.setTypeface(null, Typeface.BOLD)
+ } else {
+ attendanceItemDescription.setTypeface(null, Typeface.NORMAL)
+ }
+
+ attendanceItemAlert.isVisible =
+ item.let { (it.absence && !it.excused) || (it.lateness && !it.excused) }
+
+ attendanceItemAlert.setColorFilter(root.context.getThemeAttrColor(
+ when{
+ item.absence && !item.excused -> R.attr.colorAttendanceAbsence
+ item.lateness && !item.excused -> R.attr.colorAttendanceLateness
+ else -> android.R.attr.colorPrimary
+ }
+ ))
attendanceItemNumber.visibility = View.GONE
attendanceItemExcuseInfo.visibility = View.GONE
attendanceItemExcuseCheckbox.visibility = View.GONE
@@ -54,10 +82,12 @@ class AttendanceAdapter @Inject constructor() :
attendanceItemExcuseInfo.visibility = View.VISIBLE
attendanceItemAlert.visibility = View.INVISIBLE
}
+
SentExcuseStatus.DENIED -> {
attendanceItemExcuseInfo.setImageResource(R.drawable.ic_excuse_denied)
attendanceItemExcuseInfo.visibility = View.VISIBLE
}
+
else -> {
if (item.isExcusableOrNotExcused && excuseActionMode) {
attendanceItemNumber.visibility = View.GONE
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceDialog.kt
index c0026bee5..635033132 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceDialog.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceDialog.kt
@@ -6,10 +6,12 @@ import android.view.View
import androidx.core.os.bundleOf
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
+import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Attendance
import io.github.wulkanowy.databinding.DialogAttendanceBinding
import io.github.wulkanowy.ui.base.BaseDialogFragment
import io.github.wulkanowy.utils.descriptionRes
+import io.github.wulkanowy.utils.getThemeAttrColor
import io.github.wulkanowy.utils.serializable
import io.github.wulkanowy.utils.toFormattedString
@@ -44,6 +46,16 @@ class AttendanceDialog : BaseDialogFragment() {
with(binding) {
attendanceDialogSubjectValue.text = attendance.subject
attendanceDialogDescriptionValue.setText(attendance.descriptionRes)
+ attendanceDialogDescriptionValue.setTextColor(
+ root.context.getThemeAttrColor(
+ when {
+ attendance.absence && !attendance.excused -> R.attr.colorAttendanceAbsence
+ attendance.lateness && !attendance.excused -> R.attr.colorAttendanceLateness
+ else -> android.R.attr.textColorSecondary
+ }
+ )
+ )
+
attendanceDialogDateValue.text = attendance.date.toFormattedString()
attendanceDialogNumberValue.text = attendance.number.toString()
attendanceDialogClose.setOnClickListener { dismiss() }
diff --git a/app/src/main/res/values-night-v31/styles.xml b/app/src/main/res/values-night-v31/styles.xml
index 6e6c4d79c..808bc714d 100644
--- a/app/src/main/res/values-night-v31/styles.xml
+++ b/app/src/main/res/values-night-v31/styles.xml
@@ -32,6 +32,9 @@
- @color/material_dynamic_primary40
- @color/timetable_canceled_dark
- @color/timetable_change_dark
+ - @color/attendance_absence_dark
+ - @color/attendance_lateness_dark
+
- @color/colorErrorLight
- @color/colorDividerInverse
- @color/material_dynamic_secondary20
diff --git a/app/src/main/res/values-night/styles.xml b/app/src/main/res/values-night/styles.xml
index 5d9aa22a6..5840a051e 100644
--- a/app/src/main/res/values-night/styles.xml
+++ b/app/src/main/res/values-night/styles.xml
@@ -21,6 +21,8 @@
- @color/colorSurfaceDark
- @color/timetable_canceled_dark
- @color/timetable_change_dark
+ - @color/attendance_absence_dark
+ - @color/attendance_lateness_dark
- @color/colorErrorLight
- @color/colorDividerInverse
- @color/colorSwipeRefreshDark
diff --git a/app/src/main/res/values-v31/styles.xml b/app/src/main/res/values-v31/styles.xml
index bb47b22ed..358806681 100644
--- a/app/src/main/res/values-v31/styles.xml
+++ b/app/src/main/res/values-v31/styles.xml
@@ -34,6 +34,9 @@
- @color/material_dynamic_primary80
- @color/timetable_canceled_light
- @color/timetable_change_light
+ - @color/attendance_absence_light
+ - @color/attendance_lateness_light
+
- @color/colorError
- @color/colorDivider
- @color/material_dynamic_secondary90
diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml
index aa58fa09e..3c3fdb8e9 100644
--- a/app/src/main/res/values/attrs.xml
+++ b/app/src/main/res/values/attrs.xml
@@ -7,4 +7,6 @@
+
+
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index 87057c61d..8ad27ad88 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -49,6 +49,12 @@
#ff8f00
#ffd54f
+ #d32f2f
+ #e57373
+
+ #cd2a01
+ #f05d0e
+
#1f000000
#1fffffff
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index a0023dda1..b5b029501 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -19,6 +19,8 @@
- @color/colorSurface
- @color/timetable_canceled_light
- @color/timetable_change_light
+ - @color/attendance_absence_light
+ - @color/attendance_lateness_light
- @color/colorError
- @color/colorDivider
- @color/colorSwipeRefresh
From ed5166333ae3a574bc974737686afb0db994806a Mon Sep 17 00:00:00 2001
From: PoProstuSever <88079246+PoProstuSever@users.noreply.github.com>
Date: Tue, 6 Feb 2024 19:48:31 +0100
Subject: [PATCH 19/50] Add the ability to dynamically expand the application
window (#2413)
---
app/src/main/AndroidManifest.xml | 1 +
1 file changed, 1 insertion(+)
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 174c9a1fc..f43dfdd2c 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -44,6 +44,7 @@
android:networkSecurityConfig="@xml/network_security_config"
android:supportsRtl="false"
android:theme="@style/WulkanowyTheme"
+ android:resizeableActivity="true"
tools:ignore="DataExtractionRules,UnusedAttribute">
Date: Thu, 8 Feb 2024 07:49:17 +0100
Subject: [PATCH 20/50] Fix displaying lessons for tomorrow if there is no more
lessons for today (#2416)
---
.../IsStudentHasLessonsOnWeekendUseCase.kt | 33 +++++++++++++++++++
.../timetable/IsWeekendHasLessonsUseCase.kt | 17 ++++++++++
.../modules/dashboard/DashboardPresenter.kt | 12 +++++--
.../modules/timetable/TimetablePresenter.kt | 32 ++++--------------
4 files changed, 65 insertions(+), 29 deletions(-)
create mode 100644 app/src/main/java/io/github/wulkanowy/domain/timetable/IsStudentHasLessonsOnWeekendUseCase.kt
create mode 100644 app/src/main/java/io/github/wulkanowy/domain/timetable/IsWeekendHasLessonsUseCase.kt
diff --git a/app/src/main/java/io/github/wulkanowy/domain/timetable/IsStudentHasLessonsOnWeekendUseCase.kt b/app/src/main/java/io/github/wulkanowy/domain/timetable/IsStudentHasLessonsOnWeekendUseCase.kt
new file mode 100644
index 000000000..efe928e2b
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/domain/timetable/IsStudentHasLessonsOnWeekendUseCase.kt
@@ -0,0 +1,33 @@
+package io.github.wulkanowy.domain.timetable
+
+import io.github.wulkanowy.data.dataOrNull
+import io.github.wulkanowy.data.db.entities.Semester
+import io.github.wulkanowy.data.db.entities.Student
+import io.github.wulkanowy.data.repositories.TimetableRepository
+import io.github.wulkanowy.data.toFirstResult
+import io.github.wulkanowy.utils.monday
+import io.github.wulkanowy.utils.sunday
+import java.time.LocalDate
+import javax.inject.Inject
+
+class IsStudentHasLessonsOnWeekendUseCase @Inject constructor(
+ private val timetableRepository: TimetableRepository,
+ private val isWeekendHasLessonsUseCase: IsWeekendHasLessonsUseCase,
+) {
+
+ suspend operator fun invoke(
+ student: Student,
+ semester: Semester,
+ currentDate: LocalDate = LocalDate.now(),
+ ): Boolean {
+ val lessons = timetableRepository.getTimetable(
+ student = student,
+ semester = semester,
+ start = currentDate.monday,
+ end = currentDate.sunday,
+ forceRefresh = false,
+ timetableType = TimetableRepository.TimetableType.NORMAL
+ ).toFirstResult().dataOrNull?.lessons.orEmpty()
+ return isWeekendHasLessonsUseCase(lessons)
+ }
+}
diff --git a/app/src/main/java/io/github/wulkanowy/domain/timetable/IsWeekendHasLessonsUseCase.kt b/app/src/main/java/io/github/wulkanowy/domain/timetable/IsWeekendHasLessonsUseCase.kt
new file mode 100644
index 000000000..908c9df78
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/domain/timetable/IsWeekendHasLessonsUseCase.kt
@@ -0,0 +1,17 @@
+package io.github.wulkanowy.domain.timetable
+
+import io.github.wulkanowy.data.db.entities.Timetable
+import java.time.DayOfWeek
+import javax.inject.Inject
+
+class IsWeekendHasLessonsUseCase @Inject constructor() {
+
+ operator fun invoke(
+ lessons: List,
+ ): Boolean = lessons.any {
+ it.date.dayOfWeek in listOf(
+ DayOfWeek.SATURDAY,
+ DayOfWeek.SUNDAY,
+ )
+ }
+}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardPresenter.kt
index ad604499b..1e6f1c198 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardPresenter.kt
@@ -24,11 +24,13 @@ import io.github.wulkanowy.data.repositories.SemesterRepository
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.data.repositories.TimetableRepository
import io.github.wulkanowy.domain.adminmessage.GetAppropriateAdminMessageUseCase
+import io.github.wulkanowy.domain.timetable.IsStudentHasLessonsOnWeekendUseCase
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.AdsHelper
import io.github.wulkanowy.utils.calculatePercentage
import io.github.wulkanowy.utils.nextOrSameSchoolDay
+import io.github.wulkanowy.utils.sunday
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.combine
@@ -56,6 +58,7 @@ class DashboardPresenter @Inject constructor(
private val messageRepository: MessageRepository,
private val attendanceSummaryRepository: AttendanceSummaryRepository,
private val timetableRepository: TimetableRepository,
+ private val isStudentHasLessonsOnWeekendUseCase: IsStudentHasLessonsOnWeekendUseCase,
private val homeworkRepository: HomeworkRepository,
private val examRepository: ExamRepository,
private val conferenceRepository: ConferenceRepository,
@@ -435,14 +438,17 @@ class DashboardPresenter @Inject constructor(
private fun loadLessons(student: Student, forceRefresh: Boolean) {
flatResourceFlow {
val semester = semesterRepository.getCurrentSemester(student)
- val date = LocalDate.now().nextOrSameSchoolDay
+ val date = when (isStudentHasLessonsOnWeekendUseCase(student, semester)) {
+ true -> LocalDate.now()
+ else -> LocalDate.now().nextOrSameSchoolDay
+ }
timetableRepository.getTimetable(
student = student,
semester = semester,
start = date,
- end = date,
- forceRefresh = forceRefresh
+ end = date.sunday,
+ forceRefresh = forceRefresh,
)
}
.onEach {
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt
index 6b442d1c2..7e8c876ef 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt
@@ -2,7 +2,6 @@ package io.github.wulkanowy.ui.modules.timetable
import android.os.Handler
import android.os.Looper
-import io.github.wulkanowy.data.dataOrNull
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.db.entities.Timetable
@@ -20,8 +19,8 @@ import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.data.repositories.SemesterRepository
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.data.repositories.TimetableRepository
-import io.github.wulkanowy.data.toFirstResult
-import io.github.wulkanowy.data.waitForResult
+import io.github.wulkanowy.domain.timetable.IsStudentHasLessonsOnWeekendUseCase
+import io.github.wulkanowy.domain.timetable.IsWeekendHasLessonsUseCase
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.AnalyticsHelper
@@ -31,16 +30,12 @@ import io.github.wulkanowy.utils.isHolidays
import io.github.wulkanowy.utils.isJustFinished
import io.github.wulkanowy.utils.isShowTimeUntil
import io.github.wulkanowy.utils.left
-import io.github.wulkanowy.utils.monday
import io.github.wulkanowy.utils.nextOrSameSchoolDay
import io.github.wulkanowy.utils.nextSchoolDay
import io.github.wulkanowy.utils.previousSchoolDay
-import io.github.wulkanowy.utils.sunday
import io.github.wulkanowy.utils.toFormattedString
import io.github.wulkanowy.utils.until
-import kotlinx.coroutines.flow.firstOrNull
import timber.log.Timber
-import java.time.DayOfWeek
import java.time.Instant
import java.time.LocalDate
import java.time.LocalDate.now
@@ -54,6 +49,8 @@ class TimetablePresenter @Inject constructor(
errorHandler: ErrorHandler,
studentRepository: StudentRepository,
private val timetableRepository: TimetableRepository,
+ private val isStudentHasLessonsOnWeekendUseCase: IsStudentHasLessonsOnWeekendUseCase,
+ private val isWeekendHasLessonsUseCase: IsWeekendHasLessonsUseCase,
private val semesterRepository: SemesterRepository,
private val prefRepository: PreferencesRepository,
private val analytics: AnalyticsHelper,
@@ -165,7 +162,7 @@ class TimetablePresenter @Inject constructor(
}
.logResourceStatus("load timetable data")
.onResourceData {
- isWeekendHasLessons = isWeekendHasLessons || isWeekendHasLessons(it.lessons)
+ isWeekendHasLessons = isWeekendHasLessons || isWeekendHasLessonsUseCase(it.lessons)
view?.run {
enableSwipe(true)
@@ -199,15 +196,7 @@ class TimetablePresenter @Inject constructor(
private suspend fun checkInitialAndCurrentDate(student: Student, semester: Semester) {
if (initialDate == null) {
- val lessons = timetableRepository.getTimetable(
- student = student,
- semester = semester,
- start = now().monday,
- end = now().sunday,
- forceRefresh = false,
- timetableType = TimetableRepository.TimetableType.NORMAL
- ).toFirstResult().dataOrNull?.lessons.orEmpty()
- isWeekendHasLessons = isWeekendHasLessons(lessons)
+ isWeekendHasLessons = isStudentHasLessonsOnWeekendUseCase(student, semester)
initialDate = getInitialDate(semester)
}
@@ -216,15 +205,6 @@ class TimetablePresenter @Inject constructor(
}
}
- private fun isWeekendHasLessons(
- lessons: List,
- ): Boolean = lessons.any {
- it.date.dayOfWeek in listOf(
- DayOfWeek.SATURDAY,
- DayOfWeek.SUNDAY,
- )
- }
-
private fun getInitialDate(semester: Semester): LocalDate {
val now = now()
From 22f72981cb7eea613f6b6ac234fbb7c050ccccf2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rafa=C5=82=20Borcz?=
Date: Thu, 8 Feb 2024 09:16:09 +0100
Subject: [PATCH 21/50] Add descriptive grades (#2411)
---
.../59.json | 2501 +++++++++++++++++
.../io/github/wulkanowy/data/DataModule.kt | 4 +
.../github/wulkanowy/data/db/AppDatabase.kt | 10 +-
.../data/db/dao/GradeDescriptiveDao.kt | 15 +
.../data/db/entities/GradeDescriptive.kt | 27 +
.../wulkanowy/data/mappers/GradeMapper.kt | 16 +-
.../data/repositories/GradeRepository.kt | 63 +-
.../notifications/NewGradeNotification.kt | 26 +-
.../sync/notifications/NotificationType.kt | 4 +
.../services/sync/works/GradeWork.kt | 10 +
.../NotificationDebugPresenter.kt | 9 +
.../notification/mock/gradeDescriptive.kt | 48 +
.../ui/modules/grade/GradeAverageProvider.kt | 93 +-
.../ui/modules/grade/GradeSubject.kt | 2 +
.../grade/details/GradeDetailsPresenter.kt | 25 +-
.../grade/summary/GradeSummaryAdapter.kt | 52 +-
.../grade/summary/GradeSummaryFragment.kt | 4 +-
.../modules/grade/summary/GradeSummaryItem.kt | 9 +
.../grade/summary/GradeSummaryPresenter.kt | 36 +-
.../modules/grade/summary/GradeSummaryView.kt | 3 +-
.../main/res/layout/item_grade_summary.xml | 46 +-
app/src/main/res/values/strings.xml | 9 +
.../data/repositories/GradeRepositoryTest.kt | 14 +-
.../modules/grade/GradeAverageProviderTest.kt | 446 ++-
24 files changed, 3248 insertions(+), 224 deletions(-)
create mode 100644 app/schemas/io.github.wulkanowy.data.db.AppDatabase/59.json
create mode 100644 app/src/main/java/io/github/wulkanowy/data/db/dao/GradeDescriptiveDao.kt
create mode 100644 app/src/main/java/io/github/wulkanowy/data/db/entities/GradeDescriptive.kt
create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/gradeDescriptive.kt
create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryItem.kt
diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/59.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/59.json
new file mode 100644
index 000000000..a3f2e0dc6
--- /dev/null
+++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/59.json
@@ -0,0 +1,2501 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 59,
+ "identityHash": "3bd95e40b587e8131a2a2c23aee538c1",
+ "entities": [
+ {
+ "tableName": "Students",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`scrapper_base_url` TEXT NOT NULL, `scrapper_domain_suffix` TEXT NOT NULL DEFAULT '', `mobile_base_url` TEXT NOT NULL, `login_type` TEXT NOT NULL, `login_mode` TEXT NOT NULL, `certificate_key` TEXT NOT NULL, `private_key` TEXT NOT NULL, `is_parent` INTEGER NOT NULL, `email` TEXT NOT NULL, `password` TEXT NOT NULL, `symbol` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `user_login_id` INTEGER NOT NULL, `user_name` TEXT NOT NULL, `student_name` TEXT NOT NULL, `school_id` TEXT NOT NULL, `school_short` TEXT NOT NULL, `school_name` TEXT NOT NULL, `class_name` TEXT NOT NULL, `class_id` INTEGER NOT NULL, `is_current` INTEGER NOT NULL, `registration_date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `nick` TEXT NOT NULL, `avatar_color` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "scrapperBaseUrl",
+ "columnName": "scrapper_base_url",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "scrapperDomainSuffix",
+ "columnName": "scrapper_domain_suffix",
+ "affinity": "TEXT",
+ "notNull": true,
+ "defaultValue": "''"
+ },
+ {
+ "fieldPath": "mobileBaseUrl",
+ "columnName": "mobile_base_url",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "loginType",
+ "columnName": "login_type",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "loginMode",
+ "columnName": "login_mode",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "certificateKey",
+ "columnName": "certificate_key",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "privateKey",
+ "columnName": "private_key",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isParent",
+ "columnName": "is_parent",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "email",
+ "columnName": "email",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "password",
+ "columnName": "password",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "symbol",
+ "columnName": "symbol",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "userLoginId",
+ "columnName": "user_login_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "userName",
+ "columnName": "user_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "studentName",
+ "columnName": "student_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "schoolSymbol",
+ "columnName": "school_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "schoolShortName",
+ "columnName": "school_short",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "schoolName",
+ "columnName": "school_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "className",
+ "columnName": "class_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "classId",
+ "columnName": "class_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isCurrent",
+ "columnName": "is_current",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "registrationDate",
+ "columnName": "registration_date",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "nick",
+ "columnName": "nick",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "avatarColor",
+ "columnName": "avatar_color",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [
+ {
+ "name": "index_Students_email_symbol_student_id_school_id_class_id",
+ "unique": true,
+ "columnNames": [
+ "email",
+ "symbol",
+ "student_id",
+ "school_id",
+ "class_id"
+ ],
+ "orders": [],
+ "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Students_email_symbol_student_id_school_id_class_id` ON `${TABLE_NAME}` (`email`, `symbol`, `student_id`, `school_id`, `class_id`)"
+ }
+ ],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Semesters",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `kindergarten_diary_id` INTEGER NOT NULL DEFAULT 0, `diary_name` TEXT NOT NULL, `school_year` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `semester_name` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_current` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "diaryId",
+ "columnName": "diary_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "kindergartenDiaryId",
+ "columnName": "kindergarten_diary_id",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "0"
+ },
+ {
+ "fieldPath": "diaryName",
+ "columnName": "diary_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "schoolYear",
+ "columnName": "school_year",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "semesterId",
+ "columnName": "semester_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "semesterName",
+ "columnName": "semester_name",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "start",
+ "columnName": "start",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "end",
+ "columnName": "end",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "classId",
+ "columnName": "class_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "unitId",
+ "columnName": "unit_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "current",
+ "columnName": "is_current",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [
+ {
+ "name": "index_Semesters_student_id_diary_id_kindergarten_diary_id_semester_id",
+ "unique": true,
+ "columnNames": [
+ "student_id",
+ "diary_id",
+ "kindergarten_diary_id",
+ "semester_id"
+ ],
+ "orders": [],
+ "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Semesters_student_id_diary_id_kindergarten_diary_id_semester_id` ON `${TABLE_NAME}` (`student_id`, `diary_id`, `kindergarten_diary_id`, `semester_id`)"
+ }
+ ],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Exams",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `group` TEXT NOT NULL, `type` TEXT NOT NULL, `description` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "diaryId",
+ "columnName": "diary_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "date",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "entryDate",
+ "columnName": "entry_date",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subject",
+ "columnName": "subject",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "group",
+ "columnName": "group",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "description",
+ "columnName": "description",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "teacher",
+ "columnName": "teacher",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "teacherSymbol",
+ "columnName": "teacher_symbol",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isNotified",
+ "columnName": "is_notified",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Timetable",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `number` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `subjectOld` TEXT NOT NULL, `group` TEXT NOT NULL, `room` TEXT NOT NULL, `roomOld` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacherOld` TEXT NOT NULL, `info` TEXT NOT NULL, `student_plan` INTEGER NOT NULL, `changes` INTEGER NOT NULL, `canceled` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "diaryId",
+ "columnName": "diary_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "number",
+ "columnName": "number",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "start",
+ "columnName": "start",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "end",
+ "columnName": "end",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "date",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subject",
+ "columnName": "subject",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subjectOld",
+ "columnName": "subjectOld",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "group",
+ "columnName": "group",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "room",
+ "columnName": "room",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "roomOld",
+ "columnName": "roomOld",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "teacher",
+ "columnName": "teacher",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "teacherOld",
+ "columnName": "teacherOld",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "info",
+ "columnName": "info",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isStudentPlan",
+ "columnName": "student_plan",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "changes",
+ "columnName": "changes",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "canceled",
+ "columnName": "canceled",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isNotified",
+ "columnName": "is_notified",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Attendance",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `time_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `excused` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `excusable` INTEGER NOT NULL, `excuse_status` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "diaryId",
+ "columnName": "diary_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "timeId",
+ "columnName": "time_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "date",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "number",
+ "columnName": "number",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subject",
+ "columnName": "subject",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "presence",
+ "columnName": "presence",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "absence",
+ "columnName": "absence",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "exemption",
+ "columnName": "exemption",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lateness",
+ "columnName": "lateness",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "excused",
+ "columnName": "excused",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "deleted",
+ "columnName": "deleted",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "excusable",
+ "columnName": "excusable",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "excuseStatus",
+ "columnName": "excuse_status",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isNotified",
+ "columnName": "is_notified",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "AttendanceSummary",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `subject_id` INTEGER NOT NULL, `month` INTEGER NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `absence_excused` INTEGER NOT NULL, `absence_for_school_reasons` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `lateness_excused` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "diaryId",
+ "columnName": "diary_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subjectId",
+ "columnName": "subject_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "month",
+ "columnName": "month",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "presence",
+ "columnName": "presence",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "absence",
+ "columnName": "absence",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "absenceExcused",
+ "columnName": "absence_excused",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "absenceForSchoolReasons",
+ "columnName": "absence_for_school_reasons",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lateness",
+ "columnName": "lateness",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "latenessExcused",
+ "columnName": "lateness_excused",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "exemption",
+ "columnName": "exemption",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Grades",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `entry` TEXT NOT NULL, `value` REAL NOT NULL, `modifier` REAL NOT NULL, `comment` TEXT NOT NULL, `color` TEXT NOT NULL, `grade_symbol` TEXT NOT NULL, `description` TEXT NOT NULL, `weight` TEXT NOT NULL, `weightValue` REAL NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "semesterId",
+ "columnName": "semester_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subject",
+ "columnName": "subject",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "entry",
+ "columnName": "entry",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "value",
+ "columnName": "value",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "modifier",
+ "columnName": "modifier",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "comment",
+ "columnName": "comment",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "color",
+ "columnName": "color",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "gradeSymbol",
+ "columnName": "grade_symbol",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "description",
+ "columnName": "description",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "weight",
+ "columnName": "weight",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "weightValue",
+ "columnName": "weightValue",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "date",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "teacher",
+ "columnName": "teacher",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isRead",
+ "columnName": "is_read",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isNotified",
+ "columnName": "is_notified",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "GradesSummary",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `position` INTEGER NOT NULL, `subject` TEXT NOT NULL, `predicted_grade` TEXT NOT NULL, `final_grade` TEXT NOT NULL, `proposed_points` TEXT NOT NULL, `final_points` TEXT NOT NULL, `points_sum` TEXT NOT NULL, `average` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_predicted_grade_notified` INTEGER NOT NULL, `is_final_grade_notified` INTEGER NOT NULL, `predicted_grade_last_change` INTEGER NOT NULL, `final_grade_last_change` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "semesterId",
+ "columnName": "semester_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "position",
+ "columnName": "position",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subject",
+ "columnName": "subject",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "predictedGrade",
+ "columnName": "predicted_grade",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "finalGrade",
+ "columnName": "final_grade",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "proposedPoints",
+ "columnName": "proposed_points",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "finalPoints",
+ "columnName": "final_points",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "pointsSum",
+ "columnName": "points_sum",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "average",
+ "columnName": "average",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isPredictedGradeNotified",
+ "columnName": "is_predicted_grade_notified",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isFinalGradeNotified",
+ "columnName": "is_final_grade_notified",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "predictedGradeLastChange",
+ "columnName": "predicted_grade_last_change",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "finalGradeLastChange",
+ "columnName": "final_grade_last_change",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "GradePartialStatistics",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `class_average` TEXT NOT NULL, `student_average` TEXT NOT NULL, `class_amounts` TEXT NOT NULL, `student_amounts` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "semesterId",
+ "columnName": "semester_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subject",
+ "columnName": "subject",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "classAverage",
+ "columnName": "class_average",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "studentAverage",
+ "columnName": "student_average",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "classAmounts",
+ "columnName": "class_amounts",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "studentAmounts",
+ "columnName": "student_amounts",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "GradesPointsStatistics",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `others` REAL NOT NULL, `student` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "semesterId",
+ "columnName": "semester_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subject",
+ "columnName": "subject",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "others",
+ "columnName": "others",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "student",
+ "columnName": "student",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "GradeSemesterStatistics",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `amounts` TEXT NOT NULL, `student_grade` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "semesterId",
+ "columnName": "semester_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subject",
+ "columnName": "subject",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "amounts",
+ "columnName": "amounts",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "studentGrade",
+ "columnName": "student_grade",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Messages",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`email` TEXT NOT NULL, `message_global_key` TEXT NOT NULL, `mailbox_key` TEXT NOT NULL, `message_id` INTEGER NOT NULL, `correspondents` TEXT NOT NULL, `subject` TEXT NOT NULL, `date` INTEGER NOT NULL, `folder_id` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `read_by` INTEGER, `unread_by` INTEGER, `has_attachments` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `content` TEXT NOT NULL, `sender` TEXT, `recipients` TEXT)",
+ "fields": [
+ {
+ "fieldPath": "email",
+ "columnName": "email",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "messageGlobalKey",
+ "columnName": "message_global_key",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "mailboxKey",
+ "columnName": "mailbox_key",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "messageId",
+ "columnName": "message_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "correspondents",
+ "columnName": "correspondents",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subject",
+ "columnName": "subject",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "date",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "folderId",
+ "columnName": "folder_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "unread",
+ "columnName": "unread",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "readBy",
+ "columnName": "read_by",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "unreadBy",
+ "columnName": "unread_by",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "hasAttachments",
+ "columnName": "has_attachments",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isNotified",
+ "columnName": "is_notified",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "content",
+ "columnName": "content",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "sender",
+ "columnName": "sender",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "recipients",
+ "columnName": "recipients",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "MessageAttachments",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`message_global_key` TEXT NOT NULL, `url` TEXT NOT NULL, `filename` TEXT NOT NULL, PRIMARY KEY(`message_global_key`, `url`, `filename`))",
+ "fields": [
+ {
+ "fieldPath": "messageGlobalKey",
+ "columnName": "message_global_key",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "url",
+ "columnName": "url",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "filename",
+ "columnName": "filename",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "message_global_key",
+ "url",
+ "filename"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Notes",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `category` TEXT NOT NULL, `category_type` INTEGER NOT NULL, `is_points_show` INTEGER NOT NULL, `points` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "date",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "teacher",
+ "columnName": "teacher",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "teacherSymbol",
+ "columnName": "teacher_symbol",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "category",
+ "columnName": "category",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "categoryType",
+ "columnName": "category_type",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isPointsShow",
+ "columnName": "is_points_show",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "points",
+ "columnName": "points",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "content",
+ "columnName": "content",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isRead",
+ "columnName": "is_read",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isNotified",
+ "columnName": "is_notified",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Homework",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `attachments` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_done` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `is_added_by_user` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "semesterId",
+ "columnName": "semester_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "date",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "entryDate",
+ "columnName": "entry_date",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subject",
+ "columnName": "subject",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "content",
+ "columnName": "content",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "teacher",
+ "columnName": "teacher",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "teacherSymbol",
+ "columnName": "teacher_symbol",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "attachments",
+ "columnName": "attachments",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isDone",
+ "columnName": "is_done",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isNotified",
+ "columnName": "is_notified",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isAddedByUser",
+ "columnName": "is_added_by_user",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Subjects",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "diaryId",
+ "columnName": "diary_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "realId",
+ "columnName": "real_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "LuckyNumbers",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `lucky_number` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "date",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "luckyNumber",
+ "columnName": "lucky_number",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isNotified",
+ "columnName": "is_notified",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "CompletedLesson",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `topic` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `substitution` TEXT NOT NULL, `absence` TEXT NOT NULL, `resources` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "diaryId",
+ "columnName": "diary_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "date",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "number",
+ "columnName": "number",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subject",
+ "columnName": "subject",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "topic",
+ "columnName": "topic",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "teacher",
+ "columnName": "teacher",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "teacherSymbol",
+ "columnName": "teacher_symbol",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "substitution",
+ "columnName": "substitution",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "absence",
+ "columnName": "absence",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "resources",
+ "columnName": "resources",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Mailboxes",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`globalKey` TEXT NOT NULL, `email` TEXT NOT NULL, `symbol` TEXT NOT NULL, `schoolId` TEXT NOT NULL, `fullName` TEXT NOT NULL, `userName` TEXT NOT NULL, `studentName` TEXT NOT NULL, `schoolNameShort` TEXT NOT NULL, `type` TEXT NOT NULL, PRIMARY KEY(`globalKey`))",
+ "fields": [
+ {
+ "fieldPath": "globalKey",
+ "columnName": "globalKey",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "email",
+ "columnName": "email",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "symbol",
+ "columnName": "symbol",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "schoolId",
+ "columnName": "schoolId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "fullName",
+ "columnName": "fullName",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "userName",
+ "columnName": "userName",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "studentName",
+ "columnName": "studentName",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "schoolNameShort",
+ "columnName": "schoolNameShort",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "globalKey"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Recipients",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`mailboxGlobalKey` TEXT NOT NULL, `studentMailboxGlobalKey` TEXT NOT NULL, `fullName` TEXT NOT NULL, `userName` TEXT NOT NULL, `schoolShortName` TEXT NOT NULL, `type` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "mailboxGlobalKey",
+ "columnName": "mailboxGlobalKey",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "studentMailboxGlobalKey",
+ "columnName": "studentMailboxGlobalKey",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "fullName",
+ "columnName": "fullName",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "userName",
+ "columnName": "userName",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "schoolShortName",
+ "columnName": "schoolShortName",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "MobileDevices",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`user_login_id` INTEGER NOT NULL, `device_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "userLoginId",
+ "columnName": "user_login_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "deviceId",
+ "columnName": "device_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "date",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Teachers",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `short_name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "classId",
+ "columnName": "class_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subject",
+ "columnName": "subject",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "shortName",
+ "columnName": "short_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "School",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `address` TEXT NOT NULL, `contact` TEXT NOT NULL, `headmaster` TEXT NOT NULL, `pedagogue` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "classId",
+ "columnName": "class_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "address",
+ "columnName": "address",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "contact",
+ "columnName": "contact",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "headmaster",
+ "columnName": "headmaster",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "pedagogue",
+ "columnName": "pedagogue",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Conferences",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `subject` TEXT NOT NULL, `agenda` TEXT NOT NULL, `present_on_conference` TEXT NOT NULL, `conference_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "diaryId",
+ "columnName": "diary_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "title",
+ "columnName": "title",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subject",
+ "columnName": "subject",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "agenda",
+ "columnName": "agenda",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "presentOnConference",
+ "columnName": "present_on_conference",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "conferenceId",
+ "columnName": "conference_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "date",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isNotified",
+ "columnName": "is_notified",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "TimetableAdditional",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `repeat_id` BLOB DEFAULT NULL, `is_added_by_user` INTEGER NOT NULL DEFAULT 0)",
+ "fields": [
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "diaryId",
+ "columnName": "diary_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "start",
+ "columnName": "start",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "end",
+ "columnName": "end",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "date",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subject",
+ "columnName": "subject",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "repeatId",
+ "columnName": "repeat_id",
+ "affinity": "BLOB",
+ "notNull": false,
+ "defaultValue": "NULL"
+ },
+ {
+ "fieldPath": "isAddedByUser",
+ "columnName": "is_added_by_user",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "0"
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "StudentInfo",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `full_name` TEXT NOT NULL, `first_name` TEXT NOT NULL, `second_name` TEXT NOT NULL, `surname` TEXT NOT NULL, `birth_date` INTEGER NOT NULL, `birth_place` TEXT NOT NULL, `gender` TEXT NOT NULL, `has_polish_citizenship` INTEGER NOT NULL, `family_name` TEXT NOT NULL, `parents_names` TEXT NOT NULL, `address` TEXT NOT NULL, `registered_address` TEXT NOT NULL, `correspondence_address` TEXT NOT NULL, `phone_number` TEXT NOT NULL, `cell_phone_number` TEXT NOT NULL, `email` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `first_guardian_full_name` TEXT, `first_guardian_kinship` TEXT, `first_guardian_address` TEXT, `first_guardian_phones` TEXT, `first_guardian_email` TEXT, `second_guardian_full_name` TEXT, `second_guardian_kinship` TEXT, `second_guardian_address` TEXT, `second_guardian_phones` TEXT, `second_guardian_email` TEXT)",
+ "fields": [
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "fullName",
+ "columnName": "full_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "firstName",
+ "columnName": "first_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "secondName",
+ "columnName": "second_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "surname",
+ "columnName": "surname",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "birthDate",
+ "columnName": "birth_date",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "birthPlace",
+ "columnName": "birth_place",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "gender",
+ "columnName": "gender",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "hasPolishCitizenship",
+ "columnName": "has_polish_citizenship",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "familyName",
+ "columnName": "family_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "parentsNames",
+ "columnName": "parents_names",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "address",
+ "columnName": "address",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "registeredAddress",
+ "columnName": "registered_address",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "correspondenceAddress",
+ "columnName": "correspondence_address",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "phoneNumber",
+ "columnName": "phone_number",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "cellPhoneNumber",
+ "columnName": "cell_phone_number",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "email",
+ "columnName": "email",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "firstGuardian.fullName",
+ "columnName": "first_guardian_full_name",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "firstGuardian.kinship",
+ "columnName": "first_guardian_kinship",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "firstGuardian.address",
+ "columnName": "first_guardian_address",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "firstGuardian.phones",
+ "columnName": "first_guardian_phones",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "firstGuardian.email",
+ "columnName": "first_guardian_email",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "secondGuardian.fullName",
+ "columnName": "second_guardian_full_name",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "secondGuardian.kinship",
+ "columnName": "second_guardian_kinship",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "secondGuardian.address",
+ "columnName": "second_guardian_address",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "secondGuardian.phones",
+ "columnName": "second_guardian_phones",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "secondGuardian.email",
+ "columnName": "second_guardian_email",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "TimetableHeaders",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "diaryId",
+ "columnName": "diary_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "date",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "content",
+ "columnName": "content",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "SchoolAnnouncements",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`user_login_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "userLoginId",
+ "columnName": "user_login_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "date",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subject",
+ "columnName": "subject",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "content",
+ "columnName": "content",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isNotified",
+ "columnName": "is_notified",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Notifications",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `type` TEXT NOT NULL, `destination` TEXT NOT NULL DEFAULT '{\"type\":\"io.github.wulkanowy.ui.modules.Destination.Dashboard\"}', `date` INTEGER NOT NULL, `data` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "title",
+ "columnName": "title",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "content",
+ "columnName": "content",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "destination",
+ "columnName": "destination",
+ "affinity": "TEXT",
+ "notNull": true,
+ "defaultValue": "'{\"type\":\"io.github.wulkanowy.ui.modules.Destination.Dashboard\"}'"
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "date",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "data",
+ "columnName": "data",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "AdminMessages",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `version_name` INTEGER, `version_max` INTEGER, `target_register_host` TEXT, `target_flavor` TEXT, `destination_url` TEXT, `priority` TEXT NOT NULL, `types` TEXT NOT NULL DEFAULT '[]', `is_ok_visible` INTEGER NOT NULL DEFAULT 0, `is_x_visible` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`id`))",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "title",
+ "columnName": "title",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "content",
+ "columnName": "content",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "versionMin",
+ "columnName": "version_name",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "versionMax",
+ "columnName": "version_max",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "targetRegisterHost",
+ "columnName": "target_register_host",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "targetFlavor",
+ "columnName": "target_flavor",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "destinationUrl",
+ "columnName": "destination_url",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "priority",
+ "columnName": "priority",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "types",
+ "columnName": "types",
+ "affinity": "TEXT",
+ "notNull": true,
+ "defaultValue": "'[]'"
+ },
+ {
+ "fieldPath": "isOkVisible",
+ "columnName": "is_ok_visible",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "0"
+ },
+ {
+ "fieldPath": "isXVisible",
+ "columnName": "is_x_visible",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "0"
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "GradesDescriptive",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `description` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "semesterId",
+ "columnName": "semester_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subject",
+ "columnName": "subject",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "description",
+ "columnName": "description",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isNotified",
+ "columnName": "is_notified",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ }
+ ],
+ "views": [],
+ "setupQueries": [
+ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '3bd95e40b587e8131a2a2c23aee538c1')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/io/github/wulkanowy/data/DataModule.kt b/app/src/main/java/io/github/wulkanowy/data/DataModule.kt
index 950e817bb..7c9cf9a3c 100644
--- a/app/src/main/java/io/github/wulkanowy/data/DataModule.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/DataModule.kt
@@ -253,4 +253,8 @@ internal class DataModule {
@Singleton
@Provides
fun provideAdminMessageDao(database: AppDatabase) = database.adminMessagesDao
+
+ @Singleton
+ @Provides
+ fun provideGradeDescriptiveDao(database: AppDatabase) = database.gradeDescriptiveDao
}
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt b/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt
index a2f230f4c..8e5841fe7 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt
@@ -14,6 +14,7 @@ import io.github.wulkanowy.data.db.dao.CompletedLessonsDao
import io.github.wulkanowy.data.db.dao.ConferenceDao
import io.github.wulkanowy.data.db.dao.ExamDao
import io.github.wulkanowy.data.db.dao.GradeDao
+import io.github.wulkanowy.data.db.dao.GradeDescriptiveDao
import io.github.wulkanowy.data.db.dao.GradePartialStatisticsDao
import io.github.wulkanowy.data.db.dao.GradePointsStatisticsDao
import io.github.wulkanowy.data.db.dao.GradeSemesterStatisticsDao
@@ -44,6 +45,7 @@ import io.github.wulkanowy.data.db.entities.CompletedLesson
import io.github.wulkanowy.data.db.entities.Conference
import io.github.wulkanowy.data.db.entities.Exam
import io.github.wulkanowy.data.db.entities.Grade
+import io.github.wulkanowy.data.db.entities.GradeDescriptive
import io.github.wulkanowy.data.db.entities.GradePartialStatistics
import io.github.wulkanowy.data.db.entities.GradePointsStatistics
import io.github.wulkanowy.data.db.entities.GradeSemesterStatistics
@@ -154,7 +156,8 @@ import javax.inject.Singleton
TimetableHeader::class,
SchoolAnnouncement::class,
Notification::class,
- AdminMessage::class
+ AdminMessage::class,
+ GradeDescriptive::class,
],
autoMigrations = [
AutoMigration(from = 44, to = 45),
@@ -165,6 +168,7 @@ import javax.inject.Singleton
AutoMigration(from = 55, to = 56),
AutoMigration(from = 56, to = 57, spec = Migration57::class),
AutoMigration(from = 57, to = 58, spec = Migration58::class),
+ AutoMigration(from = 58, to = 59),
],
version = AppDatabase.VERSION_SCHEMA,
exportSchema = true
@@ -173,7 +177,7 @@ import javax.inject.Singleton
abstract class AppDatabase : RoomDatabase() {
companion object {
- const val VERSION_SCHEMA = 58
+ const val VERSION_SCHEMA = 59
fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf(
Migration2(),
@@ -298,4 +302,6 @@ abstract class AppDatabase : RoomDatabase() {
abstract val notificationDao: NotificationDao
abstract val adminMessagesDao: AdminMessageDao
+
+ abstract val gradeDescriptiveDao: GradeDescriptiveDao
}
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeDescriptiveDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeDescriptiveDao.kt
new file mode 100644
index 000000000..6282c0804
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeDescriptiveDao.kt
@@ -0,0 +1,15 @@
+package io.github.wulkanowy.data.db.dao
+
+import androidx.room.Dao
+import androidx.room.Query
+import io.github.wulkanowy.data.db.entities.GradeDescriptive
+import kotlinx.coroutines.flow.Flow
+import javax.inject.Singleton
+
+@Singleton
+@Dao
+interface GradeDescriptiveDao : BaseDao {
+
+ @Query("SELECT * FROM GradesDescriptive WHERE semester_id = :semesterId AND student_id = :studentId")
+ fun loadAll(semesterId: Int, studentId: Int): Flow>
+}
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/GradeDescriptive.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/GradeDescriptive.kt
new file mode 100644
index 000000000..9aec9599a
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/GradeDescriptive.kt
@@ -0,0 +1,27 @@
+package io.github.wulkanowy.data.db.entities
+
+import androidx.room.ColumnInfo
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+import java.io.Serializable
+
+@Entity(tableName = "GradesDescriptive")
+data class GradeDescriptive(
+
+ @ColumnInfo(name = "semester_id")
+ val semesterId: Int,
+
+ @ColumnInfo(name = "student_id")
+ val studentId: Int,
+
+ val subject: String,
+
+ val description: String,
+) : Serializable {
+
+ @PrimaryKey(autoGenerate = true)
+ var id: Long = 0
+
+ @ColumnInfo(name = "is_notified")
+ var isNotified: Boolean = true
+}
diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/GradeMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/GradeMapper.kt
index 178de682a..66e922171 100644
--- a/app/src/main/java/io/github/wulkanowy/data/mappers/GradeMapper.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/mappers/GradeMapper.kt
@@ -1,10 +1,12 @@
package io.github.wulkanowy.data.mappers
import io.github.wulkanowy.data.db.entities.Grade
+import io.github.wulkanowy.data.db.entities.GradeDescriptive
import io.github.wulkanowy.data.db.entities.GradeSummary
import io.github.wulkanowy.data.db.entities.Semester
-import io.github.wulkanowy.sdk.pojo.GradeSummary as SdkGradeSummary
import io.github.wulkanowy.sdk.pojo.Grade as SdkGrade
+import io.github.wulkanowy.sdk.pojo.GradeDescriptive as SdkGradeDescriptive
+import io.github.wulkanowy.sdk.pojo.GradeSummary as SdkGradeSummary
fun List.mapToEntities(semester: Semester) = map {
Grade(
@@ -40,3 +42,15 @@ fun List.mapToEntities(semester: Semester) = map {
average = it.average
)
}
+
+@JvmName("mapGradeDescriptiveToEntities")
+fun List.mapToEntities(semester: Semester) = map {
+ GradeDescriptive(
+ semesterId = semester.semesterId,
+ studentId = semester.studentId,
+ subject = it.subject,
+ description = it.description
+ )
+}
+
+
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/GradeRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/GradeRepository.kt
index b2bb7b906..1e2ea9354 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/GradeRepository.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/GradeRepository.kt
@@ -1,15 +1,22 @@
package io.github.wulkanowy.data.repositories
import io.github.wulkanowy.data.db.dao.GradeDao
+import io.github.wulkanowy.data.db.dao.GradeDescriptiveDao
import io.github.wulkanowy.data.db.dao.GradeSummaryDao
import io.github.wulkanowy.data.db.entities.Grade
+import io.github.wulkanowy.data.db.entities.GradeDescriptive
import io.github.wulkanowy.data.db.entities.GradeSummary
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk
-import io.github.wulkanowy.utils.*
+import io.github.wulkanowy.utils.AutoRefreshHelper
+import io.github.wulkanowy.utils.getRefreshKey
+import io.github.wulkanowy.utils.init
+import io.github.wulkanowy.utils.switchSemester
+import io.github.wulkanowy.utils.toLocalDate
+import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
@@ -22,14 +29,13 @@ import javax.inject.Singleton
class GradeRepository @Inject constructor(
private val gradeDb: GradeDao,
private val gradeSummaryDb: GradeSummaryDao,
+ private val gradeDescriptiveDb: GradeDescriptiveDao,
private val sdk: Sdk,
private val refreshHelper: AutoRefreshHelper,
) {
private val saveFetchResultMutex = Mutex()
- private val cacheKey = "grade"
-
fun getGrades(
student: Student,
semester: Semester,
@@ -41,30 +47,52 @@ class GradeRepository @Inject constructor(
//When details is empty and summary is not, app will not use summary cache - edge case
it.first.isEmpty()
},
- shouldFetch = { (details, summaries) ->
- val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester))
- details.isEmpty() || summaries.isEmpty() || forceRefresh || isExpired
+ shouldFetch = { (details, summaries, descriptive) ->
+ val isExpired =
+ refreshHelper.shouldBeRefreshed(getRefreshKey(GRADE_CACHE_KEY, semester))
+ details.isEmpty() || (summaries.isEmpty() && descriptive.isEmpty()) || forceRefresh || isExpired
},
query = {
val detailsFlow = gradeDb.loadAll(semester.semesterId, semester.studentId)
val summaryFlow = gradeSummaryDb.loadAll(semester.semesterId, semester.studentId)
- detailsFlow.combine(summaryFlow) { details, summaries -> details to summaries }
+ val descriptiveFlow =
+ gradeDescriptiveDb.loadAll(semester.semesterId, semester.studentId)
+
+ combine(detailsFlow, summaryFlow, descriptiveFlow) { details, summaries, descriptive ->
+ Triple(details, summaries, descriptive)
+ }
},
fetch = {
- val (details, summary) = sdk.init(student)
+ val (details, summary, descriptive) = sdk.init(student)
.switchSemester(semester)
.getGrades(semester.semesterId)
- details.mapToEntities(semester) to summary.mapToEntities(semester)
+ Triple(
+ details.mapToEntities(semester),
+ summary.mapToEntities(semester),
+ descriptive.mapToEntities(semester)
+ )
},
- saveFetchResult = { (oldDetails, oldSummary), (newDetails, newSummary) ->
+ saveFetchResult = { (oldDetails, oldSummary, oldDescriptive), (newDetails, newSummary, newDescriptive) ->
refreshGradeDetails(student, oldDetails, newDetails, notify)
refreshGradeSummaries(oldSummary, newSummary, notify)
+ refreshGradeDescriptions(oldDescriptive, newDescriptive, notify)
- refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester))
+ refreshHelper.updateLastRefreshTimestamp(getRefreshKey(GRADE_CACHE_KEY, semester))
}
)
+ private suspend fun refreshGradeDescriptions(
+ old: List,
+ new: List,
+ notify: Boolean
+ ) {
+ gradeDescriptiveDb.deleteAll(old uniqueSubtract new)
+ gradeDescriptiveDb.insertAll((new uniqueSubtract old).onEach {
+ if (notify) it.isNotified = false
+ })
+ }
+
private suspend fun refreshGradeDetails(
student: Student,
oldGrades: List,
@@ -132,6 +160,10 @@ class GradeRepository @Inject constructor(
return gradeSummaryDb.loadAll(semester.semesterId, semester.studentId)
}
+ fun getGradesDescriptiveFromDatabase(semester: Semester): Flow> {
+ return gradeDescriptiveDb.loadAll(semester.semesterId, semester.studentId)
+ }
+
suspend fun updateGrade(grade: Grade) {
return gradeDb.updateAll(listOf(grade))
}
@@ -143,4 +175,13 @@ class GradeRepository @Inject constructor(
suspend fun updateGradesSummary(gradesSummary: List) {
return gradeSummaryDb.updateAll(gradesSummary)
}
+
+ suspend fun updateGradesDescriptive(gradesDescriptive: List) {
+ return gradeDescriptiveDb.updateAll(gradesDescriptive)
+ }
+
+ private companion object {
+
+ private const val GRADE_CACHE_KEY = "grade"
+ }
}
diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewGradeNotification.kt b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewGradeNotification.kt
index 9b49ed178..7f90bbddc 100644
--- a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewGradeNotification.kt
+++ b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewGradeNotification.kt
@@ -4,12 +4,12 @@ import android.content.Context
import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Grade
+import io.github.wulkanowy.data.db.entities.GradeDescriptive
import io.github.wulkanowy.data.db.entities.GradeSummary
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.pojos.GroupNotificationData
import io.github.wulkanowy.data.pojos.NotificationData
import io.github.wulkanowy.ui.modules.Destination
-import io.github.wulkanowy.ui.modules.splash.SplashActivity
import io.github.wulkanowy.utils.getPlural
import javax.inject.Inject
@@ -88,4 +88,28 @@ class NewGradeNotification @Inject constructor(
appNotificationManager.sendMultipleNotifications(groupNotificationData, student)
}
+
+ suspend fun notifyDescriptive(items: List, student: Student) {
+ val notificationDataList = items.map {
+ NotificationData(
+ title = context.getPlural(R.plurals.grade_new_items_descriptive, 1),
+ content = "${it.subject}: ${it.description}",
+ destination = Destination.Grade,
+ )
+ }
+
+ val groupNotificationData = GroupNotificationData(
+ notificationDataList = notificationDataList,
+ title = context.getPlural(R.plurals.grade_new_items_descriptive, items.size),
+ content = context.getPlural(
+ R.plurals.grade_notify_new_items_descriptive,
+ items.size,
+ items.size
+ ),
+ destination = Destination.Grade,
+ type = NotificationType.NEW_GRADE_DESCRIPTIVE
+ )
+
+ appNotificationManager.sendMultipleNotifications(groupNotificationData, student)
+ }
}
diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NotificationType.kt b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NotificationType.kt
index 023ae2e4c..4e7f27351 100644
--- a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NotificationType.kt
+++ b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NotificationType.kt
@@ -37,6 +37,10 @@ enum class NotificationType(
channel = NewGradesChannel.CHANNEL_ID,
icon = R.drawable.ic_stat_grade,
),
+ NEW_GRADE_DESCRIPTIVE(
+ channel = NewGradesChannel.CHANNEL_ID,
+ icon = R.drawable.ic_stat_grade,
+ ),
NEW_HOMEWORK(
channel = NewHomeworkChannel.CHANNEL_ID,
icon = R.drawable.ic_more_homework,
diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeWork.kt
index ba21b8600..b62ad94b9 100644
--- a/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeWork.kt
+++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeWork.kt
@@ -45,5 +45,15 @@ class GradeWork @Inject constructor(
grade.isFinalGradeNotified = true
})
}
+
+ gradeRepository.getGradesDescriptiveFromDatabase(semester).first()
+ .filter { !it.isNotified }
+ .let {
+ if (it.isNotEmpty()) newGradeNotification.notifyDescriptive(it, student)
+
+ gradeRepository.updateGradesDescriptive(it.onEach { grade ->
+ grade.isNotified = true
+ })
+ }
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/NotificationDebugPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/NotificationDebugPresenter.kt
index d0dfcd696..cdd186b94 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/NotificationDebugPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/NotificationDebugPresenter.kt
@@ -18,6 +18,7 @@ import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.ui.modules.debug.notification.mock.debugAttendanceItems
import io.github.wulkanowy.ui.modules.debug.notification.mock.debugConferenceItems
import io.github.wulkanowy.ui.modules.debug.notification.mock.debugExamItems
+import io.github.wulkanowy.ui.modules.debug.notification.mock.debugGradeDescriptiveItems
import io.github.wulkanowy.ui.modules.debug.notification.mock.debugGradeDetailsItems
import io.github.wulkanowy.ui.modules.debug.notification.mock.debugGradeSummaryItems
import io.github.wulkanowy.ui.modules.debug.notification.mock.debugHomeworkItems
@@ -55,6 +56,14 @@ class NotificationDebugPresenter @Inject constructor(
NotificationDebugItem(R.string.grade_summary_final_grade) { n ->
withStudent { newGradeNotification.notifyFinal(debugGradeSummaryItems.take(n), it) }
},
+ NotificationDebugItem(R.string.grade_summary_descriptive) { n ->
+ withStudent {
+ newGradeNotification.notifyDescriptive(
+ debugGradeDescriptiveItems.take(n),
+ it
+ )
+ }
+ },
NotificationDebugItem(R.string.homework_title) { n ->
withStudent { newHomeworkNotification.notify(debugHomeworkItems.take(n), it) }
},
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/gradeDescriptive.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/gradeDescriptive.kt
new file mode 100644
index 000000000..d5a0c089b
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/gradeDescriptive.kt
@@ -0,0 +1,48 @@
+package io.github.wulkanowy.ui.modules.debug.notification.mock
+
+import io.github.wulkanowy.data.db.entities.GradeDescriptive
+
+val debugGradeDescriptiveItems = listOf(
+ generateGradeDescriptive(
+ "Matematyka",
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
+ ),
+ generateGradeDescriptive("Fizyka", "Lorem ipsum dolor sit amet, consectetur adipiscing elit."),
+ generateGradeDescriptive(
+ "Geografia",
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
+ ),
+ generateGradeDescriptive(
+ "Sieci komputerowe",
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
+ ),
+ generateGradeDescriptive(
+ "Systemy operacyjne",
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
+ ),
+ generateGradeDescriptive(
+ "Język polski",
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
+ ),
+ generateGradeDescriptive(
+ "Język angielski",
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
+ ),
+ generateGradeDescriptive("Religia", "Lorem ipsum dolor sit amet, consectetur adipiscing elit."),
+ generateGradeDescriptive(
+ "Język niemiecki",
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
+ ),
+ generateGradeDescriptive(
+ "Wychowanie fizyczne",
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
+ ),
+)
+
+private fun generateGradeDescriptive(subject: String, description: String) =
+ GradeDescriptive(
+ semesterId = 0,
+ studentId = 0,
+ subject = subject,
+ description = description
+ )
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt
index ec4bd8e8c..e8a5fa254 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt
@@ -1,15 +1,23 @@
package io.github.wulkanowy.ui.modules.grade
-import io.github.wulkanowy.data.*
+import io.github.wulkanowy.data.Resource
+import io.github.wulkanowy.data.dataOrNull
import io.github.wulkanowy.data.db.entities.Grade
+import io.github.wulkanowy.data.db.entities.GradeDescriptive
import io.github.wulkanowy.data.db.entities.GradeSummary
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
+import io.github.wulkanowy.data.errorOrNull
+import io.github.wulkanowy.data.flatResourceFlow
+import io.github.wulkanowy.data.mapData
+import io.github.wulkanowy.data.mapResourceData
import io.github.wulkanowy.data.repositories.GradeRepository
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.data.repositories.SemesterRepository
import io.github.wulkanowy.sdk.Sdk
-import io.github.wulkanowy.ui.modules.grade.GradeAverageMode.*
+import io.github.wulkanowy.ui.modules.grade.GradeAverageMode.ALL_YEAR
+import io.github.wulkanowy.ui.modules.grade.GradeAverageMode.BOTH_SEMESTERS
+import io.github.wulkanowy.ui.modules.grade.GradeAverageMode.ONE_SEMESTER
import io.github.wulkanowy.utils.calcAverage
import io.github.wulkanowy.utils.changeModifier
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -62,6 +70,7 @@ class GradeAverageProvider @Inject constructor(
forceRefresh = forceRefresh,
params = params,
)
+
BOTH_SEMESTERS -> calculateCombinedAverage(
student = student,
semesters = semesters,
@@ -69,6 +78,7 @@ class GradeAverageProvider @Inject constructor(
forceRefresh = forceRefresh,
config = params,
)
+
ALL_YEAR -> calculateCombinedAverage(
student = student,
semesters = semesters,
@@ -189,36 +199,73 @@ class GradeAverageProvider @Inject constructor(
): Flow>> {
return gradeRepository.getGrades(student, semester, forceRefresh = forceRefresh)
.mapResourceData { res ->
- val (details, summaries) = res
+ val (details, summaries, descriptives) = res
val isAnyAverage = summaries.any { it.average != .0 }
val allGrades = details.groupBy { it.subject }
+ val descriptiveGradesBySubject = descriptives.associateBy { it.subject }
- val items = summaries.emulateEmptySummaries(
- student = student,
- semester = semester,
- grades = allGrades.toList(),
- calcAverage = isAnyAverage,
- params = params,
- ).map { summary ->
- val grades = allGrades[summary.subject].orEmpty()
- GradeSubject(
- subject = summary.subject,
- average = if (!isAnyAverage || params.forceAverageCalc) {
- grades.updateModifiers(student, params)
- .calcAverage(params.isOptionalArithmeticAverage)
- } else summary.average,
- points = summary.pointsSum,
- summary = summary,
- grades = grades,
- isVulcanAverage = isAnyAverage
+ val items = summaries
+ .createEmptySummariesByGradesIfNeeded(
+ student = student,
+ semester = semester,
+ grades = allGrades.toList(),
+ calcAverage = isAnyAverage,
+ params = params,
)
- }
+ .createEmptySummariesByDescriptiveGradesIfNeeded(
+ student = student,
+ semester = semester,
+ descriptives = descriptives,
+ )
+ .map { summary ->
+ val grades = allGrades[summary.subject].orEmpty()
+ val descriptiveGrade = descriptiveGradesBySubject[summary.subject]
+
+ GradeSubject(
+ subject = summary.subject,
+ average = if (!isAnyAverage || params.forceAverageCalc) {
+ grades.updateModifiers(student, params)
+ .calcAverage(params.isOptionalArithmeticAverage)
+ } else summary.average,
+ points = summary.pointsSum,
+ summary = summary,
+ grades = grades,
+ descriptive = descriptiveGrade,
+ isVulcanAverage = isAnyAverage
+ )
+ }
items
}
}
- private fun List.emulateEmptySummaries(
+ private fun List.createEmptySummariesByDescriptiveGradesIfNeeded(
+ student: Student,
+ semester: Semester,
+ descriptives: List
+ ): List {
+ val summarySubjects = this.map { it.subject }
+ val gradeSummaryToAdd = descriptives.mapNotNull { gradeDescriptive ->
+ if (gradeDescriptive.subject in summarySubjects) return@mapNotNull null
+
+ GradeSummary(
+ studentId = student.studentId,
+ semesterId = semester.semesterId,
+ position = 0,
+ subject = gradeDescriptive.subject,
+ predictedGrade = "",
+ finalGrade = "",
+ proposedPoints = "",
+ finalPoints = "",
+ pointsSum = "",
+ average = .0
+ )
+ }
+
+ return this + gradeSummaryToAdd
+ }
+
+ private fun List.createEmptySummariesByGradesIfNeeded(
student: Student,
semester: Semester,
grades: List>>,
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeSubject.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeSubject.kt
index 57be55ee3..a465551f9 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeSubject.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeSubject.kt
@@ -1,6 +1,7 @@
package io.github.wulkanowy.ui.modules.grade
import io.github.wulkanowy.data.db.entities.Grade
+import io.github.wulkanowy.data.db.entities.GradeDescriptive
import io.github.wulkanowy.data.db.entities.GradeSummary
data class GradeSubject(
@@ -8,6 +9,7 @@ data class GradeSubject(
val average: Double,
val points: String,
val summary: GradeSummary,
+ val descriptive: GradeDescriptive?,
val grades: List,
val isVulcanAverage: Boolean
)
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt
index 4261c507d..d9621f51e 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt
@@ -1,13 +1,22 @@
package io.github.wulkanowy.ui.modules.grade.details
-import io.github.wulkanowy.data.*
import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.data.enums.GradeExpandMode
-import io.github.wulkanowy.data.enums.GradeSortingMode.*
+import io.github.wulkanowy.data.enums.GradeSortingMode.ALPHABETIC
+import io.github.wulkanowy.data.enums.GradeSortingMode.AVERAGE
+import io.github.wulkanowy.data.enums.GradeSortingMode.DATE
+import io.github.wulkanowy.data.flatResourceFlow
+import io.github.wulkanowy.data.logResourceStatus
+import io.github.wulkanowy.data.onResourceData
+import io.github.wulkanowy.data.onResourceError
+import io.github.wulkanowy.data.onResourceIntermediate
+import io.github.wulkanowy.data.onResourceNotLoading
+import io.github.wulkanowy.data.onResourceSuccess
import io.github.wulkanowy.data.repositories.GradeRepository
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.data.repositories.SemesterRepository
import io.github.wulkanowy.data.repositories.StudentRepository
+import io.github.wulkanowy.data.resourceFlow
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.ui.modules.grade.GradeAverageProvider
@@ -207,20 +216,20 @@ class GradeDetailsPresenter @Inject constructor(
AVERAGE -> gradeSubjects.sortedByDescending { it.average }
}
}
- .map { (subject, average, points, _, grades) ->
- val subItems = grades
+ .map { gradeSubject ->
+ val subItems = gradeSubject.grades
.sortedByDescending { it.date }
.map { GradeDetailsItem(it, ViewType.ITEM) }
val gradeDetailsItems = listOf(
GradeDetailsItem(
GradeDetailsHeader(
- subject = subject,
- average = average,
- pointsSum = points,
+ subject = gradeSubject.subject,
+ average = gradeSubject.average,
+ pointsSum = gradeSubject.points,
grades = subItems
).apply {
- newGrades = grades.filter { grade -> !grade.isRead }.size
+ newGrades = gradeSubject.grades.filter { grade -> !grade.isRead }.size
}, ViewType.HEADER
)
)
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryAdapter.kt
index 8dcade56e..95cf97bed 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryAdapter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryAdapter.kt
@@ -2,16 +2,16 @@ package io.github.wulkanowy.ui.modules.grade.summary
import android.annotation.SuppressLint
import android.view.LayoutInflater
-import android.view.View
import android.view.ViewGroup
+import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import io.github.wulkanowy.R
-import io.github.wulkanowy.data.db.entities.GradeSummary
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.databinding.ItemGradeSummaryBinding
import io.github.wulkanowy.databinding.ScrollableHeaderGradeSummaryBinding
import io.github.wulkanowy.sdk.scrapper.grades.isGradeValid
import io.github.wulkanowy.utils.calcFinalAverage
+import io.github.wulkanowy.utils.ifNullOrBlank
import java.util.Locale
import javax.inject.Inject
@@ -24,7 +24,7 @@ class GradeSummaryAdapter @Inject constructor(
ITEM(2)
}
- var items = emptyList()
+ var items = emptyList()
var onCalculatedHelpClickListener: () -> Unit = {}
@@ -44,9 +44,11 @@ class GradeSummaryAdapter @Inject constructor(
ViewType.HEADER.id -> HeaderViewHolder(
ScrollableHeaderGradeSummaryBinding.inflate(inflater, parent, false)
)
+
ViewType.ITEM.id -> ItemViewHolder(
ItemGradeSummaryBinding.inflate(inflater, parent, false)
)
+
else -> throw IllegalStateException()
}
}
@@ -60,19 +62,23 @@ class GradeSummaryAdapter @Inject constructor(
private fun bindHeaderViewHolder(binding: ScrollableHeaderGradeSummaryBinding) {
if (items.isEmpty()) return
+ val gradeSummaries = items
+ .filter { it.gradeDescriptive == null }
+ .map { it.gradeSummary }
val context = binding.root.context
- val finalItemsCount = items.count { isGradeValid(it.finalGrade) }
- val calculatedItemsCount = items.count { value -> value.average != 0.0 }
- val allItemsCount = items.count { !it.subject.equals("zachowanie", true) }
- val finalAverage = items.calcFinalAverage(
+ val finalItemsCount = gradeSummaries.count { isGradeValid(it.finalGrade) }
+ val calculatedItemsCount = gradeSummaries.count { value -> value.average != 0.0 }
+ val allItemsCount = gradeSummaries.count { !it.subject.equals("zachowanie", true) }
+ val finalAverage = gradeSummaries.calcFinalAverage(
preferencesRepository.gradePlusModifier,
preferencesRepository.gradeMinusModifier
)
- val calculatedAverage = items.filter { value -> value.average != 0.0 }
+ val calculatedAverage = gradeSummaries.filter { value -> value.average != 0.0 }
.map { values -> values.average }
.reversed() // fix average precision
.average()
+ .let { if (it.isNaN()) 0.0 else it }
with(binding) {
gradeSummaryScrollableHeaderFinal.text = formatAverage(finalAverage)
@@ -95,16 +101,28 @@ class GradeSummaryAdapter @Inject constructor(
}
@SuppressLint("SetTextI18n")
- private fun bindItemViewHolder(binding: ItemGradeSummaryBinding, item: GradeSummary) {
- with(binding) {
- gradeSummaryItemTitle.text = item.subject
- gradeSummaryItemPoints.text = item.pointsSum
- gradeSummaryItemAverage.text = formatAverage(item.average, "")
- gradeSummaryItemPredicted.text = "${item.predictedGrade} ${item.proposedPoints}".trim()
- gradeSummaryItemFinal.text = "${item.finalGrade} ${item.finalPoints}".trim()
+ private fun bindItemViewHolder(binding: ItemGradeSummaryBinding, item: GradeSummaryItem) {
+ val (gradeSummary, gradeDescriptive) = item
- gradeSummaryItemPointsContainer.visibility =
- if (item.pointsSum.isBlank()) View.GONE else View.VISIBLE
+ with(binding) {
+ gradeSummaryItemTitle.text = gradeSummary.subject
+ gradeSummaryItemPoints.text = gradeSummary.pointsSum
+ gradeSummaryItemAverage.text = formatAverage(gradeSummary.average, "")
+ gradeSummaryItemPredicted.text =
+ "${gradeSummary.predictedGrade} ${gradeSummary.proposedPoints}".trim()
+ gradeSummaryItemFinal.text =
+ "${gradeSummary.finalGrade} ${gradeSummary.finalPoints}".trim()
+ gradeSummaryItemDescriptive.text = gradeDescriptive?.description.ifNullOrBlank {
+ root.context.getString(R.string.all_no_data)
+ }
+
+ gradeSummaryItemFinalDivider.isVisible = gradeDescriptive == null
+ gradeSummaryItemPredictedDivider.isVisible = gradeDescriptive == null
+ gradeSummaryItemPointsDivider.isVisible = gradeDescriptive == null
+ gradeSummaryItemPredictedContainer.isVisible = gradeDescriptive == null
+ gradeSummaryItemFinalContainer.isVisible = gradeDescriptive == null
+ gradeSummaryItemDescriptiveContainer.isVisible = gradeDescriptive != null
+ gradeSummaryItemPointsContainer.isVisible = gradeSummary.pointsSum.isNotBlank()
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryFragment.kt
index abd0b13c4..35b2edd58 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryFragment.kt
@@ -5,12 +5,10 @@ import android.view.View
import android.view.View.GONE
import android.view.View.INVISIBLE
import android.view.View.VISIBLE
-import androidx.appcompat.app.AlertDialog
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R
-import io.github.wulkanowy.data.db.entities.GradeSummary
import io.github.wulkanowy.databinding.FragmentGradeSummaryBinding
import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.grade.GradeFragment
@@ -72,7 +70,7 @@ class GradeSummaryFragment :
}
}
- override fun updateData(data: List) {
+ override fun updateData(data: List) {
with(gradeSummaryAdapter) {
items = data
notifyDataSetChanged()
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryItem.kt
new file mode 100644
index 000000000..cf0f1d92e
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryItem.kt
@@ -0,0 +1,9 @@
+package io.github.wulkanowy.ui.modules.grade.summary
+
+import io.github.wulkanowy.data.db.entities.GradeDescriptive
+import io.github.wulkanowy.data.db.entities.GradeSummary
+
+data class GradeSummaryItem(
+ val gradeSummary: GradeSummary,
+ val gradeDescriptive: GradeDescriptive?
+)
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt
index 32508ff6f..d762df02b 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt
@@ -1,9 +1,16 @@
package io.github.wulkanowy.ui.modules.grade.summary
-import io.github.wulkanowy.data.*
-import io.github.wulkanowy.data.db.entities.GradeSummary
-import io.github.wulkanowy.data.enums.GradeSortingMode
-import io.github.wulkanowy.data.enums.GradeSortingMode.*
+import io.github.wulkanowy.data.enums.GradeSortingMode.ALPHABETIC
+import io.github.wulkanowy.data.enums.GradeSortingMode.AVERAGE
+import io.github.wulkanowy.data.enums.GradeSortingMode.DATE
+import io.github.wulkanowy.data.flatResourceFlow
+import io.github.wulkanowy.data.logResourceStatus
+import io.github.wulkanowy.data.mapResourceData
+import io.github.wulkanowy.data.onResourceData
+import io.github.wulkanowy.data.onResourceError
+import io.github.wulkanowy.data.onResourceIntermediate
+import io.github.wulkanowy.data.onResourceNotLoading
+import io.github.wulkanowy.data.onResourceSuccess
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter
@@ -128,7 +135,7 @@ class GradeSummaryPresenter @Inject constructor(
view?.showFinalAverageHelpDialog()
}
- private fun createGradeSummaryItems(items: List): List {
+ private fun createGradeSummaryItems(items: List): List {
return items
.filter { !checkEmpty(it) }
.let { gradeSubjects ->
@@ -136,21 +143,32 @@ class GradeSummaryPresenter @Inject constructor(
DATE -> gradeSubjects.sortedByDescending { gradeDetailsWithAverage ->
gradeDetailsWithAverage.grades.maxByOrNull { it.date }?.date
}
+
ALPHABETIC -> gradeSubjects.sortedBy { gradeDetailsWithAverage ->
gradeDetailsWithAverage.subject.lowercase()
}
+
AVERAGE -> gradeSubjects.sortedByDescending { it.average }
}
}
- .map { it.summary.copy(average = it.average) }
+ .map {
+ val gradeSummary = it.summary.copy(average = it.average)
+ val descriptive = it.descriptive
+ GradeSummaryItem(
+ gradeSummary = gradeSummary,
+ gradeDescriptive = descriptive,
+ )
+ }
+
}
private fun checkEmpty(gradeSummary: GradeSubject): Boolean {
return gradeSummary.run {
summary.finalGrade.isBlank()
- && summary.predictedGrade.isBlank()
- && average == .0
- && points.isBlank()
+ && summary.predictedGrade.isBlank()
+ && average == .0
+ && points.isBlank()
+ && descriptive == null
}
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryView.kt
index 156731c31..36bd61421 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryView.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryView.kt
@@ -1,6 +1,5 @@
package io.github.wulkanowy.ui.modules.grade.summary
-import io.github.wulkanowy.data.db.entities.GradeSummary
import io.github.wulkanowy.ui.base.BaseView
interface GradeSummaryView : BaseView {
@@ -13,7 +12,7 @@ interface GradeSummaryView : BaseView {
fun initView()
- fun updateData(data: List)
+ fun updateData(data: List)
fun resetView()
diff --git a/app/src/main/res/layout/item_grade_summary.xml b/app/src/main/res/layout/item_grade_summary.xml
index 30aa6e77b..2c8c4ea37 100644
--- a/app/src/main/res/layout/item_grade_summary.xml
+++ b/app/src/main/res/layout/item_grade_summary.xml
@@ -64,10 +64,12 @@
+ android:layout_height="1dp"
+ android:id="@+id/gradeSummaryItemPointsDivider"
+ android:background="@drawable/ic_all_divider" />
+ android:id="@+id/gradeSummaryItemPredictedDivider"
+ android:layout_height="1dp"
+ android:background="@drawable/ic_all_divider" />
+ android:layout_height="1dp"
+ android:id="@+id/gradeSummaryItemFinalDivider"
+ android:background="@drawable/ic_all_divider" />
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 60d85606d..f1fa3ce73 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -130,6 +130,7 @@
Total points
Final grade
Predicted grade
+ Descriptive grade
Calculated average
How does Calculated Average work?
The Calculated Average is the arithmetic average calculated from the subjects averages. It allows you to know the approximate final average. It is calculated in a way selected by the user in the application settings. It is recommended that you choose the appropriate option. This is because the calculation of school averages differs. Additionally, if your school reports the average of the subjects on the Vulcan page, the application downloads them and does not calculate these averages. This can be changed by forcing the calculation of the average in the application settings.\n\nAverage of grades only from selected semester:\n1. Calculating the weighted average for each subject in a given semester\n2.Adding calculated averages\n3. Calculation of the arithmetic average of the summed averages\n\nAverage of averages from both semesters:\n1.Calculating the weighted average for each subject in semester 1 and 2\n2. Calculating the arithmetic average of the calculated averages for semesters 1 and 2 for each subject.\n3. Adding calculated averages\n4. Calculation of the arithmetic average of the summed averages\n\nAverage of grades from the whole year:\n1. Calculating weighted average over the year for each subject. The final average in the 1st semester is irrelevant.\n2. Adding calculated averages\n3. Calculating the arithmetic average of summed averages
@@ -165,6 +166,10 @@
- New final grade
- New final grades
+
+ - New descriptive grade
+ - New descriptive grades
+
- You received %1$d grade
- You received %1$d grades
@@ -177,6 +182,10 @@
- You received %1$d final grade
- You received %1$d final grades
+
+ - You received %1$d descriptive grade
+ - You received %1$d descriptive grades
+
diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/GradeRepositoryTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/GradeRepositoryTest.kt
index 5a1877cc0..515b0d66d 100644
--- a/app/src/test/java/io/github/wulkanowy/data/repositories/GradeRepositoryTest.kt
+++ b/app/src/test/java/io/github/wulkanowy/data/repositories/GradeRepositoryTest.kt
@@ -2,6 +2,7 @@ package io.github.wulkanowy.data.repositories
import io.github.wulkanowy.data.dataOrNull
import io.github.wulkanowy.data.db.dao.GradeDao
+import io.github.wulkanowy.data.db.dao.GradeDescriptiveDao
import io.github.wulkanowy.data.db.dao.GradeSummaryDao
import io.github.wulkanowy.data.errorOrNull
import io.github.wulkanowy.data.mappers.mapToEntities
@@ -42,6 +43,9 @@ class GradeRepositoryTest {
@MockK
private lateinit var gradeSummaryDb: GradeSummaryDao
+ @MockK
+ private lateinit var gradeDescriptiveDb: GradeDescriptiveDao
+
@MockK(relaxUnitFun = true)
private lateinit var refreshHelper: AutoRefreshHelper
@@ -56,7 +60,8 @@ class GradeRepositoryTest {
MockKAnnotations.init(this)
every { refreshHelper.shouldBeRefreshed(any()) } returns false
- gradeRepository = GradeRepository(gradeDb, gradeSummaryDb, sdk, refreshHelper)
+ gradeRepository =
+ GradeRepository(gradeDb, gradeSummaryDb, gradeDescriptiveDb, sdk, refreshHelper)
coEvery { gradeDb.deleteAll(any()) } just Runs
coEvery { gradeDb.insertAll(any()) } returns listOf()
@@ -68,6 +73,13 @@ class GradeRepositoryTest {
)
coEvery { gradeSummaryDb.deleteAll(any()) } just Runs
coEvery { gradeSummaryDb.insertAll(any()) } returns listOf()
+
+ coEvery { gradeDescriptiveDb.loadAll(any(), any()) } returnsMany listOf(
+ flowOf(listOf()),
+ )
+
+ coEvery { gradeDescriptiveDb.deleteAll(any()) } just Runs
+ coEvery { gradeDescriptiveDb.insertAll(any()) } returns listOf()
}
@Test
diff --git a/app/src/test/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProviderTest.kt b/app/src/test/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProviderTest.kt
index 31ea3322b..6a717f6f6 100644
--- a/app/src/test/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProviderTest.kt
+++ b/app/src/test/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProviderTest.kt
@@ -1,12 +1,16 @@
package io.github.wulkanowy.ui.modules.grade
-import io.github.wulkanowy.data.*
+import io.github.wulkanowy.data.Resource
+import io.github.wulkanowy.data.dataOrNull
import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.data.db.entities.GradeSummary
import io.github.wulkanowy.data.db.entities.Student
+import io.github.wulkanowy.data.errorOrNull
import io.github.wulkanowy.data.repositories.GradeRepository
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.data.repositories.SemesterRepository
+import io.github.wulkanowy.data.resourceFlow
+import io.github.wulkanowy.data.toFirstResult
import io.github.wulkanowy.getSemesterEntity
import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.Status
@@ -158,7 +162,9 @@ class GradeAverageProviderTest {
semesters[2],
true
)
- } returns resourceFlow { noWeightGrades to noWeightGradesSummary }
+ } returns resourceFlow {
+ Triple(noWeightGrades, noWeightGradesSummary, emptyList())
+ }
val items = runBlocking {
gradeAverageProvider.getGradesDetailsWithAverage(
@@ -186,7 +192,9 @@ class GradeAverageProviderTest {
semesters[2],
true
)
- } returns resourceFlow { noWeightGrades to noWeightGradesArithmeticSummary }
+ } returns resourceFlow {
+ Triple(noWeightGrades, noWeightGradesArithmeticSummary, emptyList())
+ }
val items = runBlocking {
gradeAverageProvider.getGradesDetailsWithAverage(
@@ -211,8 +219,24 @@ class GradeAverageProviderTest {
coEvery { semesterRepository.getSemesters(student) } returns semesters
coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flow {
emit(Resource.Loading())
- emit(Resource.Intermediate(secondGradeWithModifier to secondSummariesWithModifier))
- emit(Resource.Success(secondGradeWithModifier to secondSummariesWithModifier))
+ emit(
+ Resource.Intermediate(
+ Triple(
+ secondGradeWithModifier,
+ secondSummariesWithModifier,
+ emptyList()
+ )
+ )
+ )
+ emit(
+ Resource.Success(
+ Triple(
+ secondGradeWithModifier,
+ secondSummariesWithModifier,
+ emptyList()
+ )
+ )
+ )
}
val items = runBlocking {
@@ -253,11 +277,27 @@ class GradeAverageProviderTest {
coEvery { gradeRepository.getGrades(student, semesters[2], false) } returns flow {
emit(Resource.Loading())
delay(1000)
- emit(Resource.Success(secondGradeWithModifier to secondSummariesWithModifier))
+ emit(
+ Resource.Success(
+ Triple(
+ secondGradeWithModifier,
+ secondSummariesWithModifier,
+ emptyList()
+ )
+ )
+ )
}
coEvery { gradeRepository.getGrades(student, semesters[1], false) } returns flow {
emit(Resource.Loading())
- emit(Resource.Success(secondGradeWithModifier to secondSummariesWithModifier))
+ emit(
+ Resource.Success(
+ Triple(
+ secondGradeWithModifier,
+ secondSummariesWithModifier,
+ emptyList()
+ )
+ )
+ )
}
val items = runBlocking {
@@ -296,7 +336,13 @@ class GradeAverageProviderTest {
semesters[1],
false
)
- } returns resourceFlow { secondGradeWithModifier to secondSummariesWithModifier }
+ } returns resourceFlow {
+ Triple(
+ secondGradeWithModifier,
+ secondSummariesWithModifier,
+ emptyList()
+ )
+ }
coEvery {
gradeRepository.getGrades(
student,
@@ -304,8 +350,10 @@ class GradeAverageProviderTest {
false
)
} returns resourceFlow {
- listOf(getGrade(semesters[2].semesterId, "Język polski", .0, .0, .0)) to listOf(
- getSummary(semesters[2].semesterId, "Język polski", 2.5)
+ Triple(
+ listOf(getGrade(semesters[2].semesterId, "Język polski", .0, .0, .0)),
+ listOf(getSummary(semesters[2].semesterId, "Język polski", 2.5)),
+ emptyList()
)
}
@@ -332,7 +380,13 @@ class GradeAverageProviderTest {
semesters[1],
false
)
- } returns resourceFlow { secondGradeWithModifier to secondSummariesWithModifier }
+ } returns resourceFlow {
+ Triple(
+ secondGradeWithModifier,
+ secondSummariesWithModifier,
+ emptyList()
+ )
+ }
coEvery {
gradeRepository.getGrades(
student,
@@ -340,12 +394,14 @@ class GradeAverageProviderTest {
false
)
} returns resourceFlow {
- emptyList() to listOf(
- getSummary(
- 24,
- "Język polski",
- .0
- )
+ Triple(
+ emptyList(), listOf(
+ getSummary(
+ 24,
+ "Język polski",
+ .0
+ )
+ ), emptyList()
)
}
@@ -372,14 +428,22 @@ class GradeAverageProviderTest {
semesters[1],
true
)
- } returns resourceFlow { emptyList() to emptyList() }
+ } returns resourceFlow {
+ Triple(
+ emptyList(),
+ emptyList(),
+ emptyList()
+ )
+ }
coEvery {
gradeRepository.getGrades(
student,
semesters[2],
true
)
- } returns resourceFlow { emptyList() to emptyList() }
+ } returns resourceFlow {
+ Triple(emptyList(), emptyList(), emptyList())
+ }
val items = runBlocking {
gradeAverageProvider.getGradesDetailsWithAverage(
@@ -404,7 +468,13 @@ class GradeAverageProviderTest {
semesters[2],
true
)
- } returns resourceFlow { secondGradeWithModifier to secondSummariesWithModifier }
+ } returns resourceFlow {
+ Triple(
+ secondGradeWithModifier,
+ secondSummariesWithModifier,
+ emptyList()
+ )
+ }
val items = runBlocking {
gradeAverageProvider.getGradesDetailsWithAverage(
@@ -438,7 +508,13 @@ class GradeAverageProviderTest {
semesters[2],
true
)
- } returns resourceFlow { secondGradeWithModifier to secondSummariesWithModifier }
+ } returns resourceFlow {
+ Triple(
+ secondGradeWithModifier,
+ secondSummariesWithModifier,
+ emptyList()
+ )
+ }
val items = runBlocking {
gradeAverageProvider.getGradesDetailsWithAverage(
@@ -472,7 +548,13 @@ class GradeAverageProviderTest {
semesters[2],
true
)
- } returns resourceFlow { secondGradeWithModifier to secondSummariesWithModifier }
+ } returns resourceFlow {
+ Triple(
+ secondGradeWithModifier,
+ secondSummariesWithModifier,
+ emptyList()
+ )
+ }
val items = runBlocking {
gradeAverageProvider.getGradesDetailsWithAverage(
@@ -506,7 +588,13 @@ class GradeAverageProviderTest {
semesters[2],
true
)
- } returns resourceFlow { secondGradeWithModifier to secondSummariesWithModifier }
+ } returns resourceFlow {
+ Triple(
+ secondGradeWithModifier,
+ secondSummariesWithModifier,
+ emptyList()
+ )
+ }
val items = runBlocking {
gradeAverageProvider.getGradesDetailsWithAverage(
@@ -534,7 +622,7 @@ class GradeAverageProviderTest {
semesters[2],
true
)
- } returns resourceFlow { secondGrades to secondSummaries }
+ } returns resourceFlow { Triple(secondGrades, secondSummaries, emptyList()) }
val items = runBlocking {
gradeAverageProvider.getGradesDetailsWithAverage(
@@ -564,7 +652,7 @@ class GradeAverageProviderTest {
semesters[2],
true
)
- } returns resourceFlow { secondGrades to secondSummaries }
+ } returns resourceFlow { Triple(secondGrades, secondSummaries, emptyList()) }
val items = runBlocking {
gradeAverageProvider.getGradesDetailsWithAverage(
@@ -594,7 +682,7 @@ class GradeAverageProviderTest {
semesters[1],
true
)
- } returns resourceFlow { firstGrades to firstSummaries }
+ } returns resourceFlow { Triple(firstGrades, firstSummaries, emptyList()) }
val items = runBlocking {
gradeAverageProvider.getGradesDetailsWithAverage(
@@ -625,8 +713,8 @@ class GradeAverageProviderTest {
coEvery { semesterRepository.getSemesters(student) } returns semesters
coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns flow {
emit(Resource.Loading())
- emit(Resource.Intermediate(firstGrades to firstSummaries))
- emit(Resource.Success(firstGrades to firstSummaries))
+ emit(Resource.Intermediate(Triple(firstGrades, firstSummaries, emptyList())))
+ emit(Resource.Success(Triple(firstGrades, firstSummaries, emptyList())))
}
val items = runBlocking {
@@ -675,9 +763,11 @@ class GradeAverageProviderTest {
true
)
} returns resourceFlow {
- firstGrades to listOf(
- getSummary(22, "Matematyka", 3.0),
- getSummary(22, "Fizyka", 3.5)
+ Triple(
+ firstGrades, listOf(
+ getSummary(22, "Matematyka", 3.0),
+ getSummary(22, "Fizyka", 3.5)
+ ), emptyList()
)
}
coEvery {
@@ -687,9 +777,13 @@ class GradeAverageProviderTest {
true
)
} returns resourceFlow {
- secondGrades to listOf(
- getSummary(22, "Matematyka", 3.5),
- getSummary(22, "Fizyka", 4.0)
+ Triple(
+ secondGrades,
+ listOf(
+ getSummary(22, "Matematyka", 3.5),
+ getSummary(22, "Fizyka", 4.0)
+ ),
+ emptyList()
)
}
@@ -723,17 +817,21 @@ class GradeAverageProviderTest {
emit(Resource.Loading())
emit(
Resource.Intermediate(
- firstGrades to listOf(
- getSummary(22, "Matematyka", 3.0),
- getSummary(22, "Fizyka", 3.5)
+ Triple(
+ firstGrades, listOf(
+ getSummary(22, "Matematyka", 3.0),
+ getSummary(22, "Fizyka", 3.5)
+ ), emptyList()
)
)
)
emit(
Resource.Success(
- firstGrades to listOf(
- getSummary(22, "Matematyka", 3.0),
- getSummary(22, "Fizyka", 3.5)
+ Triple(
+ firstGrades, listOf(
+ getSummary(22, "Matematyka", 3.0),
+ getSummary(22, "Fizyka", 3.5)
+ ), emptyList()
)
)
)
@@ -742,17 +840,21 @@ class GradeAverageProviderTest {
emit(Resource.Loading())
emit(
Resource.Intermediate(
- secondGrades to listOf(
- getSummary(22, "Matematyka", 3.5),
- getSummary(22, "Fizyka", 4.0)
+ Triple(
+ secondGrades, listOf(
+ getSummary(22, "Matematyka", 3.5),
+ getSummary(22, "Fizyka", 4.0)
+ ), emptyList()
)
)
)
emit(
Resource.Success(
- secondGrades to listOf(
- getSummary(22, "Matematyka", 3.5),
- getSummary(22, "Fizyka", 4.0)
+ Triple(
+ secondGrades, listOf(
+ getSummary(22, "Matematyka", 3.5),
+ getSummary(22, "Fizyka", 4.0)
+ ), emptyList()
)
)
)
@@ -803,7 +905,7 @@ class GradeAverageProviderTest {
semesters[1],
true
)
- } returns resourceFlow { firstGrades to firstSummaries }
+ } returns resourceFlow { Triple(firstGrades, firstSummaries, emptyList()) }
coEvery {
gradeRepository.getGrades(
student,
@@ -811,9 +913,11 @@ class GradeAverageProviderTest {
true
)
} returns resourceFlow {
- secondGrades to listOf(
- getSummary(22, "Matematyka", 1.1),
- getSummary(22, "Fizyka", 7.26)
+ Triple(
+ secondGrades, listOf(
+ getSummary(22, "Matematyka", 1.1),
+ getSummary(22, "Fizyka", 7.26)
+ ), emptyList()
)
}
@@ -850,9 +954,11 @@ class GradeAverageProviderTest {
true
)
} returns resourceFlow {
- firstGrades to listOf(
- getSummary(22, "Matematyka", .0),
- getSummary(22, "Fizyka", .0)
+ Triple(
+ firstGrades, listOf(
+ getSummary(22, "Matematyka", .0),
+ getSummary(22, "Fizyka", .0)
+ ), emptyList()
)
}
coEvery {
@@ -862,9 +968,11 @@ class GradeAverageProviderTest {
true
)
} returns resourceFlow {
- secondGrades to listOf(
- getSummary(22, "Matematyka", .0),
- getSummary(22, "Fizyka", .0)
+ Triple(
+ secondGrades, listOf(
+ getSummary(22, "Matematyka", .0),
+ getSummary(22, "Fizyka", .0)
+ ), emptyList()
)
}
@@ -889,24 +997,28 @@ class GradeAverageProviderTest {
coEvery { semesterRepository.getSemesters(student) } returns semesters
coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns flow {
emit(Resource.Loading())
- emit(Resource.Intermediate(firstGrades to firstSummaries))
- emit(Resource.Success(firstGrades to firstSummaries))
+ emit(Resource.Intermediate(Triple(firstGrades, firstSummaries, emptyList())))
+ emit(Resource.Success(Triple(firstGrades, firstSummaries, emptyList())))
}
coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flow {
emit(Resource.Loading())
emit(
Resource.Intermediate(
- secondGrades to listOf(
- getSummary(22, "Matematyka", 1.1),
- getSummary(22, "Fizyka", 7.26)
+ Triple(
+ secondGrades, listOf(
+ getSummary(22, "Matematyka", 1.1),
+ getSummary(22, "Fizyka", 7.26)
+ ), emptyList()
)
)
)
emit(
Resource.Success(
- secondGrades to listOf(
- getSummary(22, "Matematyka", 1.1),
- getSummary(22, "Fizyka", 7.26)
+ Triple(
+ secondGrades, listOf(
+ getSummary(22, "Matematyka", 1.1),
+ getSummary(22, "Fizyka", 7.26)
+ ), emptyList()
)
)
)
@@ -958,14 +1070,14 @@ class GradeAverageProviderTest {
semesters[1],
true
)
- } returns resourceFlow { firstGrades to emptyList() }
+ } returns resourceFlow { Triple(firstGrades, emptyList(), emptyList()) }
coEvery {
gradeRepository.getGrades(
student,
semesters[2],
true
)
- } returns resourceFlow { secondGrades to emptyList() }
+ } returns resourceFlow { Triple(secondGrades, emptyList(), emptyList()) }
val items = runBlocking {
gradeAverageProvider.getGradesDetailsWithAverage(
@@ -1000,14 +1112,14 @@ class GradeAverageProviderTest {
semesters[1],
true
)
- } returns resourceFlow { firstGrades to emptyList() }
+ } returns resourceFlow { Triple(firstGrades, emptyList(), emptyList()) }
coEvery {
gradeRepository.getGrades(
student,
semesters[2],
true
)
- } returns resourceFlow { secondGrades to emptyList() }
+ } returns resourceFlow { Triple(secondGrades, emptyList(), emptyList()) }
val items = runBlocking {
gradeAverageProvider.getGradesDetailsWithAverage(
@@ -1043,8 +1155,10 @@ class GradeAverageProviderTest {
true
)
} returns resourceFlow {
- firstGrades to listOf(
- getSummary(22, "Matematyka", 4.0)
+ Triple(
+ firstGrades, listOf(
+ getSummary(22, "Matematyka", 4.0)
+ ), emptyList()
)
}
coEvery {
@@ -1054,8 +1168,10 @@ class GradeAverageProviderTest {
true
)
} returns resourceFlow {
- secondGrades to listOf(
- getSummary(23, "Matematyka", 3.0)
+ Triple(
+ secondGrades, listOf(
+ getSummary(23, "Matematyka", 3.0)
+ ), emptyList()
)
}
@@ -1092,14 +1208,20 @@ class GradeAverageProviderTest {
semesters[1],
true
)
- } returns resourceFlow { firstGrades to firstSummaries }
+ } returns resourceFlow { Triple(firstGrades, firstSummaries, emptyList()) }
coEvery {
gradeRepository.getGrades(
student,
semesters[2],
true
)
- } returns resourceFlow { secondGrades to secondSummaries.dropLast(1) }
+ } returns resourceFlow {
+ Triple(
+ secondGrades,
+ secondSummaries.dropLast(1),
+ emptyList()
+ )
+ }
val items = runBlocking {
gradeAverageProvider.getGradesDetailsWithAverage(
@@ -1134,14 +1256,20 @@ class GradeAverageProviderTest {
semesters[1],
true
)
- } returns resourceFlow { firstGrades to firstSummaries.dropLast(1) }
+ } returns resourceFlow {
+ Triple(
+ firstGrades,
+ firstSummaries.dropLast(1),
+ emptyList()
+ )
+ }
coEvery {
gradeRepository.getGrades(
student,
semesters[2],
true
)
- } returns resourceFlow { secondGrades to secondSummaries }
+ } returns resourceFlow { Triple(secondGrades, secondSummaries, emptyList()) }
val items = runBlocking {
gradeAverageProvider.getGradesDetailsWithAverage(
@@ -1176,14 +1304,20 @@ class GradeAverageProviderTest {
semesters[1],
true
)
- } returns resourceFlow { firstGrades to firstSummaries.dropLast(1) }
+ } returns resourceFlow {
+ Triple(
+ firstGrades,
+ firstSummaries.dropLast(1),
+ emptyList()
+ )
+ }
coEvery {
gradeRepository.getGrades(
student,
semesters[2],
true
)
- } returns resourceFlow { secondGrades to secondSummaries }
+ } returns resourceFlow { Triple(secondGrades, secondSummaries, emptyList()) }
val items = runBlocking {
gradeAverageProvider.getGradesDetailsWithAverage(
@@ -1219,16 +1353,20 @@ class GradeAverageProviderTest {
true
)
} returns resourceFlow {
- listOf(
- getGrade(22, "Fizyka", 5.0, weight = 2.0),
- getGrade(22, "Fizyka", 6.0, weight = 2.0),
- getGrade(22, "Fizyka", 5.0, weight = 4.0),
- getGrade(22, "Fizyka", 6.0, weight = 2.0),
- getGrade(22, "Fizyka", 6.0, weight = 2.0),
- getGrade(22, "Fizyka", 6.0, weight = 4.0),
- getGrade(22, "Fizyka", 6.0, weight = 4.0),
- getGrade(22, "Fizyka", 6.0, weight = 2.0)
- ) to listOf(getSummary(semesterId = 22, subject = "Fizyka", average = .0))
+ Triple(
+ listOf(
+ getGrade(22, "Fizyka", 5.0, weight = 2.0),
+ getGrade(22, "Fizyka", 6.0, weight = 2.0),
+ getGrade(22, "Fizyka", 5.0, weight = 4.0),
+ getGrade(22, "Fizyka", 6.0, weight = 2.0),
+ getGrade(22, "Fizyka", 6.0, weight = 2.0),
+ getGrade(22, "Fizyka", 6.0, weight = 4.0),
+ getGrade(22, "Fizyka", 6.0, weight = 4.0),
+ getGrade(22, "Fizyka", 6.0, weight = 2.0)
+ ),
+ listOf(getSummary(semesterId = 22, subject = "Fizyka", average = .0)),
+ emptyList()
+ )
}
coEvery {
gradeRepository.getGrades(
@@ -1237,11 +1375,15 @@ class GradeAverageProviderTest {
true
)
} returns resourceFlow {
- listOf(
- getGrade(23, "Fizyka", 5.0, weight = 1.0),
- getGrade(23, "Fizyka", 5.0, weight = 2.0),
- getGrade(23, "Fizyka", 4.0, modifier = 0.3, weight = 2.0)
- ) to listOf(getSummary(semesterId = 23, subject = "Fizyka", average = .0))
+ Triple(
+ listOf(
+ getGrade(23, "Fizyka", 5.0, weight = 1.0),
+ getGrade(23, "Fizyka", 5.0, weight = 2.0),
+ getGrade(23, "Fizyka", 4.0, modifier = 0.3, weight = 2.0)
+ ),
+ listOf(getSummary(semesterId = 23, subject = "Fizyka", average = .0)),
+ emptyList()
+ )
}
val items = runBlocking {
@@ -1266,23 +1408,31 @@ class GradeAverageProviderTest {
every { preferencesRepository.gradeAverageModeFlow } returns flowOf(GradeAverageMode.ALL_YEAR)
coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns resourceFlow {
- listOf(
- getGrade(22, "Fizyka", 5.0, weight = 2.0),
- getGrade(22, "Fizyka", 6.0, weight = 2.0),
- getGrade(22, "Fizyka", 5.0, weight = 4.0),
- getGrade(22, "Fizyka", 6.0, weight = 2.0),
- getGrade(22, "Fizyka", 6.0, weight = 2.0),
- getGrade(22, "Fizyka", 6.0, weight = 4.0),
- getGrade(22, "Fizyka", 6.0, weight = 4.0),
- getGrade(22, "Fizyka", 6.0, weight = 2.0)
- ) to listOf(getSummary(semesterId = 22, subject = "Fizyka", average = .0))
+ Triple(
+ listOf(
+ getGrade(22, "Fizyka", 5.0, weight = 2.0),
+ getGrade(22, "Fizyka", 6.0, weight = 2.0),
+ getGrade(22, "Fizyka", 5.0, weight = 4.0),
+ getGrade(22, "Fizyka", 6.0, weight = 2.0),
+ getGrade(22, "Fizyka", 6.0, weight = 2.0),
+ getGrade(22, "Fizyka", 6.0, weight = 4.0),
+ getGrade(22, "Fizyka", 6.0, weight = 4.0),
+ getGrade(22, "Fizyka", 6.0, weight = 2.0)
+ ),
+ listOf(getSummary(semesterId = 22, subject = "Fizyka", average = .0)),
+ emptyList()
+ )
}
coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns resourceFlow {
- listOf(
- getGrade(23, "Fizyka", 5.0, weight = 1.0),
- getGrade(23, "Fizyka", 5.0, weight = 2.0),
- getGrade(23, "Fizyka", 4.0, modifier = 0.3, weight = 2.0)
- ) to listOf(getSummary(semesterId = 23, subject = "Fizyka", average = .0))
+ Triple(
+ listOf(
+ getGrade(23, "Fizyka", 5.0, weight = 1.0),
+ getGrade(23, "Fizyka", 5.0, weight = 2.0),
+ getGrade(23, "Fizyka", 4.0, modifier = 0.3, weight = 2.0)
+ ),
+ listOf(getSummary(semesterId = 23, subject = "Fizyka", average = .0)),
+ emptyList()
+ )
}
val items = runBlocking {
@@ -1313,23 +1463,31 @@ class GradeAverageProviderTest {
coEvery { semesterRepository.getSemesters(student) } returns semesters
coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns resourceFlow {
- listOf(
- getGrade(22, "Fizyka", 5.0, weight = 2.0),
- getGrade(22, "Fizyka", 6.0, weight = 2.0),
- getGrade(22, "Fizyka", 5.0, weight = 4.0),
- getGrade(22, "Fizyka", 6.0, weight = 2.0),
- getGrade(22, "Fizyka", 6.0, weight = 2.0),
- getGrade(22, "Fizyka", 6.0, weight = 4.0),
- getGrade(22, "Fizyka", 6.0, weight = 4.0),
- getGrade(22, "Fizyka", 6.0, weight = 2.0)
- ) to listOf(getSummary(semesterId = 22, subject = "Fizyka", average = .0))
+ Triple(
+ listOf(
+ getGrade(22, "Fizyka", 5.0, weight = 2.0),
+ getGrade(22, "Fizyka", 6.0, weight = 2.0),
+ getGrade(22, "Fizyka", 5.0, weight = 4.0),
+ getGrade(22, "Fizyka", 6.0, weight = 2.0),
+ getGrade(22, "Fizyka", 6.0, weight = 2.0),
+ getGrade(22, "Fizyka", 6.0, weight = 4.0),
+ getGrade(22, "Fizyka", 6.0, weight = 4.0),
+ getGrade(22, "Fizyka", 6.0, weight = 2.0)
+ ),
+ listOf(getSummary(semesterId = 22, subject = "Fizyka", average = .0)),
+ emptyList()
+ )
}
coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns resourceFlow {
- listOf(
- getGrade(23, "Fizyka", 5.0, weight = 1.0),
- getGrade(23, "Fizyka", 5.0, weight = 2.0),
- getGrade(23, "Fizyka", 4.0, modifier = 0.33, weight = 2.0)
- ) to listOf(getSummary(semesterId = 23, subject = "Fizyka", average = .0))
+ Triple(
+ listOf(
+ getGrade(23, "Fizyka", 5.0, weight = 1.0),
+ getGrade(23, "Fizyka", 5.0, weight = 2.0),
+ getGrade(23, "Fizyka", 4.0, modifier = 0.33, weight = 2.0)
+ ),
+ listOf(getSummary(semesterId = 23, subject = "Fizyka", average = .0)),
+ emptyList()
+ )
}
val items = runBlocking {
@@ -1366,16 +1524,20 @@ class GradeAverageProviderTest {
true
)
} returns resourceFlow {
- listOf(
- getGrade(22, "Fizyka", 5.0, weight = 2.0),
- getGrade(22, "Fizyka", 6.0, weight = 2.0),
- getGrade(22, "Fizyka", 5.0, weight = 4.0),
- getGrade(22, "Fizyka", 6.0, weight = 2.0),
- getGrade(22, "Fizyka", 6.0, weight = 2.0),
- getGrade(22, "Fizyka", 6.0, weight = 4.0),
- getGrade(22, "Fizyka", 6.0, weight = 4.0),
- getGrade(22, "Fizyka", 6.0, weight = 2.0)
- ) to listOf(getSummary(semesterId = 22, subject = "Fizyka", average = .0))
+ Triple(
+ listOf(
+ getGrade(22, "Fizyka", 5.0, weight = 2.0),
+ getGrade(22, "Fizyka", 6.0, weight = 2.0),
+ getGrade(22, "Fizyka", 5.0, weight = 4.0),
+ getGrade(22, "Fizyka", 6.0, weight = 2.0),
+ getGrade(22, "Fizyka", 6.0, weight = 2.0),
+ getGrade(22, "Fizyka", 6.0, weight = 4.0),
+ getGrade(22, "Fizyka", 6.0, weight = 4.0),
+ getGrade(22, "Fizyka", 6.0, weight = 2.0)
+ ),
+ listOf(getSummary(semesterId = 22, subject = "Fizyka", average = .0)),
+ emptyList()
+ )
}
coEvery {
gradeRepository.getGrades(
@@ -1384,11 +1546,15 @@ class GradeAverageProviderTest {
true
)
} returns resourceFlow {
- listOf(
- getGrade(23, "Fizyka", 5.0, weight = 1.0),
- getGrade(23, "Fizyka", 5.0, weight = 2.0),
- getGrade(23, "Fizyka", 4.0, modifier = 0.33, weight = 2.0)
- ) to listOf(getSummary(semesterId = 23, subject = "Fizyka", average = .0))
+ Triple(
+ listOf(
+ getGrade(23, "Fizyka", 5.0, weight = 1.0),
+ getGrade(23, "Fizyka", 5.0, weight = 2.0),
+ getGrade(23, "Fizyka", 4.0, modifier = 0.33, weight = 2.0)
+ ),
+ listOf(getSummary(semesterId = 23, subject = "Fizyka", average = .0)),
+ emptyList()
+ )
}
val items = runBlocking {
@@ -1413,9 +1579,9 @@ class GradeAverageProviderTest {
every { preferencesRepository.isOptionalArithmeticAverageFlow } returns flowOf(false)
coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns
- resourceFlow { firstGrades to firstSummaries }
+ resourceFlow { Triple(firstGrades, firstSummaries, emptyList()) }
coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns
- resourceFlow { listOf() to firstSummaries }
+ resourceFlow { Triple(listOf(), firstSummaries, emptyList()) }
val items = runBlocking {
gradeAverageProvider.getGradesDetailsWithAverage(
From 3f199cb610016c72baaa89ae369205125c864411 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rafa=C5=82=20Borcz?=
Date: Fri, 9 Feb 2024 13:40:01 +0100
Subject: [PATCH 22/50] Replace fakelog.cf to wulkanowy.net.pl (#2419)
---
.../ui/modules/login/advanced/LoginAdvancedPresenter.kt | 4 ++--
.../wulkanowy/ui/modules/login/form/LoginFormPresenter.kt | 2 +-
.../ui/modules/login/recover/LoginRecoverPresenter.kt | 2 +-
app/src/main/res/values/api_hosts.xml | 2 +-
4 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/advanced/LoginAdvancedPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/advanced/LoginAdvancedPresenter.kt
index a17ad0035..fc26f3765 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/advanced/LoginAdvancedPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/advanced/LoginAdvancedPresenter.kt
@@ -71,7 +71,7 @@ class LoginAdvancedPresenter @Inject constructor(
fun updateUsernameLabel() {
view?.apply {
- setUsernameLabel(if ("vulcan" in formHostValue || "fakelog" in formHostValue) emailLabel else nicknameLabel)
+ setUsernameLabel(if ("vulcan" in formHostValue || "wulkanowy" in formHostValue) emailLabel else nicknameLabel)
}
}
@@ -79,7 +79,7 @@ class LoginAdvancedPresenter @Inject constructor(
view?.apply {
clearPassError()
clearUsernameError()
- if (formHostValue.contains("fakelog")) {
+ if (formHostValue.contains("wulkanowy")) {
setDefaultCredentials(
"jan@fakelog.cf", "jan123", "powiatwulkanowy", "FK100000", "999999"
)
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenter.kt
index c9ae4f27f..26b15bff0 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenter.kt
@@ -90,7 +90,7 @@ class LoginFormPresenter @Inject constructor(
clearPassError()
clearUsernameError()
clearHostError()
- if (formHostValue.contains("fakelog")) {
+ if (formHostValue.contains("wulkanowy")) {
setCredentials("jan@fakelog.cf", "jan123")
} else if (formUsernameValue == "jan@fakelog.cf" && formPassValue == "jan123") {
setCredentials("", "")
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverPresenter.kt
index a424df40d..879055a9a 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverPresenter.kt
@@ -38,7 +38,7 @@ class LoginRecoverPresenter @Inject constructor(
fun onHostSelected() {
view?.run {
- if ("fakelog" in recoverHostValue) setDefaultCredentials("jan@fakelog.cf")
+ if ("wulkanowy" in recoverHostValue) setDefaultCredentials("jan@fakelog.cf")
clearUsernameError()
updateFields()
}
diff --git a/app/src/main/res/values/api_hosts.xml b/app/src/main/res/values/api_hosts.xml
index 522b6e116..94ef8abdc 100644
--- a/app/src/main/res/values/api_hosts.xml
+++ b/app/src/main/res/values/api_hosts.xml
@@ -44,7 +44,7 @@
- https://vulcan.net.pl/?login
- https://vulcan.net.pl/?login
- https://vulcan.net.pl/?email&customSuffix
- - https://fakelog.cf/?email
+ - https://wulkanowy.net.pl/?email
- Default
From 8183d7d5a0f9f62e7d63a8a42f15817525e11d7e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Miko=C5=82aj=20Pich?=
Date: Fri, 9 Feb 2024 16:35:18 +0100
Subject: [PATCH 23/50] Change default symbol for standard register variant
(#2421)
---
app/build.gradle | 2 +-
.../java/io/github/wulkanowy/ui/modules/login/LoginData.kt | 3 ++-
.../ui/modules/login/advanced/LoginAdvancedPresenter.kt | 2 +-
.../wulkanowy/ui/modules/login/form/LoginFormPresenter.kt | 4 ++--
.../ui/modules/login/recover/LoginRecoverPresenter.kt | 4 ++--
.../login/studentselect/LoginStudentSelectFragment.kt | 2 --
.../login/studentselect/LoginStudentSelectPresenter.kt | 6 +++---
.../ui/modules/login/support/LoginSupportDialog.kt | 2 +-
.../ui/modules/login/symbol/LoginSymbolPresenter.kt | 6 +++---
app/src/main/res/values/api_hosts.xml | 4 ++--
app/src/test/java/io/github/wulkanowy/TestEnityCreator.kt | 1 +
.../login/studentselect/LoginStudentSelectPresenterTest.kt | 2 +-
12 files changed, 19 insertions(+), 19 deletions(-)
diff --git a/app/build.gradle b/app/build.gradle
index b60dc3b4b..65a42ce1d 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -195,7 +195,7 @@ ext {
}
dependencies {
- implementation 'io.github.wulkanowy:sdk:2.3.7'
+ implementation 'io.github.wulkanowy:sdk:2.3.8-SNAPSHOT'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4'
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginData.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginData.kt
index 2c11bb6d5..b066cceb9 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginData.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginData.kt
@@ -7,5 +7,6 @@ data class LoginData(
val password: String,
val baseUrl: String,
val domainSuffix: String,
- val symbol: String?,
+ val defaultSymbol: String,
+ val userEnteredSymbol: String? = null,
) : Serializable
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/advanced/LoginAdvancedPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/advanced/LoginAdvancedPresenter.kt
index fc26f3765..009f26e17 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/advanced/LoginAdvancedPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/advanced/LoginAdvancedPresenter.kt
@@ -155,7 +155,7 @@ class LoginAdvancedPresenter @Inject constructor(
password = view?.formPassValue.orEmpty().trim(),
baseUrl = view?.formHostValue.orEmpty().trim(),
domainSuffix = view?.formDomainSuffix.orEmpty().trim(),
- symbol = view?.formSymbolValue.orEmpty().trim().getNormalizedSymbol(),
+ defaultSymbol = view?.formSymbolValue.orEmpty().trim().getNormalizedSymbol(),
)
when (it.data.symbols.size) {
0 -> view?.navigateToSymbol(loginData)
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenter.kt
index 26b15bff0..af89f147c 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenter.kt
@@ -148,7 +148,7 @@ class LoginFormPresenter @Inject constructor(
password = password,
baseUrl = host,
domainSuffix = domainSuffix,
- symbol = symbol
+ defaultSymbol = symbol
)
}
@@ -167,7 +167,7 @@ class LoginFormPresenter @Inject constructor(
password = loginData.password,
scrapperBaseUrl = loginData.baseUrl,
domainSuffix = loginData.domainSuffix,
- symbol = loginData.symbol.orEmpty(),
+ symbol = loginData.defaultSymbol,
)
}
.logResourceStatus("login")
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverPresenter.kt
index 879055a9a..18902e014 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverPresenter.kt
@@ -60,7 +60,7 @@ class LoginRecoverPresenter @Inject constructor(
resourceFlow {
recoverRepository.getReCaptchaSiteKey(
host,
- symbol.ifBlank { "Default" })
+ symbol.ifBlank { "default" })
}.onEach {
when (it) {
is Resource.Loading -> view?.run {
@@ -103,7 +103,7 @@ class LoginRecoverPresenter @Inject constructor(
fun onReCaptchaVerified(reCaptchaResponse: String) {
val username = view?.recoverNameValue.orEmpty()
val host = view?.recoverHostValue.orEmpty()
- val symbol = view?.formHostSymbol.ifNullOrBlank { "Default" }
+ val symbol = view?.formHostSymbol.ifNullOrBlank { "default" }
resourceFlow {
recoverRepository.sendRecoverRequest(
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectFragment.kt
index 06efd8d98..0fe36aa99 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectFragment.kt
@@ -10,13 +10,11 @@ import io.github.wulkanowy.data.pojos.RegisterUser
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.databinding.FragmentLoginStudentSelectBinding
import io.github.wulkanowy.ui.base.BaseFragment
-import io.github.wulkanowy.ui.modules.auth.AuthDialog
import io.github.wulkanowy.ui.modules.login.LoginActivity
import io.github.wulkanowy.ui.modules.login.LoginData
import io.github.wulkanowy.ui.modules.login.support.LoginSupportDialog
import io.github.wulkanowy.ui.modules.login.support.LoginSupportInfo
import io.github.wulkanowy.utils.AppInfo
-import io.github.wulkanowy.utils.openEmailClient
import io.github.wulkanowy.utils.openInternetBrowser
import io.github.wulkanowy.utils.serializable
import javax.inject.Inject
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenter.kt
index 6cbdfbb85..344414180 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenter.kt
@@ -111,8 +111,8 @@ class LoginStudentSelectPresenter @Inject constructor(
val notEmptySymbols = registerUser.symbols.filter { it.schools.isNotEmpty() }
val emptySymbols = registerUser.symbols.filter { it.schools.isEmpty() }
- if (emptySymbols.isNotEmpty() && notEmptySymbols.isNotEmpty() && emptySymbols.any { it.symbol == loginData.symbol }) {
- add(createEmptySymbolItem(emptySymbols.first { it.symbol == loginData.symbol }))
+ if (emptySymbols.isNotEmpty() && notEmptySymbols.isNotEmpty() && emptySymbols.any { it.symbol == loginData.userEnteredSymbol }) {
+ add(createEmptySymbolItem(emptySymbols.first { it.symbol == loginData.userEnteredSymbol }))
}
addAll(createNotEmptySymbolItems(notEmptySymbols, students))
@@ -317,7 +317,7 @@ class LoginStudentSelectPresenter @Inject constructor(
loginData = loginData,
registerUser = registerUser,
lastErrorMessage = lastError?.message,
- enteredSymbol = loginData.symbol,
+ enteredSymbol = loginData.userEnteredSymbol,
)
)
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/support/LoginSupportDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/support/LoginSupportDialog.kt
index fcf7f51c9..4be2dbaad 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/support/LoginSupportDialog.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/support/LoginSupportDialog.kt
@@ -105,7 +105,7 @@ class LoginSupportDialog : BaseDialogFragment() {
"${appInfo.systemManufacturer} ${appInfo.systemModel}",
appInfo.systemVersion.toString(),
"${appInfo.versionName}-${appInfo.buildFlavor}",
- supportInfo.loginData.baseUrl + "/" + supportInfo.loginData.symbol,
+ supportInfo.loginData.let { "${it.baseUrl}/${it.defaultSymbol}/${it.userEnteredSymbol}" },
preferencesRepository.installationId,
getLastErrorFromStudentSelectScreen(),
dialogLoginSupportSchoolInput.text.takeIf { !it.isNullOrBlank() }
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolPresenter.kt
index 02bfde5d7..cc88b09e9 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolPresenter.kt
@@ -60,7 +60,7 @@ class LoginSymbolPresenter @Inject constructor(
}
loginData = loginData.copy(
- symbol = view?.symbolValue?.getNormalizedSymbol(),
+ userEnteredSymbol = view?.symbolValue?.getNormalizedSymbol(),
)
resourceFlow {
studentRepository.getUserSubjectsFromScrapper(
@@ -68,7 +68,7 @@ class LoginSymbolPresenter @Inject constructor(
password = loginData.password,
scrapperBaseUrl = loginData.baseUrl,
domainSuffix = loginData.domainSuffix,
- symbol = loginData.symbol.orEmpty(),
+ symbol = loginData.userEnteredSymbol.orEmpty(),
)
}.onEach { user ->
registerUser = user.dataOrNull
@@ -93,7 +93,7 @@ class LoginSymbolPresenter @Inject constructor(
else -> {
val enteredSymbolDetails = user.data.symbols
.firstOrNull()
- ?.takeIf { it.symbol == loginData.symbol }
+ ?.takeIf { it.symbol == loginData.userEnteredSymbol }
if (enteredSymbolDetails?.error is InvalidSymbolException) {
view?.run {
diff --git a/app/src/main/res/values/api_hosts.xml b/app/src/main/res/values/api_hosts.xml
index 94ef8abdc..6439b462f 100644
--- a/app/src/main/res/values/api_hosts.xml
+++ b/app/src/main/res/values/api_hosts.xml
@@ -47,7 +47,7 @@
- https://wulkanowy.net.pl/?email
- - Default
+ - warszawa
- opole
- gdansk
- lublin
@@ -66,7 +66,7 @@
- gminaulanmajorat
- gminaozorkow
- gminalopiennikgorny
- - Default
+ - warszawa
- powiatwulkanowy
diff --git a/app/src/test/java/io/github/wulkanowy/TestEnityCreator.kt b/app/src/test/java/io/github/wulkanowy/TestEnityCreator.kt
index eac1389f4..9f5d731b6 100644
--- a/app/src/test/java/io/github/wulkanowy/TestEnityCreator.kt
+++ b/app/src/test/java/io/github/wulkanowy/TestEnityCreator.kt
@@ -42,6 +42,7 @@ fun getSemesterPojo(diaryId: Int, semesterId: Int, start: LocalDate, end: LocalD
diaryName = "$semesterId",
schoolYear = 1970,
classId = 0,
+ className = "Ti",
semesterNumber = semesterName,
unitId = 1,
start = start,
diff --git a/app/src/test/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenterTest.kt b/app/src/test/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenterTest.kt
index fad6436d8..34965f00d 100644
--- a/app/src/test/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenterTest.kt
+++ b/app/src/test/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenterTest.kt
@@ -58,7 +58,7 @@ class LoginStudentSelectPresenterTest {
login = "",
password = "",
baseUrl = "",
- symbol = null,
+ defaultSymbol = "warszawa",
domainSuffix = "",
)
From cd853e4d5720881623a18b8e1dbfbfb234640ca7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rafa=C5=82=20Borcz?=
Date: Fri, 9 Feb 2024 19:34:04 +0100
Subject: [PATCH 24/50] New Crowdin updates (#2417)
---
app/src/main/res/values-cs/strings.xml | 13 +++++++++++++
app/src/main/res/values-de/strings.xml | 9 +++++++++
app/src/main/res/values-pl/strings.xml | 13 +++++++++++++
app/src/main/res/values-ru/strings.xml | 13 +++++++++++++
app/src/main/res/values-sk/strings.xml | 13 +++++++++++++
app/src/main/res/values-uk/strings.xml | 17 +++++++++++++++--
6 files changed, 76 insertions(+), 2 deletions(-)
diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml
index b4f1f878a..85a67f9b3 100644
--- a/app/src/main/res/values-cs/strings.xml
+++ b/app/src/main/res/values-cs/strings.xml
@@ -117,6 +117,7 @@
Součet bodů
Konečná známka
Předpokládaná známka
+ Popisná známka
Vypočítaný průměr
Jak funguje vypočítaný průměr?
Vypočítaný průměr je aritmetický průměr vypočítaný z průměrů předmětů. Umožňuje vám to znát přibližný konečný průměr. Vypočítává se způsobem zvoleným uživatelem v nastavení aplikaci. Doporučuje se vybrat příslušnou možnost. Důvodem je rozdílný výpočet školních průměrů. Pokud vaše škola navíc uvádí průměr předmětů na stránce deníku Vulcan, aplikace si je stáhne a tyto průměry nepočítá. To lze změnit vynucením výpočtu průměru v nastavení aplikaci.\n\nPrůměr známek pouze z vybraného semestru:\n1. Výpočet váženého průměru pro každý předmět v daném semestru\n2. Sčítání vypočítaných průměrů\n3. Výpočet aritmetického průměru součtených průměrů\n\nPrůměr průměrů z obou semestrů:\n1. Výpočet váženého průměru pro každý předmět v semestru 1 a 2\n2. Výpočet aritmetického průměru vypočítaných průměrů za semestry 1 a 2 pro každý předmět.\n3. Sčítání vypočítaných průměrů\n4. Výpočet aritmetického průměru sečtených průměrů\n\nPrůměr známek z celého roku:\n1. Výpočet váženého průměru za rok pro každý předmět. Konečný průměr v 1. semestru je nepodstatný.\n2. Sčítání vypočítaných průměrů\n3. Výpočet aritmetického průměru součtených průměrů
@@ -160,6 +161,12 @@
- Nové konečné známky
- Nové konečné známky
+
+ - Nová popisná známka
+ - Nové popisné známky
+ - Nové popisné známky
+ - Nové popisné známky
+
- Máte %1$d novou známku
- Máte %1$d nové známky
@@ -178,6 +185,12 @@
- Máte %1$d nových konečných známek
- Máte %1$d nových konečných známek
+
+ - Máte %1$d novou popisnou známku
+ - Máte %1$d nové popisné známky
+ - Máte %1$d nových popisných známek
+ - Máte %1$d nových popisných známek
+
Lekce
Učebna
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 7e0ce8689..0fbba392c 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -117,6 +117,7 @@
Gesamtpunkte
Finaler Note
Vorhergesagte Note
+ Descriptive grade
Berechnender Durchschnitt
Wie funktioniert der berechnete Durchschnitt?
Der berechnete Mittelwert ist das arithmetische Mittel, das aus den Durchschnittswerten der Probanden errechnet wird. Es erlaubt Ihnen, den ungefähre endgültigen Durchschnitt zu kennen. Sie wird auf eine vom Anwender in den Anwendungseinstellungen gewählte Weise berechnet. Es wird empfohlen, die entsprechende Option zu wählen. Das liegt daran, dass die Berechnung der Schuldurchschnitte unterschiedlich ist. Wenn Ihre Schule den Durchschnitt der Fächer auf der Vulcan-Seite angibt, lädt die Anwendung diese Fächer herunter und berechnet nicht den Durchschnitt. Dies kann geändert werden, indem die Berechnung des Durchschnitts in den Anwendungseinstellungen erzwungen wird. \n\nDurchschnitt der Noten nur aus dem ausgewählten Semester :\n1. Berechnung des gewichteten Durchschnitts für jedes Fach in einem bestimmten Semester\n2. Addition der berechneten Durchschnittswerte\n3. Berechnung des arithmetischen Mittels der summierten Durchschnitte\nDurchschnitt der Durchschnitte aus beiden Semestern:\n1. Berechnung des gewichteten Durchschnitts für jedes Fach in Semester 1 und 2\n2. Berechnung des arithmetischen Mittels der berechneten Durchschnitte für Semester 1 und 2 für jedes Fach. \n3. Hinzufügen von berechneten Durchschnittswerten\n4. Berechnung des arithmetischen Mittels der summierten Durchschnitte\nDurchschnitt der Noten aus dem ganzen Jahr:\n1. Berechnung des gewichteten Jahresdurchschnitts für jedes Fach. Der Abschlussdurchschnitt im 1. Semester ist irrelevant. \n2. Addition der berechneten Durchschnittswerte\n3. Berechnung des arithmetischen Mittels der summierten Mittelwerte
@@ -152,6 +153,10 @@
- Neue Abschlussnote
- Neue Abschlussnoten
+
+ - New descriptive grade
+ - New descriptive grades
+
- Du hast %1$d Note bekommen
- Du hast %1$d Noten bekommen
@@ -164,6 +169,10 @@
- Sie haben %1$d Abschlussnote bekommen
- Sie haben %1$d Abschlussnoten bekommen
+
+ - You received %1$d descriptive grade
+ - You received %1$d descriptive grades
+
Lektion
Klassenzimmer
diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml
index 1b4fbe664..df9ef8dbd 100644
--- a/app/src/main/res/values-pl/strings.xml
+++ b/app/src/main/res/values-pl/strings.xml
@@ -117,6 +117,7 @@
Suma punktów
Ocena końcowa
Przewidywana ocena
+ Ocena opisowa
Obliczona średnia
Jak działa obliczona średnia?
Obliczona średnia jest średnią arytmetyczną obliczoną ze średnich przedmiotów. Pozwala ona na poznanie przybliżonej średniej końcowej. Jest obliczana w sposób wybrany przez użytkownika w ustawieniach aplikacji. Zaleca się wybranie odpowiedniej opcji. Dzieje się tak dlatego, że obliczanie średnich w szkołach różni się. Dodatkowo, jeśli twoja szkoła ma włączone średnie przedmiotów na stronie dziennika Vulcan, aplikacja pobiera je i ich nie oblicza. Można to zmienić, wymuszając obliczanie średniej w ustawieniach aplikacji.\n\nŚrednia ocen tylko z wybranego semestru:\n1. Obliczanie średniej arytmetycznej każdego przedmiotu w danym semestrze\n2. Zsumowanie obliczonych średnich\n3. Obliczanie średniej arytmetycznej zsumowanych średnich\n\nŚrednia ze średnich z obu semestrów:\n1.Obliczanie średniej arytmetycznej każdego przedmiotu w semestrze 1 i 2\n2. Obliczanie średniej arytmetycznej obliczonych średnich w semestrze 1 i 2 każdego przedmiotu.\n3. Zsumowanie obliczonych średnich\n4. Obliczanie średniej arytmetycznej zsumowanych średnich\n\nŚrednia wszystkich ocen z całego roku:\n1. Obliczanie średniej arytmetycznej z każdego przedmiotu w ciągu całego roku. Końcowa ocena w 1 semestrze jest bez znaczenia.\n2. Zsumowanie obliczonych średnich\n3. Obliczanie średniej arytmetycznej z zsumowanych średnich
@@ -160,6 +161,12 @@
- Nowe oceny końcowe
- Nowe oceny końcowe
+
+ - Nowa ocena opisowa
+ - Nowe oceny opisowe
+ - Nowe oceny opisowe
+ - Nowe oceny opisowe
+
- Masz %1$d nową ocenę
- Masz %1$d nowe oceny
@@ -178,6 +185,12 @@
- Masz %1$d nowych końcowych ocen
- Masz %1$d nowych końcowych ocen
+
+ - Masz %1$d nową ocenę opisową
+ - Masz %1$d nowe oceny opisowe
+ - Masz %1$d nowych ocen opisowych
+ - Masz %1$d nowych ocen opisowych
+
Lekcja
Sala
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index 2ca669287..fffc5ce1e 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -117,6 +117,7 @@
Сумма баллов
Итоговая оценка
Ожидаемая оценка
+ Descriptive grade
Рассчитанная средняя оценка
Как работает \"Рассчитанная средняя оценка\"?
Рассчитанная средняя оценка - это среднее арифметическое, рассчитанное на основе средних оценок по предметам. Это позволяет узнать приблизительную итоговую среднюю оценку. Она рассчитывается способом, выбранным пользователем в настройках приложения. Рекомендуется выбрать подходящий вариант, так как каждая школа по разному считает среднюю оценку. Кроме того, если ваша школа выставляет средние оценки по предметам на странице Vulcan, приложение просто загрузит их. Это можно изменить, заставив приложение считать среднюю оценку в настройках.\n\nСредняя из оценок выбранного семестра:\n1. Вычисление средневзвешенного значения по каждому предмету за семестр\n2.Суммирование вычисленных значений\n3. Вычисление среднего арифметического суммированных значений\n\nСредняя из средних оценок семестров:\n1.Расчет средневзвешенного значения для каждого предмета в семестрах. \n2. Вычисление среднего арифметического из средневзвешенных значений для каждого предмета в семестрах.\n3. Суммирование средних арифметических\n4. Вычисление среднего арифматического из суммированных значений\n\nСредняя из оценок со всего года:\n1. Расчет средневзвешенного значения по каждому предмету за год. Итоговое среднее значение за 1 семестр не имеет значения.\n2. Суммирование вычисленных средних\n3. Расчет среднего арифметического суммированных чисел
@@ -160,6 +161,12 @@
- Новые итоговые оценки
- Новые итоговые оценки
+
+ - New descriptive grade
+ - New descriptive grades
+ - New descriptive grades
+ - New descriptive grades
+
- Вы получили %1$d новую оценку
- Вы получили %1$d новые оценки
@@ -178,6 +185,12 @@
- Вы получили %1$d новых итоговых оценок
- Вы получили %1$d новых итоговые оценки
+
+ - You received %1$d descriptive grade
+ - You received %1$d descriptive grades
+ - You received %1$d descriptive grades
+ - You received %1$d descriptive grades
+
Урок
Аудитория
diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml
index aaf04bc85..1e822890b 100644
--- a/app/src/main/res/values-sk/strings.xml
+++ b/app/src/main/res/values-sk/strings.xml
@@ -117,6 +117,7 @@
Súčet bodov
Konečná známka
Predpokladaná známka
+ Popisná známka
Vypočítaný priemer
Ako funguje vypočítaný priemer?
Vypočítaný priemer je aritmetický priemer vypočítaný z priemerov predmetov. Umožňuje vám to poznať približný konečný priemer. Vypočítava sa spôsobom zvoleným užívateľom v nastaveniach aplikácii. Odporúča sa vybrať príslušnú možnosť. Dôvodom je rozdielny výpočet školských priemerov. Ak vaša škola navyše uvádza priemer predmetov na stránke denníka Vulcan, aplikácia si ich stiahne a tieto priemery nepočíta. To možno zmeniť vynútením výpočtu priemeru v nastavení aplikácii.\n\nPriemer známok iba z vybraného semestra:\n1. Výpočet váženého priemeru pre každý predmet v danom semestri\n2. Sčítanie vypočítaných priemerov\n3. Výpočet aritmetického priemeru součtených priemerov\n\nPriemer priemerov z oboch semestrov:\n1. Výpočet váženého priemeru pre každý predmet v semestri 1 a 2\n2. Výpočet aritmetického priemeru vypočítaných priemerov za semestre 1 a 2 pre každý predmet.\n3. Sčítanie vypočítaných priemerov\n4. Výpočet aritmetického priemeru součtených priemerov\n\nPriemer známok z celého roka:\n1. Výpočet váženého priemeru za rok pre každý predmet. Konečný priemer v 1. semestri je nepodstatný.\n2. Sčítanie vypočítaných priemerov\n3. Výpočet aritmetického priemeru součtených priemerov
@@ -160,6 +161,12 @@
- Nové konečné známky
- Nové konečné známky
+
+ - Nová popisná známka
+ - Nové popisné známky
+ - Nové popisné známky
+ - Nové popisné známky
+
- Máte %1$d novú známku
- Máte %1$d nové známky
@@ -178,6 +185,12 @@
- Máte %1$d nových konečných známok
- Máte %1$d nových konečných známok
+
+ - Máte %1$d novú popisnú známku
+ - Máte %1$d nové popisné známky
+ - Máte %1$d nových popisných známok
+ - Máte %1$d nových popisných známok
+
Lekcia
Učebňa
diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml
index fffae003b..47034de62 100644
--- a/app/src/main/res/values-uk/strings.xml
+++ b/app/src/main/res/values-uk/strings.xml
@@ -97,8 +97,8 @@
Увійти
Минув термін дії сесії
Минув термін дії сесії, авторизуйтеся знову
- Your account password has been changed. You need to log in to Wulkanowy again
- Password changed
+ Пароль вашого облікового запису був змінений. Ви повинні увійти в Wulkanowy знову
+ Пароль змінено
Підтримка додатку
Вам подобається цей додаток? Підтримайте його розвиток, увімкнувши неінвазивну рекламу, яку ви можете відключити в будь-який час
Увімкнути рекламу
@@ -117,6 +117,7 @@
Всього балів
Підсумкова оцінка
Передбачувана оцінка
+ Описова оцінка
Розрахована середня оцінка
Як працює \"Розрахована середня оцінка\"?
Розрахована середня оцінка - це середнє арифметичне, обчислене з середніх оцінок з предметів. Це дозволяє дізнатися приблизну кінцеву середню оцінку. Вона розраховується спосібом, обраним користувачем у налаштуваннях програми. Рекомендується вибрати відповідний варіант, тому що кожна школа по різному розраховує середню оцінку. Крім того, якщо у вашій школі повідомляється середня оцінка з предметів на сторінці Vulcan, програма тільки завантажує ці оцінки і не розраховує їх самостійно. Це можна змінити шляхом примусового розрахунку середньоЇ оцінки в налаштуваннях програми.\n\nСередні оцінки тільки за обраний семестр:\n1. Розрахунок середньозваженого числа для кожного предмета в даному семестрі\n2. Сумування розрахованих числ\n3. Розрахунок середнього арифметичного з сумованих чисел\n\nСереднє значення з обох семестрів:\n1. Обчислення середньозваженого числа для кожного предмета у 1 та 2 семестрі\n2. Обчислення середнього арифметичного з розрахованих середньозважених числ за 1 та 2 семестри для кожного предмета.\n3. Додавання розрахованих середніх\n4. Розрахунок середнього арифметичного підсумованих середніх значень\n\nСереднє значення оцінок за весь рік: \n1. Розрахунок середньозваженого числа за рік для кожного предмета. Підсумковий середній показник у 1-му семестрі не має значення.\n2. Сумування розрахованих середніх\n3. Обчислення середнього арифметичного з суммованих середніх
@@ -160,6 +161,12 @@
- Нові підсумкові оцінки
- Нові підсумкові оцінки
+
+ - Нова описова оцінка
+ - Нових описових оцінок
+ - Описових оцінок
+ - Нові описові оцінки
+
- Ви отримали %1$d нову оцінку
- Ви отримали %1$d нові оцінки
@@ -178,6 +185,12 @@
- Ви отримали %1$d нових підсумкових оцінок
- Ви отримали %1$d нових підсумкових оцінок
+
+ - Ви отримали %1$d описову оцінку
+ - Ви отримали %1$d нові описові оцінки
+ - Ви отримали %1$d нових описових оцінок
+ - Ви отримали %1$d нових описових оцінок
+
Урок
Аудиторія
From 2d4a1bff830b172232b6ca7074f097057bc5a608 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Miko=C5=82aj=20Pich?=
Date: Fri, 9 Feb 2024 19:44:55 +0100
Subject: [PATCH 25/50] Version 2.4.0
---
app/build.gradle | 8 ++++----
app/src/main/play/release-notes/pl-PL/default.txt | 8 +++++---
2 files changed, 9 insertions(+), 7 deletions(-)
diff --git a/app/build.gradle b/app/build.gradle
index 65a42ce1d..766bee8dd 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -27,8 +27,8 @@ android {
testApplicationId "io.github.tests.wulkanowy"
minSdkVersion 21
targetSdkVersion 34
- versionCode 145
- versionName "2.3.5"
+ versionCode 146
+ versionName "2.4.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
resValue "string", "app_name", "Wulkanowy"
@@ -164,7 +164,7 @@ play {
defaultToAppBundles = false
track = 'production'
releaseStatus = ReleaseStatus.IN_PROGRESS
- userFraction = 0.15d
+ userFraction = 0.50d
updatePriority = 1
enabled.set(false)
}
@@ -195,7 +195,7 @@ ext {
}
dependencies {
- implementation 'io.github.wulkanowy:sdk:2.3.8-SNAPSHOT'
+ implementation 'io.github.wulkanowy:sdk:2.4.0'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4'
diff --git a/app/src/main/play/release-notes/pl-PL/default.txt b/app/src/main/play/release-notes/pl-PL/default.txt
index 04f3ba463..012bbd26d 100644
--- a/app/src/main/play/release-notes/pl-PL/default.txt
+++ b/app/src/main/play/release-notes/pl-PL/default.txt
@@ -1,6 +1,8 @@
-Wersja 2.3.5
+Wersja 2.4.0
-— naprawiliśmy ładowanie frekwencji dla szkół używających eduOne
-— naprawiliśmy wielokrotne wysyłanie powiadomień o zmianach w planie lekcji
+— naprawiliśmy logowanie do aplikacji na odmianie standardowej
+— naprawiliśmy wyświetlanie lekcji na kolejny dzień w kafelku na ekranie Start
+— dodaliśmy oceny opisowe
+— dodaliśmy kolorowe opisy we frekwencji we wpisach innych niż obecność
Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases
From cc01525f167d4d313bc8f1a48a1d8e895e6db431 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Fri, 16 Feb 2024 18:49:46 +0000
Subject: [PATCH 26/50] Bump com.google.gms:google-services from 4.4.0 to 4.4.1
(#2423)
---
build.gradle | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/build.gradle b/build.gradle
index e6e090b80..f7f3d209e 100644
--- a/build.gradle
+++ b/build.gradle
@@ -16,7 +16,7 @@ buildscript {
classpath "com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin:$kotlin_version-1.0.16"
classpath 'com.android.tools.build:gradle:8.2.2'
classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
- classpath 'com.google.gms:google-services:4.4.0'
+ classpath 'com.google.gms:google-services:4.4.1'
classpath 'com.huawei.agconnect:agcp:1.9.1.303'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.9'
classpath "com.github.triplet.gradle:play-publisher:3.8.4"
From b5e17c4ff7b607eed9d1f9b65e82114c7b00d01a Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Fri, 16 Feb 2024 18:50:07 +0000
Subject: [PATCH 27/50] Bump com.google.firebase:firebase-bom from 32.7.1 to
32.7.2 (#2424)
---
app/build.gradle | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/build.gradle b/app/build.gradle
index 766bee8dd..5ca8c3b5b 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -252,7 +252,7 @@ dependencies {
implementation 'com.fredporciuncula:flow-preferences:1.9.1'
implementation 'org.apache.commons:commons-text:1.11.0'
- playImplementation platform('com.google.firebase:firebase-bom:32.7.1')
+ playImplementation platform('com.google.firebase:firebase-bom:32.7.2')
playImplementation 'com.google.firebase:firebase-analytics'
playImplementation 'com.google.firebase:firebase-messaging'
playImplementation 'com.google.firebase:firebase-crashlytics:'
From 6f4a8d5534916eabfa32f21dbd34139bbac1ced7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Miko=C5=82aj=20Pich?=
Date: Sat, 17 Feb 2024 11:41:32 +0100
Subject: [PATCH 28/50] Add missing symbol and custom domain suffix validation
(#2425)
---
app/build.gradle | 2 +-
.../modules/login/form/LoginFormFragment.kt | 11 ++++++
.../modules/login/form/LoginFormPresenter.kt | 36 +++++++++++++------
.../ui/modules/login/form/LoginFormView.kt | 4 +++
.../login/symbol/LoginSymbolPresenter.kt | 15 +++++---
.../main/res/layout/fragment_login_form.xml | 1 +
app/src/main/res/values/strings.xml | 1 +
7 files changed, 55 insertions(+), 15 deletions(-)
diff --git a/app/build.gradle b/app/build.gradle
index 5ca8c3b5b..f40b74dbb 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -195,7 +195,7 @@ ext {
}
dependencies {
- implementation 'io.github.wulkanowy:sdk:2.4.0'
+ implementation 'io.github.wulkanowy:sdk:2.4.1-SNAPSHOT'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4'
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormFragment.kt
index 975cad185..1c4920696 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormFragment.kt
@@ -94,6 +94,7 @@ class LoginFormFragment : BaseFragment(R.layout.fragme
loginFormUsername.doOnTextChanged { _, _, _, _ -> presenter.onUsernameTextChanged() }
loginFormPass.doOnTextChanged { _, _, _, _ -> presenter.onPassTextChanged() }
loginFormHost.setOnItemClickListener { _, _, _, _ -> presenter.onHostSelected() }
+ loginFormDomainSuffix.doOnTextChanged { _, _, _, _ -> presenter.onDomainSuffixChanged() }
loginFormSignIn.setOnClickListener { presenter.onSignInClick() }
loginFormAdvancedButton.setOnClickListener { presenter.onAdvancedLoginClick() }
loginFormPrivacyLink.setOnClickListener { presenter.onPrivacyLinkClick() }
@@ -188,6 +189,12 @@ class LoginFormFragment : BaseFragment(R.layout.fragme
}
}
+ override fun setDomainSuffixInvalid() {
+ with(binding.loginFormDomainSuffixLayout) {
+ error = getString(R.string.login_invalid_domain_suffix)
+ }
+ }
+
override fun clearUsernameError() {
binding.loginFormUsernameLayout.error = null
binding.loginFormErrorBox.isVisible = false
@@ -206,6 +213,10 @@ class LoginFormFragment : BaseFragment(R.layout.fragme
binding.loginFormErrorBox.isVisible = false
}
+ override fun clearDomainSuffixError() {
+ binding.loginFormDomainSuffixLayout.error = null
+ }
+
override fun showSoftKeyboard() {
activity?.showSoftInput()
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenter.kt
index af89f147c..69e1d027d 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenter.kt
@@ -101,6 +101,12 @@ class LoginFormPresenter @Inject constructor(
}
}
+ fun onDomainSuffixChanged() {
+ view?.apply {
+ clearDomainSuffixError()
+ }
+ }
+
fun updateCustomDomainSuffixVisibility() {
view?.run {
showDomainSuffixInput("customSuffix" in formHostValue)
@@ -159,7 +165,7 @@ class LoginFormPresenter @Inject constructor(
fun onSignInClick() {
val loginData = getLoginData()
- if (!validateCredentials(loginData.login, loginData.password, loginData.baseUrl)) return
+ if (!validateCredentials(loginData)) return
resourceFlow {
studentRepository.getUserSubjectsFromScrapper(
@@ -229,24 +235,29 @@ class LoginFormPresenter @Inject constructor(
view?.onRecoverClick()
}
- private fun validateCredentials(login: String, password: String, host: String): Boolean {
+ private fun validateCredentials(loginData: LoginData): Boolean {
var isCorrect = true
- if (login.isEmpty()) {
+ if (loginData.login.isEmpty()) {
view?.setErrorUsernameRequired()
isCorrect = false
} else {
- if ("@" in login && "login" in host) {
+ if ("@" in loginData.login && "login" in loginData.baseUrl) {
view?.setErrorLoginRequired()
isCorrect = false
}
- if ("@" !in login && "email" in host) {
+ if ("@" !in loginData.login && "email" in loginData.baseUrl) {
view?.setErrorEmailRequired()
isCorrect = false
}
- if ("@" in login && "||" !in login && "login" !in host && "email" !in host) {
- val emailHost = login.substringAfter("@")
- val emailDomain = URL(host).host
+
+ val isEmailLogin = "@" in loginData.login
+ val isEmailWithLogin = "||" !in loginData.login
+ val isLoginNotRequired = "login" !in loginData.baseUrl
+ val isEmailNotRequired = "email" !in loginData.baseUrl
+ if (isEmailLogin && isEmailWithLogin && isLoginNotRequired && isEmailNotRequired) {
+ val emailHost = loginData.login.substringAfter("@")
+ val emailDomain = URL(loginData.baseUrl).host
if (!emailHost.equals(emailDomain, true)) {
view?.setErrorEmailInvalid(domain = emailDomain)
isCorrect = false
@@ -254,16 +265,21 @@ class LoginFormPresenter @Inject constructor(
}
}
- if (password.isEmpty()) {
+ if (loginData.password.isEmpty()) {
view?.setErrorPassRequired(focus = isCorrect)
isCorrect = false
}
- if (password.length < 6 && password.isNotEmpty()) {
+ if (loginData.password.length < 6 && loginData.password.isNotEmpty()) {
view?.setErrorPassInvalid(focus = isCorrect)
isCorrect = false
}
+ if (loginData.domainSuffix !in listOf("", "rc", "kurs")) {
+ view?.setDomainSuffixInvalid()
+ isCorrect = false
+ }
+
return isCorrect
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormView.kt
index f2b7b1003..6ea22d180 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormView.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormView.kt
@@ -46,12 +46,16 @@ interface LoginFormView : BaseView {
fun setErrorEmailInvalid(domain: String)
+ fun setDomainSuffixInvalid()
+
fun clearUsernameError()
fun clearPassError()
fun clearHostError()
+ fun clearDomainSuffixError()
+
fun showSoftKeyboard()
fun hideSoftKeyboard()
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolPresenter.kt
index cc88b09e9..5c31f14d4 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolPresenter.kt
@@ -96,10 +96,7 @@ class LoginSymbolPresenter @Inject constructor(
?.takeIf { it.symbol == loginData.userEnteredSymbol }
if (enteredSymbolDetails?.error is InvalidSymbolException) {
- view?.run {
- setErrorSymbolInvalid()
- showContact(true)
- }
+ showInvalidSymbolError()
} else {
Timber.i("Login with symbol result: Success")
view?.navigateToStudentSelect(loginData, requireNotNull(user.data))
@@ -128,6 +125,9 @@ class LoginSymbolPresenter @Inject constructor(
loginErrorHandler.dispatch(user.error)
lastError = user.error
view?.showContact(true)
+ if (user.error is InvalidSymbolException) {
+ showInvalidSymbolError()
+ }
}
}
}.onResourceNotLoading {
@@ -145,6 +145,13 @@ class LoginSymbolPresenter @Inject constructor(
return normalizedSymbol in definitelyInvalidSymbols
}
+ private fun showInvalidSymbolError() {
+ view?.run {
+ setErrorSymbolInvalid()
+ showContact(true)
+ }
+ }
+
fun onFaqClick() {
view?.openFaqPage()
}
diff --git a/app/src/main/res/layout/fragment_login_form.xml b/app/src/main/res/layout/fragment_login_form.xml
index fc5e5f35e..10864e640 100644
--- a/app/src/main/res/layout/fragment_login_form.xml
+++ b/app/src/main/res/layout/fragment_login_form.xml
@@ -261,6 +261,7 @@
android:layout_marginEnd="24dp"
android:layout_marginRight="24dp"
android:hint="@string/login_domain_suffix_hint"
+ app:errorEnabled="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/loginFormHostLayout"
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index f1fa3ce73..0a4dcf7f4 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -61,6 +61,7 @@
Invalid email
Use the assigned login instead of email
Use the assigned login or email in @%1$s
+ Invalid domain suffix
Invalid symbol. If you cannot find it, please contact the school
Don\'t make this up! If you cannot find it, please contact the school
Student not found. Validate the symbol and the chosen variation of the UONET+ register
From 736d16a7ab475c86c5306df17de5be8457533620 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Miko=C5=82aj=20Pich?=
Date: Sat, 17 Feb 2024 12:31:14 +0100
Subject: [PATCH 29/50] Make WebkitCookieManagerProxy no-op if webview is not
available on the device (#2427)
---
.../utils/WebkitCookieManagerProxy.kt | 20 +++++++++++++++----
1 file changed, 16 insertions(+), 4 deletions(-)
diff --git a/app/src/main/java/io/github/wulkanowy/utils/WebkitCookieManagerProxy.kt b/app/src/main/java/io/github/wulkanowy/utils/WebkitCookieManagerProxy.kt
index a54978717..3d41c711c 100644
--- a/app/src/main/java/io/github/wulkanowy/utils/WebkitCookieManagerProxy.kt
+++ b/app/src/main/java/io/github/wulkanowy/utils/WebkitCookieManagerProxy.kt
@@ -1,5 +1,6 @@
package io.github.wulkanowy.utils
+import android.util.AndroidRuntimeException
import java.net.CookiePolicy
import java.net.CookieStore
import java.net.HttpCookie
@@ -9,7 +10,18 @@ import java.net.CookieManager as JavaCookieManager
class WebkitCookieManagerProxy : JavaCookieManager(null, CookiePolicy.ACCEPT_ALL) {
- private val webkitCookieManager: WebkitCookieManager = WebkitCookieManager.getInstance()
+ private val webkitCookieManager: WebkitCookieManager? = getWebkitCookieManager()
+
+ /**
+ * @see [https://stackoverflow.com/a/70354583/6695449]
+ */
+ private fun getWebkitCookieManager(): WebkitCookieManager? {
+ return try {
+ WebkitCookieManager.getInstance()
+ } catch (e: AndroidRuntimeException) {
+ null
+ }
+ }
override fun put(uri: URI?, responseHeaders: Map>?) {
if (uri == null || responseHeaders == null) return
@@ -23,7 +35,7 @@ class WebkitCookieManagerProxy : JavaCookieManager(null, CookiePolicy.ACCEPT_ALL
// process each of the headers
for (headerValue in responseHeaders[headerKey].orEmpty()) {
- webkitCookieManager.setCookie(url, headerValue)
+ webkitCookieManager?.setCookie(url, headerValue)
}
}
}
@@ -34,7 +46,7 @@ class WebkitCookieManagerProxy : JavaCookieManager(null, CookiePolicy.ACCEPT_ALL
): Map> {
require(!(uri == null || requestHeaders == null)) { "Argument is null" }
val res = mutableMapOf>()
- val cookie = webkitCookieManager.getCookie(uri.toString())
+ val cookie = webkitCookieManager?.getCookie(uri.toString())
if (cookie != null) res["Cookie"] = listOf(cookie)
return res
}
@@ -50,7 +62,7 @@ class WebkitCookieManagerProxy : JavaCookieManager(null, CookiePolicy.ACCEPT_ALL
cookies.remove(uri, cookie)
override fun removeAll(): Boolean {
- webkitCookieManager.removeAllCookies(null)
+ webkitCookieManager?.removeAllCookies(null) ?: return false
return true
}
}
From e757585bd38030e09282d1ce6ceb0d083298e6b6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rafa=C5=82=20Borcz?=
Date: Sat, 17 Feb 2024 12:43:52 +0100
Subject: [PATCH 30/50] New Crowdin updates (#2426)
---
app/src/main/res/values-cs/strings.xml | 1 +
app/src/main/res/values-de/strings.xml | 1 +
app/src/main/res/values-pl/strings.xml | 1 +
app/src/main/res/values-ru/strings.xml | 1 +
app/src/main/res/values-sk/strings.xml | 1 +
app/src/main/res/values-uk/strings.xml | 1 +
6 files changed, 6 insertions(+)
diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml
index 85a67f9b3..e1cafa6ea 100644
--- a/app/src/main/res/values-cs/strings.xml
+++ b/app/src/main/res/values-cs/strings.xml
@@ -56,6 +56,7 @@
Neplatný e-mail
Místo e-mailu použijte přiřazené přihlašovací údaje
Použijte přiřazené přihlašovací nebo e-mail v @%1$s
+ Invalid domain suffix
Neplatný symbol. Pokud jej nemůžete najít, kontaktujte školu
Nevymýšlejte si! Pokud symbol nemůžete najít, kontaktujte školu
Žák nebyl nalezen. Zkontrolujte správnost symbolu a vybrané varianty deníku UONET+
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 0fbba392c..5bd71bb29 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -56,6 +56,7 @@
Ungültige email
Den zugewiesenen Login anstelle von email verwenden
Benutze den zugewiesenen Login oder E-Mail in @%1$s
+ Invalid domain suffix
Invalid symbol. If you cannot find it, please contact the school
Don\'t make this up! If you cannot find it, please contact the school
Schüler nicht gefunden. Überprüfen Sie das Symbol und die gewählte Variation des UONET+ Registers
diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml
index df9ef8dbd..70d4982b9 100644
--- a/app/src/main/res/values-pl/strings.xml
+++ b/app/src/main/res/values-pl/strings.xml
@@ -56,6 +56,7 @@
Nieprawidłowy adres e-mail
Użyj loginu zamiast adresu e-mail
Użyj loginu lub adresu e-mail w @%1$s
+ Nieprawidłowy sufiks domeny
Nieprawidłowy symbol. Jeśli nie możesz go znaleźć, skontaktuj się ze szkołą
Nie zmyślaj! Jeśli nie możesz znaleźć symbolu, skontaktuj się ze szkołą
Nie znaleziono ucznia. Sprawdź poprawność symbolu i wybranej odmiany dziennika UONET+
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index fffc5ce1e..717e02131 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -56,6 +56,7 @@
Неверный e-mail
Используйте назначенный логин вместо e-mail
Используйте назначенный логин или email в @%1$s
+ Invalid domain suffix
Invalid symbol. If you cannot find it, please contact the school
Don\'t make this up! If you cannot find it, please contact the school
Ученик не найден. Проверьте symbol и выбранный тип дненика UONET+
diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml
index 1e822890b..368ead9d5 100644
--- a/app/src/main/res/values-sk/strings.xml
+++ b/app/src/main/res/values-sk/strings.xml
@@ -56,6 +56,7 @@
Neplatný e-mail
Namiesto e-mailu použite priradené prihlasovacie údaje
Použite priradené prihlasovacie alebo e-mail v @%1$s
+ Invalid domain suffix
Neplatný symbol. Pokiaľ ho nemôžete nájsť, kontaktujte školu
Nevymýšľajte si! Pokiaľ symbol nemôžete nájsť, kontaktujte školu
Žiak nebol nájdený. Skontrolujte správnosť symbolu a vybrané varianty denníka UONET+
diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml
index 47034de62..3d10f1179 100644
--- a/app/src/main/res/values-uk/strings.xml
+++ b/app/src/main/res/values-uk/strings.xml
@@ -56,6 +56,7 @@
Недійсна адреса e-mail
Використовуйте призначений логін замість адреси e-mail
Використовуйте призначений логін або адресу e-mail в @%1$s
+ Invalid domain suffix
Некоректний символ. Якщо ви не можете знайти його, будь ласка, зв\'яжіться зі школою
Не вигадуйте! Якщо ви не можете знайти його, будь ласка, зв\'яжіться зі школою
Студента не знайдено. Перевірте symbol та обраний тип щоденника UONET+
From 3cf6c295b01512f6acabd27e864d821c3f5a79cb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Miko=C5=82aj=20Pich?=
Date: Sat, 17 Feb 2024 13:07:45 +0100
Subject: [PATCH 31/50] Version 2.4.1
---
app/build.gradle | 6 +++---
app/src/main/play/release-notes/pl-PL/default.txt | 7 ++-----
2 files changed, 5 insertions(+), 8 deletions(-)
diff --git a/app/build.gradle b/app/build.gradle
index f40b74dbb..e6d35f548 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -27,8 +27,8 @@ android {
testApplicationId "io.github.tests.wulkanowy"
minSdkVersion 21
targetSdkVersion 34
- versionCode 146
- versionName "2.4.0"
+ versionCode 147
+ versionName "2.4.1"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
resValue "string", "app_name", "Wulkanowy"
@@ -195,7 +195,7 @@ ext {
}
dependencies {
- implementation 'io.github.wulkanowy:sdk:2.4.1-SNAPSHOT'
+ implementation 'io.github.wulkanowy:sdk:2.4.1'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4'
diff --git a/app/src/main/play/release-notes/pl-PL/default.txt b/app/src/main/play/release-notes/pl-PL/default.txt
index 012bbd26d..5736992bf 100644
--- a/app/src/main/play/release-notes/pl-PL/default.txt
+++ b/app/src/main/play/release-notes/pl-PL/default.txt
@@ -1,8 +1,5 @@
-Wersja 2.4.0
+Wersja 2.4.1
-— naprawiliśmy logowanie do aplikacji na odmianie standardowej
-— naprawiliśmy wyświetlanie lekcji na kolejny dzień w kafelku na ekranie Start
-— dodaliśmy oceny opisowe
-— dodaliśmy kolorowe opisy we frekwencji we wpisach innych niż obecność
+- drobne poprawki stabilności aplikacji i odświeżania danych
Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases
From 7d8be1b9fc63e7e232ea93754abd385a62bd825c Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Sun, 18 Feb 2024 14:21:38 +0000
Subject: [PATCH 32/50] Bump coroutines from 1.7.3 to 1.8.0 (#2428)
---
app/build.gradle | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/build.gradle b/app/build.gradle
index e6d35f548..0b9512cb9 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -191,7 +191,7 @@ ext {
room = "2.6.1"
chucker = "4.0.0"
mockk = "1.13.9"
- coroutines = "1.7.3"
+ coroutines = "1.8.0"
}
dependencies {
From cfec79405fe8c683012ff5583ccbe508bee502c9 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Sun, 18 Feb 2024 14:21:59 +0000
Subject: [PATCH 33/50] Bump org.jetbrains.kotlinx:kotlinx-serialization-json
(#2429)
---
app/build.gradle | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/build.gradle b/app/build.gradle
index 0b9512cb9..1408fedf1 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -199,7 +199,7 @@ dependencies {
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4'
- implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2"
+ implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines"
implementation 'androidx.core:core-ktx:1.12.0'
From ec101c1f52a6ea150d52484314cf66003891c0f5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rafa=C5=82=20Borcz?=
Date: Mon, 19 Feb 2024 11:30:05 +0100
Subject: [PATCH 34/50] Fix error handling in widgets (#2430)
---
.../java/io/github/wulkanowy/data/Resource.kt | 11 +++-
.../LuckyNumberWidgetProvider.kt | 15 ++---
.../timetablewidget/TimetableWidgetFactory.kt | 59 +++++++++++++------
.../timetablewidget/TimetableWidgetItem.kt | 5 ++
.../layout/item_widget_timetable_error.xml | 12 ++++
app/src/main/res/layout/widget_timetable.xml | 2 -
6 files changed, 72 insertions(+), 32 deletions(-)
create mode 100644 app/src/main/res/layout/item_widget_timetable_error.xml
diff --git a/app/src/main/java/io/github/wulkanowy/data/Resource.kt b/app/src/main/java/io/github/wulkanowy/data/Resource.kt
index d7c2aeed9..108b0d58e 100644
--- a/app/src/main/java/io/github/wulkanowy/data/Resource.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/Resource.kt
@@ -30,8 +30,15 @@ val Resource.dataOrNull: T?
get() = when (this) {
is Resource.Success -> this.data
is Resource.Intermediate -> this.data
- is Resource.Loading -> null
- is Resource.Error -> null
+ else -> null
+ }
+
+val Resource.dataOrThrow: T
+ get() = when (this) {
+ is Resource.Success -> this.data
+ is Resource.Intermediate -> this.data
+ is Resource.Loading -> throw IllegalStateException("Resource is in loading state")
+ is Resource.Error -> throw this.error
}
val Resource.errorOrNull: Throwable?
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetProvider.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetProvider.kt
index bafb2d7e5..1ab079a3a 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetProvider.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetProvider.kt
@@ -11,10 +11,8 @@ import android.view.View
import android.widget.RemoteViews
import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R
-import io.github.wulkanowy.data.Resource
-import io.github.wulkanowy.data.dataOrNull
+import io.github.wulkanowy.data.dataOrThrow
import io.github.wulkanowy.data.db.SharedPrefProvider
-import io.github.wulkanowy.data.db.entities.LuckyNumber
import io.github.wulkanowy.data.repositories.LuckyNumberRepository
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.data.toFirstResult
@@ -69,8 +67,7 @@ class LuckyNumberWidgetProvider : AppWidgetProvider() {
appWidgetIds?.forEach { widgetId ->
val studentId = sharedPref.getLong(getStudentWidgetKey(widgetId), 0)
- val luckyNumberResource = getLuckyNumber(studentId, widgetId)
- val luckyNumber = luckyNumberResource.dataOrNull?.luckyNumber?.toString()
+ val luckyNumber = getLuckyNumber(studentId, widgetId)?.luckyNumber?.toString()
val remoteView = RemoteViews(context.packageName, R.layout.widget_luckynumber)
.apply {
setTextViewText(R.id.luckyNumberWidgetValue, luckyNumber ?: "-")
@@ -143,18 +140,18 @@ class LuckyNumberWidgetProvider : AppWidgetProvider() {
sharedPref.putLong(getStudentWidgetKey(appWidgetId), it.id)
}
}
+
else -> null
}
if (currentStudent != null) {
luckyNumberRepository.getLuckyNumber(currentStudent, forceRefresh = false)
.toFirstResult()
- } else {
- Resource.Success(null)
- }
+ .dataOrThrow
+ } else null
} catch (e: Exception) {
Timber.e(e, "An error has occurred in lucky number provider")
- Resource.Error(e)
+ null
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetFactory.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetFactory.kt
index 4e0578e2b..4cfc03229 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetFactory.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetFactory.kt
@@ -11,7 +11,7 @@ import android.view.View.VISIBLE
import android.widget.RemoteViews
import android.widget.RemoteViewsService
import io.github.wulkanowy.R
-import io.github.wulkanowy.data.dataOrNull
+import io.github.wulkanowy.data.dataOrThrow
import io.github.wulkanowy.data.db.SharedPrefProvider
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
@@ -27,6 +27,7 @@ import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Co
import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getStudentWidgetKey
import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getTodayLastLessonEndDateTimeWidgetKey
import io.github.wulkanowy.utils.getCompatColor
+import io.github.wulkanowy.utils.getErrorString
import io.github.wulkanowy.utils.getPlural
import io.github.wulkanowy.utils.toFormattedString
import kotlinx.coroutines.runBlocking
@@ -67,25 +68,31 @@ class TimetableWidgetFactory(
override fun onDestroy() {}
override fun onDataSetChanged() {
- intent?.extras?.getInt(EXTRA_APPWIDGET_ID)?.let { appWidgetId ->
- val date = LocalDate.ofEpochDay(sharedPref.getLong(getDateWidgetKey(appWidgetId), 0))
- val studentId = sharedPref.getLong(getStudentWidgetKey(appWidgetId), 0)
+ val appWidgetId = intent?.extras?.getInt(EXTRA_APPWIDGET_ID) ?: return
+ val date = LocalDate.ofEpochDay(sharedPref.getLong(getDateWidgetKey(appWidgetId), 0))
+ val studentId = sharedPref.getLong(getStudentWidgetKey(appWidgetId), 0)
+ items = emptyList()
+
+ runBlocking {
runCatching {
- runBlocking {
- val student = getStudent(studentId) ?: return@runBlocking
- val semester = semesterRepository.getCurrentSemester(student)
- items = createItems(
- lessons = getLessons(student, semester, date),
- lastSync = timetableRepository.getLastRefreshTimestamp(semester, date, date)
- )
+ val student = getStudent(studentId) ?: return@runBlocking
+ val semester = semesterRepository.getCurrentSemester(student)
+ val lessons = getLessons(student, semester, date)
+ val lastSync = timetableRepository.getLastRefreshTimestamp(semester, date, date)
+
+ createItems(lessons, lastSync)
+ }
+ .onFailure {
+ items = listOf(TimetableWidgetItem.Error(it))
+ Timber.e(it, "An error has occurred in timetable widget factory")
+ }
+ .onSuccess {
+ items = it
if (date == LocalDate.now()) {
updateTodayLastLessonEnd(appWidgetId)
}
}
- }.onFailure {
- Timber.e(it, "An error has occurred in timetable widget factory")
- }
}
}
@@ -98,7 +105,7 @@ class TimetableWidgetFactory(
student: Student, semester: Semester, date: LocalDate
): List {
val timetable = timetableRepository.getTimetable(student, semester, date, date, false)
- val lessons = timetable.toFirstResult().dataOrNull?.lessons.orEmpty()
+ val lessons = timetable.toFirstResult().dataOrThrow.lessons
return lessons.sortedBy { it.number }
}
@@ -110,6 +117,7 @@ class TimetableWidgetFactory(
BETWEEN_AND_BEFORE_LESSONS -> 0
else -> null
}
+
return buildList {
lessons.forEach {
if (prefRepository.showTimetableGaps != NO_GAPS && prevNum != null && it.number > prevNum!! + 1) {
@@ -133,15 +141,12 @@ class TimetableWidgetFactory(
sharedPref.putLong(key, todayLastLessonEnd.epochSecond, true)
}
- companion object {
- const val TIME_FORMAT_STYLE = "HH:mm"
- }
-
override fun getViewAt(position: Int): RemoteViews? {
return when (val item = items.getOrNull(position) ?: return null) {
is TimetableWidgetItem.Normal -> getNormalItemRemoteView(item)
is TimetableWidgetItem.Empty -> getEmptyItemRemoteView(item)
is TimetableWidgetItem.Synchronized -> getSynchronizedItemRemoteView(item)
+ is TimetableWidgetItem.Error -> getErrorItemRemoteView(item)
}
}
@@ -213,6 +218,18 @@ class TimetableWidgetFactory(
}
}
+ private fun getErrorItemRemoteView(item: TimetableWidgetItem.Error): RemoteViews {
+ return RemoteViews(
+ context.packageName,
+ R.layout.item_widget_timetable_error
+ ).apply {
+ setTextViewText(
+ R.id.timetable_widget_item_error_message,
+ context.resources.getErrorString(item.error)
+ )
+ }
+ }
+
private fun updateTheme() {
when (context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) {
Configuration.UI_MODE_NIGHT_YES -> {
@@ -300,4 +317,8 @@ class TimetableWidgetFactory(
synchronizationTime,
)
}
+
+ private companion object {
+ private const val TIME_FORMAT_STYLE = "HH:mm"
+ }
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetItem.kt
index 166b1a8fb..fb02f8919 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetItem.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetItem.kt
@@ -17,10 +17,15 @@ sealed class TimetableWidgetItem(val type: TimetableWidgetItemType) {
data class Synchronized(
val timestamp: Instant,
) : TimetableWidgetItem(TimetableWidgetItemType.SYNCHRONIZED)
+
+ data class Error(
+ val error: Throwable
+ ) : TimetableWidgetItem(TimetableWidgetItemType.ERROR)
}
enum class TimetableWidgetItemType {
NORMAL,
EMPTY,
SYNCHRONIZED,
+ ERROR,
}
diff --git a/app/src/main/res/layout/item_widget_timetable_error.xml b/app/src/main/res/layout/item_widget_timetable_error.xml
new file mode 100644
index 000000000..6f9ab067a
--- /dev/null
+++ b/app/src/main/res/layout/item_widget_timetable_error.xml
@@ -0,0 +1,12 @@
+
+
diff --git a/app/src/main/res/layout/widget_timetable.xml b/app/src/main/res/layout/widget_timetable.xml
index b07cc78f6..b438da6c3 100644
--- a/app/src/main/res/layout/widget_timetable.xml
+++ b/app/src/main/res/layout/widget_timetable.xml
@@ -114,7 +114,5 @@
android:text="@string/widget_timetable_no_items"
android:textAppearance="?attr/textAppearanceBody1"
android:visibility="gone" />
-
-
From 729e72cddb4c9b04e509914a66e654b9608db3df Mon Sep 17 00:00:00 2001
From: Kacper Majcher
Date: Wed, 21 Feb 2024 21:36:20 +0100
Subject: [PATCH 35/50] Fix text pasting into date field in additional lesson
add dialog (#2433)
---
app/src/main/res/layout/dialog_additional_add.xml | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/app/src/main/res/layout/dialog_additional_add.xml b/app/src/main/res/layout/dialog_additional_add.xml
index 4be436d76..78967394b 100644
--- a/app/src/main/res/layout/dialog_additional_add.xml
+++ b/app/src/main/res/layout/dialog_additional_add.xml
@@ -39,7 +39,7 @@
android:layout_height="wrap_content"
android:editable="false"
android:focusable="false"
- android:inputType="text"
+ android:inputType="none"
tools:ignore="Deprecated" />
@@ -67,7 +67,7 @@
android:layout_height="wrap_content"
android:editable="false"
android:focusable="false"
- android:inputType="text"
+ android:inputType="none"
tools:ignore="Deprecated" />
@@ -87,7 +87,7 @@
android:layout_height="wrap_content"
android:editable="false"
android:focusable="false"
- android:inputType="text"
+ android:inputType="none"
tools:ignore="Deprecated" />
From 2776d019b957062b87a31f93ff6a282f5548124b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Miko=C5=82aj=20Pich?=
Date: Thu, 22 Feb 2024 15:52:40 +0100
Subject: [PATCH 36/50] =?UTF-8?q?Revert=20"Bump=20com.google.android.ump:u?=
=?UTF-8?q?ser-messaging-platform=20from=202.1.0=20to=202.2=E2=80=A6"=20(#?=
=?UTF-8?q?2434)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
This reverts commit fc91936884715cc1d46d5af17086364961f8631d.
---
app/build.gradle | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/build.gradle b/app/build.gradle
index 1408fedf1..c3a7baabd 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -262,7 +262,7 @@ dependencies {
playImplementation "com.google.android.play:integrity:1.3.0"
playImplementation 'com.google.android.play:app-update-ktx:2.1.0'
playImplementation 'com.google.android.play:review-ktx:2.0.1'
- playImplementation "com.google.android.ump:user-messaging-platform:2.2.0"
+ playImplementation "com.google.android.ump:user-messaging-platform:2.1.0"
hmsImplementation 'com.huawei.hms:hianalytics:6.12.0.301'
hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.9.1.303'
From b613b844692580874099e0d205919fb11b1d7b7f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Miko=C5=82aj=20Pich?=
Date: Thu, 22 Feb 2024 16:15:24 +0100
Subject: [PATCH 37/50] Version 2.4.2
---
app/build.gradle | 8 ++++----
app/src/main/play/release-notes/pl-PL/default.txt | 6 ++++--
2 files changed, 8 insertions(+), 6 deletions(-)
diff --git a/app/build.gradle b/app/build.gradle
index c3a7baabd..26c2547e5 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -27,8 +27,8 @@ android {
testApplicationId "io.github.tests.wulkanowy"
minSdkVersion 21
targetSdkVersion 34
- versionCode 147
- versionName "2.4.1"
+ versionCode 148
+ versionName "2.4.2"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
resValue "string", "app_name", "Wulkanowy"
@@ -164,8 +164,8 @@ play {
defaultToAppBundles = false
track = 'production'
releaseStatus = ReleaseStatus.IN_PROGRESS
- userFraction = 0.50d
- updatePriority = 1
+ userFraction = 0.99d
+ updatePriority = 2
enabled.set(false)
}
diff --git a/app/src/main/play/release-notes/pl-PL/default.txt b/app/src/main/play/release-notes/pl-PL/default.txt
index 5736992bf..ef6308b6c 100644
--- a/app/src/main/play/release-notes/pl-PL/default.txt
+++ b/app/src/main/play/release-notes/pl-PL/default.txt
@@ -1,5 +1,7 @@
-Wersja 2.4.1
+Wersja 2.4.2
-- drobne poprawki stabilności aplikacji i odświeżania danych
+- naprawiliśmy crash przy przełączaniu uczniów, motywów i języków
+- naprawiliśmy crash przy dodawaniu dodatkowych lekcji
+- naprawiliśmy obsługę błędów widżetach
Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases
From 31854fc4b86f3b66f63720709d423a62fa13b2c1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Miko=C5=82aj=20Pich?=
Date: Sun, 25 Feb 2024 16:35:56 +0100
Subject: [PATCH 38/50] Fix text cut off across the app when text size is set
to 200% (#2435)
---
app/src/main/res/layout/activity_main.xml | 2 +-
.../main/res/layout/dialog_account_edit.xml | 6 ++--
.../main/res/layout/dialog_additional_add.xml | 6 ++--
app/src/main/res/layout/dialog_attendance.xml | 3 +-
app/src/main/res/layout/dialog_conference.xml | 3 +-
app/src/main/res/layout/dialog_exam.xml | 6 ++--
app/src/main/res/layout/dialog_grade.xml | 3 +-
app/src/main/res/layout/dialog_homework.xml | 6 ++--
.../main/res/layout/dialog_homework_add.xml | 6 ++--
.../res/layout/dialog_lesson_completed.xml | 3 +-
.../main/res/layout/dialog_mobile_device.xml | 18 +++++-----
app/src/main/res/layout/dialog_note.xml | 3 +-
.../res/layout/dialog_school_announcement.xml | 3 +-
app/src/main/res/layout/dialog_timetable.xml | 3 +-
.../main/res/layout/header_grade_details.xml | 15 +++++++++
app/src/main/res/layout/item_timetable.xml | 33 ++++++++++++-------
.../layout/subitem_dashboard_small_grade.xml | 4 +++
17 files changed, 86 insertions(+), 37 deletions(-)
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index d14de50a1..a9284234e 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -16,7 +16,7 @@
+ android:layout_height="wrap_content" />
diff --git a/app/src/main/res/layout/dialog_homework.xml b/app/src/main/res/layout/dialog_homework.xml
index 8c6cf0a76..10b719077 100644
--- a/app/src/main/res/layout/dialog_homework.xml
+++ b/app/src/main/res/layout/dialog_homework.xml
@@ -27,7 +27,7 @@
android:id="@+id/homeworkDialogRead"
style="@style/Widget.Material3.Button.TextButton.Dialog"
android:layout_width="wrap_content"
- android:layout_height="36dp"
+ android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginEnd="8dp"
android:layout_marginBottom="24dp"
@@ -35,6 +35,7 @@
android:insetTop="0dp"
android:insetRight="0dp"
android:insetBottom="0dp"
+ android:minHeight="36dp"
android:text="@string/homework_mark_as_done"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/homeworkDialogClose" />
@@ -43,13 +44,14 @@
android:id="@+id/homeworkDialogClose"
style="@style/Widget.Material3.Button.TextButton.Dialog"
android:layout_width="wrap_content"
- android:layout_height="36dp"
+ android:layout_height="wrap_content"
android:layout_marginEnd="24dp"
android:layout_marginBottom="24dp"
android:insetLeft="0dp"
android:insetTop="0dp"
android:insetRight="0dp"
android:insetBottom="0dp"
+ android:minHeight="36dp"
android:text="@string/all_close"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
diff --git a/app/src/main/res/layout/dialog_homework_add.xml b/app/src/main/res/layout/dialog_homework_add.xml
index e0ff5b749..dc7ae32d5 100644
--- a/app/src/main/res/layout/dialog_homework_add.xml
+++ b/app/src/main/res/layout/dialog_homework_add.xml
@@ -94,7 +94,7 @@
android:id="@+id/homeworkDialogClose"
style="@style/Widget.Material3.Button.TextButton.Dialog"
android:layout_width="wrap_content"
- android:layout_height="36dp"
+ android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginTop="24dp"
android:layout_marginEnd="8dp"
@@ -103,6 +103,7 @@
android:insetTop="0dp"
android:insetRight="0dp"
android:insetBottom="0dp"
+ android:minHeight="36dp"
android:text="@string/all_close"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/homeworkDialogAdd"
@@ -112,13 +113,14 @@
android:id="@+id/homeworkDialogAdd"
style="@style/Widget.Material3.Button.TextButton.Dialog"
android:layout_width="wrap_content"
- android:layout_height="36dp"
+ android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:layout_marginBottom="24dp"
android:insetLeft="0dp"
android:insetTop="0dp"
android:insetRight="0dp"
android:insetBottom="0dp"
+ android:minHeight="36dp"
android:text="@string/all_add"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
diff --git a/app/src/main/res/layout/dialog_lesson_completed.xml b/app/src/main/res/layout/dialog_lesson_completed.xml
index 3a1d3fd00..fc32a252a 100644
--- a/app/src/main/res/layout/dialog_lesson_completed.xml
+++ b/app/src/main/res/layout/dialog_lesson_completed.xml
@@ -212,7 +212,7 @@
android:id="@+id/completedLessonDialogClose"
style="@style/Widget.Material3.Button.TextButton.Dialog"
android:layout_width="wrap_content"
- android:layout_height="36dp"
+ android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="24dp"
@@ -220,6 +220,7 @@
android:insetTop="0dp"
android:insetRight="0dp"
android:insetBottom="0dp"
+ android:minHeight="36dp"
android:text="@string/all_close"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
diff --git a/app/src/main/res/layout/dialog_mobile_device.xml b/app/src/main/res/layout/dialog_mobile_device.xml
index 9b81737fb..c526ed74c 100644
--- a/app/src/main/res/layout/dialog_mobile_device.xml
+++ b/app/src/main/res/layout/dialog_mobile_device.xml
@@ -18,10 +18,10 @@
android:layout_marginTop="24dp"
android:adjustViewBounds="true"
android:contentDescription="@string/mobile_device_qr"
- tools:src="@tools:sample/avatars"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="parent" />
+ app:layout_constraintTop_toTopOf="parent"
+ tools:src="@tools:sample/avatars" />
+
+ app:constraint_referenced_ids="mobileDeviceQr,mobileDeviceDialogTokenTitle,mobileDeviceDialogTokenValue,mobileDeviceDialogSymbolTitle,mobileDeviceDialogSymbolValue,mobileDeviceDialogPinTitle,mobileDeviceDialogPinValue,mobileDeviceDialogClose"
+ tools:visibility="visible" />
+ tools:visibility="invisible" />
diff --git a/app/src/main/res/layout/dialog_note.xml b/app/src/main/res/layout/dialog_note.xml
index 9c8b18b32..3b88ea5f8 100644
--- a/app/src/main/res/layout/dialog_note.xml
+++ b/app/src/main/res/layout/dialog_note.xml
@@ -180,7 +180,7 @@
android:id="@+id/noteDialogClose"
style="@style/Widget.Material3.Button.TextButton.Dialog"
android:layout_width="wrap_content"
- android:layout_height="36dp"
+ android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="24dp"
@@ -188,6 +188,7 @@
android:insetTop="0dp"
android:insetRight="0dp"
android:insetBottom="0dp"
+ android:minHeight="36dp"
android:text="@string/all_close"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
diff --git a/app/src/main/res/layout/dialog_school_announcement.xml b/app/src/main/res/layout/dialog_school_announcement.xml
index 4e0ef556f..a771b772f 100644
--- a/app/src/main/res/layout/dialog_school_announcement.xml
+++ b/app/src/main/res/layout/dialog_school_announcement.xml
@@ -122,7 +122,7 @@
android:id="@+id/announcementDialogClose"
style="@style/Widget.Material3.Button.TextButton.Dialog"
android:layout_width="wrap_content"
- android:layout_height="36dp"
+ android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="24dp"
@@ -130,6 +130,7 @@
android:insetTop="0dp"
android:insetRight="0dp"
android:insetBottom="0dp"
+ android:minHeight="36dp"
android:text="@string/all_close"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
diff --git a/app/src/main/res/layout/dialog_timetable.xml b/app/src/main/res/layout/dialog_timetable.xml
index aeb01b3ba..de2696482 100644
--- a/app/src/main/res/layout/dialog_timetable.xml
+++ b/app/src/main/res/layout/dialog_timetable.xml
@@ -263,7 +263,7 @@
android:id="@+id/timetableDialogClose"
style="@style/Widget.Material3.Button.TextButton.Dialog"
android:layout_width="wrap_content"
- android:layout_height="36dp"
+ android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="24dp"
@@ -271,6 +271,7 @@
android:insetTop="0dp"
android:insetRight="0dp"
android:insetBottom="0dp"
+ android:minHeight="36dp"
android:text="@string/all_close"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
diff --git a/app/src/main/res/layout/header_grade_details.xml b/app/src/main/res/layout/header_grade_details.xml
index f2ba9a8c9..e43e8993f 100644
--- a/app/src/main/res/layout/header_grade_details.xml
+++ b/app/src/main/res/layout/header_grade_details.xml
@@ -45,6 +45,9 @@
android:textColor="?android:textColorSecondary"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toStartOf="@id/gradeHeaderPointsSum"
+ app:layout_constraintHorizontal_bias="0"
+ app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="@id/gradeHeaderSubject"
app:layout_constraintTop_toBottomOf="@+id/gradeHeaderSubject"
tools:text="Average: 6,00" />
@@ -55,8 +58,12 @@
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginTop="5dp"
+ android:ellipsize="end"
+ android:maxLines="1"
android:textColor="?android:textColorSecondary"
android:textSize="12sp"
+ app:layout_constrainedWidth="true"
+ app:layout_constraintEnd_toStartOf="@id/gradeHeaderNumber"
app:layout_constraintStart_toEndOf="@+id/gradeHeaderAverage"
app:layout_constraintTop_toBottomOf="@+id/gradeHeaderSubject"
tools:text="Points: 123/200 (61,5%)" />
@@ -67,8 +74,13 @@
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginTop="5dp"
+ android:layout_marginEnd="8dp"
+ android:ellipsize="end"
+ android:maxLines="1"
android:textColor="?android:textColorSecondary"
android:textSize="12sp"
+ app:layout_constrainedWidth="true"
+ app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/gradeHeaderPointsSum"
app:layout_constraintTop_toBottomOf="@id/gradeHeaderSubject"
tools:text="12 grades" />
@@ -85,6 +97,9 @@
android:paddingRight="5dp"
android:textColor="?colorOnPrimary"
android:textSize="14sp"
+ app:autoSizeMaxTextSize="16dp"
+ app:autoSizeMinTextSize="10dp"
+ app:autoSizeTextType="uniform"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
diff --git a/app/src/main/res/layout/item_timetable.xml b/app/src/main/res/layout/item_timetable.xml
index 57af6f7ea..b9966c121 100644
--- a/app/src/main/res/layout/item_timetable.xml
+++ b/app/src/main/res/layout/item_timetable.xml
@@ -1,7 +1,6 @@
@@ -49,8 +49,9 @@
android:maxLines="1"
android:textColor="?android:textColorSecondary"
android:textSize="13sp"
+ app:layout_constraintBottom_toTopOf="@id/timetableItemTimeFinish"
app:layout_constraintStart_toEndOf="@id/timetableItemNumber"
- app:layout_constraintTop_toTopOf="@id/timetableItemNumber"
+ app:layout_constraintTop_toTopOf="parent"
tools:text="11:11" />
@@ -83,13 +91,14 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
- android:layout_marginTop="0dp"
- android:layout_marginEnd="5dp"
+ android:layout_marginEnd="0dp"
+ android:ellipsize="end"
+ android:maxLines="1"
android:textColor="?android:textColorSecondary"
android:textSize="13sp"
app:layout_constraintEnd_toStartOf="@+id/timetableItemTeacher"
app:layout_constraintStart_toEndOf="@+id/timetableItemRoom"
- app:layout_constraintTop_toTopOf="@+id/timetableItemTimeFinish"
+ app:layout_constraintTop_toTopOf="@id/timetableItemTimeFinish"
tools:text="(2/2)"
tools:visibility="visible" />
@@ -98,13 +107,15 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
- android:layout_marginEnd="16dp"
+ android:layout_marginEnd="8dp"
android:ellipsize="end"
android:maxLines="1"
android:textColor="?android:textColorSecondary"
android:textSize="13sp"
- app:layout_constraintBottom_toBottomOf="@+id/timetableItemNumber"
+ app:layout_constrainedWidth="true"
+ app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/timetableItemGroup"
+ app:layout_constraintTop_toTopOf="@id/timetableItemTimeFinish"
tools:text="Agata Kowalska - Błaszczyk"
tools:visibility="visible" />
diff --git a/app/src/main/res/layout/subitem_dashboard_small_grade.xml b/app/src/main/res/layout/subitem_dashboard_small_grade.xml
index 6800b72e9..3684c2677 100644
--- a/app/src/main/res/layout/subitem_dashboard_small_grade.xml
+++ b/app/src/main/res/layout/subitem_dashboard_small_grade.xml
@@ -1,5 +1,6 @@
From e378b4c70adc8b4e4be7f302c51a16c9377066cc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Miko=C5=82aj=20Pich?=
Date: Sun, 25 Feb 2024 16:36:50 +0100
Subject: [PATCH 39/50] Fix loading timetable and attendance when should be
refreshed returns true (#2436)
---
.../wulkanowy/data/db/dao/TimetableDao.kt | 2 +-
.../data/repositories/AttendanceRepository.kt | 10 +++------
.../data/repositories/TimetableRepository.kt | 22 ++++++++++++++-----
.../IsStudentHasLessonsOnWeekendUseCase.kt | 11 ++--------
.../services/sync/works/TimetableWork.kt | 4 +---
.../modules/attendance/AttendancePresenter.kt | 18 ++++++---------
.../modules/dashboard/DashboardPresenter.kt | 2 +-
.../modules/timetable/TimetablePresenter.kt | 7 +++---
8 files changed, 34 insertions(+), 42 deletions(-)
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/TimetableDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/TimetableDao.kt
index b4b7379f2..40d97ea96 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/dao/TimetableDao.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/TimetableDao.kt
@@ -15,5 +15,5 @@ interface TimetableDao : BaseDao {
fun loadAll(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): Flow>
@Query("SELECT * FROM Timetable WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :from AND date <= :end")
- fun load(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): List
+ suspend fun load(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): List
}
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt
index 6d782047b..bbf627de0 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt
@@ -16,10 +16,8 @@ import io.github.wulkanowy.utils.monday
import io.github.wulkanowy.utils.sunday
import io.github.wulkanowy.utils.switchSemester
import io.github.wulkanowy.utils.uniqueSubtract
-import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.sync.Mutex
-import kotlinx.coroutines.withContext
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.LocalTime
@@ -58,11 +56,9 @@ class AttendanceRepository @Inject constructor(
attendanceDb.loadAll(semester.diaryId, semester.studentId, start.monday, end.sunday)
},
fetch = {
- val lessons = withContext(Dispatchers.IO) {
- timetableDb.load(
- semester.diaryId, semester.studentId, start.monday, end.sunday
- )
- }
+ val lessons = timetableDb.load(
+ semester.diaryId, semester.studentId, start.monday, end.sunday
+ )
sdk.init(student)
.switchSemester(semester)
.getAttendance(start.monday, end.sunday)
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt
index 9305d3b31..acbd02d18 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt
@@ -3,13 +3,23 @@ package io.github.wulkanowy.data.repositories
import io.github.wulkanowy.data.db.dao.TimetableAdditionalDao
import io.github.wulkanowy.data.db.dao.TimetableDao
import io.github.wulkanowy.data.db.dao.TimetableHeaderDao
-import io.github.wulkanowy.data.db.entities.*
+import io.github.wulkanowy.data.db.entities.Semester
+import io.github.wulkanowy.data.db.entities.Student
+import io.github.wulkanowy.data.db.entities.Timetable
+import io.github.wulkanowy.data.db.entities.TimetableAdditional
+import io.github.wulkanowy.data.db.entities.TimetableHeader
import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.data.pojos.TimetableFull
import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.services.alarm.TimetableNotificationSchedulerHelper
-import io.github.wulkanowy.utils.*
+import io.github.wulkanowy.utils.AutoRefreshHelper
+import io.github.wulkanowy.utils.getRefreshKey
+import io.github.wulkanowy.utils.init
+import io.github.wulkanowy.utils.monday
+import io.github.wulkanowy.utils.sunday
+import io.github.wulkanowy.utils.switchSemester
+import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.sync.Mutex
@@ -121,12 +131,12 @@ class TimetableRepository @Inject constructor(
}
}
- fun getTimetableFromDatabase(
+ suspend fun getTimetableFromDatabase(
semester: Semester,
- from: LocalDate,
+ start: LocalDate,
end: LocalDate
- ): Flow> {
- return timetableDb.loadAll(semester.diaryId, semester.studentId, from, end)
+ ): List {
+ return timetableDb.load(semester.diaryId, semester.studentId, start, end)
}
suspend fun updateTimetable(timetable: List) {
diff --git a/app/src/main/java/io/github/wulkanowy/domain/timetable/IsStudentHasLessonsOnWeekendUseCase.kt b/app/src/main/java/io/github/wulkanowy/domain/timetable/IsStudentHasLessonsOnWeekendUseCase.kt
index efe928e2b..ffd005740 100644
--- a/app/src/main/java/io/github/wulkanowy/domain/timetable/IsStudentHasLessonsOnWeekendUseCase.kt
+++ b/app/src/main/java/io/github/wulkanowy/domain/timetable/IsStudentHasLessonsOnWeekendUseCase.kt
@@ -1,10 +1,7 @@
package io.github.wulkanowy.domain.timetable
-import io.github.wulkanowy.data.dataOrNull
import io.github.wulkanowy.data.db.entities.Semester
-import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.TimetableRepository
-import io.github.wulkanowy.data.toFirstResult
import io.github.wulkanowy.utils.monday
import io.github.wulkanowy.utils.sunday
import java.time.LocalDate
@@ -16,18 +13,14 @@ class IsStudentHasLessonsOnWeekendUseCase @Inject constructor(
) {
suspend operator fun invoke(
- student: Student,
semester: Semester,
currentDate: LocalDate = LocalDate.now(),
): Boolean {
- val lessons = timetableRepository.getTimetable(
- student = student,
+ val lessons = timetableRepository.getTimetableFromDatabase(
semester = semester,
start = currentDate.monday,
end = currentDate.sunday,
- forceRefresh = false,
- timetableType = TimetableRepository.TimetableType.NORMAL
- ).toFirstResult().dataOrNull?.lessons.orEmpty()
+ )
return isWeekendHasLessonsUseCase(lessons)
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/TimetableWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/TimetableWork.kt
index ac9a8eb4c..2d10d925c 100644
--- a/app/src/main/java/io/github/wulkanowy/services/sync/works/TimetableWork.kt
+++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/TimetableWork.kt
@@ -6,7 +6,6 @@ import io.github.wulkanowy.data.repositories.TimetableRepository
import io.github.wulkanowy.data.waitForResult
import io.github.wulkanowy.services.sync.notifications.ChangeTimetableNotification
import io.github.wulkanowy.utils.nextOrSameSchoolDay
-import kotlinx.coroutines.flow.first
import java.time.LocalDate.now
import javax.inject.Inject
@@ -31,10 +30,9 @@ class TimetableWork @Inject constructor(
timetableRepository.getTimetableFromDatabase(
semester = semester,
- from = startDate,
+ start = startDate,
end = endDate,
)
- .first()
.filterNot { it.isNotified }
.let {
if (it.isNotEmpty()) changeTimetableNotification.notify(it, student)
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt
index f66479daf..82fe69cb7 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt
@@ -4,18 +4,14 @@ import android.annotation.SuppressLint
import io.github.wulkanowy.data.*
import io.github.wulkanowy.data.db.entities.Attendance
import io.github.wulkanowy.data.db.entities.Semester
-import io.github.wulkanowy.data.db.entities.Student
-import io.github.wulkanowy.data.db.entities.Timetable
import io.github.wulkanowy.data.repositories.AttendanceRepository
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.data.repositories.SemesterRepository
import io.github.wulkanowy.data.repositories.StudentRepository
-import io.github.wulkanowy.data.repositories.TimetableRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.*
-import kotlinx.coroutines.flow.catch
-import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.onEach
import timber.log.Timber
import java.time.DayOfWeek
@@ -210,7 +206,7 @@ class AttendancePresenter @Inject constructor(
val semester = semesterRepository.getCurrentSemester(student)
- checkInitialAndCurrentDate(student, semester)
+ checkInitialAndCurrentDate(semester)
attendanceRepository.getAttendance(
student = student,
semester = semester,
@@ -266,15 +262,13 @@ class AttendancePresenter @Inject constructor(
.launch()
}
- private suspend fun checkInitialAndCurrentDate(student: Student, semester: Semester) {
+ private suspend fun checkInitialAndCurrentDate(semester: Semester) {
if (initialDate == null) {
- val lessons = attendanceRepository.getAttendance(
- student = student,
+ val lessons = attendanceRepository.getAttendanceFromDatabase(
semester = semester,
start = now().monday,
end = now().sunday,
- forceRefresh = false,
- ).toFirstResult().dataOrNull.orEmpty()
+ ).firstOrNull().orEmpty()
isWeekendHasLessons = isWeekendHasLessons(lessons)
initialDate = getInitialDate(semester)
}
@@ -316,6 +310,7 @@ class AttendancePresenter @Inject constructor(
showContent(false)
showExcuseButton(false)
}
+
is Resource.Success -> {
Timber.i("Excusing for absence result: Success")
analytics.logEvent("excuse_absence", "items" to attendanceToExcuseList.size)
@@ -328,6 +323,7 @@ class AttendancePresenter @Inject constructor(
}
loadData(forceRefresh = true)
}
+
is Resource.Error -> {
Timber.i("Excusing for absence result: An exception occurred")
errorHandler.dispatch(it.error)
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardPresenter.kt
index 1e6f1c198..784ac112f 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardPresenter.kt
@@ -438,7 +438,7 @@ class DashboardPresenter @Inject constructor(
private fun loadLessons(student: Student, forceRefresh: Boolean) {
flatResourceFlow {
val semester = semesterRepository.getCurrentSemester(student)
- val date = when (isStudentHasLessonsOnWeekendUseCase(student, semester)) {
+ val date = when (isStudentHasLessonsOnWeekendUseCase(semester)) {
true -> LocalDate.now()
else -> LocalDate.now().nextOrSameSchoolDay
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt
index 7e8c876ef..e83f25176 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt
@@ -3,7 +3,6 @@ package io.github.wulkanowy.ui.modules.timetable
import android.os.Handler
import android.os.Looper
import io.github.wulkanowy.data.db.entities.Semester
-import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.db.entities.Timetable
import io.github.wulkanowy.data.enums.TimetableGapsMode.BETWEEN_AND_BEFORE_LESSONS
import io.github.wulkanowy.data.enums.TimetableGapsMode.NO_GAPS
@@ -150,7 +149,7 @@ class TimetablePresenter @Inject constructor(
val student = studentRepository.getCurrentStudent()
val semester = semesterRepository.getCurrentSemester(student)
- checkInitialAndCurrentDate(student, semester)
+ checkInitialAndCurrentDate(semester)
timetableRepository.getTimetable(
student = student,
semester = semester,
@@ -194,9 +193,9 @@ class TimetablePresenter @Inject constructor(
.launch()
}
- private suspend fun checkInitialAndCurrentDate(student: Student, semester: Semester) {
+ private suspend fun checkInitialAndCurrentDate(semester: Semester) {
if (initialDate == null) {
- isWeekendHasLessons = isStudentHasLessonsOnWeekendUseCase(student, semester)
+ isWeekendHasLessons = isStudentHasLessonsOnWeekendUseCase(semester)
initialDate = getInitialDate(semester)
}
From d5c17285c1ce29c87e3f28cf380b8691d7bb468a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Miko=C5=82aj=20Pich?=
Date: Sun, 25 Feb 2024 16:37:28 +0100
Subject: [PATCH 40/50] Fix error handling in login (#2437)
---
app/build.gradle | 2 +-
.../main/java/io/github/wulkanowy/ui/base/ErrorHandler.kt | 6 +++++-
.../io/github/wulkanowy/ui/modules/auth/AuthPresenter.kt | 6 +++++-
.../wulkanowy/ui/modules/login/form/LoginFormPresenter.kt | 4 ++++
.../java/io/github/wulkanowy/utils/ExceptionExtension.kt | 2 ++
app/src/main/res/values/api_hosts.xml | 2 +-
app/src/main/res/values/strings.xml | 1 +
7 files changed, 19 insertions(+), 4 deletions(-)
diff --git a/app/build.gradle b/app/build.gradle
index 26c2547e5..b81236672 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -195,7 +195,7 @@ ext {
}
dependencies {
- implementation 'io.github.wulkanowy:sdk:2.4.1'
+ implementation 'io.github.wulkanowy:sdk:2.4.2-SNAPSHOT'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4'
diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/ErrorHandler.kt b/app/src/main/java/io/github/wulkanowy/ui/base/ErrorHandler.kt
index e17c0c9ec..7109f1ffd 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/base/ErrorHandler.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/base/ErrorHandler.kt
@@ -34,7 +34,7 @@ open class ErrorHandler @Inject constructor(@ApplicationContext protected val co
}
protected open fun proceed(error: Throwable) {
- showErrorMessage(context.resources.getErrorString(error), error)
+ showDefaultMessage(error)
when (error) {
is PasswordChangeRequiredException -> onPasswordChangeRequired(error.redirectUrl)
is ScramblerException -> onDecryptionFailed()
@@ -45,6 +45,10 @@ open class ErrorHandler @Inject constructor(@ApplicationContext protected val co
}
}
+ fun showDefaultMessage(error: Throwable) {
+ showErrorMessage(context.resources.getErrorString(error), error)
+ }
+
open fun clear() {
showErrorMessage = { _, _ -> }
onExpiredCredentials = {}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/auth/AuthPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/auth/AuthPresenter.kt
index 8f579712b..3c061f498 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/auth/AuthPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/auth/AuthPresenter.kt
@@ -62,7 +62,11 @@ class AuthPresenter @Inject constructor(
}
isSuccess
}
- .onFailure { errorHandler.dispatch(it) }
+ .onFailure {
+ errorHandler.dispatch(it)
+ view?.showProgress(false)
+ view?.showContent(true)
+ }
.onSuccess {
if (it) {
view?.showSuccess(true)
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenter.kt
index 69e1d027d..39bc3f02d 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenter.kt
@@ -14,6 +14,7 @@ import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.data.resourceFlow
import io.github.wulkanowy.domain.adminmessage.GetAppropriateAdminMessageUseCase
+import io.github.wulkanowy.sdk.scrapper.login.InvalidSymbolException
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.modules.login.LoginData
import io.github.wulkanowy.ui.modules.login.LoginErrorHandler
@@ -204,6 +205,9 @@ class LoginFormPresenter @Inject constructor(
}
.onResourceError {
loginErrorHandler.dispatch(it)
+ if (it is InvalidSymbolException) {
+ loginErrorHandler.showDefaultMessage(it)
+ }
lastError = it
view?.showContact(true)
analytics.logEvent(
diff --git a/app/src/main/java/io/github/wulkanowy/utils/ExceptionExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/ExceptionExtension.kt
index 18fc10bba..1c2290510 100644
--- a/app/src/main/java/io/github/wulkanowy/utils/ExceptionExtension.kt
+++ b/app/src/main/java/io/github/wulkanowy/utils/ExceptionExtension.kt
@@ -3,6 +3,7 @@ package io.github.wulkanowy.utils
import android.content.res.Resources
import io.github.wulkanowy.R
import io.github.wulkanowy.sdk.exception.FeatureNotAvailableException
+import io.github.wulkanowy.sdk.scrapper.exception.AccountInactiveException
import io.github.wulkanowy.sdk.scrapper.exception.CloudflareVerificationException
import io.github.wulkanowy.sdk.scrapper.exception.FeatureDisabledException
import io.github.wulkanowy.sdk.scrapper.exception.ScrapperException
@@ -33,6 +34,7 @@ fun Resources.getErrorString(error: Throwable): String = when (error) {
is ServiceUnavailableException -> R.string.error_service_unavailable
is FeatureDisabledException -> R.string.error_feature_disabled
is FeatureNotAvailableException -> R.string.error_feature_not_available
+ is AccountInactiveException -> R.string.error_account_inactive
is VulcanException -> R.string.error_unknown_uonet
is ScrapperException -> R.string.error_unknown_app
is CloudflareVerificationException -> R.string.error_cloudflare_captcha
diff --git a/app/src/main/res/values/api_hosts.xml b/app/src/main/res/values/api_hosts.xml
index 6439b462f..9768329d0 100644
--- a/app/src/main/res/values/api_hosts.xml
+++ b/app/src/main/res/values/api_hosts.xml
@@ -66,7 +66,7 @@
- gminaulanmajorat
- gminaozorkow
- gminalopiennikgorny
- - warszawa
+ - saas1
- powiatwulkanowy
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 0a4dcf7f4..faed4d186 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -852,6 +852,7 @@
No internet connection
An error occurred. Check your device clock
+ This account is inactive. Try logging in again
Connection to register failed. Servers can be overloaded. Please try again later
Loading data failed. Please try again later
Register password change required
From 7effb7aca27b9b2f1c0aa05b1726bf6eaedcd82d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Miko=C5=82aj=20Pich?=
Date: Sun, 25 Feb 2024 18:14:39 +0100
Subject: [PATCH 41/50] Add option to excuse a whole day
---
.../data/repositories/AttendanceRepository.kt | 25 +++++++++-
.../modules/attendance/AttendanceFragment.kt | 30 +++++++++---
.../modules/attendance/AttendancePresenter.kt | 47 +++++++++++++++----
.../ui/modules/attendance/AttendanceView.kt | 4 +-
.../main/res/layout/fragment_attendance.xml | 13 +++++
app/src/main/res/values/strings.xml | 4 +-
6 files changed, 104 insertions(+), 19 deletions(-)
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt
index bbf627de0..2d817d8a4 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt
@@ -88,9 +88,12 @@ class AttendanceRepository @Inject constructor(
return attendanceDb.updateAll(timetable)
}
+ @JvmName("excuseForAbsenceLessons")
suspend fun excuseForAbsence(
- student: Student, semester: Semester,
- absenceList: List, reason: String? = null
+ student: Student,
+ semester: Semester,
+ absenceList: List,
+ reason: String? = null
) {
val items = absenceList.map { attendance ->
Absent(
@@ -102,4 +105,22 @@ class AttendanceRepository @Inject constructor(
.switchSemester(semester)
.excuseForAbsence(items, reason)
}
+
+ @JvmName("excuseForAbsenceDays")
+ suspend fun excuseForAbsence(
+ student: Student,
+ semester: Semester,
+ days: List,
+ reason: String? = null
+ ) {
+ val items = days.map { day ->
+ Absent(
+ date = LocalDateTime.of(day, LocalTime.of(0, 0)),
+ timeId = null,
+ )
+ }
+ sdk.init(student)
+ .switchSemester(semester)
+ .excuseForAbsence(items, reason)
+ }
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceFragment.kt
index 6e842b4d7..c8e0a33f4 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceFragment.kt
@@ -2,8 +2,14 @@ package io.github.wulkanowy.ui.modules.attendance
import android.content.DialogInterface.BUTTON_POSITIVE
import android.os.Bundle
-import android.view.*
-import android.view.View.*
+import android.view.LayoutInflater
+import android.view.Menu
+import android.view.MenuInflater
+import android.view.MenuItem
+import android.view.View
+import android.view.View.GONE
+import android.view.View.INVISIBLE
+import android.view.View.VISIBLE
import androidx.appcompat.view.ActionMode
import androidx.core.view.isVisible
import androidx.recyclerview.widget.LinearLayoutManager
@@ -123,6 +129,7 @@ class AttendanceFragment : BaseFragment(R.layout.frag
attendanceNextButton.setOnClickListener { presenter.onNextDay() }
attendanceExcuseButton.setOnClickListener { presenter.onExcuseButtonClick() }
+ attendanceExcuseDayButton.setOnClickListener { presenter.onExcuseDayButtonClick() }
attendanceNavContainer.elevation = requireContext().dpToPx(3f)
}
@@ -215,6 +222,10 @@ class AttendanceFragment : BaseFragment(R.layout.frag
binding.attendanceExcuseButton.isVisible = show
}
+ override fun showExcuseDayButton(show: Boolean) {
+ binding.attendanceExcuseDayButton.isVisible = show
+ }
+
override fun showAttendanceDialog(lesson: Attendance) {
(activity as? MainActivity)?.showDialogFragment(AttendanceDialog.newInstance(lesson))
}
@@ -257,11 +268,18 @@ class AttendanceFragment : BaseFragment(R.layout.frag
actionMode = (activity as MainActivity?)?.startSupportActionMode(actionModeCallback)
}
- override fun startSendMessageIntent(date: LocalDate, numbers: String, reason: String) {
- val reasonFullText = getString(
- R.string.attendance_excuse_formula,
+ override fun startSendMessageIntent(date: LocalDate, lessons: String, reason: String) {
+ val reasonFullText = if (lessons.isEmpty()) {
+ getString(
+ R.string.attendance_excuse_day_formula,
+ date,
+ if (reason.isNotBlank()) " ${getString(R.string.attendance_excuse_reason)} " else "",
+ reason.ifBlank { "" }
+ )
+ } else getString(
+ R.string.attendance_excuse_lessons_formula,
date,
- numbers,
+ lessons,
if (reason.isNotBlank()) " ${getString(R.string.attendance_excuse_reason)} " else "",
reason.ifBlank { "" }
)
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt
index 82fe69cb7..cd75f5c2c 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt
@@ -38,6 +38,7 @@ class AttendancePresenter @Inject constructor(
private lateinit var lastError: Throwable
private val attendanceToExcuseList = mutableListOf()
+ private var isWholeDayExcusable = false
private var isVulcanExcusedFunctionEnabled = false
@@ -129,6 +130,14 @@ class AttendancePresenter @Inject constructor(
fun onExcuseButtonClick() {
view?.startActionMode()
+
+ if (isWholeDayExcusable) {
+ view?.showExcuseDayButton(true)
+ }
+ }
+
+ fun onExcuseDayButtonClick() {
+ view?.showExcuseDialog()
}
fun onExcuseCheckboxSelect(attendanceItem: Attendance, checked: Boolean) {
@@ -152,7 +161,7 @@ class AttendancePresenter @Inject constructor(
fun onExcuseDialogSubmit(reason: String) {
view?.finishActionMode()
- if (attendanceToExcuseList.isEmpty()) return
+ if (attendanceToExcuseList.isEmpty() && !isWholeDayExcusable) return
if (isVulcanExcusedFunctionEnabled) {
excuseAbsence(
@@ -163,8 +172,8 @@ class AttendancePresenter @Inject constructor(
val attendanceToExcuseNumbers = attendanceToExcuseList.map { it.number }
view?.startSendMessageIntent(
- date = attendanceToExcuseList[0].date,
- numbers = attendanceToExcuseNumbers.joinToString(", "),
+ date = currentDate ?: attendanceToExcuseList[0].date,
+ lessons = attendanceToExcuseNumbers.joinToString(", "),
reason = reason
)
}
@@ -174,6 +183,7 @@ class AttendancePresenter @Inject constructor(
view?.apply {
showExcuseCheckboxes(true)
showExcuseButton(false)
+ showExcuseDayButton(false)
enableSwipe(false)
showDayNavigation(false)
}
@@ -185,6 +195,7 @@ class AttendancePresenter @Inject constructor(
view?.apply {
showExcuseCheckboxes(false)
showExcuseButton(true)
+ showExcuseDayButton(false)
enableSwipe(true)
showDayNavigation(true)
}
@@ -217,7 +228,10 @@ class AttendancePresenter @Inject constructor(
}
.logResourceStatus("load attendance")
.onResourceLoading {
- view?.showExcuseButton(false)
+ view?.apply {
+ showExcuseButton(false)
+ showExcuseDayButton(false)
+ }
}
.mapResourceData {
if (prefRepository.isShowPresent) {
@@ -240,15 +254,16 @@ class AttendancePresenter @Inject constructor(
}
}
.onResourceIntermediate { view?.showRefresh(true) }
- .onResourceSuccess {
- isVulcanExcusedFunctionEnabled = it.any { item -> item.excusable }
- val anyExcusables = it.any { it.isExcusableOrNotExcused }
+ .onResourceSuccess { items ->
+ isVulcanExcusedFunctionEnabled = items.any { item -> item.excusable }
+ isWholeDayExcusable = items.all { it.isExcusableOrNotExcused }
+ val anyExcusables = items.any { it.isExcusableOrNotExcused }
view?.showExcuseButton(anyExcusables && (isParent || isVulcanExcusedFunctionEnabled))
analytics.logEvent(
"load_data",
"type" to "attendance",
- "items" to it.size
+ "items" to items.size
)
}
.onResourceNotLoading {
@@ -301,7 +316,19 @@ class AttendancePresenter @Inject constructor(
resourceFlow {
val student = studentRepository.getCurrentStudent()
val semester = semesterRepository.getCurrentSemester(student)
- attendanceRepository.excuseForAbsence(student, semester, toExcuseList, reason)
+ if (toExcuseList.isEmpty()) {
+ attendanceRepository.excuseForAbsence(
+ student = student,
+ semester = semester,
+ days = listOfNotNull(currentDate),
+ reason = reason
+ )
+ } else attendanceRepository.excuseForAbsence(
+ student = student,
+ semester = semester,
+ absenceList = toExcuseList,
+ reason = reason
+ )
}.onEach {
when (it) {
is Resource.Loading -> view?.run {
@@ -309,6 +336,7 @@ class AttendancePresenter @Inject constructor(
showProgress(true)
showContent(false)
showExcuseButton(false)
+ showExcuseDayButton(false)
}
is Resource.Success -> {
@@ -317,6 +345,7 @@ class AttendancePresenter @Inject constructor(
attendanceToExcuseList.clear()
view?.run {
showExcuseButton(false)
+ showExcuseDayButton(false)
showMessage(excuseSuccessString)
showContent(true)
showProgress(false)
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceView.kt
index 2629c217e..5af491e74 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceView.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceView.kt
@@ -48,6 +48,8 @@ interface AttendanceView : BaseView {
fun showExcuseButton(show: Boolean)
+ fun showExcuseDayButton(show: Boolean)
+
fun showAttendanceDialog(lesson: Attendance)
fun showDatePickerDialog(selectedDate: LocalDate)
@@ -56,7 +58,7 @@ interface AttendanceView : BaseView {
fun openSummaryView()
- fun startSendMessageIntent(date: LocalDate, numbers: String, reason: String)
+ fun startSendMessageIntent(date: LocalDate, lessons: String, reason: String)
fun startActionMode()
diff --git a/app/src/main/res/layout/fragment_attendance.xml b/app/src/main/res/layout/fragment_attendance.xml
index 078daf610..e3dd2ebb6 100644
--- a/app/src/main/res/layout/fragment_attendance.xml
+++ b/app/src/main/res/layout/fragment_attendance.xml
@@ -69,6 +69,19 @@
app:icon="@drawable/ic_all_done_all"
tools:visibility="visible" />
+
+
Absence excuse request sent successfully!
You must select at least one absence!
Excuse
+ Excuse entire day
z powodu
- Dzień dobry,\nProszę o usprawiedliwienie mojego dziecka w dniu %s z lekcji %s%s%s.\n\nPozdrawiam.
+ Dzień dobry,\nProszę o usprawiedliwienie mojego dziecka w dniu %s z lekcji %s%s%s.\n\nPozdrawiam.
+ Dzień dobry,\nProszę o usprawiedliwienie mojego dziecka w dniu %s%s%s.\n\nPozdrawiam.
- New attendance
- New attendance
From 74a20b2f65cb7af7be333fba86990f3961d94643 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rafa=C5=82=20Borcz?=
Date: Tue, 27 Feb 2024 09:42:44 +0100
Subject: [PATCH 42/50] Add Github Sponsor (#2444)
---
.github/FUNDING.yml | 4 ++++
1 file changed, 4 insertions(+)
create mode 100644 .github/FUNDING.yml
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 000000000..cdce0759b
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,4 @@
+# These are supported funding model platforms
+
+github: wulkanowy
+custom: https://www.paypal.com/paypalme/wulkanowy
From 1b8c3899842505a4f1616fac8ce7b550afb602ad Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 27 Feb 2024 08:52:47 +0000
Subject: [PATCH 43/50] Bump io.coil-kt:coil from 2.5.0 to 2.6.0 (#2441)
---
app/build.gradle | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/build.gradle b/app/build.gradle
index b81236672..e88d9205d 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -246,7 +246,7 @@ dependencies {
implementation 'com.github.Faierbel:slf4j-timber:2.0'
implementation 'com.github.bastienpaulfr:Treessence:1.1.2'
implementation "com.mikepenz:aboutlibraries-core:$about_libraries"
- implementation 'io.coil-kt:coil:2.5.0'
+ implementation 'io.coil-kt:coil:2.6.0'
implementation "io.github.wulkanowy:AppKillerManager:3.0.1"
implementation 'me.xdrop:fuzzywuzzy:1.4.0'
implementation 'com.fredporciuncula:flow-preferences:1.9.1'
From 1ab300d74f4ea41c395f4db1d7b8f926e363b3c6 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 27 Feb 2024 08:53:00 +0000
Subject: [PATCH 44/50] Bump android_hilt from 1.1.0 to 1.2.0 (#2443)
---
app/build.gradle | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/build.gradle b/app/build.gradle
index e88d9205d..07efeb2f2 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -187,7 +187,7 @@ huaweiPublish {
ext {
work_manager = "2.9.0"
- android_hilt = "1.1.0"
+ android_hilt = "1.2.0"
room = "2.6.1"
chucker = "4.0.0"
mockk = "1.13.9"
From 7a4032dda4e3061a09ecffd69712b3598e82e414 Mon Sep 17 00:00:00 2001
From: JestemKamil <84380834+JestemKamil@users.noreply.github.com>
Date: Thu, 29 Feb 2024 21:30:02 +0100
Subject: [PATCH 45/50] Add mute message senders (#2415)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Co-authored-by: Mikołaj Pich
---
.../60.json | 2527 +++++++++++++++++
app/src/main/assets/contributors.json | 4 +
.../io/github/wulkanowy/data/DataModule.kt | 4 +
.../github/wulkanowy/data/db/AppDatabase.kt | 8 +-
.../wulkanowy/data/db/dao/MessagesDao.kt | 10 +-
.../data/db/dao/MutedMessageSendersDao.kt | 20 +
.../data/db/entities/MessageWithAttachment.kt | 8 +-
.../db/entities/MessageWithMutedAuthor.kt | 12 +
.../data/db/entities/MutedMessageSender.kt | 15 +
.../data/repositories/MessageRepository.kt | 35 +-
.../modules/dashboard/DashboardPresenter.kt | 1 +
.../message/preview/MessagePreviewAdapter.kt | 6 +
.../message/preview/MessagePreviewFragment.kt | 18 +-
.../preview/MessagePreviewPresenter.kt | 106 +-
.../message/preview/MessagePreviewView.kt | 6 +
.../modules/message/tab/MessageTabAdapter.kt | 16 +-
.../modules/message/tab/MessageTabDataItem.kt | 1 +
.../message/tab/MessageTabPresenter.kt | 38 +-
.../res/drawable/ic_circle_notification.xml | 10 +
.../res/drawable/ic_notifications_off.xml | 5 +
app/src/main/res/layout/item_message.xml | 6 +-
.../res/menu/action_menu_message_preview.xml | 7 +
app/src/main/res/values/strings.xml | 6 +
.../repositories/MessageRepositoryTest.kt | 47 +-
24 files changed, 2827 insertions(+), 89 deletions(-)
create mode 100644 app/schemas/io.github.wulkanowy.data.db.AppDatabase/60.json
create mode 100644 app/src/main/java/io/github/wulkanowy/data/db/dao/MutedMessageSendersDao.kt
create mode 100644 app/src/main/java/io/github/wulkanowy/data/db/entities/MessageWithMutedAuthor.kt
create mode 100644 app/src/main/java/io/github/wulkanowy/data/db/entities/MutedMessageSender.kt
create mode 100644 app/src/main/res/drawable/ic_circle_notification.xml
create mode 100644 app/src/main/res/drawable/ic_notifications_off.xml
diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/60.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/60.json
new file mode 100644
index 000000000..20eacad1c
--- /dev/null
+++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/60.json
@@ -0,0 +1,2527 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 60,
+ "identityHash": "3672d3f4d5e6b874e5a22d2bb458dc65",
+ "entities": [
+ {
+ "tableName": "Students",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`scrapper_base_url` TEXT NOT NULL, `scrapper_domain_suffix` TEXT NOT NULL DEFAULT '', `mobile_base_url` TEXT NOT NULL, `login_type` TEXT NOT NULL, `login_mode` TEXT NOT NULL, `certificate_key` TEXT NOT NULL, `private_key` TEXT NOT NULL, `is_parent` INTEGER NOT NULL, `email` TEXT NOT NULL, `password` TEXT NOT NULL, `symbol` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `user_login_id` INTEGER NOT NULL, `user_name` TEXT NOT NULL, `student_name` TEXT NOT NULL, `school_id` TEXT NOT NULL, `school_short` TEXT NOT NULL, `school_name` TEXT NOT NULL, `class_name` TEXT NOT NULL, `class_id` INTEGER NOT NULL, `is_current` INTEGER NOT NULL, `registration_date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `nick` TEXT NOT NULL, `avatar_color` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "scrapperBaseUrl",
+ "columnName": "scrapper_base_url",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "scrapperDomainSuffix",
+ "columnName": "scrapper_domain_suffix",
+ "affinity": "TEXT",
+ "notNull": true,
+ "defaultValue": "''"
+ },
+ {
+ "fieldPath": "mobileBaseUrl",
+ "columnName": "mobile_base_url",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "loginType",
+ "columnName": "login_type",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "loginMode",
+ "columnName": "login_mode",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "certificateKey",
+ "columnName": "certificate_key",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "privateKey",
+ "columnName": "private_key",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isParent",
+ "columnName": "is_parent",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "email",
+ "columnName": "email",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "password",
+ "columnName": "password",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "symbol",
+ "columnName": "symbol",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "userLoginId",
+ "columnName": "user_login_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "userName",
+ "columnName": "user_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "studentName",
+ "columnName": "student_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "schoolSymbol",
+ "columnName": "school_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "schoolShortName",
+ "columnName": "school_short",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "schoolName",
+ "columnName": "school_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "className",
+ "columnName": "class_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "classId",
+ "columnName": "class_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isCurrent",
+ "columnName": "is_current",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "registrationDate",
+ "columnName": "registration_date",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "nick",
+ "columnName": "nick",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "avatarColor",
+ "columnName": "avatar_color",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [
+ {
+ "name": "index_Students_email_symbol_student_id_school_id_class_id",
+ "unique": true,
+ "columnNames": [
+ "email",
+ "symbol",
+ "student_id",
+ "school_id",
+ "class_id"
+ ],
+ "orders": [],
+ "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Students_email_symbol_student_id_school_id_class_id` ON `${TABLE_NAME}` (`email`, `symbol`, `student_id`, `school_id`, `class_id`)"
+ }
+ ],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Semesters",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `kindergarten_diary_id` INTEGER NOT NULL DEFAULT 0, `diary_name` TEXT NOT NULL, `school_year` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `semester_name` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_current` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "diaryId",
+ "columnName": "diary_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "kindergartenDiaryId",
+ "columnName": "kindergarten_diary_id",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "0"
+ },
+ {
+ "fieldPath": "diaryName",
+ "columnName": "diary_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "schoolYear",
+ "columnName": "school_year",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "semesterId",
+ "columnName": "semester_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "semesterName",
+ "columnName": "semester_name",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "start",
+ "columnName": "start",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "end",
+ "columnName": "end",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "classId",
+ "columnName": "class_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "unitId",
+ "columnName": "unit_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "current",
+ "columnName": "is_current",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [
+ {
+ "name": "index_Semesters_student_id_diary_id_kindergarten_diary_id_semester_id",
+ "unique": true,
+ "columnNames": [
+ "student_id",
+ "diary_id",
+ "kindergarten_diary_id",
+ "semester_id"
+ ],
+ "orders": [],
+ "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Semesters_student_id_diary_id_kindergarten_diary_id_semester_id` ON `${TABLE_NAME}` (`student_id`, `diary_id`, `kindergarten_diary_id`, `semester_id`)"
+ }
+ ],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Exams",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `group` TEXT NOT NULL, `type` TEXT NOT NULL, `description` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "diaryId",
+ "columnName": "diary_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "date",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "entryDate",
+ "columnName": "entry_date",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subject",
+ "columnName": "subject",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "group",
+ "columnName": "group",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "description",
+ "columnName": "description",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "teacher",
+ "columnName": "teacher",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "teacherSymbol",
+ "columnName": "teacher_symbol",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isNotified",
+ "columnName": "is_notified",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Timetable",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `number` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `subjectOld` TEXT NOT NULL, `group` TEXT NOT NULL, `room` TEXT NOT NULL, `roomOld` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacherOld` TEXT NOT NULL, `info` TEXT NOT NULL, `student_plan` INTEGER NOT NULL, `changes` INTEGER NOT NULL, `canceled` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "diaryId",
+ "columnName": "diary_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "number",
+ "columnName": "number",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "start",
+ "columnName": "start",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "end",
+ "columnName": "end",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "date",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subject",
+ "columnName": "subject",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subjectOld",
+ "columnName": "subjectOld",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "group",
+ "columnName": "group",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "room",
+ "columnName": "room",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "roomOld",
+ "columnName": "roomOld",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "teacher",
+ "columnName": "teacher",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "teacherOld",
+ "columnName": "teacherOld",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "info",
+ "columnName": "info",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isStudentPlan",
+ "columnName": "student_plan",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "changes",
+ "columnName": "changes",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "canceled",
+ "columnName": "canceled",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isNotified",
+ "columnName": "is_notified",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Attendance",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `time_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `excused` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `excusable` INTEGER NOT NULL, `excuse_status` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "diaryId",
+ "columnName": "diary_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "timeId",
+ "columnName": "time_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "date",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "number",
+ "columnName": "number",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subject",
+ "columnName": "subject",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "presence",
+ "columnName": "presence",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "absence",
+ "columnName": "absence",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "exemption",
+ "columnName": "exemption",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lateness",
+ "columnName": "lateness",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "excused",
+ "columnName": "excused",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "deleted",
+ "columnName": "deleted",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "excusable",
+ "columnName": "excusable",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "excuseStatus",
+ "columnName": "excuse_status",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isNotified",
+ "columnName": "is_notified",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "AttendanceSummary",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `subject_id` INTEGER NOT NULL, `month` INTEGER NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `absence_excused` INTEGER NOT NULL, `absence_for_school_reasons` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `lateness_excused` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "diaryId",
+ "columnName": "diary_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subjectId",
+ "columnName": "subject_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "month",
+ "columnName": "month",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "presence",
+ "columnName": "presence",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "absence",
+ "columnName": "absence",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "absenceExcused",
+ "columnName": "absence_excused",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "absenceForSchoolReasons",
+ "columnName": "absence_for_school_reasons",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lateness",
+ "columnName": "lateness",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "latenessExcused",
+ "columnName": "lateness_excused",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "exemption",
+ "columnName": "exemption",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Grades",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `entry` TEXT NOT NULL, `value` REAL NOT NULL, `modifier` REAL NOT NULL, `comment` TEXT NOT NULL, `color` TEXT NOT NULL, `grade_symbol` TEXT NOT NULL, `description` TEXT NOT NULL, `weight` TEXT NOT NULL, `weightValue` REAL NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "semesterId",
+ "columnName": "semester_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subject",
+ "columnName": "subject",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "entry",
+ "columnName": "entry",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "value",
+ "columnName": "value",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "modifier",
+ "columnName": "modifier",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "comment",
+ "columnName": "comment",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "color",
+ "columnName": "color",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "gradeSymbol",
+ "columnName": "grade_symbol",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "description",
+ "columnName": "description",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "weight",
+ "columnName": "weight",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "weightValue",
+ "columnName": "weightValue",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "date",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "teacher",
+ "columnName": "teacher",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isRead",
+ "columnName": "is_read",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isNotified",
+ "columnName": "is_notified",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "GradesSummary",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `position` INTEGER NOT NULL, `subject` TEXT NOT NULL, `predicted_grade` TEXT NOT NULL, `final_grade` TEXT NOT NULL, `proposed_points` TEXT NOT NULL, `final_points` TEXT NOT NULL, `points_sum` TEXT NOT NULL, `average` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_predicted_grade_notified` INTEGER NOT NULL, `is_final_grade_notified` INTEGER NOT NULL, `predicted_grade_last_change` INTEGER NOT NULL, `final_grade_last_change` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "semesterId",
+ "columnName": "semester_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "position",
+ "columnName": "position",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subject",
+ "columnName": "subject",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "predictedGrade",
+ "columnName": "predicted_grade",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "finalGrade",
+ "columnName": "final_grade",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "proposedPoints",
+ "columnName": "proposed_points",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "finalPoints",
+ "columnName": "final_points",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "pointsSum",
+ "columnName": "points_sum",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "average",
+ "columnName": "average",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isPredictedGradeNotified",
+ "columnName": "is_predicted_grade_notified",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isFinalGradeNotified",
+ "columnName": "is_final_grade_notified",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "predictedGradeLastChange",
+ "columnName": "predicted_grade_last_change",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "finalGradeLastChange",
+ "columnName": "final_grade_last_change",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "GradePartialStatistics",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `class_average` TEXT NOT NULL, `student_average` TEXT NOT NULL, `class_amounts` TEXT NOT NULL, `student_amounts` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "semesterId",
+ "columnName": "semester_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subject",
+ "columnName": "subject",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "classAverage",
+ "columnName": "class_average",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "studentAverage",
+ "columnName": "student_average",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "classAmounts",
+ "columnName": "class_amounts",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "studentAmounts",
+ "columnName": "student_amounts",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "GradesPointsStatistics",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `others` REAL NOT NULL, `student` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "semesterId",
+ "columnName": "semester_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subject",
+ "columnName": "subject",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "others",
+ "columnName": "others",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "student",
+ "columnName": "student",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "GradeSemesterStatistics",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `amounts` TEXT NOT NULL, `student_grade` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "semesterId",
+ "columnName": "semester_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subject",
+ "columnName": "subject",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "amounts",
+ "columnName": "amounts",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "studentGrade",
+ "columnName": "student_grade",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Messages",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`email` TEXT NOT NULL, `message_global_key` TEXT NOT NULL, `mailbox_key` TEXT NOT NULL, `message_id` INTEGER NOT NULL, `correspondents` TEXT NOT NULL, `subject` TEXT NOT NULL, `date` INTEGER NOT NULL, `folder_id` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `read_by` INTEGER, `unread_by` INTEGER, `has_attachments` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `content` TEXT NOT NULL, `sender` TEXT, `recipients` TEXT)",
+ "fields": [
+ {
+ "fieldPath": "email",
+ "columnName": "email",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "messageGlobalKey",
+ "columnName": "message_global_key",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "mailboxKey",
+ "columnName": "mailbox_key",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "messageId",
+ "columnName": "message_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "correspondents",
+ "columnName": "correspondents",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subject",
+ "columnName": "subject",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "date",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "folderId",
+ "columnName": "folder_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "unread",
+ "columnName": "unread",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "readBy",
+ "columnName": "read_by",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "unreadBy",
+ "columnName": "unread_by",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "hasAttachments",
+ "columnName": "has_attachments",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isNotified",
+ "columnName": "is_notified",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "content",
+ "columnName": "content",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "sender",
+ "columnName": "sender",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "recipients",
+ "columnName": "recipients",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "MessageAttachments",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`message_global_key` TEXT NOT NULL, `url` TEXT NOT NULL, `filename` TEXT NOT NULL, PRIMARY KEY(`message_global_key`, `url`, `filename`))",
+ "fields": [
+ {
+ "fieldPath": "messageGlobalKey",
+ "columnName": "message_global_key",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "url",
+ "columnName": "url",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "filename",
+ "columnName": "filename",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "message_global_key",
+ "url",
+ "filename"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Notes",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `category` TEXT NOT NULL, `category_type` INTEGER NOT NULL, `is_points_show` INTEGER NOT NULL, `points` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "date",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "teacher",
+ "columnName": "teacher",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "teacherSymbol",
+ "columnName": "teacher_symbol",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "category",
+ "columnName": "category",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "categoryType",
+ "columnName": "category_type",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isPointsShow",
+ "columnName": "is_points_show",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "points",
+ "columnName": "points",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "content",
+ "columnName": "content",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isRead",
+ "columnName": "is_read",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isNotified",
+ "columnName": "is_notified",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Homework",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `attachments` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_done` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `is_added_by_user` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "semesterId",
+ "columnName": "semester_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "date",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "entryDate",
+ "columnName": "entry_date",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subject",
+ "columnName": "subject",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "content",
+ "columnName": "content",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "teacher",
+ "columnName": "teacher",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "teacherSymbol",
+ "columnName": "teacher_symbol",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "attachments",
+ "columnName": "attachments",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isDone",
+ "columnName": "is_done",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isNotified",
+ "columnName": "is_notified",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isAddedByUser",
+ "columnName": "is_added_by_user",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Subjects",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "diaryId",
+ "columnName": "diary_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "realId",
+ "columnName": "real_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "LuckyNumbers",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `lucky_number` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "date",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "luckyNumber",
+ "columnName": "lucky_number",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isNotified",
+ "columnName": "is_notified",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "CompletedLesson",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `topic` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `substitution` TEXT NOT NULL, `absence` TEXT NOT NULL, `resources` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "diaryId",
+ "columnName": "diary_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "date",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "number",
+ "columnName": "number",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subject",
+ "columnName": "subject",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "topic",
+ "columnName": "topic",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "teacher",
+ "columnName": "teacher",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "teacherSymbol",
+ "columnName": "teacher_symbol",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "substitution",
+ "columnName": "substitution",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "absence",
+ "columnName": "absence",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "resources",
+ "columnName": "resources",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Mailboxes",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`globalKey` TEXT NOT NULL, `email` TEXT NOT NULL, `symbol` TEXT NOT NULL, `schoolId` TEXT NOT NULL, `fullName` TEXT NOT NULL, `userName` TEXT NOT NULL, `studentName` TEXT NOT NULL, `schoolNameShort` TEXT NOT NULL, `type` TEXT NOT NULL, PRIMARY KEY(`globalKey`))",
+ "fields": [
+ {
+ "fieldPath": "globalKey",
+ "columnName": "globalKey",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "email",
+ "columnName": "email",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "symbol",
+ "columnName": "symbol",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "schoolId",
+ "columnName": "schoolId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "fullName",
+ "columnName": "fullName",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "userName",
+ "columnName": "userName",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "studentName",
+ "columnName": "studentName",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "schoolNameShort",
+ "columnName": "schoolNameShort",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "globalKey"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Recipients",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`mailboxGlobalKey` TEXT NOT NULL, `studentMailboxGlobalKey` TEXT NOT NULL, `fullName` TEXT NOT NULL, `userName` TEXT NOT NULL, `schoolShortName` TEXT NOT NULL, `type` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "mailboxGlobalKey",
+ "columnName": "mailboxGlobalKey",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "studentMailboxGlobalKey",
+ "columnName": "studentMailboxGlobalKey",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "fullName",
+ "columnName": "fullName",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "userName",
+ "columnName": "userName",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "schoolShortName",
+ "columnName": "schoolShortName",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "MobileDevices",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`user_login_id` INTEGER NOT NULL, `device_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "userLoginId",
+ "columnName": "user_login_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "deviceId",
+ "columnName": "device_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "date",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Teachers",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `short_name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "classId",
+ "columnName": "class_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subject",
+ "columnName": "subject",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "shortName",
+ "columnName": "short_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "School",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `address` TEXT NOT NULL, `contact` TEXT NOT NULL, `headmaster` TEXT NOT NULL, `pedagogue` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "classId",
+ "columnName": "class_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "address",
+ "columnName": "address",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "contact",
+ "columnName": "contact",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "headmaster",
+ "columnName": "headmaster",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "pedagogue",
+ "columnName": "pedagogue",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Conferences",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `subject` TEXT NOT NULL, `agenda` TEXT NOT NULL, `present_on_conference` TEXT NOT NULL, `conference_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "diaryId",
+ "columnName": "diary_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "title",
+ "columnName": "title",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subject",
+ "columnName": "subject",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "agenda",
+ "columnName": "agenda",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "presentOnConference",
+ "columnName": "present_on_conference",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "conferenceId",
+ "columnName": "conference_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "date",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isNotified",
+ "columnName": "is_notified",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "TimetableAdditional",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `repeat_id` BLOB DEFAULT NULL, `is_added_by_user` INTEGER NOT NULL DEFAULT 0)",
+ "fields": [
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "diaryId",
+ "columnName": "diary_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "start",
+ "columnName": "start",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "end",
+ "columnName": "end",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "date",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subject",
+ "columnName": "subject",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "repeatId",
+ "columnName": "repeat_id",
+ "affinity": "BLOB",
+ "notNull": false,
+ "defaultValue": "NULL"
+ },
+ {
+ "fieldPath": "isAddedByUser",
+ "columnName": "is_added_by_user",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "0"
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "StudentInfo",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `full_name` TEXT NOT NULL, `first_name` TEXT NOT NULL, `second_name` TEXT NOT NULL, `surname` TEXT NOT NULL, `birth_date` INTEGER NOT NULL, `birth_place` TEXT NOT NULL, `gender` TEXT NOT NULL, `has_polish_citizenship` INTEGER NOT NULL, `family_name` TEXT NOT NULL, `parents_names` TEXT NOT NULL, `address` TEXT NOT NULL, `registered_address` TEXT NOT NULL, `correspondence_address` TEXT NOT NULL, `phone_number` TEXT NOT NULL, `cell_phone_number` TEXT NOT NULL, `email` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `first_guardian_full_name` TEXT, `first_guardian_kinship` TEXT, `first_guardian_address` TEXT, `first_guardian_phones` TEXT, `first_guardian_email` TEXT, `second_guardian_full_name` TEXT, `second_guardian_kinship` TEXT, `second_guardian_address` TEXT, `second_guardian_phones` TEXT, `second_guardian_email` TEXT)",
+ "fields": [
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "fullName",
+ "columnName": "full_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "firstName",
+ "columnName": "first_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "secondName",
+ "columnName": "second_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "surname",
+ "columnName": "surname",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "birthDate",
+ "columnName": "birth_date",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "birthPlace",
+ "columnName": "birth_place",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "gender",
+ "columnName": "gender",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "hasPolishCitizenship",
+ "columnName": "has_polish_citizenship",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "familyName",
+ "columnName": "family_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "parentsNames",
+ "columnName": "parents_names",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "address",
+ "columnName": "address",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "registeredAddress",
+ "columnName": "registered_address",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "correspondenceAddress",
+ "columnName": "correspondence_address",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "phoneNumber",
+ "columnName": "phone_number",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "cellPhoneNumber",
+ "columnName": "cell_phone_number",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "email",
+ "columnName": "email",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "firstGuardian.fullName",
+ "columnName": "first_guardian_full_name",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "firstGuardian.kinship",
+ "columnName": "first_guardian_kinship",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "firstGuardian.address",
+ "columnName": "first_guardian_address",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "firstGuardian.phones",
+ "columnName": "first_guardian_phones",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "firstGuardian.email",
+ "columnName": "first_guardian_email",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "secondGuardian.fullName",
+ "columnName": "second_guardian_full_name",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "secondGuardian.kinship",
+ "columnName": "second_guardian_kinship",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "secondGuardian.address",
+ "columnName": "second_guardian_address",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "secondGuardian.phones",
+ "columnName": "second_guardian_phones",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "secondGuardian.email",
+ "columnName": "second_guardian_email",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "TimetableHeaders",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "diaryId",
+ "columnName": "diary_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "date",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "content",
+ "columnName": "content",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "SchoolAnnouncements",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`user_login_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "userLoginId",
+ "columnName": "user_login_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "date",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subject",
+ "columnName": "subject",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "content",
+ "columnName": "content",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isNotified",
+ "columnName": "is_notified",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Notifications",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `type` TEXT NOT NULL, `destination` TEXT NOT NULL DEFAULT '{\"type\":\"io.github.wulkanowy.ui.modules.Destination.Dashboard\"}', `date` INTEGER NOT NULL, `data` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "title",
+ "columnName": "title",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "content",
+ "columnName": "content",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "destination",
+ "columnName": "destination",
+ "affinity": "TEXT",
+ "notNull": true,
+ "defaultValue": "'{\"type\":\"io.github.wulkanowy.ui.modules.Destination.Dashboard\"}'"
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "date",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "data",
+ "columnName": "data",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "AdminMessages",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `version_name` INTEGER, `version_max` INTEGER, `target_register_host` TEXT, `target_flavor` TEXT, `destination_url` TEXT, `priority` TEXT NOT NULL, `types` TEXT NOT NULL DEFAULT '[]', `is_ok_visible` INTEGER NOT NULL DEFAULT 0, `is_x_visible` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`id`))",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "title",
+ "columnName": "title",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "content",
+ "columnName": "content",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "versionMin",
+ "columnName": "version_name",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "versionMax",
+ "columnName": "version_max",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "targetRegisterHost",
+ "columnName": "target_register_host",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "targetFlavor",
+ "columnName": "target_flavor",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "destinationUrl",
+ "columnName": "destination_url",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "priority",
+ "columnName": "priority",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "types",
+ "columnName": "types",
+ "affinity": "TEXT",
+ "notNull": true,
+ "defaultValue": "'[]'"
+ },
+ {
+ "fieldPath": "isOkVisible",
+ "columnName": "is_ok_visible",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "0"
+ },
+ {
+ "fieldPath": "isXVisible",
+ "columnName": "is_x_visible",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "0"
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "MutedMessageSenders",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`author` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "author",
+ "columnName": "author",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "GradesDescriptive",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `description` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "semesterId",
+ "columnName": "semester_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subject",
+ "columnName": "subject",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "description",
+ "columnName": "description",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isNotified",
+ "columnName": "is_notified",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ }
+ ],
+ "views": [],
+ "setupQueries": [
+ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '3672d3f4d5e6b874e5a22d2bb458dc65')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/assets/contributors.json b/app/src/main/assets/contributors.json
index a7629c22f..97ac9356f 100644
--- a/app/src/main/assets/contributors.json
+++ b/app/src/main/assets/contributors.json
@@ -54,5 +54,9 @@
{
"displayName": "Antoni Paduch",
"githubUsername": "janAte1"
+ },
+ {
+ "displayName": "Kamil Wąsik",
+ "githubUsername": "JestemKamil"
}
]
diff --git a/app/src/main/java/io/github/wulkanowy/data/DataModule.kt b/app/src/main/java/io/github/wulkanowy/data/DataModule.kt
index 7c9cf9a3c..6b6c9d329 100644
--- a/app/src/main/java/io/github/wulkanowy/data/DataModule.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/DataModule.kt
@@ -254,6 +254,10 @@ internal class DataModule {
@Provides
fun provideAdminMessageDao(database: AppDatabase) = database.adminMessagesDao
+ @Singleton
+ @Provides
+ fun provideMutesDao(database: AppDatabase) = database.mutedMessageSendersDao
+
@Singleton
@Provides
fun provideGradeDescriptiveDao(database: AppDatabase) = database.gradeDescriptiveDao
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt b/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt
index 8e5841fe7..21a6e3f3e 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt
@@ -25,6 +25,7 @@ import io.github.wulkanowy.data.db.dao.MailboxDao
import io.github.wulkanowy.data.db.dao.MessageAttachmentDao
import io.github.wulkanowy.data.db.dao.MessagesDao
import io.github.wulkanowy.data.db.dao.MobileDeviceDao
+import io.github.wulkanowy.data.db.dao.MutedMessageSendersDao
import io.github.wulkanowy.data.db.dao.NoteDao
import io.github.wulkanowy.data.db.dao.NotificationDao
import io.github.wulkanowy.data.db.dao.RecipientDao
@@ -56,6 +57,7 @@ import io.github.wulkanowy.data.db.entities.Mailbox
import io.github.wulkanowy.data.db.entities.Message
import io.github.wulkanowy.data.db.entities.MessageAttachment
import io.github.wulkanowy.data.db.entities.MobileDevice
+import io.github.wulkanowy.data.db.entities.MutedMessageSender
import io.github.wulkanowy.data.db.entities.Note
import io.github.wulkanowy.data.db.entities.Notification
import io.github.wulkanowy.data.db.entities.Recipient
@@ -157,6 +159,7 @@ import javax.inject.Singleton
SchoolAnnouncement::class,
Notification::class,
AdminMessage::class,
+ MutedMessageSender::class,
GradeDescriptive::class,
],
autoMigrations = [
@@ -169,6 +172,7 @@ import javax.inject.Singleton
AutoMigration(from = 56, to = 57, spec = Migration57::class),
AutoMigration(from = 57, to = 58, spec = Migration58::class),
AutoMigration(from = 58, to = 59),
+ AutoMigration(from = 59, to = 60),
],
version = AppDatabase.VERSION_SCHEMA,
exportSchema = true
@@ -177,7 +181,7 @@ import javax.inject.Singleton
abstract class AppDatabase : RoomDatabase() {
companion object {
- const val VERSION_SCHEMA = 59
+ const val VERSION_SCHEMA = 60
fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf(
Migration2(),
@@ -303,5 +307,7 @@ abstract class AppDatabase : RoomDatabase() {
abstract val adminMessagesDao: AdminMessageDao
+ abstract val mutedMessageSendersDao: MutedMessageSendersDao
+
abstract val gradeDescriptiveDao: GradeDescriptiveDao
}
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/MessagesDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/MessagesDao.kt
index 1709f7636..11e6da1e7 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/dao/MessagesDao.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/MessagesDao.kt
@@ -5,15 +5,23 @@ import androidx.room.Query
import androidx.room.Transaction
import io.github.wulkanowy.data.db.entities.Message
import io.github.wulkanowy.data.db.entities.MessageWithAttachment
+import io.github.wulkanowy.data.db.entities.MessageWithMutedAuthor
import kotlinx.coroutines.flow.Flow
@Dao
interface MessagesDao : BaseDao {
-
@Transaction
@Query("SELECT * FROM Messages WHERE message_global_key = :messageGlobalKey")
fun loadMessageWithAttachment(messageGlobalKey: String): Flow
+ @Transaction
+ @Query("SELECT * FROM Messages WHERE mailbox_key = :mailboxKey AND folder_id = :folder ORDER BY date DESC")
+ fun loadMessagesWithMutedAuthor(mailboxKey: String, folder: Int): Flow>
+
+ @Transaction
+ @Query("SELECT * FROM Messages WHERE email = :email AND folder_id = :folder ORDER BY date DESC")
+ fun loadMessagesWithMutedAuthor(folder: Int, email: String): Flow>
+
@Query("SELECT * FROM Messages WHERE mailbox_key = :mailboxKey AND folder_id = :folder ORDER BY date DESC")
fun loadAll(mailboxKey: String, folder: Int): Flow>
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/MutedMessageSendersDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/MutedMessageSendersDao.kt
new file mode 100644
index 000000000..0a8664010
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/MutedMessageSendersDao.kt
@@ -0,0 +1,20 @@
+package io.github.wulkanowy.data.db.dao
+
+import androidx.room.Dao
+import androidx.room.Insert
+import androidx.room.OnConflictStrategy
+import androidx.room.Query
+import io.github.wulkanowy.data.db.entities.MutedMessageSender
+
+@Dao
+interface MutedMessageSendersDao : BaseDao {
+
+ @Query("SELECT COUNT(*) FROM MutedMessageSenders WHERE author = :author")
+ suspend fun checkMute(author: String): Boolean
+
+ @Insert(onConflict = OnConflictStrategy.IGNORE)
+ suspend fun insertMute(mute: MutedMessageSender): Long
+
+ @Query("DELETE FROM MutedMessageSenders WHERE author = :author")
+ suspend fun deleteMute(author: String)
+}
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/MessageWithAttachment.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/MessageWithAttachment.kt
index cd468215d..fc890e760 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/entities/MessageWithAttachment.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/MessageWithAttachment.kt
@@ -2,11 +2,15 @@ package io.github.wulkanowy.data.db.entities
import androidx.room.Embedded
import androidx.room.Relation
+import java.io.Serializable
data class MessageWithAttachment(
@Embedded
val message: Message,
@Relation(parentColumn = "message_global_key", entityColumn = "message_global_key")
- val attachments: List
-)
+ val attachments: List,
+
+ @Relation(parentColumn = "correspondents", entityColumn = "author")
+ val mutedMessageSender: MutedMessageSender?,
+) : Serializable
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/MessageWithMutedAuthor.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/MessageWithMutedAuthor.kt
new file mode 100644
index 000000000..e3cd1ca7d
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/MessageWithMutedAuthor.kt
@@ -0,0 +1,12 @@
+package io.github.wulkanowy.data.db.entities
+
+import androidx.room.Embedded
+import androidx.room.Relation
+
+data class MessageWithMutedAuthor(
+ @Embedded
+ val message: Message,
+
+ @Relation(parentColumn = "correspondents", entityColumn = "author")
+ val mutedMessageSender: MutedMessageSender?,
+)
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/MutedMessageSender.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/MutedMessageSender.kt
new file mode 100644
index 000000000..f1770e64c
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/MutedMessageSender.kt
@@ -0,0 +1,15 @@
+package io.github.wulkanowy.data.db.entities
+
+import androidx.room.ColumnInfo
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+import java.io.Serializable
+
+@Entity(tableName = "MutedMessageSenders")
+data class MutedMessageSender(
+ @ColumnInfo(name = "author")
+ val author: String,
+) : Serializable {
+ @PrimaryKey(autoGenerate = true)
+ var id: Long = 0
+}
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/MessageRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/MessageRepository.kt
index c8fccb23d..6d591c5bb 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/MessageRepository.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/MessageRepository.kt
@@ -8,9 +8,12 @@ import io.github.wulkanowy.data.db.SharedPrefProvider
import io.github.wulkanowy.data.db.dao.MailboxDao
import io.github.wulkanowy.data.db.dao.MessageAttachmentDao
import io.github.wulkanowy.data.db.dao.MessagesDao
+import io.github.wulkanowy.data.db.dao.MutedMessageSendersDao
import io.github.wulkanowy.data.db.entities.Mailbox
import io.github.wulkanowy.data.db.entities.Message
import io.github.wulkanowy.data.db.entities.MessageWithAttachment
+import io.github.wulkanowy.data.db.entities.MessageWithMutedAuthor
+import io.github.wulkanowy.data.db.entities.MutedMessageSender
import io.github.wulkanowy.data.db.entities.Recipient
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.enums.MessageFolder
@@ -42,6 +45,7 @@ import javax.inject.Singleton
@Singleton
class MessageRepository @Inject constructor(
private val messagesDb: MessagesDao,
+ private val mutedMessageSendersDao: MutedMessageSendersDao,
private val messageAttachmentDao: MessageAttachmentDao,
private val sdk: Sdk,
@ApplicationContext private val context: Context,
@@ -51,7 +55,6 @@ class MessageRepository @Inject constructor(
private val mailboxDao: MailboxDao,
private val getMailboxByStudentUseCase: GetMailboxByStudentUseCase,
) {
-
private val saveFetchResultMutex = Mutex()
private val messagesCacheKey = "message"
@@ -63,7 +66,7 @@ class MessageRepository @Inject constructor(
folder: MessageFolder,
forceRefresh: Boolean,
notify: Boolean = false,
- ): Flow>> = networkBoundResource(
+ ): Flow>> = networkBoundResource(
mutex = saveFetchResultMutex,
isResultEmpty = { it.isEmpty() },
shouldFetch = {
@@ -74,8 +77,8 @@ class MessageRepository @Inject constructor(
},
query = {
if (mailbox == null) {
- messagesDb.loadAll(folder.id, student.email)
- } else messagesDb.loadAll(mailbox.globalKey, folder.id)
+ messagesDb.loadMessagesWithMutedAuthor(folder.id, student.email)
+ } else messagesDb.loadMessagesWithMutedAuthor(mailbox.globalKey, folder.id)
},
fetch = {
sdk.init(student).getMessages(
@@ -83,10 +86,12 @@ class MessageRepository @Inject constructor(
mailboxKey = mailbox?.globalKey,
).mapToEntities(student, mailbox, mailboxDao.loadAll(student.email))
},
- saveFetchResult = { old, new ->
+ saveFetchResult = { oldWithAuthors, new ->
+ val old = oldWithAuthors.map { it.message }
messagesDb.deleteAll(old uniqueSubtract new)
messagesDb.insertAll((new uniqueSubtract old).onEach {
- it.isNotified = !notify
+ val muted = isMuted(it.correspondents)
+ it.isNotified = !notify || muted
})
refreshHelper.updateLastRefreshTimestamp(
@@ -106,9 +111,7 @@ class MessageRepository @Inject constructor(
Timber.d("Message content in db empty: ${it.message.content.isBlank()}")
(it.message.unread && markAsRead) || it.message.content.isBlank()
},
- query = {
- messagesDb.loadMessageWithAttachment(message.messageGlobalKey)
- },
+ query = { messagesDb.loadMessageWithAttachment(message.messageGlobalKey) },
fetch = {
sdk.init(student).getMessageDetails(
messageKey = it!!.message.messageGlobalKey,
@@ -236,4 +239,18 @@ class MessageRepository @Inject constructor(
context.getString(R.string.pref_key_message_draft),
value?.let { json.encodeToString(it) }
)
+
+ suspend fun isMuted(author: String): Boolean {
+ return mutedMessageSendersDao.checkMute(author)
+ }
+
+ suspend fun muteMessage(author: String) {
+ if (isMuted(author)) return
+ mutedMessageSendersDao.insertMute(MutedMessageSender(author))
+ }
+
+ suspend fun unmuteMessage(author: String) {
+ if (!isMuted(author)) return
+ mutedMessageSendersDao.deleteMute(author)
+ }
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardPresenter.kt
index 784ac112f..3fec62562 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardPresenter.kt
@@ -304,6 +304,7 @@ class DashboardPresenter @Inject constructor(
forceRefresh = forceRefresh
)
}
+ .mapResourceData { it.map { messageWithAuthor -> messageWithAuthor.message } }
.onResourceError { errorHandler.dispatch(it) }
.takeIf { DashboardItem.Tile.MESSAGES in selectedTiles } ?: flowSuccess
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewAdapter.kt
index d3c6b95c7..b83f7e232 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewAdapter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewAdapter.kt
@@ -50,12 +50,15 @@ class MessagePreviewAdapter @Inject constructor() :
ViewType.MESSAGE.id -> MessageViewHolder(
ItemMessagePreviewBinding.inflate(inflater, parent, false)
)
+
ViewType.DIVIDER.id -> DividerViewHolder(
ItemMessageDividerBinding.inflate(inflater, parent, false)
)
+
ViewType.ATTACHMENT.id -> AttachmentViewHolder(
ItemMessageAttachmentBinding.inflate(inflater, parent, false)
)
+
else -> throw IllegalStateException()
}
}
@@ -66,6 +69,7 @@ class MessagePreviewAdapter @Inject constructor() :
holder,
requireNotNull(messageWithAttachment).message
)
+
is AttachmentViewHolder -> bindAttachment(
holder,
requireNotNull(messageWithAttachment).attachments[position - 2]
@@ -82,9 +86,11 @@ class MessagePreviewAdapter @Inject constructor() :
recipientCount > 1 -> {
context.getString(R.string.message_read_by, message.readBy, recipientCount)
}
+
message.readBy == 1 || (isReceived && !message.unread) -> {
context.getString(R.string.message_read, context.getString(R.string.all_yes))
}
+
else -> context.getString(R.string.message_read, context.getString(R.string.all_no))
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewFragment.kt
index 3ed685cd7..3b33bb51f 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewFragment.kt
@@ -50,12 +50,20 @@ class MessagePreviewFragment :
private var menuPrintButton: MenuItem? = null
+ private var menuMuteButton: MenuItem? = null
+
override val titleStringId: Int
get() = R.string.message_title
override val deleteMessageSuccessString: String
get() = getString(R.string.message_delete_success)
+ override val muteMessageSuccessString: String
+ get() = getString(R.string.message_mute_success)
+
+ override val unmuteMessageSuccessString: String
+ get() = getString(R.string.message_unmute_success)
+
override val messageNoSubjectString: String
get() = getString(R.string.message_no_subject)
@@ -106,6 +114,7 @@ class MessagePreviewFragment :
menuDeleteButton = menu.findItem(R.id.messagePreviewMenuDelete)
menuShareButton = menu.findItem(R.id.messagePreviewMenuShare)
menuPrintButton = menu.findItem(R.id.messagePreviewMenuPrint)
+ menuMuteButton = menu.findItem(R.id.messagePreviewMenuMute)
presenter.onCreateOptionsMenu()
menu.findItem(R.id.mainMenuAccount).isVisible = false
@@ -118,6 +127,7 @@ class MessagePreviewFragment :
R.id.messagePreviewMenuDelete -> presenter.onMessageDelete()
R.id.messagePreviewMenuShare -> presenter.onShare()
R.id.messagePreviewMenuPrint -> presenter.onPrint()
+ R.id.messagePreviewMenuMute -> presenter.onMute()
else -> false
}
}
@@ -129,6 +139,11 @@ class MessagePreviewFragment :
}
}
+ override fun updateMuteToggleButton(isMuted: Boolean) {
+ menuMuteButton?.setTitle(if (isMuted) R.string.message_unmute else R.string.message_mute)
+
+ }
+
override fun showProgress(show: Boolean) {
binding.messagePreviewProgress.visibility = if (show) VISIBLE else GONE
}
@@ -143,6 +158,7 @@ class MessagePreviewFragment :
menuDeleteButton?.isVisible = show
menuShareButton?.isVisible = show
menuPrintButton?.isVisible = show
+ menuMuteButton?.isVisible = show && isReplayable
}
override fun setDeletedOptionsLabels() {
@@ -213,7 +229,7 @@ class MessagePreviewFragment :
}
override fun onSaveInstanceState(outState: Bundle) {
- outState.putSerializable(MESSAGE_ID_KEY, presenter.message)
+ outState.putSerializable(MESSAGE_ID_KEY, presenter.messageWithAttachments)
super.onSaveInstanceState(outState)
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewPresenter.kt
index cd7b72843..2eff245ff 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewPresenter.kt
@@ -5,7 +5,7 @@ import androidx.core.text.parseAsHtml
import io.github.wulkanowy.R
import io.github.wulkanowy.data.*
import io.github.wulkanowy.data.db.entities.Message
-import io.github.wulkanowy.data.db.entities.MessageAttachment
+import io.github.wulkanowy.data.db.entities.MessageWithAttachment
import io.github.wulkanowy.data.enums.MessageFolder
import io.github.wulkanowy.data.repositories.MessageRepository
import io.github.wulkanowy.data.repositories.PreferencesRepository
@@ -26,9 +26,7 @@ class MessagePreviewPresenter @Inject constructor(
private val analytics: AnalyticsHelper
) : BasePresenter(errorHandler, studentRepository) {
- var message: Message? = null
-
- var attachments: List? = null
+ var messageWithAttachments: MessageWithAttachment? = null
private lateinit var lastError: Throwable
@@ -38,7 +36,6 @@ class MessagePreviewPresenter @Inject constructor(
super.onAttachView(view)
view.initView()
errorHandler.showErrorMessage = ::showErrorViewOnError
- this.message = message
loadData(requireNotNull(message))
}
@@ -66,13 +63,12 @@ class MessagePreviewPresenter @Inject constructor(
.logResourceStatus("message ${messageToLoad.messageId} preview")
.onResourceData {
if (it != null) {
- message = it.message
- attachments = it.attachments
+ messageWithAttachments = it
view?.apply {
setMessageWithAttachment(it)
showContent(true)
initOptions()
-
+ updateMuteToggleButton(isMuted = it.mutedMessageSender != null)
if (preferencesRepository.isIncognitoMode && it.message.unread) {
showMessage(R.string.message_incognito_description)
}
@@ -83,8 +79,7 @@ class MessagePreviewPresenter @Inject constructor(
popView()
}
}
- }
- .onResourceSuccess {
+ }.onResourceSuccess {
if (it != null) {
analytics.logEvent(
"load_item",
@@ -92,31 +87,28 @@ class MessagePreviewPresenter @Inject constructor(
"length" to it.message.content.length
)
}
- }
- .onResourceNotLoading { view?.showProgress(false) }
- .onResourceError {
+ }.onResourceNotLoading { view?.showProgress(false) }.onResourceError {
retryCallback = { onMessageLoadRetry(messageToLoad) }
errorHandler.dispatch(it)
- }
- .launch()
+ }.launch()
}
fun onReply(): Boolean {
- return if (message != null) {
- view?.openMessageReply(message)
+ return if (messageWithAttachments?.message != null) {
+ view?.openMessageReply(messageWithAttachments?.message)
true
} else false
}
fun onForward(): Boolean {
- return if (message != null) {
- view?.openMessageForward(message)
+ return if (messageWithAttachments?.message != null) {
+ view?.openMessageForward(messageWithAttachments?.message)
true
} else false
}
fun onShare(): Boolean {
- val message = message ?: return false
+ val message = messageWithAttachments?.message ?: return false
val subject = message.subject.ifBlank { view?.messageNoSubjectString.orEmpty() }
val text = buildString {
@@ -129,13 +121,15 @@ class MessagePreviewPresenter @Inject constructor(
appendLine(message.content.parseAsHtml())
- if (!attachments.isNullOrEmpty()) {
+ if (!messageWithAttachments?.attachments.isNullOrEmpty()) {
appendLine()
appendLine("Załączniki:")
- append(attachments.orEmpty().joinToString(separator = "\n") { attachment ->
- "${attachment.filename}: ${attachment.url}"
- })
+ append(
+ messageWithAttachments?.attachments.orEmpty()
+ .joinToString(separator = "\n") { attachment ->
+ "${attachment.filename}: ${attachment.url}"
+ })
}
}
@@ -148,7 +142,7 @@ class MessagePreviewPresenter @Inject constructor(
@SuppressLint("NewApi")
fun onPrint(): Boolean {
- val message = message ?: return false
+ val message = messageWithAttachments?.message ?: return false
val subject = message.subject.ifBlank { view?.messageNoSubjectString.orEmpty() }
val dateString = message.date.toFormattedString("yyyy-MM-dd HH:mm:ss")
@@ -159,8 +153,7 @@ class MessagePreviewPresenter @Inject constructor(
append("Od
${message.sender}
")
append("DO
${message.recipients}")
}
- val messageContent = "${message.content}
"
- .replace(Regex("[\\n\\r]{2,}"), "")
+ val messageContent = "
${message.content}
".replace(Regex("[\\n\\r]{2,}"), "")
.replace(Regex("[\\n\\r]"), "
")
val jobName = buildString {
@@ -171,9 +164,7 @@ class MessagePreviewPresenter @Inject constructor(
}
view?.apply {
- val html = printHTML
- .replace("%SUBJECT%", subject)
- .replace("%CONTENT%", messageContent)
+ val html = printHTML.replace("%SUBJECT%", subject).replace("%CONTENT%", messageContent)
.replace("%INFO%", infoContent)
printDocument(html, jobName)
}
@@ -182,7 +173,7 @@ class MessagePreviewPresenter @Inject constructor(
}
private fun deleteMessage() {
- message ?: return
+ messageWithAttachments?.message ?: return
view?.run {
showContent(false)
@@ -191,24 +182,22 @@ class MessagePreviewPresenter @Inject constructor(
showErrorView(false)
}
- Timber.i("Delete message ${message?.messageGlobalKey}")
+ Timber.i("Delete message ${messageWithAttachments?.message?.messageGlobalKey}")
presenterScope.launch {
runCatching {
val student = studentRepository.getCurrentStudent(decryptPass = true)
val mailbox = messageRepository.getMailboxByStudent(student)
- messageRepository.deleteMessage(student, mailbox, message!!)
+ messageRepository.deleteMessage(student, mailbox, messageWithAttachments?.message!!)
+ }.onFailure {
+ retryCallback = { onMessageDelete() }
+ errorHandler.dispatch(it)
+ }.onSuccess {
+ view?.run {
+ showMessage(deleteMessageSuccessString)
+ popView()
+ }
}
- .onFailure {
- retryCallback = { onMessageDelete() }
- errorHandler.dispatch(it)
- }
- .onSuccess {
- view?.run {
- showMessage(deleteMessageSuccessString)
- popView()
- }
- }
view?.showProgress(false)
}
@@ -232,10 +221,10 @@ class MessagePreviewPresenter @Inject constructor(
private fun initOptions() {
view?.apply {
showOptions(
- show = message != null,
- isReplayable = message?.folderId != MessageFolder.SENT.id,
+ show = messageWithAttachments?.message != null,
+ isReplayable = messageWithAttachments?.message?.folderId != MessageFolder.SENT.id,
)
- message?.let {
+ messageWithAttachments?.message?.let {
when (it.folderId == MessageFolder.TRASHED.id) {
true -> setDeletedOptionsLabels()
false -> setNotDeletedOptionsLabels()
@@ -248,4 +237,29 @@ class MessagePreviewPresenter @Inject constructor(
fun onCreateOptionsMenu() {
initOptions()
}
+
+ fun onMute(): Boolean {
+ val message = messageWithAttachments?.message ?: return false
+ val isMuted = messageWithAttachments?.mutedMessageSender != null
+
+ presenterScope.launch {
+ runCatching {
+ when (isMuted) {
+ true -> {
+ messageRepository.unmuteMessage(message.correspondents)
+ view?.run { showMessage(unmuteMessageSuccessString) }
+ }
+
+ false -> {
+ messageRepository.muteMessage(message.correspondents)
+ view?.run { showMessage(muteMessageSuccessString) }
+ }
+ }
+ }.onFailure {
+ errorHandler.dispatch(it)
+ }
+ }
+ view?.updateMuteToggleButton(isMuted)
+ return true
+ }
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewView.kt
index 7f5f140b2..cbe1c3cbc 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewView.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewView.kt
@@ -9,6 +9,10 @@ interface MessagePreviewView : BaseView {
val deleteMessageSuccessString: String
+ val muteMessageSuccessString: String
+
+ val unmuteMessageSuccessString: String
+
val messageNoSubjectString: String
val printHTML: String
@@ -19,6 +23,8 @@ interface MessagePreviewView : BaseView {
fun setMessageWithAttachment(item: MessageWithAttachment)
+ fun updateMuteToggleButton(isMuted: Boolean)
+
fun showProgress(show: Boolean)
fun showContent(show: Boolean)
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabAdapter.kt
index 9792c7085..fadc77e6d 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabAdapter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabAdapter.kt
@@ -18,8 +18,7 @@ import io.github.wulkanowy.utils.getThemeAttrColor
import io.github.wulkanowy.utils.toFormattedString
import javax.inject.Inject
-class MessageTabAdapter @Inject constructor() :
- RecyclerView.Adapter() {
+class MessageTabAdapter @Inject constructor() : RecyclerView.Adapter() {
lateinit var onItemClickListener: (MessageTabDataItem.MessageItem, position: Int) -> Unit
@@ -52,10 +51,11 @@ class MessageTabAdapter @Inject constructor() :
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val inflater = LayoutInflater.from(parent.context)
- return when (MessageItemViewType.values()[viewType]) {
+ return when (MessageItemViewType.entries[viewType]) {
MessageItemViewType.FILTERS -> HeaderViewHolder(
ItemMessageChipsBinding.inflate(inflater, parent, false)
)
+
MessageItemViewType.MESSAGE -> ItemViewHolder(
ItemMessageBinding.inflate(inflater, parent, false)
)
@@ -137,7 +137,12 @@ class MessageTabAdapter @Inject constructor() :
ImageViewCompat.setImageTintList(this, ColorStateList.valueOf(currentTextColor))
isVisible = message.hasAttachments
}
- messageItemUnreadIndicator.isVisible = message.unread
+ messageItemUnreadIndicator.isVisible = message.unread || item.isMuted
+
+ when (item.isMuted) {
+ true -> messageItemUnreadIndicator.setImageResource(R.drawable.ic_notifications_off)
+ else -> messageItemUnreadIndicator.setImageResource(R.drawable.ic_circle_notification)
+ }
root.setOnClickListener {
holder.bindingAdapterPosition.let {
@@ -165,8 +170,7 @@ class MessageTabAdapter @Inject constructor() :
RecyclerView.ViewHolder(binding.root)
private class MessageTabDiffUtil(
- private val old: List,
- private val new: List
+ private val old: List, private val new: List
) : DiffUtil.Callback() {
override fun getOldListSize(): Int = old.size
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabDataItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabDataItem.kt
index c0bd4170e..ef640e040 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabDataItem.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabDataItem.kt
@@ -6,6 +6,7 @@ sealed class MessageTabDataItem(val viewType: MessageItemViewType) {
data class MessageItem(
val message: Message,
+ val isMuted: Boolean,
val isSelected: Boolean,
val isActionMode: Boolean
) : MessageTabDataItem(MessageItemViewType.MESSAGE)
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt
index 90f93b145..f82837214 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt
@@ -4,6 +4,7 @@ import io.github.wulkanowy.R
import io.github.wulkanowy.data.*
import io.github.wulkanowy.data.db.entities.Mailbox
import io.github.wulkanowy.data.db.entities.Message
+import io.github.wulkanowy.data.db.entities.MessageWithMutedAuthor
import io.github.wulkanowy.data.enums.MessageFolder
import io.github.wulkanowy.data.repositories.MessageRepository
import io.github.wulkanowy.data.repositories.StudentRepository
@@ -39,7 +40,7 @@ class MessageTabPresenter @Inject constructor(
private var mailboxes: List = emptyList()
private var selectedMailbox: Mailbox? = null
- private var messages = emptyList()
+ private var messages = emptyList()
private val searchChannel = Channel()
@@ -141,7 +142,7 @@ class MessageTabPresenter @Inject constructor(
}
fun onActionModeSelectCheckAll() {
- val messagesToSelect = getFilteredData()
+ val messagesToSelect = getFilteredData().map { it.message }
val isAllSelected = messagesToDelete.containsAll(messagesToSelect)
if (isAllSelected) {
@@ -188,7 +189,7 @@ class MessageTabPresenter @Inject constructor(
view?.showActionMode(false)
}
- val filteredData = getFilteredData()
+ val filteredData = getFilteredData().map { it.message }
view?.run {
updateActionModeTitle(messagesToDelete.size)
@@ -320,25 +321,31 @@ class MessageTabPresenter @Inject constructor(
}
}
- private fun getFilteredData(): List {
+ private fun getFilteredData(): List {
if (lastSearchQuery.trim().isEmpty()) {
- val sortedMessages = messages.sortedByDescending { it.date }
+ val sortedMessages = messages.sortedByDescending { it.message.date }
return when {
- (onlyUnread == true) && onlyWithAttachments -> sortedMessages.filter { it.unread == onlyUnread && it.hasAttachments == onlyWithAttachments }
- (onlyUnread == true) -> sortedMessages.filter { it.unread == onlyUnread }
- onlyWithAttachments -> sortedMessages.filter { it.hasAttachments == onlyWithAttachments }
+ (onlyUnread == true) && onlyWithAttachments -> sortedMessages.filter {
+ it.message.unread == onlyUnread && it.message.hasAttachments == onlyWithAttachments
+ }
+
+ (onlyUnread == true) -> sortedMessages.filter { it.message.unread == onlyUnread }
+ onlyWithAttachments -> sortedMessages.filter { it.message.hasAttachments == onlyWithAttachments }
else -> sortedMessages
}
} else {
val sortedMessages = messages
- .map { it to calculateMatchRatio(it, lastSearchQuery) }
- .sortedWith(compareBy> { -it.second }.thenByDescending { it.first.date })
+ .map { it to calculateMatchRatio(it.message, lastSearchQuery) }
+ .sortedWith(compareBy> { -it.second }.thenByDescending { it.first.message.date })
.filter { it.second > 6000 }
.map { it.first }
return when {
- (onlyUnread == true) && onlyWithAttachments -> sortedMessages.filter { it.unread == onlyUnread && it.hasAttachments == onlyWithAttachments }
- (onlyUnread == true) -> sortedMessages.filter { it.unread == onlyUnread }
- onlyWithAttachments -> sortedMessages.filter { it.hasAttachments == onlyWithAttachments }
+ (onlyUnread == true) && onlyWithAttachments -> sortedMessages.filter {
+ it.message.unread == onlyUnread && it.message.hasAttachments == onlyWithAttachments
+ }
+
+ (onlyUnread == true) -> sortedMessages.filter { it.message.unread == onlyUnread }
+ onlyWithAttachments -> sortedMessages.filter { it.message.hasAttachments == onlyWithAttachments }
else -> sortedMessages
}
}
@@ -367,8 +374,9 @@ class MessageTabPresenter @Inject constructor(
addAll(data.map { message ->
MessageTabDataItem.MessageItem(
- message = message,
- isSelected = messagesToDelete.any { it.messageGlobalKey == message.messageGlobalKey },
+ message = message.message,
+ isMuted = message.mutedMessageSender != null,
+ isSelected = messagesToDelete.any { it.messageGlobalKey == message.message.messageGlobalKey },
isActionMode = isActionMode
)
})
diff --git a/app/src/main/res/drawable/ic_circle_notification.xml b/app/src/main/res/drawable/ic_circle_notification.xml
new file mode 100644
index 000000000..6059212cb
--- /dev/null
+++ b/app/src/main/res/drawable/ic_circle_notification.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_notifications_off.xml b/app/src/main/res/drawable/ic_notifications_off.xml
new file mode 100644
index 000000000..094ed75fa
--- /dev/null
+++ b/app/src/main/res/drawable/ic_notifications_off.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/layout/item_message.xml b/app/src/main/res/layout/item_message.xml
index 39fbaad01..1346c3f05 100644
--- a/app/src/main/res/layout/item_message.xml
+++ b/app/src/main/res/layout/item_message.xml
@@ -81,9 +81,9 @@
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index faed4d186..5bb06a419 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -864,4 +864,10 @@
Feature disabled by your school
Feature not available. Login in a mode other than Mobile API
This field is required
+
+
+ Mute
+ Unmute
+ You have muted this user
+ You have unmuted this user
diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/MessageRepositoryTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/MessageRepositoryTest.kt
index 3a18ee979..58937e776 100644
--- a/app/src/test/java/io/github/wulkanowy/data/repositories/MessageRepositoryTest.kt
+++ b/app/src/test/java/io/github/wulkanowy/data/repositories/MessageRepositoryTest.kt
@@ -6,8 +6,10 @@ import io.github.wulkanowy.data.db.SharedPrefProvider
import io.github.wulkanowy.data.db.dao.MailboxDao
import io.github.wulkanowy.data.db.dao.MessageAttachmentDao
import io.github.wulkanowy.data.db.dao.MessagesDao
+import io.github.wulkanowy.data.db.dao.MutedMessageSendersDao
import io.github.wulkanowy.data.db.entities.Message
import io.github.wulkanowy.data.db.entities.MessageWithAttachment
+import io.github.wulkanowy.data.db.entities.MutedMessageSender
import io.github.wulkanowy.data.enums.MessageFolder
import io.github.wulkanowy.data.errorOrNull
import io.github.wulkanowy.data.toFirstResult
@@ -19,9 +21,16 @@ import io.github.wulkanowy.sdk.pojo.Folder
import io.github.wulkanowy.utils.AutoRefreshHelper
import io.github.wulkanowy.utils.Status
import io.github.wulkanowy.utils.status
-import io.mockk.*
+import io.mockk.MockKAnnotations
+import io.mockk.Runs
+import io.mockk.checkEquals
+import io.mockk.coEvery
+import io.mockk.coVerify
+import io.mockk.every
import io.mockk.impl.annotations.MockK
import io.mockk.impl.annotations.SpyK
+import io.mockk.just
+import io.mockk.mockk
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.toList
@@ -45,6 +54,9 @@ class MessageRepositoryTest {
@MockK
private lateinit var messageDb: MessagesDao
+ @MockK
+ private lateinit var mutesDb: MutedMessageSendersDao
+
@MockK
private lateinit var messageAttachmentDao: MessageAttachmentDao
@@ -73,9 +85,22 @@ class MessageRepositoryTest {
fun setUp() {
MockKAnnotations.init(this)
every { refreshHelper.shouldBeRefreshed(any()) } returns false
-
+ coEvery { mutesDb.checkMute(any()) } returns false
+ coEvery {
+ messageDb.loadMessagesWithMutedAuthor(
+ mailboxKey = any(),
+ folder = any()
+ )
+ } returns flowOf(emptyList())
+ coEvery {
+ messageDb.loadMessagesWithMutedAuthor(
+ folder = any(),
+ email = any()
+ )
+ } returns flowOf(emptyList())
repository = MessageRepository(
messagesDb = messageDb,
+ mutedMessageSendersDao = mutesDb,
messageAttachmentDao = messageAttachmentDao,
sdk = sdk,
context = context,
@@ -131,7 +156,11 @@ class MessageRepositoryTest {
@Test
fun `get message when content already in db`() {
val testMessage = getMessageEntity(123, "Test", false)
- val messageWithAttachment = MessageWithAttachment(testMessage, emptyList())
+ val messageWithAttachment = MessageWithAttachment(
+ testMessage,
+ emptyList(),
+ MutedMessageSender("Jan Kowalski - P - (WULKANOWY)")
+ )
coEvery { messageDb.loadMessageWithAttachment("v4") } returns flowOf(
messageWithAttachment
@@ -149,8 +178,16 @@ class MessageRepositoryTest {
val testMessage = getMessageEntity(123, "", true)
val testMessageWithContent = testMessage.copy().apply { content = "Test" }
- val mWa = MessageWithAttachment(testMessage, emptyList())
- val mWaWithContent = MessageWithAttachment(testMessageWithContent, emptyList())
+ val mWa = MessageWithAttachment(
+ testMessage,
+ emptyList(),
+ MutedMessageSender("Jan Kowalski - P - (WULKANOWY)")
+ )
+ val mWaWithContent = MessageWithAttachment(
+ testMessageWithContent,
+ emptyList(),
+ MutedMessageSender("Jan Kowalski - P - (WULKANOWY)")
+ )
coEvery {
messageDb.loadMessageWithAttachment("v4")
From 2c1337bb518893397e04b3ae99384e20c564e6c7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rafa=C5=82=20Borcz?=
Date: Thu, 29 Feb 2024 21:36:51 +0100
Subject: [PATCH 46/50] New Crowdin updates (#2439)
---
app/src/main/res/values-cs/strings.xml | 1 +
app/src/main/res/values-de/strings.xml | 1 +
app/src/main/res/values-pl/strings.xml | 1 +
app/src/main/res/values-ru/strings.xml | 1 +
app/src/main/res/values-sk/strings.xml | 1 +
app/src/main/res/values-uk/strings.xml | 1 +
6 files changed, 6 insertions(+)
diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml
index e1cafa6ea..2e0104b10 100644
--- a/app/src/main/res/values-cs/strings.xml
+++ b/app/src/main/res/values-cs/strings.xml
@@ -854,6 +854,7 @@
Žádné internetové připojení
Vyskytla se chyba. Zkontrolujte hodiny svého zařízení
+ This account is inactive. Try logging in again
Nelze se připojit ke deníku. Servery mohou být přetíženy. Prosím zkuste to znovu později
Načítání dat se nezdařilo. Prosím zkuste to znovu později
Je vyžadována změna hesla pro deník
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 5bd71bb29..b04558aa2 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -760,6 +760,7 @@
Keine Internetverbindung
Es ist ein Fehler aufgetreten. Überprüfen Sie Ihre Geräteuhr
+ This account is inactive. Try logging in again
Registrierungsverbindung fehlgeschlagen. Server können überlastet sein. Bitte versuchen Sie es später noch einmal
Das Laden der Daten ist fehlgeschlagen. Bitte versuchen Sie es später noch einmal
Passwortänderung für Registrierung erforderlich
diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml
index 70d4982b9..9a7ee3f81 100644
--- a/app/src/main/res/values-pl/strings.xml
+++ b/app/src/main/res/values-pl/strings.xml
@@ -854,6 +854,7 @@
Brak połączenia z internetem
Wystąpił błąd. Sprawdź poprawność daty w urządzeniu
+ Konto jest nieaktywne. Spróbuj zalogować się ponownie
Nie udało się połączyć z dziennikiem. Serwery mogą być przeciążone. Spróbuj ponownie później
Ładowanie danych nie powiodło się. Spróbuj ponownie później
Wymagana zmiana hasła do dziennika
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index 717e02131..b7786546d 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -854,6 +854,7 @@
Интернет-соединение отсутствует
Произошла ошибка. Проверьте время на вашем устройстве
+ This account is inactive. Try logging in again
Не удалось подключиться к дневнику. Возможно, сервера перегружены, повторите попытку позже
Не удалось загрузить данные, повторите попытку позже
Необходимо изменить пароль дневника
diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml
index 368ead9d5..d34302ec3 100644
--- a/app/src/main/res/values-sk/strings.xml
+++ b/app/src/main/res/values-sk/strings.xml
@@ -854,6 +854,7 @@
Žiadne internetové pripojenie
Vyskytla sa chyba. Skontrolujte hodiny svojho zariadenia
+ This account is inactive. Try logging in again
Nedá sa pripojiť ku denníku. Servery môžu byť preťažené. Prosím skúste to znova neskôr
Načítanie údajov zlyhalo. Skúste neskôr prosím
Je vyžadovaná zmena hesla pre denník
diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml
index 3d10f1179..228b87d44 100644
--- a/app/src/main/res/values-uk/strings.xml
+++ b/app/src/main/res/values-uk/strings.xml
@@ -854,6 +854,7 @@
Немає з\'єднання з інтернетом
Сталася помилка. Перевірте годинник пристрою
+ This account is inactive. Try logging in again
Помилка підключення до щоденнику. Сервери можуть бути перевантажені, спробуйте пізніше
Помилка завантаження даних, спробуйте пізніше
Необхідна зміна пароля щоденника
From 28c234a8fdae60d21f212b23100bbc0502b01c0d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Miko=C5=82aj=20Pich?=
Date: Thu, 29 Feb 2024 23:48:44 +0100
Subject: [PATCH 47/50] Move excuse whole day button to action bar
---
.../ui/modules/attendance/AttendanceFragment.kt | 8 +++-----
.../modules/attendance/AttendancePresenter.kt | 17 +++++------------
.../ui/modules/attendance/AttendanceView.kt | 2 --
.../wulkanowy/utils/AttendanceExtension.kt | 3 +++
app/src/main/res/layout/fragment_attendance.xml | 13 -------------
.../main/res/menu/context_menu_attendance.xml | 7 +++++++
6 files changed, 18 insertions(+), 32 deletions(-)
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceFragment.kt
index c8e0a33f4..9b45352bd 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceFragment.kt
@@ -69,6 +69,8 @@ class AttendanceFragment : BaseFragment(R.layout.frag
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
val inflater = mode.menuInflater
inflater.inflate(R.menu.context_menu_attendance, menu)
+ menu.findItem(R.id.excuseMenuDaySubmit).setVisible(presenter.isWholeDayExcusable)
+
return true
}
@@ -84,6 +86,7 @@ class AttendanceFragment : BaseFragment(R.layout.frag
override fun onActionItemClicked(mode: ActionMode, menu: MenuItem): Boolean {
return when (menu.itemId) {
+ R.id.excuseMenuDaySubmit -> presenter.onExcuseDayButtonClick()
R.id.excuseMenuSubmit -> presenter.onExcuseSubmitButtonClick()
else -> false
}
@@ -129,7 +132,6 @@ class AttendanceFragment : BaseFragment(R.layout.frag
attendanceNextButton.setOnClickListener { presenter.onNextDay() }
attendanceExcuseButton.setOnClickListener { presenter.onExcuseButtonClick() }
- attendanceExcuseDayButton.setOnClickListener { presenter.onExcuseDayButtonClick() }
attendanceNavContainer.elevation = requireContext().dpToPx(3f)
}
@@ -222,10 +224,6 @@ class AttendanceFragment : BaseFragment(R.layout.frag
binding.attendanceExcuseButton.isVisible = show
}
- override fun showExcuseDayButton(show: Boolean) {
- binding.attendanceExcuseDayButton.isVisible = show
- }
-
override fun showAttendanceDialog(lesson: Attendance) {
(activity as? MainActivity)?.showDialogFragment(AttendanceDialog.newInstance(lesson))
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt
index cd75f5c2c..6b001e1d7 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt
@@ -38,7 +38,7 @@ class AttendancePresenter @Inject constructor(
private lateinit var lastError: Throwable
private val attendanceToExcuseList = mutableListOf()
- private var isWholeDayExcusable = false
+ var isWholeDayExcusable = false
private var isVulcanExcusedFunctionEnabled = false
@@ -130,14 +130,12 @@ class AttendancePresenter @Inject constructor(
fun onExcuseButtonClick() {
view?.startActionMode()
-
- if (isWholeDayExcusable) {
- view?.showExcuseDayButton(true)
- }
}
- fun onExcuseDayButtonClick() {
+ fun onExcuseDayButtonClick(): Boolean {
view?.showExcuseDialog()
+
+ return true
}
fun onExcuseCheckboxSelect(attendanceItem: Attendance, checked: Boolean) {
@@ -183,7 +181,6 @@ class AttendancePresenter @Inject constructor(
view?.apply {
showExcuseCheckboxes(true)
showExcuseButton(false)
- showExcuseDayButton(false)
enableSwipe(false)
showDayNavigation(false)
}
@@ -195,7 +192,6 @@ class AttendancePresenter @Inject constructor(
view?.apply {
showExcuseCheckboxes(false)
showExcuseButton(true)
- showExcuseDayButton(false)
enableSwipe(true)
showDayNavigation(true)
}
@@ -230,7 +226,6 @@ class AttendancePresenter @Inject constructor(
.onResourceLoading {
view?.apply {
showExcuseButton(false)
- showExcuseDayButton(false)
}
}
.mapResourceData {
@@ -256,7 +251,7 @@ class AttendancePresenter @Inject constructor(
.onResourceIntermediate { view?.showRefresh(true) }
.onResourceSuccess { items ->
isVulcanExcusedFunctionEnabled = items.any { item -> item.excusable }
- isWholeDayExcusable = items.all { it.isExcusableOrNotExcused }
+ isWholeDayExcusable = items.all { it.isAbsenceExcusable }
val anyExcusables = items.any { it.isExcusableOrNotExcused }
view?.showExcuseButton(anyExcusables && (isParent || isVulcanExcusedFunctionEnabled))
@@ -336,7 +331,6 @@ class AttendancePresenter @Inject constructor(
showProgress(true)
showContent(false)
showExcuseButton(false)
- showExcuseDayButton(false)
}
is Resource.Success -> {
@@ -345,7 +339,6 @@ class AttendancePresenter @Inject constructor(
attendanceToExcuseList.clear()
view?.run {
showExcuseButton(false)
- showExcuseDayButton(false)
showMessage(excuseSuccessString)
showContent(true)
showProgress(false)
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceView.kt
index 5af491e74..8cfaf1591 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceView.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceView.kt
@@ -48,8 +48,6 @@ interface AttendanceView : BaseView {
fun showExcuseButton(show: Boolean)
- fun showExcuseDayButton(show: Boolean)
-
fun showAttendanceDialog(lesson: Attendance)
fun showDatePickerDialog(selectedDate: LocalDate)
diff --git a/app/src/main/java/io/github/wulkanowy/utils/AttendanceExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/AttendanceExtension.kt
index 397c95953..19d0929db 100644
--- a/app/src/main/java/io/github/wulkanowy/utils/AttendanceExtension.kt
+++ b/app/src/main/java/io/github/wulkanowy/utils/AttendanceExtension.kt
@@ -19,6 +19,9 @@ private inline val AttendanceSummary.allAbsences: Double
inline val Attendance.isExcusableOrNotExcused: Boolean
get() = (excusable || ((absence || lateness) && !excused)) && excuseStatus == null
+inline val Attendance.isAbsenceExcusable: Boolean
+ get() = (excusable && absence && !excused) && excuseStatus == null
+
fun AttendanceSummary.calculatePercentage() = calculatePercentage(allPresences, allAbsences)
fun List.calculatePercentage(): Double {
diff --git a/app/src/main/res/layout/fragment_attendance.xml b/app/src/main/res/layout/fragment_attendance.xml
index e3dd2ebb6..078daf610 100644
--- a/app/src/main/res/layout/fragment_attendance.xml
+++ b/app/src/main/res/layout/fragment_attendance.xml
@@ -69,19 +69,6 @@
app:icon="@drawable/ic_all_done_all"
tools:visibility="visible" />
-
-
+
+
-
Date: Fri, 1 Mar 2024 00:06:54 +0100
Subject: [PATCH 48/50] New Crowdin updates (#2445)
---
app/src/main/res/values-cs/strings.xml | 5 +++++
app/src/main/res/values-de/strings.xml | 5 +++++
app/src/main/res/values-pl/strings.xml | 5 +++++
app/src/main/res/values-ru/strings.xml | 5 +++++
app/src/main/res/values-sk/strings.xml | 5 +++++
app/src/main/res/values-uk/strings.xml | 5 +++++
6 files changed, 30 insertions(+)
diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml
index 2e0104b10..5c4c52da8 100644
--- a/app/src/main/res/values-cs/strings.xml
+++ b/app/src/main/res/values-cs/strings.xml
@@ -866,4 +866,9 @@
Funkce je deaktivována přes vaší školou
Funkce není k dispozici. Přihlaste se v jiném režimu než Mobile API
Toto pole je povinné
+
+ Mute
+ Unmute
+ You have muted this user
+ You have unmuted this user
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index b04558aa2..a346bbd2f 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -772,4 +772,9 @@
Funktion, die von Ihrer Schule deaktiviert wurde
Feature in diesem Modus nicht verfügbar
Dieses Feld ist erforderlich
+
+ Mute
+ Unmute
+ You have muted this user
+ You have unmuted this user
diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml
index 9a7ee3f81..56a85ea2a 100644
--- a/app/src/main/res/values-pl/strings.xml
+++ b/app/src/main/res/values-pl/strings.xml
@@ -866,4 +866,9 @@
Funkcja wyłączona przez szkołę
Funkcja niedostępna. Zaloguj się w trybie innym niż Mobilne API
To pole jest wymagane
+
+ Wycisz
+ Wyłącz wyciszenie
+ Wyciszyleś tego użytkownika
+ Wyłączyłeś wyciszenie tego użytkownika
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index b7786546d..f7469675e 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -866,4 +866,9 @@
Функция отключена вашей школой
Функция недоступна в режиме Mobile API. Воспользуйтесь другим режимом
Это поле обязательно
+
+ Mute
+ Unmute
+ You have muted this user
+ You have unmuted this user
diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml
index d34302ec3..56238c10a 100644
--- a/app/src/main/res/values-sk/strings.xml
+++ b/app/src/main/res/values-sk/strings.xml
@@ -866,4 +866,9 @@
Funkcia je deaktivovaná cez vašou školou
Funkcia nie je k dispozícii. Prihláste sa v inom režime než Mobile API
Toto pole je povinné
+
+ Mute
+ Unmute
+ You have muted this user
+ You have unmuted this user
diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml
index 228b87d44..a82027479 100644
--- a/app/src/main/res/values-uk/strings.xml
+++ b/app/src/main/res/values-uk/strings.xml
@@ -866,4 +866,9 @@
Функція вимкнена вашою школою
Функція недоступна в режимі Mobile API. Увійдіть в інший режим
Це поле обовʼязкове
+
+ Mute
+ Unmute
+ You have muted this user
+ You have unmuted this user
From c04752ed39e847009bc4ab1997a4aef43a545ca0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Miko=C5=82aj=20Pich?=
Date: Fri, 1 Mar 2024 10:32:55 +0100
Subject: [PATCH 49/50] Fix timetable items layout (#2446)
---
app/src/main/res/layout/item_timetable.xml | 11 +++++++----
.../main/res/layout/subitem_dashboard_small_grade.xml | 1 +
2 files changed, 8 insertions(+), 4 deletions(-)
diff --git a/app/src/main/res/layout/item_timetable.xml b/app/src/main/res/layout/item_timetable.xml
index b9966c121..d13105229 100644
--- a/app/src/main/res/layout/item_timetable.xml
+++ b/app/src/main/res/layout/item_timetable.xml
@@ -24,6 +24,7 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintVertical_bias="0"
tools:text="5" />
@@ -179,7 +182,7 @@
android:visibility="gone"
app:backgroundTint="?colorPrimary"
app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintTop_toTopOf="@id/timetableItemTimeStart"
tools:text="jeszcze 15 min"
tools:visibility="visible" />
diff --git a/app/src/main/res/layout/subitem_dashboard_small_grade.xml b/app/src/main/res/layout/subitem_dashboard_small_grade.xml
index 3684c2677..5d48313a3 100644
--- a/app/src/main/res/layout/subitem_dashboard_small_grade.xml
+++ b/app/src/main/res/layout/subitem_dashboard_small_grade.xml
@@ -11,6 +11,7 @@
android:gravity="center"
android:maxLength="5"
android:minWidth="20dp"
+ android:padding="1dp"
android:textColor="@android:color/white"
android:textSize="12sp"
android:textStyle="bold"
From ea28fc783cf2c24e25606f5ba13dff120d1101ab Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Miko=C5=82aj=20Pich?=
Date: Fri, 1 Mar 2024 21:14:43 +0100
Subject: [PATCH 50/50] Add message from trash restoring (#2438)
---
.../wulkanowy/data/enums/MessageFolder.kt | 7 ++-
.../data/repositories/MessageRepository.kt | 50 ++++++++++-----
.../message/preview/MessagePreviewFragment.kt | 27 ++++----
.../preview/MessagePreviewPresenter.kt | 61 +++++++++++++++----
.../message/preview/MessagePreviewView.kt | 8 +--
.../message/send/SendMessagePresenter.kt | 2 +-
.../modules/message/tab/MessageTabFragment.kt | 16 +++--
.../message/tab/MessageTabPresenter.kt | 23 ++++++-
.../ic_menu_message_delete_forever.xml | 9 +++
.../res/drawable/ic_menu_message_restore.xml | 9 +++
.../res/menu/action_menu_message_preview.xml | 14 +++++
.../res/menu/context_menu_message_tab.xml | 14 +++++
app/src/main/res/values/strings.xml | 3 +
13 files changed, 192 insertions(+), 51 deletions(-)
create mode 100644 app/src/main/res/drawable/ic_menu_message_delete_forever.xml
create mode 100644 app/src/main/res/drawable/ic_menu_message_restore.xml
diff --git a/app/src/main/java/io/github/wulkanowy/data/enums/MessageFolder.kt b/app/src/main/java/io/github/wulkanowy/data/enums/MessageFolder.kt
index 899ba9085..7cb4202a1 100644
--- a/app/src/main/java/io/github/wulkanowy/data/enums/MessageFolder.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/enums/MessageFolder.kt
@@ -3,5 +3,10 @@ package io.github.wulkanowy.data.enums
enum class MessageFolder(val id: Int = 1) {
RECEIVED(1),
SENT(2),
- TRASHED(3)
+ TRASHED(3),
+ ;
+
+ companion object {
+ fun byId(id: Int) = entries.first { it.id == id }
+ }
}
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/MessageRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/MessageRepository.kt
index 6d591c5bb..96f048706 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/MessageRepository.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/MessageRepository.kt
@@ -18,6 +18,7 @@ import io.github.wulkanowy.data.db.entities.Recipient
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.enums.MessageFolder
import io.github.wulkanowy.data.enums.MessageFolder.RECEIVED
+import io.github.wulkanowy.data.enums.MessageFolder.SENT
import io.github.wulkanowy.data.enums.MessageFolder.TRASHED
import io.github.wulkanowy.data.mappers.mapFromEntities
import io.github.wulkanowy.data.mappers.mapToEntities
@@ -25,6 +26,7 @@ import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.data.onResourceError
import io.github.wulkanowy.data.onResourceSuccess
import io.github.wulkanowy.data.pojos.MessageDraft
+import io.github.wulkanowy.data.toFirstResult
import io.github.wulkanowy.data.waitForResult
import io.github.wulkanowy.domain.messages.GetMailboxByStudentUseCase
import io.github.wulkanowy.sdk.Sdk
@@ -34,7 +36,6 @@ import io.github.wulkanowy.utils.getRefreshKey
import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.first
import kotlinx.coroutines.sync.Mutex
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
@@ -155,17 +156,30 @@ class MessageRepository @Inject constructor(
subject: String,
content: String,
recipients: List,
- mailboxId: String,
+ mailbox: Mailbox,
) {
sdk.init(student).sendMessage(
subject = subject,
content = content,
recipients = recipients.mapFromEntities(),
- mailboxId = mailboxId,
+ mailboxId = mailbox.globalKey,
)
+ refreshFolders(student, mailbox, listOf(SENT))
}
- suspend fun deleteMessages(student: Student, mailbox: Mailbox?, messages: List) {
+ suspend fun restoreMessages(student: Student, mailbox: Mailbox?, messages: List) {
+ sdk.init(student).restoreMessages(
+ messages = messages.map { it.messageGlobalKey },
+ )
+
+ refreshFolders(student, mailbox)
+ }
+
+ suspend fun deleteMessage(student: Student, message: Message) {
+ deleteMessages(student, listOf(message))
+ }
+
+ suspend fun deleteMessages(student: Student, messages: List) {
val firstMessage = messages.first()
sdk.init(student).deleteMessages(
messages = messages.map { it.messageGlobalKey },
@@ -184,18 +198,24 @@ class MessageRepository @Inject constructor(
}
messagesDb.updateAll(deletedMessages)
- } else messagesDb.deleteAll(messages)
-
- getMessages(
- student = student,
- mailbox = mailbox,
- folder = TRASHED,
- forceRefresh = true,
- ).first()
+ } else {
+ messagesDb.deleteAll(messages)
+ }
}
- suspend fun deleteMessage(student: Student, mailbox: Mailbox?, message: Message) {
- deleteMessages(student, mailbox, listOf(message))
+ private suspend fun refreshFolders(
+ student: Student,
+ mailbox: Mailbox?,
+ folders: List = MessageFolder.entries
+ ) {
+ folders.forEach {
+ getMessages(
+ student = student,
+ mailbox = mailbox,
+ folder = it,
+ forceRefresh = true,
+ ).toFirstResult()
+ }
}
suspend fun getMailboxes(student: Student, forceRefresh: Boolean) = networkBoundResource(
@@ -240,7 +260,7 @@ class MessageRepository @Inject constructor(
value?.let { json.encodeToString(it) }
)
- suspend fun isMuted(author: String): Boolean {
+ private suspend fun isMuted(author: String): Boolean {
return mutedMessageSendersDao.checkMute(author)
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewFragment.kt
index 3b33bb51f..75778bac5 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewFragment.kt
@@ -44,8 +44,12 @@ class MessagePreviewFragment :
private var menuForwardButton: MenuItem? = null
+ private var menuRestoreButton: MenuItem? = null
+
private var menuDeleteButton: MenuItem? = null
+ private var menuDeleteForeverButton: MenuItem? = null
+
private var menuShareButton: MenuItem? = null
private var menuPrintButton: MenuItem? = null
@@ -64,6 +68,9 @@ class MessagePreviewFragment :
override val unmuteMessageSuccessString: String
get() = getString(R.string.message_unmute_success)
+ override val restoreMessageSuccessString: String
+ get() = getString(R.string.message_restore_success)
+
override val messageNoSubjectString: String
get() = getString(R.string.message_no_subject)
@@ -111,7 +118,9 @@ class MessagePreviewFragment :
inflater.inflate(R.menu.action_menu_message_preview, menu)
menuReplyButton = menu.findItem(R.id.messagePreviewMenuReply)
menuForwardButton = menu.findItem(R.id.messagePreviewMenuForward)
+ menuRestoreButton = menu.findItem(R.id.messagePreviewMenuRestore)
menuDeleteButton = menu.findItem(R.id.messagePreviewMenuDelete)
+ menuDeleteForeverButton = menu.findItem(R.id.messagePreviewMenuDeleteForever)
menuShareButton = menu.findItem(R.id.messagePreviewMenuShare)
menuPrintButton = menu.findItem(R.id.messagePreviewMenuPrint)
menuMuteButton = menu.findItem(R.id.messagePreviewMenuMute)
@@ -124,7 +133,9 @@ class MessagePreviewFragment :
return when (item.itemId) {
R.id.messagePreviewMenuReply -> presenter.onReply()
R.id.messagePreviewMenuForward -> presenter.onForward()
+ R.id.messagePreviewMenuRestore -> presenter.onMessageRestore()
R.id.messagePreviewMenuDelete -> presenter.onMessageDelete()
+ R.id.messagePreviewMenuDeleteForever -> presenter.onMessageDelete()
R.id.messagePreviewMenuShare -> presenter.onShare()
R.id.messagePreviewMenuPrint -> presenter.onPrint()
R.id.messagePreviewMenuMute -> presenter.onMute()
@@ -152,23 +163,17 @@ class MessagePreviewFragment :
binding.messagePreviewRecycler.visibility = if (show) VISIBLE else GONE
}
- override fun showOptions(show: Boolean, isReplayable: Boolean) {
- menuReplyButton?.isVisible = isReplayable
+ override fun showOptions(show: Boolean, isReplayable: Boolean, isRestorable: Boolean) {
+ menuReplyButton?.isVisible = show && isReplayable
menuForwardButton?.isVisible = show
- menuDeleteButton?.isVisible = show
+ menuRestoreButton?.isVisible = show && isRestorable
+ menuDeleteButton?.isVisible = show && !isRestorable
+ menuDeleteForeverButton?.isVisible = show && isRestorable
menuShareButton?.isVisible = show
menuPrintButton?.isVisible = show
menuMuteButton?.isVisible = show && isReplayable
}
- override fun setDeletedOptionsLabels() {
- menuDeleteButton?.setTitle(R.string.message_delete_forever)
- }
-
- override fun setNotDeletedOptionsLabels() {
- menuDeleteButton?.setTitle(R.string.message_move_to_trash)
- }
-
override fun showErrorView(show: Boolean) {
binding.messagePreviewError.visibility = if (show) VISIBLE else GONE
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewPresenter.kt
index 2eff245ff..9bb0d32a4 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewPresenter.kt
@@ -14,9 +14,11 @@ import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.AnalyticsHelper
import io.github.wulkanowy.utils.toFormattedString
+import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
+import kotlin.time.Duration.Companion.seconds
class MessagePreviewPresenter @Inject constructor(
errorHandler: ErrorHandler,
@@ -74,6 +76,7 @@ class MessagePreviewPresenter @Inject constructor(
}
}
} else {
+ delay(1.seconds)
view?.run {
showMessage(messageNotExists)
popView()
@@ -172,13 +175,51 @@ class MessagePreviewPresenter @Inject constructor(
return true
}
+ private fun restoreMessage() {
+ val message = messageWithAttachments?.message ?: return
+
+ view?.run {
+ showContent(false)
+ showProgress(true)
+ showOptions(
+ show = false,
+ isReplayable = false,
+ isRestorable = false,
+ )
+ showErrorView(false)
+ }
+ Timber.i("Restore message ${message.messageGlobalKey}")
+ presenterScope.launch {
+ runCatching {
+ val student = studentRepository.getCurrentStudent(decryptPass = true)
+ val mailbox = messageRepository.getMailboxByStudent(student)
+ messageRepository.restoreMessages(student, mailbox, listOfNotNull(message))
+ }
+ .onFailure {
+ retryCallback = { onMessageRestore() }
+ errorHandler.dispatch(it)
+ }
+ .onSuccess {
+ view?.run {
+ showMessage(restoreMessageSuccessString)
+ popView()
+ }
+ }
+ view?.showProgress(false)
+ }
+ }
+
private fun deleteMessage() {
messageWithAttachments?.message ?: return
view?.run {
showContent(false)
showProgress(true)
- showOptions(show = false, isReplayable = false)
+ showOptions(
+ show = false,
+ isReplayable = false,
+ isRestorable = false,
+ )
showErrorView(false)
}
@@ -187,8 +228,7 @@ class MessagePreviewPresenter @Inject constructor(
presenterScope.launch {
runCatching {
val student = studentRepository.getCurrentStudent(decryptPass = true)
- val mailbox = messageRepository.getMailboxByStudent(student)
- messageRepository.deleteMessage(student, mailbox, messageWithAttachments?.message!!)
+ messageRepository.deleteMessage(student, messageWithAttachments?.message!!)
}.onFailure {
retryCallback = { onMessageDelete() }
errorHandler.dispatch(it)
@@ -213,6 +253,11 @@ class MessagePreviewPresenter @Inject constructor(
}
}
+ fun onMessageRestore(): Boolean {
+ restoreMessage()
+ return true
+ }
+
fun onMessageDelete(): Boolean {
deleteMessage()
return true
@@ -222,15 +267,9 @@ class MessagePreviewPresenter @Inject constructor(
view?.apply {
showOptions(
show = messageWithAttachments?.message != null,
- isReplayable = messageWithAttachments?.message?.folderId != MessageFolder.SENT.id,
+ isReplayable = messageWithAttachments?.message?.folderId == MessageFolder.RECEIVED.id,
+ isRestorable = messageWithAttachments?.message?.folderId == MessageFolder.TRASHED.id,
)
- messageWithAttachments?.message?.let {
- when (it.folderId == MessageFolder.TRASHED.id) {
- true -> setDeletedOptionsLabels()
- false -> setNotDeletedOptionsLabels()
- }
- }
-
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewView.kt
index cbe1c3cbc..ee0b6ce0a 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewView.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewView.kt
@@ -13,6 +13,8 @@ interface MessagePreviewView : BaseView {
val unmuteMessageSuccessString: String
+ val restoreMessageSuccessString: String
+
val messageNoSubjectString: String
val printHTML: String
@@ -35,11 +37,7 @@ interface MessagePreviewView : BaseView {
fun setErrorRetryCallback(callback: () -> Unit)
- fun showOptions(show: Boolean, isReplayable: Boolean)
-
- fun setDeletedOptionsLabels()
-
- fun setNotDeletedOptionsLabels()
+ fun showOptions(show: Boolean, isReplayable: Boolean, isRestorable: Boolean)
fun openMessageReply(message: Message?)
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessagePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessagePresenter.kt
index e776e9941..6155baea3 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessagePresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessagePresenter.kt
@@ -203,7 +203,7 @@ class SendMessagePresenter @Inject constructor(
subject = subject,
content = content,
recipients = recipients,
- mailboxId = mailbox.globalKey,
+ mailbox = mailbox,
)
}.logResourceStatus("sending message").onEach {
when (it) {
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabFragment.kt
index 4364e8681..12f9d3234 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabFragment.kt
@@ -5,7 +5,9 @@ import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
-import android.view.View.*
+import android.view.View.GONE
+import android.view.View.INVISIBLE
+import android.view.View.VISIBLE
import android.widget.CompoundButton
import androidx.annotation.StringRes
import androidx.appcompat.view.ActionMode
@@ -64,10 +66,12 @@ class MessageTabFragment : BaseFragment(R.layout.frag
}
override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
- if (presenter.folder == MessageFolder.TRASHED) {
- val menuItem = menu.findItem(R.id.messageTabContextMenuDelete)
- menuItem.setTitle(R.string.message_delete_forever)
- }
+ val isTrashFolder = presenter.folder == MessageFolder.TRASHED
+
+ menu.findItem(R.id.messageTabContextMenuDelete).setVisible(!isTrashFolder)
+ menu.findItem(R.id.messageTabContextMenuDeleteForever).setVisible(isTrashFolder)
+ menu.findItem(R.id.messageTabContextMenuRestore).setVisible(isTrashFolder)
+
return presenter.onPrepareActionMode()
}
@@ -79,6 +83,8 @@ class MessageTabFragment : BaseFragment(R.layout.frag
override fun onActionItemClicked(mode: ActionMode, menu: MenuItem): Boolean {
when (menu.itemId) {
R.id.messageTabContextMenuDelete -> presenter.onActionModeSelectDelete()
+ R.id.messageTabContextMenuRestore -> presenter.onActionModeSelectRestore()
+ R.id.messageTabContextMenuDeleteForever -> presenter.onActionModeSelectDelete()
R.id.messageTabContextMenuSelectAll -> presenter.onActionModeSelectCheckAll()
}
return true
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt
index f82837214..cda0b32bd 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt
@@ -121,8 +121,27 @@ class MessageTabPresenter @Inject constructor(
return true
}
+ fun onActionModeSelectRestore() {
+ Timber.i("Restore ${messagesToDelete.size} messages")
+ val messageList = messagesToDelete.toList()
+
+ presenterScope.launch {
+ view?.run {
+ showProgress(true)
+ showContent(false)
+ showActionMode(false)
+ }
+ runCatching {
+ val student = studentRepository.getCurrentStudent(true)
+ messageRepository.restoreMessages(student, selectedMailbox, messageList)
+ }
+ .onFailure(errorHandler::dispatch)
+ .onSuccess { view?.showMessage(R.string.message_messages_restored) }
+ }
+ }
+
fun onActionModeSelectDelete() {
- Timber.i("Delete ${messagesToDelete.size} messages)")
+ Timber.i("Delete ${messagesToDelete.size} messages")
val messageList = messagesToDelete.toList()
presenterScope.launch {
@@ -134,7 +153,7 @@ class MessageTabPresenter @Inject constructor(
runCatching {
val student = studentRepository.getCurrentStudent(true)
- messageRepository.deleteMessages(student, selectedMailbox, messageList)
+ messageRepository.deleteMessages(student, messageList)
}
.onFailure(errorHandler::dispatch)
.onSuccess { view?.showMessage(R.string.message_messages_deleted) }
diff --git a/app/src/main/res/drawable/ic_menu_message_delete_forever.xml b/app/src/main/res/drawable/ic_menu_message_delete_forever.xml
new file mode 100644
index 000000000..a7b5ac53b
--- /dev/null
+++ b/app/src/main/res/drawable/ic_menu_message_delete_forever.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_menu_message_restore.xml b/app/src/main/res/drawable/ic_menu_message_restore.xml
new file mode 100644
index 000000000..5c8544f28
--- /dev/null
+++ b/app/src/main/res/drawable/ic_menu_message_restore.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/menu/action_menu_message_preview.xml b/app/src/main/res/menu/action_menu_message_preview.xml
index 224788674..04af86713 100644
--- a/app/src/main/res/menu/action_menu_message_preview.xml
+++ b/app/src/main/res/menu/action_menu_message_preview.xml
@@ -29,6 +29,13 @@
android:title="@string/message_forward"
app:iconTint="@color/material_on_surface_emphasis_medium"
app:showAsAction="ifRoom" />
+
+
-
Select all
Unselect all
+ Restore from trash
Move to trash
Delete permanently
+ Message restored successfully
Message deleted successfully
student
parent
@@ -364,6 +366,7 @@
- %1$d selected
Messages deleted
+ Messages restored
Choose mailbox
Incognito mode is on
Thanks to incognito mode sender is not notified when you read the message