From 57681b35ea0be4e8b7df011050410445dce126ae Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Wed, 17 Mar 2021 22:58:17 +0000 Subject: [PATCH 001/186] Bump mockk from 1.10.6 to 1.11.0 (#1229) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index b8b6f016..3e56ea24 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -145,7 +145,7 @@ ext { work_hilt = "1.0.0-beta01" room = "2.3.0-beta03" chucker = "3.4.0" - mockk = "1.10.6" + mockk = "1.11.0" moshi = "1.11.0" } From 87e7e00705c25c0d9807207bfc7c9c5e9fd21f74 Mon Sep 17 00:00:00 2001 From: MRmlik12 <44818681+MRmlik12@users.noreply.github.com> Date: Fri, 19 Mar 2021 15:57:12 +0100 Subject: [PATCH 002/186] Remove firebase inappmessage dependency (#1235) --- app/build.gradle | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 3e56ea24..6b36d629 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -208,8 +208,6 @@ dependencies { playImplementation platform('com.google.firebase:firebase-bom:26.7.0') playImplementation 'com.google.firebase:firebase-analytics-ktx' - playImplementation 'com.google.firebase:firebase-inappmessaging-display-ktx' - playImplementation "com.google.firebase:firebase-inappmessaging-ktx" playImplementation 'com.google.firebase:firebase-messaging:' playImplementation 'com.google.firebase:firebase-crashlytics:' playImplementation 'com.google.android.play:core-ktx:1.8.1' From c9dc9a323fc6518ea7168f17fe36494f52db086e Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Fri, 19 Mar 2021 15:02:28 +0000 Subject: [PATCH 003/186] Bump gradle from 4.1.2 to 4.1.3 (#1234) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 8a10f65b..77374fe8 100644 --- a/build.gradle +++ b/build.gradle @@ -13,7 +13,7 @@ buildscript { } dependencies { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath 'com.android.tools.build:gradle:4.1.2' + classpath 'com.android.tools.build:gradle:4.1.3' classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" classpath 'com.google.gms:google-services:4.3.5' classpath 'com.huawei.agconnect:agcp:1.5.0.300' From 5bee155f1ed7b2e898bb1a6a3668b86358b095e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Sat, 20 Mar 2021 13:10:53 +0100 Subject: [PATCH 004/186] Remove listenablefuture from dependencies (#1237) --- app/build.gradle | 1 - 1 file changed, 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 6b36d629..c31ced27 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -211,7 +211,6 @@ dependencies { playImplementation 'com.google.firebase:firebase-messaging:' playImplementation 'com.google.firebase:firebase-crashlytics:' playImplementation 'com.google.android.play:core-ktx:1.8.1' - playImplementation 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava' hmsImplementation 'com.huawei.hms:hianalytics:5.2.0.300' hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.5.0.300' From 15603357499961ae70dcf2753bd46362a4bc501a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Sat, 20 Mar 2021 13:27:47 +0100 Subject: [PATCH 005/186] Fix very rare crash in login recovery (#1231) --- .../login/recover/LoginRecoverFragment.kt | 22 +++++++++++---- .../res/layout/fragment_login_recover.xml | 28 +++++++++++-------- 2 files changed, 33 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverFragment.kt index f0c9652f..2e2f9f5c 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverFragment.kt @@ -9,6 +9,7 @@ import android.view.View.VISIBLE import android.webkit.JavascriptInterface import android.webkit.WebView import android.webkit.WebViewClient +import androidx.core.view.isVisible import androidx.core.widget.doOnTextChanged import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R @@ -42,10 +43,12 @@ class LoginRecoverFragment : private lateinit var hostSymbols: Array override val recoverHostValue: String - get() = hostValues.getOrNull(hostKeys.indexOf(bindingLocal.loginRecoverHost.text.toString())).orEmpty() + get() = hostValues.getOrNull(hostKeys.indexOf(bindingLocal.loginRecoverHost.text.toString())) + .orEmpty() override val formHostSymbol: String - get() = hostSymbols.getOrNull(hostKeys.indexOf(bindingLocal.loginRecoverHost.text.toString())).orEmpty() + get() = hostSymbols.getOrNull(hostKeys.indexOf(bindingLocal.loginRecoverHost.text.toString())) + .orEmpty() override val recoverNameValue: String get() = bindingLocal.loginRecoverName.text.toString().trim() @@ -82,7 +85,9 @@ class LoginRecoverFragment : with(bindingLocal.loginRecoverHost) { setText(hostKeys.getOrNull(0).orEmpty()) - setAdapter(LoginSymbolAdapter(context, R.layout.support_simple_spinner_dropdown_item, hostKeys)) + setAdapter( + LoginSymbolAdapter(context, R.layout.support_simple_spinner_dropdown_item, hostKeys) + ) setOnClickListener { if (bindingLocal.loginRecoverFormContainer.visibility == GONE) dismissDropDown() } } } @@ -127,6 +132,7 @@ class LoginRecoverFragment : override fun showErrorView(show: Boolean) { bindingLocal.loginRecoverError.visibility = if (show) VISIBLE else GONE + bindingLocal.loginRecoverErrorDetails.isVisible = true } override fun setErrorDetails(message: String) { @@ -166,7 +172,7 @@ class LoginRecoverFragment : with(bindingLocal.loginRecoverWebView) { settings.javaScriptEnabled = true webViewClient = object : WebViewClient() { - private var recoverWebViewSuccess: Boolean = true + private var recoverWebViewSuccess = true override fun onPageFinished(view: WebView?, url: String?) { if (recoverWebViewSuccess) { @@ -175,10 +181,16 @@ class LoginRecoverFragment : } else { showProgress(false) showErrorView(true) + bindingLocal.loginRecoverErrorDetails.isVisible = false } } - override fun onReceivedError(view: WebView, errorCode: Int, description: String, failingUrl: String) { + override fun onReceivedError( + view: WebView, + errorCode: Int, + description: String, + failingUrl: String + ) { recoverWebViewSuccess = false } } diff --git a/app/src/main/res/layout/fragment_login_recover.xml b/app/src/main/res/layout/fragment_login_recover.xml index 629ed54d..4277155b 100644 --- a/app/src/main/res/layout/fragment_login_recover.xml +++ b/app/src/main/res/layout/fragment_login_recover.xml @@ -26,7 +26,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingBottom="16dp" - android:visibility="visible"> + android:visibility="gone"> @@ -156,7 +156,7 @@ android:orientation="vertical" android:visibility="invisible" tools:ignore="UseCompoundDrawables" - tools:visibility="invisible"> + tools:visibility="visible"> - + android:layout_marginTop="16dp"> + android:text="@string/all_details" + app:layout_constraintEnd_toStartOf="@id/loginRecoverErrorRetry" + app:layout_constraintHorizontal_chainStyle="packed" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> - + android:text="@string/all_retry" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toEndOf="@id/loginRecoverErrorDetails" + app:layout_constraintTop_toTopOf="parent" /> + + tools:visibility="gone"> - From efafd2094a0d913cd40bef53cc78434a94bcf3a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Sat, 20 Mar 2021 14:01:17 +0100 Subject: [PATCH 006/186] Add dialog with info about dropping support for android 4 (#1221) --- .../repositories/PreferencesRepository.kt | 5 +++ .../ui/modules/splash/SplashActivity.kt | 16 +++++++++ .../ui/modules/splash/SplashPresenter.kt | 35 ++++++++++++++++--- .../wulkanowy/ui/modules/splash/SplashView.kt | 2 ++ app/src/main/res/values/strings.xml | 7 ++++ .../ui/modules/splash/SplashPresenterTest.kt | 17 ++++++++- 6 files changed, 76 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/PreferencesRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/PreferencesRepository.kt index 11adbd0f..5bd1f3c1 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/PreferencesRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/PreferencesRepository.kt @@ -2,6 +2,7 @@ package io.github.wulkanowy.data.repositories import android.content.Context import android.content.SharedPreferences +import androidx.core.content.edit import dagger.hilt.android.qualifiers.ApplicationContext import io.github.wulkanowy.R import io.github.wulkanowy.ui.modules.grade.GradeAverageMode @@ -145,6 +146,10 @@ class PreferencesRepository @Inject constructor( R.bool.pref_default_subjects_without_grades ) + var isKitkatDialogDisabled: Boolean + get() = sharedPref.getBoolean("kitkat_dialog_disabled", false) + set(value) = sharedPref.edit { putBoolean("kitkat_dialog_disabled", value) } + private fun getString(id: Int, default: Int) = getString(context.getString(id), default) private fun getString(id: String, default: Int) = diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/splash/SplashActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/splash/SplashActivity.kt index 80138175..2b96a9a0 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/splash/SplashActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/splash/SplashActivity.kt @@ -3,17 +3,23 @@ package io.github.wulkanowy.ui.modules.splash import android.os.Bundle import android.widget.Toast import android.widget.Toast.LENGTH_LONG +import androidx.appcompat.app.AlertDialog import androidx.viewbinding.ViewBinding import dagger.hilt.android.AndroidEntryPoint +import io.github.wulkanowy.R import io.github.wulkanowy.ui.base.BaseActivity import io.github.wulkanowy.ui.modules.login.LoginActivity import io.github.wulkanowy.ui.modules.main.MainActivity +import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.openInternetBrowser import javax.inject.Inject @AndroidEntryPoint class SplashActivity : BaseActivity(), SplashView { + @Inject + lateinit var appInfo: AppInfo + @Inject override lateinit var presenter: SplashPresenter @@ -40,4 +46,14 @@ class SplashActivity : BaseActivity(), SplashView override fun showError(text: String, error: Throwable) { Toast.makeText(this, text, LENGTH_LONG).show() } + + override fun showKitkatView() { + AlertDialog.Builder(this) + .setTitle(R.string.drop_kitkat_title) + .setMessage(R.string.drop_kitkat_content) + .setPositiveButton(android.R.string.ok, null) + .setNeutralButton(R.string.drop_kitkat_again) { _, _ -> presenter.onNeutralButtonSelected() } + .setOnDismissListener { presenter.onKitkatViewDismissed() } + .show() + } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/splash/SplashPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/splash/SplashPresenter.kt index 79588917..4590d91a 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/splash/SplashPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/splash/SplashPresenter.kt @@ -1,9 +1,12 @@ package io.github.wulkanowy.ui.modules.splash +import android.os.Build import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler +import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.flowWithResource import kotlinx.coroutines.flow.onEach import timber.log.Timber @@ -11,25 +14,47 @@ import javax.inject.Inject class SplashPresenter @Inject constructor( errorHandler: ErrorHandler, - studentRepository: StudentRepository + studentRepository: StudentRepository, + private val preferencesRepository: PreferencesRepository, + private val appInfo: AppInfo ) : BasePresenter(errorHandler, studentRepository) { + private var externalUrl: String? = null + fun onAttachView(view: SplashView, externalUrl: String?) { super.onAttachView(view) + this.externalUrl = externalUrl + if (appInfo.systemVersion < Build.VERSION_CODES.LOLLIPOP && !preferencesRepository.isKitkatDialogDisabled) { + view.showKitkatView() + } else { + loadCorrectDataOrUser() + } + } + + private fun loadCorrectDataOrUser() { if (!externalUrl.isNullOrBlank()) { - return view.openExternalUrlAndFinish(externalUrl) + view?.openExternalUrlAndFinish(externalUrl!!) + return } flowWithResource { studentRepository.isCurrentStudentSet() }.onEach { when (it.status) { Status.LOADING -> Timber.d("Is current user set check started") - Status.SUCCESS -> with(view) { - if (it.data!!) openMainView() - else openLoginView() + Status.SUCCESS -> { + if (it.data!!) view?.openMainView() + else view?.openLoginView() } Status.ERROR -> errorHandler.dispatch(it.error!!) } }.launch() } + + fun onKitkatViewDismissed() { + loadCorrectDataOrUser() + } + + fun onNeutralButtonSelected() { + preferencesRepository.isKitkatDialogDisabled = true + } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/splash/SplashView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/splash/SplashView.kt index a5aa1409..c9c5fe9a 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/splash/SplashView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/splash/SplashView.kt @@ -9,4 +9,6 @@ interface SplashView : BaseView { fun openMainView() fun openExternalUrlAndFinish(url: String) + + fun showKitkatView() } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index db5ed7b4..c4109d2b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -541,6 +541,12 @@ Debug + + End of support + We are ending support for your device. No more new features will appear for it in Wulkanowy. However, we will be releasing critical patches until the end of 2021 so you have time to switch to a newer model + Don\'t show again + + Black Red @@ -554,6 +560,7 @@ Copied Undo + Download of updates has started… An update has just been downloaded. diff --git a/app/src/test/java/io/github/wulkanowy/ui/modules/splash/SplashPresenterTest.kt b/app/src/test/java/io/github/wulkanowy/ui/modules/splash/SplashPresenterTest.kt index 71d17b3b..48c2aa9c 100644 --- a/app/src/test/java/io/github/wulkanowy/ui/modules/splash/SplashPresenterTest.kt +++ b/app/src/test/java/io/github/wulkanowy/ui/modules/splash/SplashPresenterTest.kt @@ -1,10 +1,13 @@ package io.github.wulkanowy.ui.modules.splash import io.github.wulkanowy.MainCoroutineRule +import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.ErrorHandler +import io.github.wulkanowy.utils.AppInfo import io.mockk.MockKAnnotations import io.mockk.coEvery +import io.mockk.every import io.mockk.impl.annotations.MockK import io.mockk.verify import org.junit.Before @@ -22,6 +25,12 @@ class SplashPresenterTest { @MockK lateinit var studentRepository: StudentRepository + @MockK + lateinit var preferencesRepository: PreferencesRepository + + @MockK + lateinit var appInfo: AppInfo + @MockK(relaxed = true) lateinit var errorHandler: ErrorHandler @@ -30,19 +39,25 @@ class SplashPresenterTest { @Before fun setUp() { MockKAnnotations.init(this) - presenter = SplashPresenter(errorHandler, studentRepository) + presenter = SplashPresenter(errorHandler, studentRepository, preferencesRepository, appInfo) } @Test fun testOpenLoginView() { + every { appInfo.systemVersion } returns 30 + every { preferencesRepository.isKitkatDialogDisabled } returns true coEvery { studentRepository.isCurrentStudentSet() } returns false + presenter.onAttachView(splashView, null) verify { splashView.openLoginView() } } @Test fun testMainMainView() { + every { appInfo.systemVersion } returns 30 + every { preferencesRepository.isKitkatDialogDisabled } returns true coEvery { studentRepository.isCurrentStudentSet() } returns true + presenter.onAttachView(splashView, null) verify { splashView.openMainView() } } From e03b0dfa0150505da6c67c9bc4917ed292719e66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Sun, 21 Mar 2021 12:04:55 +0100 Subject: [PATCH 007/186] Fix missing avatars in widgets (#1238) --- .../ui/base/WidgetConfigureAdapter.kt | 29 ++++-- .../TimetableWidgetProvider.kt | 93 ++++++++++++++++--- 2 files changed, 101 insertions(+), 21 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/WidgetConfigureAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/base/WidgetConfigureAdapter.kt index 8e6130fb..99440f83 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/WidgetConfigureAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/WidgetConfigureAdapter.kt @@ -1,18 +1,20 @@ package io.github.wulkanowy.ui.base import android.annotation.SuppressLint -import android.graphics.PorterDuff import android.view.LayoutInflater 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.Student import io.github.wulkanowy.databinding.ItemAccountBinding +import io.github.wulkanowy.utils.createNameInitialsDrawable import io.github.wulkanowy.utils.getThemeAttrColor import io.github.wulkanowy.utils.nickOrName import javax.inject.Inject -class WidgetConfigureAdapter @Inject constructor() : RecyclerView.Adapter() { +class WidgetConfigureAdapter @Inject constructor() : + RecyclerView.Adapter() { var items = emptyList>() @@ -27,16 +29,31 @@ class WidgetConfigureAdapter @Inject constructor() : RecyclerView.Adapter 1 with(holder.binding) { accountItemName.text = "${student.nickOrName} ${student.className}" accountItemSchool.text = student.schoolName + accountItemImage.setImageDrawable(avatar) - with(accountItemImage) { - val colorImage = if (isCurrent) context.getThemeAttrColor(R.attr.colorPrimary) - else context.getThemeAttrColor(R.attr.colorOnSurface, 153) + with(accountItemAccountType) { + setText(if (student.isParent) R.string.account_type_parent else R.string.account_type_student) + isVisible = isDuplicatedStudent + } - setColorFilter(colorImage, PorterDuff.Mode.SRC_IN) + with(accountItemCheck) { + isVisible = isCurrent + borderColor = checkBackgroundColor + circleColor = checkBackgroundColor } root.setOnClickListener { onClickListener(student) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetProvider.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetProvider.kt index 1d63f094..071a2d17 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetProvider.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetProvider.kt @@ -13,6 +13,8 @@ import android.content.Intent import android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK import android.content.Intent.FLAG_ACTIVITY_NEW_TASK import android.content.res.Configuration +import android.graphics.Bitmap +import android.graphics.Canvas import android.widget.RemoteViews import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R @@ -25,6 +27,8 @@ import io.github.wulkanowy.services.widgets.TimetableWidgetService import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.utils.AnalyticsHelper +import io.github.wulkanowy.utils.createNameInitialsDrawable +import io.github.wulkanowy.utils.getCompatColor import io.github.wulkanowy.utils.nextOrSameSchoolDay import io.github.wulkanowy.utils.nextSchoolDay import io.github.wulkanowy.utils.nickOrName @@ -72,7 +76,8 @@ class TimetableWidgetProvider : HiltBroadcastReceiver() { fun getThemeWidgetKey(appWidgetId: Int) = "timetable_widget_theme_$appWidgetId" - fun getCurrentThemeWidgetKey(appWidgetId: Int) = "timetable_widget_current_theme_$appWidgetId" + fun getCurrentThemeWidgetKey(appWidgetId: Int) = + "timetable_widget_current_theme_$appWidgetId" } override fun onReceive(context: Context, intent: Intent) { @@ -88,21 +93,29 @@ class TimetableWidgetProvider : HiltBroadcastReceiver() { private suspend fun onUpdate(context: Context, intent: Intent) { if (intent.getStringExtra(EXTRA_BUTTON_TYPE) === null) { intent.getIntArrayExtra(EXTRA_APPWIDGET_IDS)?.forEach { appWidgetId -> - val student = getStudent(sharedPref.getLong(getStudentWidgetKey(appWidgetId), 0), appWidgetId) + val student = + getStudent(sharedPref.getLong(getStudentWidgetKey(appWidgetId), 0), appWidgetId) updateWidget(context, appWidgetId, now().nextOrSameSchoolDay, student) } } else { val buttonType = intent.getStringExtra(EXTRA_BUTTON_TYPE) val toggledWidgetId = intent.getIntExtra(EXTRA_TOGGLED_WIDGET_ID, 0) - val student = getStudent(sharedPref.getLong(getStudentWidgetKey(toggledWidgetId), 0), toggledWidgetId) - val savedDate = LocalDate.ofEpochDay(sharedPref.getLong(getDateWidgetKey(toggledWidgetId), 0)) + val student = getStudent( + sharedPref.getLong(getStudentWidgetKey(toggledWidgetId), 0), + toggledWidgetId + ) + val savedDate = + LocalDate.ofEpochDay(sharedPref.getLong(getDateWidgetKey(toggledWidgetId), 0)) val date = when (buttonType) { BUTTON_RESET -> now().nextOrSameSchoolDay BUTTON_NEXT -> savedDate.nextSchoolDay BUTTON_PREV -> savedDate.previousSchoolDay else -> now().nextOrSameSchoolDay } - if (!buttonType.isNullOrBlank()) analytics.logEvent("changed_timetable_widget_day", "button" to buttonType) + if (!buttonType.isNullOrBlank()) analytics.logEvent( + "changed_timetable_widget_day", + "button" to buttonType + ) updateWidget(context, toggledWidgetId, date, student) } } @@ -121,9 +134,15 @@ class TimetableWidgetProvider : HiltBroadcastReceiver() { } @SuppressLint("DefaultLocale") - private fun updateWidget(context: Context, appWidgetId: Int, date: LocalDate, student: Student?) { + private fun updateWidget( + context: Context, + appWidgetId: Int, + date: LocalDate, + student: Student? + ) { val savedConfigureTheme = sharedPref.getLong(getThemeWidgetKey(appWidgetId), 0) - val isSystemDarkMode = context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES + val isSystemDarkMode = + context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES var currentTheme = 0L var layoutId = R.layout.widget_timetable @@ -134,21 +153,28 @@ class TimetableWidgetProvider : HiltBroadcastReceiver() { val nextNavIntent = createNavIntent(context, appWidgetId, appWidgetId, BUTTON_NEXT) val prevNavIntent = createNavIntent(context, -appWidgetId, appWidgetId, BUTTON_PREV) - val resetNavIntent = createNavIntent(context, Int.MAX_VALUE - appWidgetId, appWidgetId, BUTTON_RESET) + val resetNavIntent = + createNavIntent(context, Int.MAX_VALUE - appWidgetId, appWidgetId, BUTTON_RESET) val adapterIntent = Intent(context, TimetableWidgetService::class.java) .apply { putExtra(EXTRA_APPWIDGET_ID, appWidgetId) //make Intent unique action = appWidgetId.toString() } - val accountIntent = PendingIntent.getActivity(context, -Int.MAX_VALUE + appWidgetId, + val accountIntent = PendingIntent.getActivity( + context, -Int.MAX_VALUE + appWidgetId, Intent(context, TimetableWidgetConfigureActivity::class.java).apply { addFlags(FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TASK) putExtra(EXTRA_APPWIDGET_ID, appWidgetId) putExtra(EXTRA_FROM_PROVIDER, true) - }, FLAG_UPDATE_CURRENT) - val appIntent = PendingIntent.getActivity(context, MainView.Section.TIMETABLE.id, - MainActivity.getStartIntent(context, MainView.Section.TIMETABLE, true), FLAG_UPDATE_CURRENT) + }, FLAG_UPDATE_CURRENT + ) + val appIntent = PendingIntent.getActivity( + context, + MainView.Section.TIMETABLE.id, + MainActivity.getStartIntent(context, MainView.Section.TIMETABLE, true), + FLAG_UPDATE_CURRENT + ) val remoteView = RemoteViews(context.packageName, layoutId).apply { setEmptyView(R.id.timetableWidgetList, R.id.timetableWidgetEmpty) @@ -160,6 +186,11 @@ class TimetableWidgetProvider : HiltBroadcastReceiver() { R.id.timetableWidgetName, student?.nickOrName ?: context.getString(R.string.all_no_data) ) + + student?.let { + setImageViewBitmap(R.id.timetableWidgetAccount, context.createAvatarBitmap(it)) + } + setRemoteAdapter(R.id.timetableWidgetList, adapterIntent) setOnClickPendingIntent(R.id.timetableWidgetNext, nextNavIntent) setOnClickPendingIntent(R.id.timetableWidgetPrev, prevNavIntent) @@ -181,13 +212,20 @@ class TimetableWidgetProvider : HiltBroadcastReceiver() { } } - private fun createNavIntent(context: Context, code: Int, appWidgetId: Int, buttonType: String): PendingIntent { - return PendingIntent.getBroadcast(context, code, + private fun createNavIntent( + context: Context, + code: Int, + appWidgetId: Int, + buttonType: String + ): PendingIntent { + return PendingIntent.getBroadcast( + context, code, Intent(context, TimetableWidgetProvider::class.java).apply { action = ACTION_APPWIDGET_UPDATE putExtra(EXTRA_BUTTON_TYPE, buttonType) putExtra(EXTRA_TOGGLED_WIDGET_ID, appWidgetId) - }, FLAG_UPDATE_CURRENT) + }, FLAG_UPDATE_CURRENT + ) } private suspend fun getStudent(studentId: Long, appWidgetId: Int) = try { @@ -208,4 +246,29 @@ class TimetableWidgetProvider : HiltBroadcastReceiver() { } null } + + private fun Context.createAvatarBitmap(student: Student): Bitmap { + val avatarColor = if (student.avatarColor == -2937041L) { + getCompatColor(R.color.colorPrimaryLight).toLong() + } else { + student.avatarColor + } + val avatarDrawable = createNameInitialsDrawable(student.nickOrName, avatarColor, 0.5f) + + val avatarBitmap = + if (avatarDrawable.intrinsicWidth <= 0 || avatarDrawable.intrinsicHeight <= 0) { + Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888) + } else { + Bitmap.createBitmap( + avatarDrawable.intrinsicWidth, + avatarDrawable.intrinsicHeight, + Bitmap.Config.ARGB_8888 + ) + } + + val canvas = Canvas(avatarBitmap) + avatarDrawable.setBounds(0, 0, canvas.width, canvas.height) + avatarDrawable.draw(canvas) + return avatarBitmap + } } From 8733e7782f5d87e8ae8525cb51644d04ac3a68ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Sun, 21 Mar 2021 22:37:34 +0100 Subject: [PATCH 008/186] Fix colorPrimary and class name in widget account manager (#1241) --- .../ui/base/WidgetConfigureAdapter.kt | 14 +++++++++----- .../ui/modules/account/AccountAdapter.kt | 7 +++---- .../LuckyNumberWidgetConfigureActivity.kt | 9 ++++++--- .../LuckyNumberWidgetConfigurePresenter.kt | 9 +++++---- .../LuckyNumberWidgetConfigureView.kt | 4 ++-- .../TimetableWidgetConfigureActivity.kt | 18 +++++++++++++----- .../TimetableWidgetConfigurePresenter.kt | 15 ++++++++++----- .../TimetableWidgetConfigureView.kt | 4 ++-- app/src/main/res/values-night/styles.xml | 8 ++++++++ app/src/main/res/values/styles.xml | 2 +- 10 files changed, 59 insertions(+), 31 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/WidgetConfigureAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/base/WidgetConfigureAdapter.kt index 99440f83..a43aaffb 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/WidgetConfigureAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/WidgetConfigureAdapter.kt @@ -7,6 +7,7 @@ import androidx.core.view.isVisible import androidx.recyclerview.widget.RecyclerView import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.databinding.ItemAccountBinding import io.github.wulkanowy.utils.createNameInitialsDrawable import io.github.wulkanowy.utils.getThemeAttrColor @@ -16,7 +17,9 @@ import javax.inject.Inject class WidgetConfigureAdapter @Inject constructor() : RecyclerView.Adapter() { - var items = emptyList>() + var items = emptyList() + + var selectedId = -1L var onClickListener: (Student) -> Unit = {} @@ -28,12 +31,13 @@ class WidgetConfigureAdapter @Inject constructor() : @SuppressLint("SetTextI18n") override fun onBindViewHolder(holder: ItemViewHolder, position: Int) { - val (student, isCurrent) = items[position] + val (student, semesters) = items[position] + val semester = semesters.maxByOrNull { it.semesterId } val context = holder.binding.root.context val checkBackgroundColor = context.getThemeAttrColor(R.attr.colorSurface) val avatar = context.createNameInitialsDrawable(student.nickOrName, student.avatarColor) val isDuplicatedStudent = items.filter { - val studentToCompare = it.first + val studentToCompare = it.student studentToCompare.studentId == student.studentId && studentToCompare.schoolSymbol == student.schoolSymbol @@ -41,7 +45,7 @@ class WidgetConfigureAdapter @Inject constructor() : }.size > 1 with(holder.binding) { - accountItemName.text = "${student.nickOrName} ${student.className}" + accountItemName.text = "${student.nickOrName} ${semester?.diaryName.orEmpty()}" accountItemSchool.text = student.schoolName accountItemImage.setImageDrawable(avatar) @@ -51,7 +55,7 @@ class WidgetConfigureAdapter @Inject constructor() : } with(accountItemCheck) { - isVisible = isCurrent + isVisible = student.id == selectedId borderColor = checkBackgroundColor circleColor = checkBackgroundColor } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountAdapter.kt index 227f0661..523fbbd7 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountAdapter.kt @@ -74,9 +74,8 @@ class AccountAdapter @Inject constructor() : RecyclerView.Adapter 1 && isAccountQuickDialogMode with(binding) { - accountItemName.text = "${student.nickOrName} ${diary?.diaryName.orEmpty()}" + accountItemName.text = "${student.nickOrName} ${semester?.diaryName.orEmpty()}" accountItemSchool.text = studentWithSemesters.student.schoolName accountItemImage.setImageDrawable(avatar) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigureActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigureActivity.kt index 692615d9..024beff8 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigureActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigureActivity.kt @@ -11,7 +11,7 @@ import androidx.appcompat.app.AlertDialog import androidx.recyclerview.widget.LinearLayoutManager import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R -import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.databinding.ActivityWidgetConfigureBinding import io.github.wulkanowy.ui.base.BaseActivity import io.github.wulkanowy.ui.base.WidgetConfigureAdapter @@ -38,7 +38,9 @@ class LuckyNumberWidgetConfigureActivity : override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setResult(RESULT_CANCELED) - setContentView(ActivityWidgetConfigureBinding.inflate(layoutInflater).apply { binding = this }.root) + setContentView( + ActivityWidgetConfigureBinding.inflate(layoutInflater).apply { binding = this }.root + ) intent.extras.let { presenter.onAttachView(this, it?.getInt(EXTRA_APPWIDGET_ID)) @@ -70,8 +72,9 @@ class LuckyNumberWidgetConfigureActivity : .show() } - override fun updateData(data: List>) { + override fun updateData(data: List, selectedStudentId: Long) { with(configureAdapter) { + selectedId = selectedStudentId items = data notifyDataSetChanged() } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigurePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigurePresenter.kt index f4041e9e..5b6af69a 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigurePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigurePresenter.kt @@ -51,16 +51,17 @@ class LuckyNumberWidgetConfigurePresenter @Inject constructor( when (it.status) { Status.LOADING -> Timber.d("Lucky number widget configure students data load") Status.SUCCESS -> { - val widgetId = appWidgetId?.let { id -> sharedPref.getLong(getStudentWidgetKey(id), 0) } + val selectedStudentId = appWidgetId?.let { id -> + sharedPref.getLong(getStudentWidgetKey(id), 0) + } ?: -1 + when { it.data!!.isEmpty() -> view?.openLoginView() it.data.size == 1 -> { selectedStudent = it.data.single().student view?.showThemeDialog() } - else -> view?.updateData(it.data.map { entity -> - entity.student to (entity.student.id == widgetId) - }) + else -> view?.updateData(it.data, selectedStudentId) } } Status.ERROR -> errorHandler.dispatch(it.error!!) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigureView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigureView.kt index c8c348ed..b4556f7e 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigureView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigureView.kt @@ -1,6 +1,6 @@ package io.github.wulkanowy.ui.modules.luckynumberwidget -import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.ui.base.BaseView interface LuckyNumberWidgetConfigureView : BaseView { @@ -9,7 +9,7 @@ interface LuckyNumberWidgetConfigureView : BaseView { fun showThemeDialog() - fun updateData(data: List>) + fun updateData(data: List, selectedStudentId: Long) fun updateLuckyNumberWidget(widgetId: Int) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigureActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigureActivity.kt index 23d1f27a..a27dba88 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigureActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigureActivity.kt @@ -12,7 +12,7 @@ import androidx.appcompat.app.AlertDialog import androidx.recyclerview.widget.LinearLayoutManager import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R -import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.databinding.ActivityWidgetConfigureBinding import io.github.wulkanowy.ui.base.BaseActivity import io.github.wulkanowy.ui.base.WidgetConfigureAdapter @@ -37,13 +37,19 @@ class TimetableWidgetConfigureActivity : private var dialog: AlertDialog? = null - override public fun onCreate(savedInstanceState: Bundle?) { + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setResult(RESULT_CANCELED) - setContentView(ActivityWidgetConfigureBinding.inflate(layoutInflater).apply { binding = this }.root) + setContentView( + ActivityWidgetConfigureBinding.inflate(layoutInflater).apply { binding = this }.root + ) intent.extras.let { - presenter.onAttachView(this, it?.getInt(EXTRA_APPWIDGET_ID), it?.getBoolean(EXTRA_FROM_PROVIDER)) + presenter.onAttachView( + this, + it?.getInt(EXTRA_APPWIDGET_ID), + it?.getBoolean(EXTRA_FROM_PROVIDER) + ) } } @@ -61,6 +67,7 @@ class TimetableWidgetConfigureActivity : getString(R.string.widget_timetable_theme_light), getString(R.string.widget_timetable_theme_dark) ) + if (appInfo.systemVersion >= Build.VERSION_CODES.Q) items += getString(R.string.widget_timetable_theme_system) dialog = AlertDialog.Builder(this, R.style.WulkanowyTheme_WidgetAccountSwitcher) @@ -72,8 +79,9 @@ class TimetableWidgetConfigureActivity : .show() } - override fun updateData(data: List>) { + override fun updateData(data: List, selectedStudentId: Long) { with(configureAdapter) { + selectedId = selectedStudentId items = data notifyDataSetChanged() } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigurePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigurePresenter.kt index 67805fe0..2a40c8e4 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigurePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigurePresenter.kt @@ -25,7 +25,11 @@ class TimetableWidgetConfigurePresenter @Inject constructor( private var selectedStudent: Student? = null - fun onAttachView(view: TimetableWidgetConfigureView, appWidgetId: Int?, isFromProvider: Boolean?) { + fun onAttachView( + view: TimetableWidgetConfigureView, + appWidgetId: Int?, + isFromProvider: Boolean? + ) { super.onAttachView(view) this.appWidgetId = appWidgetId this.isFromProvider = isFromProvider ?: false @@ -56,16 +60,17 @@ class TimetableWidgetConfigurePresenter @Inject constructor( when (it.status) { Status.LOADING -> Timber.d("Timetable widget configure students data load") Status.SUCCESS -> { - val widgetId = appWidgetId?.let { id -> sharedPref.getLong(getStudentWidgetKey(id), 0) } + val selectedStudentId = appWidgetId?.let { id -> + sharedPref.getLong(getStudentWidgetKey(id), 0) + } ?: -1 + when { it.data!!.isEmpty() -> view?.openLoginView() it.data.size == 1 && !isFromProvider -> { selectedStudent = it.data.single().student view?.showThemeDialog() } - else -> view?.updateData(it.data.map { entity -> - entity.student to (entity.student.id == widgetId) - }) + else -> view?.updateData(it.data, selectedStudentId) } } Status.ERROR -> errorHandler.dispatch(it.error!!) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigureView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigureView.kt index 056225ab..accdc28d 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigureView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigureView.kt @@ -1,13 +1,13 @@ package io.github.wulkanowy.ui.modules.timetablewidget -import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.ui.base.BaseView interface TimetableWidgetConfigureView : BaseView { fun initView() - fun updateData(data: List>) + fun updateData(data: List, selectedStudentId: Long) fun updateTimetableWidget(widgetId: Int) diff --git a/app/src/main/res/values-night/styles.xml b/app/src/main/res/values-night/styles.xml index b218fbc8..68a9fa2f 100644 --- a/app/src/main/res/values-night/styles.xml +++ b/app/src/main/res/values-night/styles.xml @@ -49,4 +49,12 @@ ?colorSurface @android:color/black + + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index fc7ffb77..297a4f28 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -26,7 +26,7 @@ @drawable/layer_splash_background - + + - - - \ No newline at end of file diff --git a/app/src/main/res/values/preferences_values.xml b/app/src/main/res/values/preferences_values.xml index 44f54c17..2d2eedb4 100644 --- a/app/src/main/res/values/preferences_values.xml +++ b/app/src/main/res/values/preferences_values.xml @@ -109,15 +109,4 @@ both_semesters all_year - - - Don\'t show - Show all - Show smaller - - - no - yes - small - diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1b7c9e42..3d54c16a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -40,7 +40,6 @@ Hybrid Token PIN - API key Symbol Sign in Password too short @@ -66,7 +65,6 @@ Email Discord Send email - Describe details of problem: Zgłoszenie: Problemy z logowaniem Informacje o aplikacji:\n\nUrządzenie: %1$s\nWersja SDK: %2$s\nWersja aplikacji: %3$s\nDodatkowe informacje: %4$s\nOstatni błąd: %5$s\n\nOpis problemu: Make sure you select the correct UONET+ register variation! @@ -91,13 +89,10 @@ Weight Weight: %s Comment - No new grades Number of new ratings: %1$d Average: %1$.2f Points: %s No average - Predicted: %1$s - Final: %1$s Total points Final grade Predicted grade @@ -188,10 +183,6 @@ Unknown Number of lesson No entries - - %1$d absence - %1$d absences - Absence reason (optional) Send Absence excuse request sent successfully! @@ -200,7 +191,6 @@ - Attendance Total @@ -216,7 +206,6 @@ Trash (no subject) No messages - An error occurred while downloading message content From: To: Date: %s @@ -357,8 +346,6 @@ Student logout Student account Parent account - Mobile API mode - Hybrid mode Edit data Accounts manager Select student @@ -479,7 +466,6 @@ Mark current lesson Show groups next to subjects Show chart list in class grades - Show whole class lessons Show subjects without grades Grades color scheme Subjects sorting @@ -532,7 +518,6 @@ - New entries in register New grades Lucky number New messages diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 08867b79..8fb07e1a 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -11,6 +11,7 @@ @color/colorError @color/colorDivider @color/colorSwipeRefresh + @android:color/darker_gray ?android:textColorPrimary @style/PreferenceThemeOverlay false @@ -24,6 +25,8 @@ + + + + From ec6d18968f8d7c996314c8a1f1c3db7dcba5b4c4 Mon Sep 17 00:00:00 2001 From: Mateusz Idziejczak Date: Sat, 7 Aug 2021 10:27:51 +0200 Subject: [PATCH 156/186] Fix filter search bug (#1427) * Fix filter search bug * refractor --- .../wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 cf181182..93c7408f 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 @@ -185,7 +185,9 @@ class MessageTabPresenter @Inject constructor( .debounce(250) .map { query -> lastSearchQuery = query - getFilteredData(query) + val isOnlyUnread = view?.onlyUnread == true + val isOnlyWithAttachments = view?.onlyWithAttachments == true + getFilteredData(query, isOnlyUnread, isOnlyWithAttachments) } .catch { Timber.e(it) } .collect { From 2a913461551c57919bb0a797067d106a13777fcf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Aug 2021 13:53:54 +0000 Subject: [PATCH 157/186] Bump activity-ktx from 1.3.0 to 1.3.1 (#1434) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 0b279ca4..dd6f3e5a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -172,7 +172,7 @@ dependencies { implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.1" implementation "androidx.core:core-ktx:1.6.0" - implementation "androidx.activity:activity-ktx:1.3.0" + implementation "androidx.activity:activity-ktx:1.3.1" implementation "androidx.appcompat:appcompat:1.3.1" implementation "androidx.appcompat:appcompat-resources:1.3.1" implementation "androidx.fragment:fragment-ktx:1.3.6" From 7c94837af01d56a737b66235454740e00d7af509 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Aug 2021 13:54:31 +0000 Subject: [PATCH 158/186] Bump coil from 1.3.1 to 1.3.2 (#1433) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index dd6f3e5a..9783c14d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -214,7 +214,7 @@ dependencies { implementation "at.favre.lib:slf4j-timber:1.0.1" implementation 'com.github.bastienpaulfr:Treessence:1.0.4' implementation "com.mikepenz:aboutlibraries-core:$about_libraries" - implementation "io.coil-kt:coil:1.3.1" + implementation "io.coil-kt:coil:1.3.2" implementation "io.github.wulkanowy:AppKillerManager:3.0.0" implementation 'me.xdrop:fuzzywuzzy:1.3.1' implementation 'com.fredporciuncula:flow-preferences:1.5.0' From 4ae3f7b0163357f48057eeba26e84a38523f5e86 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Aug 2021 13:54:48 +0000 Subject: [PATCH 159/186] Bump google-services from 4.3.8 to 4.3.9 (#1432) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 8f70f811..23c0276f 100644 --- a/build.gradle +++ b/build.gradle @@ -14,7 +14,7 @@ buildscript { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath 'com.android.tools.build:gradle:7.0.0' classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" - classpath 'com.google.gms:google-services:4.3.8' + classpath 'com.google.gms:google-services:4.3.9' classpath 'com.huawei.agconnect:agcp:1.6.0.300' classpath 'com.google.firebase:firebase-crashlytics-gradle:2.7.1' classpath "com.github.triplet.gradle:play-publisher:2.8.0" From 72ef5f428ea17a63d6fc3ba161a55cfcd09c0aa3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Aug 2021 13:55:03 +0000 Subject: [PATCH 160/186] Bump firebase-bom from 28.3.0 to 28.3.1 (#1431) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 9783c14d..05d1a150 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -219,7 +219,7 @@ dependencies { implementation 'me.xdrop:fuzzywuzzy:1.3.1' implementation 'com.fredporciuncula:flow-preferences:1.5.0' - playImplementation platform('com.google.firebase:firebase-bom:28.3.0') + playImplementation platform('com.google.firebase:firebase-bom:28.3.1') playImplementation 'com.google.firebase:firebase-analytics-ktx' playImplementation 'com.google.firebase:firebase-messaging:' playImplementation 'com.google.firebase:firebase-crashlytics:' From 626169de115072abb1612a04b3544a03796de43f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Tue, 10 Aug 2021 11:55:51 +0200 Subject: [PATCH 161/186] Add drag and drop to dashboard tiles (#1415) --- .../repositories/PreferencesRepository.kt | 30 +++++++++- .../ui/modules/dashboard/DashboardAdapter.kt | 57 +++++++++++++------ .../ui/modules/dashboard/DashboardFragment.kt | 9 +++ .../dashboard/DashboardItemMoveCallback.kt | 55 ++++++++++++++++++ .../modules/dashboard/DashboardPresenter.kt | 18 +++++- .../item_dashboard_horizontal_group.xml | 5 +- .../res/layout/subitem_dashboard_grades.xml | 13 ++++- app/src/main/res/values/strings.xml | 2 +- 8 files changed, 162 insertions(+), 27 deletions(-) create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardItemMoveCallback.kt diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/PreferencesRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/PreferencesRepository.kt index 5b97b65d..1e6cd511 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/PreferencesRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/PreferencesRepository.kt @@ -2,9 +2,12 @@ package io.github.wulkanowy.data.repositories import android.content.Context import android.content.SharedPreferences -import com.squareup.moshi.Moshi +import androidx.core.content.edit import com.fredporciuncula.flow.preferences.FlowSharedPreferences import com.fredporciuncula.flow.preferences.Preference +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.Moshi +import com.squareup.moshi.adapter import dagger.hilt.android.qualifiers.ApplicationContext import io.github.wulkanowy.R import io.github.wulkanowy.ui.modules.dashboard.DashboardItem @@ -21,8 +24,13 @@ import javax.inject.Singleton class PreferencesRepository @Inject constructor( private val sharedPref: SharedPreferences, private val flowSharedPref: FlowSharedPreferences, - @ApplicationContext val context: Context + @ApplicationContext val context: Context, + moshi: Moshi ) { + @OptIn(ExperimentalStdlibApi::class) + private val dashboardItemsPositionAdapter: JsonAdapter> = + moshi.adapter() + val startMenuIndex: Int get() = getString(R.string.pref_key_start_menu, R.string.pref_default_startup).toInt() @@ -160,6 +168,19 @@ class PreferencesRepository @Inject constructor( R.bool.pref_default_optional_arithmetic_average ) + var dashboardItemsPosition: Map? + get() { + val json = sharedPref.getString(PREF_KEY_DASHBOARD_ITEMS_POSITION, null) ?: return null + + return dashboardItemsPositionAdapter.fromJson(json) + } + set(value) = sharedPref.edit { + putString( + PREF_KEY_DASHBOARD_ITEMS_POSITION, + dashboardItemsPositionAdapter.toJson(value) + ) + } + val selectedDashboardTilesFlow: Flow> get() = selectedDashboardTilesPreference.asFlow() .map { set -> @@ -199,4 +220,9 @@ class PreferencesRepository @Inject constructor( private fun getBoolean(id: String, default: Int) = sharedPref.getBoolean(id, context.resources.getBoolean(default)) + + private companion object { + + private const val PREF_KEY_DASHBOARD_ITEMS_POSITION = "dashboard_items_position" + } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardAdapter.kt index 9f3d546e..fa081ce7 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardAdapter.kt @@ -12,7 +12,6 @@ import androidx.core.view.updateLayoutParams import androidx.core.view.updateMarginsRelative import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Timetable @@ -38,10 +37,9 @@ import java.util.Timer import javax.inject.Inject import kotlin.concurrent.timer -class DashboardAdapter @Inject constructor() : - ListAdapter(DashboardAdapterDiffCallback()) { +class DashboardAdapter @Inject constructor() : RecyclerView.Adapter() { - var lessonsTimer: Timer? = null + private var lessonsTimer: Timer? = null var onAccountTileClickListener: () -> Unit = {} @@ -63,7 +61,23 @@ class DashboardAdapter @Inject constructor() : var onConferencesTileClickListener: () -> Unit = {} - override fun getItemViewType(position: Int) = getItem(position).type.ordinal + val items = mutableListOf() + + fun submitList(newItems: List) { + val diffResult = + DiffUtil.calculateDiff(DiffCallback(newItems, items.toMutableList())) + + with(items) { + clear() + addAll(newItems) + } + + diffResult.dispatchUpdatesTo(this) + } + + override fun getItemCount() = items.size + + override fun getItemViewType(position: Int) = items[position].type.ordinal override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { val inflater = LayoutInflater.from(parent.context) @@ -119,7 +133,7 @@ class DashboardAdapter @Inject constructor() : } private fun bindAccountViewHolder(accountViewHolder: AccountViewHolder, position: Int) { - val item = getItem(position) as DashboardItem.Account + val item = items[position] as DashboardItem.Account val student = item.student val isLoading = item.isLoading @@ -147,7 +161,7 @@ class DashboardAdapter @Inject constructor() : horizontalGroupViewHolder: HorizontalGroupViewHolder, position: Int ) { - val item = getItem(position) as DashboardItem.HorizontalGroup + val item = items[position] as DashboardItem.HorizontalGroup val unreadMessagesCount = item.unreadMessagesCount val attendancePercentage = item.attendancePercentage val luckyNumber = item.luckyNumber @@ -221,7 +235,7 @@ class DashboardAdapter @Inject constructor() : } private fun bindGradesViewHolder(gradesViewHolder: GradesViewHolder, position: Int) { - val item = getItem(position) as DashboardItem.Grades + val item = items[position] as DashboardItem.Grades val subjectWithGrades = item.subjectWithGrades.orEmpty() val gradeTheme = item.gradeTheme val error = item.error @@ -250,7 +264,7 @@ class DashboardAdapter @Inject constructor() : } private fun bindLessonsViewHolder(lessonsViewHolder: LessonsViewHolder, position: Int) { - val item = getItem(position) as DashboardItem.Lessons + val item = items[position] as DashboardItem.Lessons val timetableFull = item.lessons val binding = lessonsViewHolder.binding @@ -519,7 +533,7 @@ class DashboardAdapter @Inject constructor() : } private fun bindHomeworkViewHolder(homeworkViewHolder: HomeworkViewHolder, position: Int) { - val item = getItem(position) as DashboardItem.Homework + val item = items[position] as DashboardItem.Homework val homeworkList = item.homework.orEmpty() val error = item.error val isLoading = item.isLoading @@ -557,7 +571,7 @@ class DashboardAdapter @Inject constructor() : announcementsViewHolder: AnnouncementsViewHolder, position: Int ) { - val item = getItem(position) as DashboardItem.Announcements + val item = items[position] as DashboardItem.Announcements val schoolAnnouncementList = item.announcement.orEmpty() val error = item.error val isLoading = item.isLoading @@ -594,7 +608,7 @@ class DashboardAdapter @Inject constructor() : } private fun bindExamsViewHolder(examsViewHolder: ExamsViewHolder, position: Int) { - val item = getItem(position) as DashboardItem.Exams + val item = items[position] as DashboardItem.Exams val exams = item.exams.orEmpty() val error = item.error val isLoading = item.isLoading @@ -630,7 +644,7 @@ class DashboardAdapter @Inject constructor() : conferencesViewHolder: ConferencesViewHolder, position: Int ) { - val item = getItem(position) as DashboardItem.Conferences + val item = items[position] as DashboardItem.Conferences val conferences = item.conferences.orEmpty() val error = item.error val isLoading = item.isLoading @@ -703,13 +717,20 @@ class DashboardAdapter @Inject constructor() : val adapter by lazy { DashboardConferencesAdapter() } } - class DashboardAdapterDiffCallback : DiffUtil.ItemCallback() { + private class DiffCallback( + private val newList: List, + private val oldList: List + ) : DiffUtil.Callback() { - override fun areItemsTheSame(oldItem: DashboardItem, newItem: DashboardItem) = - oldItem.type == newItem.type + override fun getNewListSize() = newList.size - override fun areContentsTheSame(oldItem: DashboardItem, newItem: DashboardItem) = - oldItem == newItem + override fun getOldListSize() = oldList.size + + override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) = + newList[newItemPosition] == oldList[oldItemPosition] + + override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) = + newList[newItemPosition].type == oldList[oldItemPosition].type } private companion object { 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 283f5745..3392280b 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 @@ -8,6 +8,7 @@ import android.view.View import androidx.appcompat.app.AlertDialog import androidx.core.view.isVisible import androidx.recyclerview.widget.DefaultItemAnimator +import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R @@ -68,6 +69,12 @@ class DashboardFragment : BaseFragment(R.layout.fragme override fun initView() { val mainActivity = requireActivity() as MainActivity + val itemTouchHelper = ItemTouchHelper( + DashboardItemMoveCallback( + dashboardAdapter, + presenter::onDragAndDropEnd + ) + ) dashboardAdapter.apply { onAccountTileClickListener = { mainActivity.pushView(AccountFragment.newInstance()) } @@ -104,6 +111,8 @@ class DashboardFragment : BaseFragment(R.layout.fragme adapter = dashboardAdapter (itemAnimator as DefaultItemAnimator).supportsChangeAnimations = false } + + itemTouchHelper.attachToRecyclerView(dashboardRecycler) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardItemMoveCallback.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardItemMoveCallback.kt new file mode 100644 index 00000000..cf4097a4 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardItemMoveCallback.kt @@ -0,0 +1,55 @@ +package io.github.wulkanowy.ui.modules.dashboard + +import androidx.recyclerview.widget.ItemTouchHelper +import androidx.recyclerview.widget.RecyclerView +import java.util.Collections + +class DashboardItemMoveCallback( + private val dashboardAdapter: DashboardAdapter, + private var onUserInteractionEndListener: (List) -> Unit = {} +) : ItemTouchHelper.Callback() { + + override fun isLongPressDragEnabled() = true + + override fun isItemViewSwipeEnabled() = false + + override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { + //Not implemented + } + + override fun getMovementFlags( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder + ): Int { + val dragFlags = if (viewHolder.bindingAdapterPosition != 0) { + ItemTouchHelper.UP or ItemTouchHelper.DOWN + } else 0 + + return makeMovementFlags(dragFlags, 0) + } + + override fun canDropOver( + recyclerView: RecyclerView, + current: RecyclerView.ViewHolder, + target: RecyclerView.ViewHolder + ) = target.bindingAdapterPosition != 0 + + override fun onMove( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + target: RecyclerView.ViewHolder + ): Boolean { + val list = dashboardAdapter.items.toMutableList() + + Collections.swap(list, viewHolder.bindingAdapterPosition, target.bindingAdapterPosition) + + dashboardAdapter.submitList(list) + return true + } + + override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) { + super.clearView(recyclerView, viewHolder) + + onUserInteractionEndListener(dashboardAdapter.items.toList()) + } +} 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 12374859..0e24f0a1 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 @@ -68,6 +68,16 @@ class DashboardPresenter @Inject constructor( .launch("dashboard_pref") } + fun onDragAndDropEnd(list: List) { + dashboardItemLoadedList.clear() + dashboardItemLoadedList.addAll(list) + + val positionList = + list.mapIndexed { index, dashboardItem -> Pair(dashboardItem.type, index) }.toMap() + + preferencesRepository.dashboardItemsPosition = positionList + } + fun loadData(forceRefresh: Boolean = false, tilesToLoad: Set) { val oldDashboardDataToLoad = dashboardTilesToLoad @@ -622,6 +632,7 @@ class DashboardPresenter @Inject constructor( private fun updateData(dashboardItem: DashboardItem, forceRefresh: Boolean) { val isForceRefreshError = forceRefresh && dashboardItem.error != null + val dashboardItemsPosition = preferencesRepository.dashboardItemsPosition with(dashboardItemLoadedList) { removeAll { it.type == dashboardItem.type && !isForceRefreshError } @@ -636,7 +647,12 @@ class DashboardPresenter @Inject constructor( } } - dashboardItemLoadedList.sortBy { tile -> dashboardItemsToLoad.single { it == tile.type }.ordinal } + dashboardItemLoadedList.sortBy { tile -> + dashboardItemsPosition?.getOrDefault( + tile.type, + tile.type.ordinal + 100 + ) ?: tile.type.ordinal + } val isItemsLoaded = dashboardItemsToLoad.all { type -> dashboardItemLoadedList.any { it.type == type } } diff --git a/app/src/main/res/layout/item_dashboard_horizontal_group.xml b/app/src/main/res/layout/item_dashboard_horizontal_group.xml index a8532e6f..bbd8f351 100644 --- a/app/src/main/res/layout/item_dashboard_horizontal_group.xml +++ b/app/src/main/res/layout/item_dashboard_horizontal_group.xml @@ -4,8 +4,9 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginHorizontal="12dp" - android:layout_marginVertical="2dp"> + android:paddingHorizontal="12dp" + android:layout_marginVertical="2dp" + android:clipToPadding="false"> + app:layout_constraintTop_toTopOf="parent"> + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c6323668..c1b3a3ee 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -501,7 +501,7 @@ until %1$s No upcoming lessons - An error occurred while loading the lesson + An error occurred while loading the lessons Homework No homework to do From 9c819835ca8debbe5890add702148630c2ecef02 Mon Sep 17 00:00:00 2001 From: Mateusz Idziejczak Date: Sun, 15 Aug 2021 15:59:32 +0200 Subject: [PATCH 162/186] Add last sync date in sync settings (#1436) --- .../data/repositories/PreferencesRepository.kt | 15 +++++++++++++++ .../github/wulkanowy/services/sync/SyncWorker.kt | 3 +++ .../ui/modules/settings/sync/SyncFragment.kt | 6 ++++++ .../ui/modules/settings/sync/SyncPresenter.kt | 15 ++++++++++++++- .../ui/modules/settings/sync/SyncView.kt | 2 ++ app/src/main/res/values/preferences_defaults.xml | 1 + app/src/main/res/values/preferences_keys.xml | 1 + app/src/main/res/values/strings.xml | 1 + 8 files changed, 43 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/PreferencesRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/PreferencesRepository.kt index 1e6cd511..e725c42a 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/PreferencesRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/PreferencesRepository.kt @@ -13,9 +13,12 @@ import io.github.wulkanowy.R import io.github.wulkanowy.ui.modules.dashboard.DashboardItem import io.github.wulkanowy.ui.modules.grade.GradeAverageMode import io.github.wulkanowy.ui.modules.grade.GradeSortingMode +import io.github.wulkanowy.utils.toLocalDateTime +import io.github.wulkanowy.utils.toTimestamp import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map +import java.time.LocalDateTime import javax.inject.Inject import javax.inject.Singleton @@ -168,6 +171,13 @@ class PreferencesRepository @Inject constructor( R.bool.pref_default_optional_arithmetic_average ) + var lasSyncDate: LocalDateTime + get() = getLong( + R.string.pref_key_last_sync_date, + R.string.pref_default_last_sync_date + ).toLocalDateTime() + set(value) = sharedPref.edit().putLong("last_sync_date", value.toTimestamp()).apply() + var dashboardItemsPosition: Map? get() { val json = sharedPref.getString(PREF_KEY_DASHBOARD_ITEMS_POSITION, null) ?: return null @@ -211,6 +221,11 @@ class PreferencesRepository @Inject constructor( return flowSharedPref.getStringSet(prefKey, defaultSet) } + private fun getLong(id: Int, default: Int) = getLong(context.getString(id), default) + + private fun getLong(id: String, default: Int) = + sharedPref.getLong(id, context.resources.getString(default).toLong()) + private fun getString(id: Int, default: Int) = getString(context.getString(id), default) private fun getString(id: String, default: Int) = diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/SyncWorker.kt b/app/src/main/java/io/github/wulkanowy/services/sync/SyncWorker.kt index 49d61a41..ea1f79cb 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/SyncWorker.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/SyncWorker.kt @@ -22,6 +22,8 @@ import io.github.wulkanowy.services.sync.works.Work import io.github.wulkanowy.utils.getCompatColor import kotlinx.coroutines.coroutineScope import timber.log.Timber +import java.time.LocalDateTime +import java.time.ZoneId import kotlin.random.Random @HiltWorker @@ -48,6 +50,7 @@ class SyncWorker @AssistedInject constructor( Timber.i("${work::class.java.simpleName} is starting") work.doWork(student, semester) Timber.i("${work::class.java.simpleName} result: Success") + preferencesRepository.lasSyncDate = LocalDateTime.now(ZoneId.systemDefault()) null } catch (e: Throwable) { Timber.w("${work::class.java.simpleName} result: An exception ${e.message} occurred") 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 207fe2ff..34298809 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 @@ -39,6 +39,12 @@ class SyncFragment : PreferenceFragmentCompat(), } } + override fun setLastSyncDate(lastSyncDate: String) { + findPreference(getString(R.string.pref_key_services_force_sync))?.run { + summary = getString(R.string.pref_services_last_full_sync_date, lastSyncDate) + } + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) presenter.onAttachView(this) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncPresenter.kt index 36b8d792..63e86a47 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncPresenter.kt @@ -8,6 +8,7 @@ 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.isHolidays +import io.github.wulkanowy.utils.toFormattedString import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.onEach import timber.log.Timber @@ -27,6 +28,7 @@ class SyncPresenter @Inject constructor( Timber.i("Settings sync view was initialized") view.setServicesSuspended(preferencesRepository.serviceEnableKey, now().isHolidays) view.initView() + setSyncDateInView() } fun onSharedPreferenceChanged(key: String) { @@ -63,10 +65,21 @@ class SyncPresenter @Inject constructor( } else -> Timber.d("Sync now state: ${workInfo.state}") } - if (workInfo.state.isFinished) setSyncInProgress(false) + if (workInfo.state.isFinished) { + setSyncInProgress(false) + setSyncDateInView() + } }.catch { Timber.e(it, "Sync now failed") }.launch("sync") } } + + private fun setSyncDateInView() { + val lastSyncDate = preferencesRepository.lasSyncDate + + if (lastSyncDate.year == 1970) return + + view?.setLastSyncDate(lastSyncDate.toFormattedString("dd.MM.yyyy HH:mm:ss")) + } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncView.kt index 9da473ba..6ffe156f 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncView.kt @@ -10,6 +10,8 @@ interface SyncView : BaseView { fun initView() + fun setLastSyncDate(lastSyncDate: String) + fun setServicesSuspended(serviceEnablesKey: String, isHolidays: Boolean) fun setSyncInProgress(inProgress: Boolean) diff --git a/app/src/main/res/values/preferences_defaults.xml b/app/src/main/res/values/preferences_defaults.xml index 5721763f..1ba9359c 100644 --- a/app/src/main/res/values/preferences_defaults.xml +++ b/app/src/main/res/values/preferences_defaults.xml @@ -25,6 +25,7 @@ false false false + 0 LUCKY_NUMBER MESSAGES diff --git a/app/src/main/res/values/preferences_keys.xml b/app/src/main/res/values/preferences_keys.xml index 876e4333..aa60bb7e 100644 --- a/app/src/main/res/values/preferences_keys.xml +++ b/app/src/main/res/values/preferences_keys.xml @@ -31,4 +31,5 @@ optional_arithmetic_average message_send_is_draft message_send_recipients + last_sync_date diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c1b3a3ee..0b25f4a5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -615,6 +615,7 @@ Synced! Sync failed Sync in progress + Last full sync: %s Value of the plus Value of the minus From 428e40d7fe6f9b8b67a078868bbc98bd9040d753 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 17 Aug 2021 13:32:34 +0000 Subject: [PATCH 163/186] Bump hianalytics from 6.1.0.300 to 6.1.1.300 (#1441) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 05d1a150..fae7d6e6 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -225,7 +225,7 @@ dependencies { playImplementation 'com.google.firebase:firebase-crashlytics:' playImplementation 'com.google.android.play:core-ktx:1.8.1' - hmsImplementation 'com.huawei.hms:hianalytics:6.1.0.300' + hmsImplementation 'com.huawei.hms:hianalytics:6.1.1.300' hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.6.0.300' releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker" From 9c5d2fbf8462b5f9c927154818e037b6abdb7835 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 17 Aug 2021 13:32:48 +0000 Subject: [PATCH 164/186] Bump google-services from 4.3.9 to 4.3.10 (#1439) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 23c0276f..fecbb019 100644 --- a/build.gradle +++ b/build.gradle @@ -14,7 +14,7 @@ buildscript { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath 'com.android.tools.build:gradle:7.0.0' classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" - classpath 'com.google.gms:google-services:4.3.9' + classpath 'com.google.gms:google-services:4.3.10' classpath 'com.huawei.agconnect:agcp:1.6.0.300' classpath 'com.google.firebase:firebase-crashlytics-gradle:2.7.1' classpath "com.github.triplet.gradle:play-publisher:2.8.0" From d3b3939d2649ff7d1e9d779d31f6ecdd9b3f495d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 17 Aug 2021 13:44:06 +0000 Subject: [PATCH 165/186] Bump timber from 4.7.1 to 5.0.1 (#1440) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index fae7d6e6..5e1ae9ab 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -210,7 +210,7 @@ dependencies { implementation "com.squareup.moshi:moshi-adapters:$moshi" kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshi" - implementation "com.jakewharton.timber:timber:4.7.1" + implementation "com.jakewharton.timber:timber:5.0.1" implementation "at.favre.lib:slf4j-timber:1.0.1" implementation 'com.github.bastienpaulfr:Treessence:1.0.4' implementation "com.mikepenz:aboutlibraries-core:$about_libraries" From eb94e06d54c88a6f1069df4f06e5f6ef5560fe87 Mon Sep 17 00:00:00 2001 From: Mateusz Idziejczak Date: Sun, 22 Aug 2021 16:33:12 +0200 Subject: [PATCH 166/186] Fix buggy timers in timetable (#1428) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Rafał Borcz --- .../ui/modules/timetable/TimetableAdapter.kt | 201 +++++++++++++----- .../ui/modules/timetable/TimetableFragment.kt | 20 +- 2 files changed, 152 insertions(+), 69 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableAdapter.kt index f049f828..87b3362d 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableAdapter.kt @@ -7,6 +7,7 @@ import android.view.View.VISIBLE import android.view.ViewGroup import android.widget.TextView import androidx.core.view.ViewCompat +import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Timetable @@ -26,31 +27,58 @@ import kotlin.concurrent.timer class TimetableAdapter @Inject constructor() : RecyclerView.Adapter() { - private enum class ViewType(val id: Int) { - ITEM_NORMAL(1), - ITEM_SMALL(2) + private enum class ViewType { + ITEM_NORMAL, + ITEM_SMALL } - var items = mutableListOf() - set(value) { - field = value - resetTimers() - } - var onClickListener: (Timetable) -> Unit = {} - var showWholeClassPlan: String = "no" + private var showWholeClassPlan: String = "no" - var showGroupsInPlan: Boolean = false + private var showGroupsInPlan: Boolean = false - var showTimers: Boolean = false + private var showTimers: Boolean = false - private val timers = mutableMapOf() + private val timers = mutableMapOf() - fun resetTimers() { - Timber.d("Timetable timers (${timers.size}) reset") + private val items = mutableListOf() + + fun submitList( + newTimetable: List, + showWholeClassPlan: String = this.showWholeClassPlan, + showGroupsInPlan: Boolean = this.showGroupsInPlan, + showTimers: Boolean = this.showTimers + ) { + val isFlagsDifferent = this.showWholeClassPlan != showWholeClassPlan + || this.showGroupsInPlan != showGroupsInPlan + || this.showTimers != showTimers + + val diffResult = DiffUtil.calculateDiff( + TimetableAdapterDiffCallback( + oldList = items.toMutableList(), + newList = newTimetable, + isFlagsDifferent = isFlagsDifferent + ) + ) + + this.showGroupsInPlan = showGroupsInPlan + this.showTimers = showTimers + this.showWholeClassPlan = showWholeClassPlan + + items.clear() + items.addAll(newTimetable) + + diffResult.dispatchUpdatesTo(this) + } + + fun clearTimers() { + Timber.d("Timetable timers (${timers.size}) cleared") with(timers) { - forEach { (_, timer) -> timer.cancel() } + forEach { (_, timer) -> + timer?.cancel() + timer?.purge() + } clear() } } @@ -58,16 +86,20 @@ class TimetableAdapter @Inject constructor() : RecyclerView.Adapter ViewType.ITEM_SMALL.id - else -> ViewType.ITEM_NORMAL.id + !items[position].isStudentPlan && showWholeClassPlan == "small" -> ViewType.ITEM_SMALL.ordinal + else -> ViewType.ITEM_NORMAL.ordinal } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { val inflater = LayoutInflater.from(parent.context) return when (viewType) { - ViewType.ITEM_NORMAL.id -> ItemViewHolder(ItemTimetableBinding.inflate(inflater, parent, false)) - ViewType.ITEM_SMALL.id -> SmallItemViewHolder(ItemTimetableSmallBinding.inflate(inflater, parent, false)) + ViewType.ITEM_NORMAL.ordinal -> ItemViewHolder( + ItemTimetableBinding.inflate(inflater, parent, false) + ) + ViewType.ITEM_SMALL.ordinal -> SmallItemViewHolder( + ItemTimetableSmallBinding.inflate(inflater, parent, false) + ) else -> throw IllegalStateException() } } @@ -111,6 +143,12 @@ class TimetableAdapter @Inject constructor() : RecyclerView.Adapter i < position && !item.isStudentPlan }.size)?.let { - if (!it.canceled && it.isStudentPlan) it.end - else null - } + return items.filter { it.isStudentPlan } + .getOrNull(position - 1 - items.filterIndexed { i, item -> i < position && !item.isStudentPlan }.size) + ?.let { + if (!it.canceled && it.isStudentPlan) it.end + else null + } } private fun updateTimeLeft(binding: ItemTimetableBinding, lesson: Timetable, position: Int) { @@ -148,11 +188,18 @@ class TimetableAdapter @Inject constructor() : RecyclerView.Adapter, + private val newList: List, + private val isFlagsDifferent: Boolean + ) : DiffUtil.Callback() { + + override fun getOldListSize() = oldList.size + + override fun getNewListSize() = newList.size + + override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) = + oldList[oldItemPosition].id == newList[newItemPosition].id + + override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) = + oldList[oldItemPosition] == newList[newItemPosition] && !isFlagsDifferent + } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt index 2bc7aa59..a374e166 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt @@ -49,7 +49,7 @@ class TimetableFragment : BaseFragment(R.layout.fragme override val titleStringId get() = R.string.timetable_title - override val isViewEmpty get() = timetableAdapter.items.isEmpty() + override val isViewEmpty get() = timetableAdapter.itemCount > 0 override val currentStackSize get() = (activity as? MainActivity)?.currentStackSize @@ -109,20 +109,16 @@ class TimetableFragment : BaseFragment(R.layout.fragme showGroupsInPlanType: Boolean, showTimetableTimers: Boolean ) { - with(timetableAdapter) { - items = data.toMutableList() - showTimers = showTimetableTimers + timetableAdapter.submitList( + newTimetable = data.toMutableList(), + showGroupsInPlan = showGroupsInPlanType, + showTimers = showTimetableTimers, showWholeClassPlan = showWholeClassPlanType - showGroupsInPlan = showGroupsInPlanType - notifyDataSetChanged() - } + ) } override fun clearData() { - with(timetableAdapter) { - items = mutableListOf() - notifyDataSetChanged() - } + timetableAdapter.submitList(listOf()) } override fun updateNavigationDay(date: String) { @@ -226,7 +222,7 @@ class TimetableFragment : BaseFragment(R.layout.fragme } override fun onDestroyView() { - timetableAdapter.resetTimers() + timetableAdapter.clearTimers() presenter.onDetachView() super.onDestroyView() } From 02b87c8c6a7b419a552abff2d63bfb7f0b840637 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Aug 2021 13:42:20 +0000 Subject: [PATCH 167/186] Bump firebase-bom from 28.3.1 to 28.4.0 (#1446) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 5e1ae9ab..1338bd8d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -219,7 +219,7 @@ dependencies { implementation 'me.xdrop:fuzzywuzzy:1.3.1' implementation 'com.fredporciuncula:flow-preferences:1.5.0' - playImplementation platform('com.google.firebase:firebase-bom:28.3.1') + playImplementation platform('com.google.firebase:firebase-bom:28.4.0') playImplementation 'com.google.firebase:firebase-analytics-ktx' playImplementation 'com.google.firebase:firebase-messaging:' playImplementation 'com.google.firebase:firebase-crashlytics:' From 1cfabe43a5e837b46d8041137f1f1246512a6660 Mon Sep 17 00:00:00 2001 From: Mateusz Idziejczak Date: Mon, 23 Aug 2021 15:48:48 +0200 Subject: [PATCH 168/186] Add captions for averages from how many items have been counted (#1437) --- .../grade/summary/GradeSummaryAdapter.kt | 38 ++++++++++++++----- .../scrollable_header_grade_summary.xml | 20 ++++++++++ app/src/main/res/values/strings.xml | 1 + 3 files changed, 50 insertions(+), 9 deletions(-) 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 4de6044b..9a888ddc 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 @@ -5,6 +5,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup 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 @@ -35,8 +36,12 @@ class GradeSummaryAdapter @Inject constructor( val inflater = LayoutInflater.from(parent.context) return when (viewType) { - ViewType.HEADER.id -> HeaderViewHolder(ScrollableHeaderGradeSummaryBinding.inflate(inflater, parent, false)) - ViewType.ITEM.id -> ItemViewHolder(ItemGradeSummaryBinding.inflate(inflater, parent, false)) + ViewType.HEADER.id -> HeaderViewHolder( + ScrollableHeaderGradeSummaryBinding.inflate(inflater, parent, false) + ) + ViewType.ITEM.id -> ItemViewHolder( + ItemGradeSummaryBinding.inflate(inflater, parent, false) + ) else -> throw IllegalStateException() } } @@ -51,13 +56,27 @@ class GradeSummaryAdapter @Inject constructor( private fun bindHeaderViewHolder(binding: ScrollableHeaderGradeSummaryBinding) { if (items.isEmpty()) return + val context = binding.root.context + val finalItemsCount = items.count { it.finalGrade.matches("[0-6][+-]?".toRegex()) } + val calculatedItemsCount = items.count { value -> value.average != 0.0 } + val finalAverage = items.calcAverage( + preferencesRepository.gradePlusModifier, + preferencesRepository.gradeMinusModifier + ) + val calculatedAverage = items.filter { value -> value.average != 0.0 } + .map { values -> values.average } + .reversed() // fix average precision + .average() + with(binding) { - gradeSummaryScrollableHeaderFinal.text = formatAverage(items.calcAverage(preferencesRepository.gradePlusModifier, preferencesRepository.gradeMinusModifier)) - gradeSummaryScrollableHeaderCalculated.text = formatAverage(items - .filter { value -> value.average != 0.0 } - .map { values -> values.average } - .reversed() // fix average precision - .average() + gradeSummaryScrollableHeaderFinal.text = formatAverage(finalAverage) + gradeSummaryScrollableHeaderCalculated.text = formatAverage(calculatedAverage) + gradeSummaryScrollableHeaderFinalSubjectCount.text = + context.getString(R.string.grade_summary_from_subjects, finalItemsCount, items.size) + gradeSummaryScrollableHeaderCalculatedSubjectCount.text = context.getString( + R.string.grade_summary_from_subjects, + calculatedItemsCount, + items.size ) } } @@ -71,7 +90,8 @@ class GradeSummaryAdapter @Inject constructor( gradeSummaryItemPredicted.text = "${item.predictedGrade} ${item.proposedPoints}".trim() gradeSummaryItemFinal.text = "${item.finalGrade} ${item.finalPoints}".trim() - gradeSummaryItemPointsContainer.visibility = if (item.pointsSum.isBlank()) View.GONE else View.VISIBLE + gradeSummaryItemPointsContainer.visibility = + if (item.pointsSum.isBlank()) View.GONE else View.VISIBLE } } diff --git a/app/src/main/res/layout/scrollable_header_grade_summary.xml b/app/src/main/res/layout/scrollable_header_grade_summary.xml index 2eea20f4..29657ba1 100644 --- a/app/src/main/res/layout/scrollable_header_grade_summary.xml +++ b/app/src/main/res/layout/scrollable_header_grade_summary.xml @@ -20,6 +20,7 @@ android:layout_height="wrap_content" android:gravity="center" android:minLines="2" + android:textStyle="bold" android:text="@string/grade_summary_calculated_average" android:textSize="16sp" /> @@ -30,6 +31,15 @@ android:gravity="center_horizontal" android:textSize="21sp" tools:text="6,00" /> + + @@ -53,5 +64,14 @@ android:gravity="center_horizontal" android:textSize="21sp" tools:text="6,00" /> + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0b25f4a5..8b3f3b8e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -101,6 +101,7 @@ Predicted grade Calculated average Final average + From %d of %d subjects Summary Class Mark as read From 076948a680b1e576cbdf1386b498dc763c165fbe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Aug 2021 13:53:04 +0000 Subject: [PATCH 169/186] Bump gradle from 7.0.0 to 7.0.1 (#1445) --- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.jar | Bin 59203 -> 59536 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index fecbb019..d169291d 100644 --- a/build.gradle +++ b/build.gradle @@ -12,7 +12,7 @@ buildscript { } dependencies { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath 'com.android.tools.build:gradle:7.0.0' + classpath 'com.android.tools.build:gradle:7.0.1' classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" classpath 'com.google.gms:google-services:4.3.10' classpath 'com.huawei.agconnect:agcp:1.6.0.300' diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e708b1c023ec8b20f512888fe07c5bd3ff77bb8f..7454180f2ae8848c63b8b4dea2cb829da983f2fa 100644 GIT binary patch delta 18435 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 delta 17845 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 Date: Mon, 23 Aug 2021 18:02:21 +0200 Subject: [PATCH 170/186] New timetable widget design (#1384) --- app/build.gradle | 2 +- .../background_widget_header_timetable.xml | 7 +++++++ ...ackground_widget_header_timetable_dark.xml | 7 +++++++ .../background_widget_item_timetable.xml | 5 +++++ .../background_widget_item_timetable_dark.xml | 5 +++++ .../drawable/background_widget_timetable.xml | 5 +++++ .../background_widget_timetable_dark.xml | 5 +++++ .../drawable/img_timetable_widget_preview.png | Bin 5284 -> 25538 bytes .../main/res/layout/item_widget_timetable.xml | 7 +------ .../res/layout/item_widget_timetable_dark.xml | 7 +------ .../layout/item_widget_timetable_small.xml | 2 +- .../item_widget_timetable_small_dark.xml | 2 +- app/src/main/res/layout/widget_timetable.xml | 13 ++++++++++--- .../main/res/layout/widget_timetable_dark.xml | 13 ++++++++++--- 14 files changed, 59 insertions(+), 21 deletions(-) create mode 100644 app/src/main/res/drawable/background_widget_header_timetable.xml create mode 100644 app/src/main/res/drawable/background_widget_header_timetable_dark.xml create mode 100644 app/src/main/res/drawable/background_widget_item_timetable.xml create mode 100644 app/src/main/res/drawable/background_widget_item_timetable_dark.xml create mode 100644 app/src/main/res/drawable/background_widget_timetable.xml create mode 100644 app/src/main/res/drawable/background_widget_timetable_dark.xml mode change 100644 => 100755 app/src/main/res/drawable/img_timetable_widget_preview.png diff --git a/app/build.gradle b/app/build.gradle index 1338bd8d..409e6c4d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -164,7 +164,7 @@ ext { } dependencies { - implementation "io.github.wulkanowy:sdk:496dc01d15" + implementation "io.github.wulkanowy:sdk:b991d0c" coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' diff --git a/app/src/main/res/drawable/background_widget_header_timetable.xml b/app/src/main/res/drawable/background_widget_header_timetable.xml new file mode 100644 index 00000000..98eec700 --- /dev/null +++ b/app/src/main/res/drawable/background_widget_header_timetable.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/background_widget_header_timetable_dark.xml b/app/src/main/res/drawable/background_widget_header_timetable_dark.xml new file mode 100644 index 00000000..616a9127 --- /dev/null +++ b/app/src/main/res/drawable/background_widget_header_timetable_dark.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/background_widget_item_timetable.xml b/app/src/main/res/drawable/background_widget_item_timetable.xml new file mode 100644 index 00000000..08854fba --- /dev/null +++ b/app/src/main/res/drawable/background_widget_item_timetable.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/background_widget_item_timetable_dark.xml b/app/src/main/res/drawable/background_widget_item_timetable_dark.xml new file mode 100644 index 00000000..e432a648 --- /dev/null +++ b/app/src/main/res/drawable/background_widget_item_timetable_dark.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/background_widget_timetable.xml b/app/src/main/res/drawable/background_widget_timetable.xml new file mode 100644 index 00000000..2267587d --- /dev/null +++ b/app/src/main/res/drawable/background_widget_timetable.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/background_widget_timetable_dark.xml b/app/src/main/res/drawable/background_widget_timetable_dark.xml new file mode 100644 index 00000000..6fe7d0ab --- /dev/null +++ b/app/src/main/res/drawable/background_widget_timetable_dark.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/img_timetable_widget_preview.png b/app/src/main/res/drawable/img_timetable_widget_preview.png old mode 100644 new mode 100755 index 550260258a82ae1fcb8c54530efff52985580153..8494301878a1bdcb838aee6ee82be8b42c3904cd GIT binary patch literal 25538 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 - - diff --git a/app/src/main/res/layout/item_widget_timetable_dark.xml b/app/src/main/res/layout/item_widget_timetable_dark.xml index 34535c69..06233244 100644 --- a/app/src/main/res/layout/item_widget_timetable_dark.xml +++ b/app/src/main/res/layout/item_widget_timetable_dark.xml @@ -6,12 +6,12 @@ android:layout_height="wrap_content" android:minHeight="45dp" android:orientation="vertical" + android:background="@drawable/background_widget_item_timetable_dark" tools:context=".ui.modules.timetablewidget.TimetableWidgetFactory"> - - diff --git a/app/src/main/res/layout/item_widget_timetable_small.xml b/app/src/main/res/layout/item_widget_timetable_small.xml index 36a6bbdd..1bf4072d 100644 --- a/app/src/main/res/layout/item_widget_timetable_small.xml +++ b/app/src/main/res/layout/item_widget_timetable_small.xml @@ -91,7 +91,7 @@ tools:text="@tools:sample/lorem/random" /> - diff --git a/app/src/main/res/layout/item_widget_timetable_small_dark.xml b/app/src/main/res/layout/item_widget_timetable_small_dark.xml index a02d8585..50bbbd03 100644 --- a/app/src/main/res/layout/item_widget_timetable_small_dark.xml +++ b/app/src/main/res/layout/item_widget_timetable_small_dark.xml @@ -90,7 +90,7 @@ tools:text="@tools:sample/lorem/random" /> - diff --git a/app/src/main/res/layout/widget_timetable.xml b/app/src/main/res/layout/widget_timetable.xml index 8a08b5d2..059bb741 100644 --- a/app/src/main/res/layout/widget_timetable.xml +++ b/app/src/main/res/layout/widget_timetable.xml @@ -3,13 +3,13 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" - android:background="@android:color/white" + android:background="@drawable/background_widget_timetable" tools:context=".ui.modules.timetablewidget.TimetableWidgetProvider"> + android:background="@drawable/background_widget_header_timetable"> @@ -77,7 +78,12 @@ android:id="@+id/timetableWidgetList" android:layout_width="match_parent" android:layout_height="match_parent" - android:layout_marginTop="56dp" + android:layout_marginLeft="10dp" + android:layout_marginTop="66dp" + android:layout_marginRight="10dp" + android:layout_marginBottom="10dp" + android:divider="#00ffffff" + android:dividerHeight="4dp" tools:listitem="@layout/item_widget_timetable" /> diff --git a/app/src/main/res/layout/widget_timetable_dark.xml b/app/src/main/res/layout/widget_timetable_dark.xml index 5533eaee..9c8b8c56 100644 --- a/app/src/main/res/layout/widget_timetable_dark.xml +++ b/app/src/main/res/layout/widget_timetable_dark.xml @@ -3,13 +3,13 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" - android:background="@color/colorWidgetBackground" + android:background="@drawable/background_widget_timetable_dark" tools:context=".ui.modules.timetablewidget.TimetableWidgetProvider"> + android:background="@drawable/background_widget_header_timetable_dark"> @@ -77,7 +78,12 @@ android:id="@+id/timetableWidgetList" android:layout_width="match_parent" android:layout_height="match_parent" - android:layout_marginTop="56dp" + android:layout_marginTop="66dp" + android:layout_marginBottom="10dp" + android:layout_marginLeft="10dp" + android:layout_marginRight="10dp" + android:divider="#00ffffff" + android:dividerHeight="4dp" tools:listitem="@layout/item_widget_timetable_dark" /> From 2979d8b62ac47cc034028e34286dd7805930866b Mon Sep 17 00:00:00 2001 From: Mateusz Idziejczak Date: Mon, 23 Aug 2021 18:16:41 +0200 Subject: [PATCH 171/186] Show information when the recipient has read the message (#1430) --- .../data/repositories/MessageRepository.kt | 5 +- .../message/preview/MessagePreviewAdapter.kt | 52 ++++++++++++++++--- .../main/res/layout/item_message_preview.xml | 13 ++++- app/src/main/res/values/strings.xml | 7 ++- 4 files changed, 64 insertions(+), 13 deletions(-) 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 2034000e..2d70e26e 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 @@ -20,14 +20,12 @@ import io.github.wulkanowy.data.pojos.MessageDraftJsonAdapter import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.pojo.Folder import io.github.wulkanowy.sdk.pojo.SentMessage -import io.github.wulkanowy.ui.modules.message.send.RecipientChipItem import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.getRefreshKey import io.github.wulkanowy.utils.init import io.github.wulkanowy.utils.networkBoundResource import io.github.wulkanowy.utils.uniqueSubtract import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map import kotlinx.coroutines.sync.Mutex import timber.log.Timber import java.time.LocalDateTime.now @@ -79,8 +77,9 @@ class MessageRepository @Inject constructor( }, saveFetchResult = { old, (downloadedMessage, attachments) -> checkNotNull(old, { "Fetched message no longer exist!" }) - messagesDb.updateAll(listOf(old.message.copy(unread = !markAsRead).apply { + messagesDb.updateAll(listOf(old.message.apply { id = old.message.id + unread = !markAsRead content = content.ifBlank { downloadedMessage } })) messageAttachmentDao.insertAttachments(attachments) 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 206a7460..421453c9 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 @@ -33,7 +33,8 @@ class MessagePreviewAdapter @Inject constructor() : private var attachments: List = emptyList() - override fun getItemCount() = if (messageWithAttachment == null) 0 else attachments.size + 1 + if (attachments.isNotEmpty()) 1 else 0 + override fun getItemCount() = + if (messageWithAttachment == null) 0 else attachments.size + 1 + if (attachments.isNotEmpty()) 1 else 0 override fun getItemViewType(position: Int) = when (position) { 0 -> ViewType.MESSAGE.id @@ -45,25 +46,60 @@ class MessagePreviewAdapter @Inject constructor() : val inflater = LayoutInflater.from(parent.context) return when (viewType) { - 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)) + 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() } } override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { when (holder) { - is MessageViewHolder -> bindMessage(holder, requireNotNull(messageWithAttachment).message) - is AttachmentViewHolder -> bindAttachment(holder, requireNotNull(messageWithAttachment).attachments[position - 2]) + is MessageViewHolder -> bindMessage( + holder, + requireNotNull(messageWithAttachment).message + ) + is AttachmentViewHolder -> bindAttachment( + holder, + requireNotNull(messageWithAttachment).attachments[position - 2] + ) } } @SuppressLint("SetTextI18n") private fun bindMessage(holder: MessageViewHolder, message: Message) { + val context = holder.binding.root.context + val recipientCount = message.unreadBy + message.readBy + + val readText = when { + recipientCount > 1 -> { + context.resources.getQuantityString( + R.plurals.message_read_by, + message.readBy, + message.readBy, + recipientCount + ) + } + message.readBy == 1 -> { + 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)) + } + with(holder.binding) { - messagePreviewSubject.text = message.subject.ifBlank { root.context.getString(R.string.message_no_subject) } - messagePreviewDate.text = root.context.getString(R.string.message_date, message.date.toFormattedString("yyyy-MM-dd HH:mm:ss")) + messagePreviewSubject.text = + message.subject.ifBlank { root.context.getString(R.string.message_no_subject) } + messagePreviewDate.text = root.context.getString( + R.string.message_date, + message.date.toFormattedString("yyyy-MM-dd HH:mm:ss") + ) + messagePreviewRead.text = readText messagePreviewContent.text = message.content messagePreviewFromSender.text = message.sender messagePreviewToRecipient.text = message.recipient diff --git a/app/src/main/res/layout/item_message_preview.xml b/app/src/main/res/layout/item_message_preview.xml index 83a4406b..c5dc2fa9 100644 --- a/app/src/main/res/layout/item_message_preview.xml +++ b/app/src/main/res/layout/item_message_preview.xml @@ -78,6 +78,17 @@ app:layout_constraintTop_toBottomOf="@id/messagePreviewToLabel" tools:text="@tools:sample/date/ddmmyy" /> + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8b3f3b8e..b655769d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -101,7 +101,7 @@ Predicted grade Calculated average Final average - From %d of %d subjects + From %1$d of %2$d subjects Summary Class Mark as read @@ -237,6 +237,11 @@ The message content must be at least 3 characters Only unread Only with attachments + Read: %s + + Read by: %1$d of %2$d people + Read by: %1$d of %2$d people + %d message %d messages From a6a2bcff3b2beec3b535f4e6364f30c28a7bf937 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Tue, 24 Aug 2021 19:51:08 +0200 Subject: [PATCH 172/186] Remove Zachowanie from all count of subjects (#1447) --- .../ui/modules/grade/summary/GradeSummaryAdapter.kt | 9 +++++++-- app/src/main/res/values/strings.xml | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) 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 9a888ddc..0754361c 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 @@ -59,6 +59,7 @@ class GradeSummaryAdapter @Inject constructor( val context = binding.root.context val finalItemsCount = items.count { it.finalGrade.matches("[0-6][+-]?".toRegex()) } val calculatedItemsCount = items.count { value -> value.average != 0.0 } + val allItemsCount = items.count { !it.subject.equals("zachowanie", true) } val finalAverage = items.calcAverage( preferencesRepository.gradePlusModifier, preferencesRepository.gradeMinusModifier @@ -72,11 +73,15 @@ class GradeSummaryAdapter @Inject constructor( gradeSummaryScrollableHeaderFinal.text = formatAverage(finalAverage) gradeSummaryScrollableHeaderCalculated.text = formatAverage(calculatedAverage) gradeSummaryScrollableHeaderFinalSubjectCount.text = - context.getString(R.string.grade_summary_from_subjects, finalItemsCount, items.size) + context.getString( + R.string.grade_summary_from_subjects, + finalItemsCount, + allItemsCount + ) gradeSummaryScrollableHeaderCalculatedSubjectCount.text = context.getString( R.string.grade_summary_from_subjects, calculatedItemsCount, - items.size + allItemsCount ) } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b655769d..ee911cfb 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -101,7 +101,7 @@ Predicted grade Calculated average Final average - From %1$d of %2$d subjects + from %1$d of %2$d subjects Summary Class Mark as read From b4b9d91ea6f706efe6bb007b4989e7c4f29b7e91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Wed, 25 Aug 2021 20:49:44 +0200 Subject: [PATCH 173/186] Update dependencies (#1448) --- app/build.gradle | 18 +++++------------- app/jacoco.gradle | 6 +++--- .../alarm/TimetableNotificationReceiver.kt | 4 ++-- .../wulkanowy/ui/modules/main/MainActivity.kt | 4 ++-- .../message/send/SendMessageActivity.kt | 4 +--- .../message/send/SendMessagePresenter.kt | 2 -- .../modules/message/tab/MessageTabFragment.kt | 2 -- .../modules/message/tab/MessageTabPresenter.kt | 3 +-- .../timetablewidget/TimetableWidgetProvider.kt | 2 ++ build.gradle | 2 +- gradle.properties | 8 +++++--- 11 files changed, 22 insertions(+), 33 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 409e6c4d..d5fccac6 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -3,11 +3,11 @@ apply plugin: 'kotlin-android' apply plugin: 'kotlin-parcelize' apply plugin: 'kotlin-kapt' apply plugin: 'dagger.hilt.android.plugin' +apply plugin: 'com.google.gms.google-services' apply plugin: 'com.google.firebase.crashlytics' apply plugin: 'com.github.triplet.play' apply plugin: 'ru.cian.huawei-publish' apply plugin: 'com.mikepenz.aboutlibraries.plugin' -apply plugin: 'com.google.gms.google-services' apply plugin: 'com.huawei.agconnect' apply from: 'jacoco.gradle' apply from: 'sonarqube.gradle' @@ -15,7 +15,6 @@ apply from: 'hooks.gradle' android { compileSdkVersion 30 - buildToolsVersion '30.0.3' defaultConfig { applicationId "io.github.wulkanowy" @@ -98,7 +97,7 @@ android { } buildFeatures { - viewBinding = true + viewBinding true } testOptions.unitTests { @@ -107,12 +106,12 @@ android { compileOptions { coreLibraryDesugaringEnabled true - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 } kotlinOptions { - jvmTarget = "1.8" + jvmTarget = "11" freeCompilerArgs += ["-Xopt-in=kotlin.RequiresOptIn", "-Xjvm-default=all"] } @@ -128,12 +127,6 @@ android { kapt { correctErrorTypes true - - javacOptions { - //Bug workaround https://youtrack.jetbrains.com/issue/KT-47416 - option("-Adagger.fastInit=ENABLED") - option("-Adagger.hilt.android.internal.disableAndroidSuperclassValidation=true") - } } play { @@ -168,7 +161,6 @@ dependencies { coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.1" implementation "androidx.core:core-ktx:1.6.0" diff --git a/app/jacoco.gradle b/app/jacoco.gradle index 9c1f5b1c..f253673e 100644 --- a/app/jacoco.gradle +++ b/app/jacoco.gradle @@ -2,7 +2,7 @@ apply plugin: "jacoco" jacoco { toolVersion "0.8.7" - reportsDirectory = file("$buildDir/reports") + reportsDirectory.set(file("$buildDir/reports")) } tasks.withType(Test) { @@ -16,8 +16,8 @@ task jacocoTestReport(type: JacocoReport) { description = "Generate Jacoco coverage reports" reports { - xml.enabled = true - html.enabled = true + xml.required.set(true) + html.required.set(true) } def excludes = ['**/R.class', diff --git a/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationReceiver.kt b/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationReceiver.kt index 8eefc032..406d91f5 100644 --- a/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationReceiver.kt +++ b/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationReceiver.kt @@ -1,6 +1,5 @@ package io.github.wulkanowy.services.alarm -import android.annotation.SuppressLint import android.app.PendingIntent import android.app.PendingIntent.FLAG_UPDATE_CURRENT import android.content.Context @@ -20,6 +19,7 @@ import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.utils.flowWithResource import io.github.wulkanowy.utils.getCompatColor import io.github.wulkanowy.utils.toLocalDateTime +import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -50,7 +50,7 @@ class TimetableNotificationReceiver : HiltBroadcastReceiver() { const val LESSON_END = "end_timestamp" } - @SuppressLint("CheckResult") + @OptIn(DelicateCoroutinesApi::class) override fun onReceive(context: Context, intent: Intent) { super.onReceive(context, intent) Timber.d("Receiving intent... ${intent.toUri(0)}") 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 188cf5fc..b44bb4b2 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 @@ -230,8 +230,8 @@ class MainActivity : BaseActivity(), MainVie .setIcon(R.drawable.ic_main_more) } selectedItemId = startMenuIndex - setOnNavigationItemSelectedListener { presenter.onTabSelected(it.itemId, false) } - setOnNavigationItemReselectedListener { presenter.onTabSelected(it.itemId, true) } + setOnItemSelectedListener { presenter.onTabSelected(it.itemId, false) } + setOnItemReselectedListener { presenter.onTabSelected(it.itemId, true) } } with(navController) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessageActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessageActivity.kt index f169de9f..d49c9b83 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessageActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessageActivity.kt @@ -23,8 +23,6 @@ import io.github.wulkanowy.ui.base.BaseActivity import io.github.wulkanowy.utils.dpToPx import io.github.wulkanowy.utils.hideSoftInput import io.github.wulkanowy.utils.showSoftInput -import kotlinx.coroutines.FlowPreview -import timber.log.Timber import javax.inject.Inject @AndroidEntryPoint @@ -67,7 +65,6 @@ class SendMessageActivity : BaseActivity presenter.clearDraft() 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 b1cf3b64..f6154218 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 @@ -1,6 +1,5 @@ package io.github.wulkanowy.ui.modules.message.send -import io.github.wulkanowy.R import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.Recipient @@ -41,7 +40,6 @@ class SendMessagePresenter @Inject constructor( private val messageUpdateChannel = Channel() - @FlowPreview fun onAttachView(view: SendMessageView, message: Message?, reply: Boolean?) { super.onAttachView(view) view.initView() 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 dfd95b72..54ee74eb 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 @@ -21,7 +21,6 @@ import io.github.wulkanowy.ui.modules.message.MessageFragment import io.github.wulkanowy.ui.modules.message.preview.MessagePreviewFragment import io.github.wulkanowy.ui.widgets.DividerItemDecoration import io.github.wulkanowy.utils.getThemeAttrColor -import kotlinx.coroutines.FlowPreview import javax.inject.Inject @AndroidEntryPoint @@ -58,7 +57,6 @@ class MessageTabFragment : BaseFragment(R.layout.frag setHasOptionsMenu(true) } - @FlowPreview override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) binding = FragmentMessageTabBinding.bind(view) 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 93c7408f..3e5e09b4 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 @@ -44,7 +44,6 @@ class MessageTabPresenter @Inject constructor( private val searchChannel = Channel() - @FlowPreview fun onAttachView(view: MessageTabView, folder: MessageFolder) { super.onAttachView(view) view.initView() @@ -178,7 +177,7 @@ class MessageTabPresenter @Inject constructor( } } - @FlowPreview + @OptIn(FlowPreview::class) private fun initializeSearchStream() { launch { searchChannel.consumeAsFlow() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetProvider.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetProvider.kt index 733eb17f..f9079b5f 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetProvider.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetProvider.kt @@ -35,6 +35,7 @@ import io.github.wulkanowy.utils.nextSchoolDay import io.github.wulkanowy.utils.nickOrName import io.github.wulkanowy.utils.previousSchoolDay import io.github.wulkanowy.utils.toFormattedString +import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import timber.log.Timber @@ -81,6 +82,7 @@ class TimetableWidgetProvider : HiltBroadcastReceiver() { "timetable_widget_current_theme_$appWidgetId" } + @OptIn(DelicateCoroutinesApi::class) override fun onReceive(context: Context, intent: Intent) { super.onReceive(context, intent) GlobalScope.launch { diff --git a/build.gradle b/build.gradle index d169291d..afe0285c 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ buildscript { ext { - kotlin_version = '1.5.21' + kotlin_version = '1.5.30' about_libraries = '8.9.1' hilt_version = "2.38.1" } diff --git a/gradle.properties b/gradle.properties index 998e3195..38603830 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,17 +4,19 @@ # any settings specified in this file. # For more details on how to configure your build environment visit # http://www.gradle.org/docs/current/userguide/build_environment.html - +# # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. org.gradle.jvmargs=-Xmx1536m +# android.enableJetifier=true android.useAndroidX=true +# kotlin.code.style=official -kapt.incremental.apt=true +# kapt.use.worker.api=true kapt.include.compile.classpath=false - +# # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects From 4a38a0be709ac180cf8fb780da81b2852ecc2098 Mon Sep 17 00:00:00 2001 From: Piotr Romanowski Date: Thu, 26 Aug 2021 19:35:41 +0200 Subject: [PATCH 174/186] Add change password snackbar (#1336) --- .../github/wulkanowy/ui/base/BaseActivity.kt | 21 ++++++++++++------- .../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 | 5 +++++ .../settings/advanced/AdvancedFragment.kt | 4 ++++ .../settings/appearance/AppearanceFragment.kt | 4 ++++ .../notifications/NotificationsFragment.kt | 4 ++++ .../ui/modules/settings/sync/SyncFragment.kt | 4 ++++ .../wulkanowy/utils/ContextExtension.kt | 2 +- app/src/main/res/values/strings.xml | 1 + 12 files changed, 47 insertions(+), 9 deletions(-) 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 9b93953d..0521b4a0 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 @@ -3,8 +3,6 @@ package io.github.wulkanowy.ui.base import android.app.ActivityManager import android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK import android.content.Intent.FLAG_ACTIVITY_NEW_TASK -import android.os.Build.VERSION.SDK_INT -import android.os.Build.VERSION_CODES.LOLLIPOP import android.os.Bundle import android.view.View import android.widget.Toast @@ -19,6 +17,7 @@ import io.github.wulkanowy.ui.modules.login.LoginActivity import io.github.wulkanowy.utils.FragmentLifecycleLogger import io.github.wulkanowy.utils.getThemeAttrColor import io.github.wulkanowy.utils.lifecycleAwareVariable +import io.github.wulkanowy.utils.openInternetBrowser import javax.inject.Inject abstract class BaseActivity, VB : ViewBinding> : @@ -43,12 +42,10 @@ abstract class BaseActivity, VB : ViewBinding> : supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleLogger, true) AppCompatDelegate.setCompatVectorFromResourcesEnabled(true) - if (SDK_INT >= LOLLIPOP) { - @Suppress("DEPRECATION") - setTaskDescription( - ActivityManager.TaskDescription(null, null, getThemeAttrColor(R.attr.colorSurface)) - ) - } + @Suppress("DEPRECATION") + setTaskDescription( + ActivityManager.TaskDescription(null, null, getThemeAttrColor(R.attr.colorSurface)) + ) } override fun showError(text: String, error: Throwable) { @@ -77,6 +74,14 @@ abstract class BaseActivity, VB : ViewBinding> : .show() } + override fun showChangePasswordSnackbar(redirectUrl: String) { + messageContainer?.let { + Snackbar.make(it, R.string.error_password_change_required, LENGTH_LONG) + .setAction(R.string.all_change) { openInternetBrowser(redirectUrl) } + .show() + } + } + override fun openClearLoginView() { startActivity(LoginActivity.getStartIntent(this) .apply { addFlags(FLAG_ACTIVITY_CLEAR_TASK or FLAG_ACTIVITY_NEW_TASK) }) 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 1c31976e..25a53395 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 @@ -30,6 +30,10 @@ abstract class BaseDialogFragment : DialogFragment(), BaseView (activity as? BaseActivity<*, *>)?.openClearLoginView() } + override fun showChangePasswordSnackbar(redirectUrl: String) { + (activity as? BaseActivity<*, *>)?.showChangePasswordSnackbar(redirectUrl) + } + override fun showErrorDetailsDialog(error: Throwable) { ErrorDialog.newInstance(error).show(childFragmentManager, error.toString()) } 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 c6a2e1d1..dbc5af3a 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 @@ -45,4 +45,8 @@ abstract class BaseFragment(@LayoutRes layoutId: Int) : Fragme override fun openClearLoginView() { (activity as? BaseActivity<*, *>)?.openClearLoginView() } + + override fun showChangePasswordSnackbar(redirectUrl: String) { + (activity as? BaseActivity<*, *>)?.showChangePasswordSnackbar(redirectUrl) + } } 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 b222b0ab..be530049 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 @@ -34,6 +34,7 @@ open class BasePresenter( showErrorMessage = view::showError onSessionExpired = view::showExpiredDialog 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 0f4df92c..d3165ea4 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 @@ -11,4 +11,6 @@ interface BaseView { fun openClearLoginView() fun showErrorDetailsDialog(error: Throwable) + + fun showChangePasswordSnackbar(redirectUrl: String) } 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 34dd3ec1..7c32ef18 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 @@ -3,6 +3,7 @@ package io.github.wulkanowy.ui.base import android.content.res.Resources import io.github.wulkanowy.data.exceptions.NoCurrentStudentException import io.github.wulkanowy.sdk.scrapper.login.BadCredentialsException +import io.github.wulkanowy.sdk.scrapper.login.PasswordChangeRequiredException import io.github.wulkanowy.utils.getString import io.github.wulkanowy.utils.security.ScramblerException import timber.log.Timber @@ -16,6 +17,8 @@ open class ErrorHandler @Inject constructor(protected val resources: Resources) var onNoCurrentStudent: () -> Unit = {} + var onPasswordChangeRequired: (String) -> Unit = {} + fun dispatch(error: Throwable) { Timber.e(error, "An exception occurred while the Wulkanowy was running") proceed(error) @@ -24,6 +27,7 @@ open class ErrorHandler @Inject constructor(protected val resources: Resources) protected open fun proceed(error: Throwable) { showErrorMessage(resources.getString(error), error) when (error) { + is PasswordChangeRequiredException -> onPasswordChangeRequired(error.redirectUrl) is ScramblerException, is BadCredentialsException -> onSessionExpired() is NoCurrentStudentException -> onNoCurrentStudent() } @@ -33,5 +37,6 @@ open class ErrorHandler @Inject constructor(protected val resources: Resources) showErrorMessage = { _, _ -> } onSessionExpired = {} onNoCurrentStudent = {} + onPasswordChangeRequired = {} } } 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 524d7ba6..9f29731f 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 @@ -58,6 +58,10 @@ class AdvancedFragment : PreferenceFragmentCompat(), (activity as? BaseActivity<*, *>)?.showExpiredDialog() } + override fun showChangePasswordSnackbar(redirectUrl: String) { + (activity as? BaseActivity<*, *>)?.showChangePasswordSnackbar(redirectUrl) + } + override fun openClearLoginView() { (activity as? BaseActivity<*, *>)?.openClearLoginView() } 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 5ac801dc..a7ee800b 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 @@ -70,6 +70,10 @@ class AppearanceFragment : PreferenceFragmentCompat(), (activity as? BaseActivity<*, *>)?.showExpiredDialog() } + override fun showChangePasswordSnackbar(redirectUrl: String) { + (activity as? BaseActivity<*, *>)?.showChangePasswordSnackbar(redirectUrl) + } + override fun openClearLoginView() { (activity as? BaseActivity<*, *>)?.openClearLoginView() } 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 96d14a1b..0fc7e68e 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 @@ -107,6 +107,10 @@ class NotificationsFragment : PreferenceFragmentCompat(), (activity as? BaseActivity<*, *>)?.showExpiredDialog() } + override fun showChangePasswordSnackbar(redirectUrl: String) { + (activity as? BaseActivity<*, *>)?.showChangePasswordSnackbar(redirectUrl) + } + override fun openClearLoginView() { (activity as? BaseActivity<*, *>)?.openClearLoginView() } 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 34298809..83caa3b0 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 @@ -86,6 +86,10 @@ class SyncFragment : PreferenceFragmentCompat(), (activity as? BaseActivity<*, *>)?.showExpiredDialog() } + override fun showChangePasswordSnackbar(redirectUrl: String) { + (activity as? BaseActivity<*, *>)?.showChangePasswordSnackbar(redirectUrl) + } + override fun openClearLoginView() { (activity as? BaseActivity<*, *>)?.openClearLoginView() } diff --git a/app/src/main/java/io/github/wulkanowy/utils/ContextExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/ContextExtension.kt index be664a47..cb31389e 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/ContextExtension.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/ContextExtension.kt @@ -56,7 +56,7 @@ fun Context.getCompatDrawable(@DrawableRes drawableRes: Int, @ColorRes colorRes: fun Context.getCompatBitmap(@DrawableRes drawableRes: Int, @ColorRes colorRes: Int) = getCompatDrawable(drawableRes, colorRes)?.toBitmap() -fun Context.openInternetBrowser(uri: String, onActivityNotFound: (uri: String) -> Unit) { +fun Context.openInternetBrowser(uri: String, onActivityNotFound: (uri: String) -> Unit = {}) { Intent.parseUri(uri, 0).let { if (it.resolveActivity(packageManager) != null) startActivity(it) else onActivityNotFound(uri) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ee911cfb..39478a2e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -678,6 +678,7 @@ Copied Undo + Change From cebd1aa75d77b53eeb10b116d40fe7fda3154505 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Sat, 28 Aug 2021 12:14:01 +0200 Subject: [PATCH 175/186] Remove lithuanian lang (#1449) --- .../res/values-lt-v29/preferences_values.xml | 9 - .../main/res/values-lt/preferences_values.xml | 48 -- app/src/main/res/values-lt/strings.xml | 548 ------------------ 3 files changed, 605 deletions(-) delete mode 100644 app/src/main/res/values-lt-v29/preferences_values.xml delete mode 100644 app/src/main/res/values-lt/preferences_values.xml delete mode 100644 app/src/main/res/values-lt/strings.xml diff --git a/app/src/main/res/values-lt-v29/preferences_values.xml b/app/src/main/res/values-lt-v29/preferences_values.xml deleted file mode 100644 index 18cbd4cf..00000000 --- a/app/src/main/res/values-lt-v29/preferences_values.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - Sistemos tema - Šviesi - Tamsi - Juoda (AMOLED) - - diff --git a/app/src/main/res/values-lt/preferences_values.xml b/app/src/main/res/values-lt/preferences_values.xml deleted file mode 100644 index 5999a2fd..00000000 --- a/app/src/main/res/values-lt/preferences_values.xml +++ /dev/null @@ -1,48 +0,0 @@ - - - - Šviesi - Tamsi - Juoda (AMOLED) - - - Sistemos kalba - Polski - English - Pусский - Українська - Deutsch - Čeština - Slovenčina - - - 15 minučių - 30 minučių - 1 valandą - 2 valandas - 6 valandas - 12 valandas - 24 valandas - - - 0,00 - 0,25 - 0,33 - 0,5 - 0,75 - - - Pagal abėcėlę - Pagal datą - - - Dzienniczek+ - Wulkanowy - Laipsnio spalvos registre - - - Antrojo semestro laipsnių vidurkis - Abiejų semestrų laipsnių vidurkis - Visų metų laipsnių vidurkis - - diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml deleted file mode 100644 index cf9d44d1..00000000 --- a/app/src/main/res/values-lt/strings.xml +++ /dev/null @@ -1,548 +0,0 @@ - - - - Prisijungti - Wulkanowy - Laipsnis - Attendance - Exams - Timetable - Nustatymai - Daugiau - Apie - Peržiūrėti žurnalą - Prisidėję - Licencijos - Žinutės - Nauja žinutė - Pastabos ir pasiekimai - Namų darbai - Paskyros valdymas - Pasirinkite paskyrą - Paskyros informacija - Studentų informacija - - Semestras %1$d, %2$d/%3$d - - Sign in with the student or parent account - Enter the symbol from the register page - Vartotojo vardas - El. paštas - Prisijunkite, PESEL arba el. paštas - Slaptažodis - UONET+ register variant - Mobile API - Scraper - Hibridas - Token - PIN - Symbol - Prisijungti - Slaptažodis per trumpas - Login details are incorrect. Make sure the correct UONET+ register variation is selected in the field below - Netinkamas PIN kodas - Invalid token - Token expired - Neteisingas el. paštas - Use the assigned login instead of email - Use the assigned login or email in @%1$s - Netinkamas simbolis - Student not found. Validate the symbol and the chosen variation of the UONET+ register - This field is required - Selected student is already logged in - The symbol can be found on the register page in Uczeń → Dostęp Mobilny → Zarejestruj urządzenie mobilne.\n\nMake sure that you have set the appropriate register variant in the UONET+ register variant field on the previous screen. Wulkanowy does not detect pre-school students at the moment - Select students to log in to the application - Papildomi nustatymai - 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 - Privatumo politika - Trouble signing in? Contact us! - El. paštas - Discord - Send email - Make sure you select the correct UONET+ register variation! - I forgot my password - Recover your account - Atkurti - Student is already signed in - - Paskyros valdymas - Prisijungti - Sesijos laikas baigėsi - Sesijos laikas baigėsi, prašome prisijungti iš naujo - - Laipsnis - Semestras %d - Keisti semestrą - Jokių laipsnių - Svoris - Svoris: %s - Pastabos - Number of new ratings: %1$d - Vidurkis: %1$.2f - Puantai: %s - Jokiu vidurkis - Bendras punktų skaičius - Galutinis laipsnis - Numatomas laipsnis - Calculated average - Final average - Summary - Class - Mark as read - Partial - Semester - Points - Legend - Average: %1$s - Class - Student - - %d grade - %d grades - %d grades - %d grades - - - New grade - New grades - New grades - New grades - - - New predicted grade - New predicted grades - New predicted grades - New predicted grades - - - New final grade - New final grades - New final grades - New final grades - - - You received %1$d grade - You received %1$d grades - You received %1$d grades - You received %1$d grades - - - You received %1$d predicted grade - You received %1$d predicted grades - You received %1$d predicted grades - You received %1$d predicted grades - - - You received %1$d final grade - You received %1$d final grades - You received %1$d final grades - 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 - - Completed lessons - Show completed lessons - No info about completed lessons - Topic - Absence - Resources - - Additional lessons - Show additional lessons - No info about additional lessons - - 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 - - Total - - No exams this week - Type - Entry date - - New exam - New exams - New exams - New exams - - - %d exam - %d exams - %d exams - %d exams - - - Inbox - Sent - Trash - (no subject) - No messages - From: - To: - Date: %s - Reply - Forward - Delete - Move to trash - Delete permanently - Message deleted successfully - 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 - - %d message - %d messages - %d messages - %d messages - - - New message - New messages - New messages - New messages - - - You received %1$d message - You received %1$d messages - You received %1$d messages - You received %1$d messages - - - No info about notes - Points - - %d note - %d notes - %d notes - %d notes - - - New note - New notes - New notes - New notes - - - You received %1$d note - You received %1$d notes - You received %1$d notes - You received %1$d notes - - - - %d praise - %d praises - %d praises - %d praises - - - New praise - New praises - New praises - New praises - - - You received %1$d praise - You received %1$d praises - You received %1$d praises - You received %1$d praises - - - - %d neutral note - %d neutral notes - %d neutral notes - %d neutral notes - - - New neutral note - New neutral notes - New neutral notes - New neutral notes - - - You received %1$d neutral note - You received %1$d neutral notes - You received %1$d neutral notes - You received %1$d neutral notes - - - No info about homework - Mark as done - Mark as undone - Attachments - - New homework - New homework - New homework - New homework - - - %d homework - %d 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 - - 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 - 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 - - Nick - Add nick - Choose avatar color - - Share logs - Refresh - - 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 - - No lessons - Choose theme - Light - Dark - System Theme - - App appearance & behavior - Default view - Calculation of the end-of-year average - Force average calculation by app - Show presence - Theme - Expand grades - Mark current lesson - Show groups next to subjects - Show chart list in class grades - Show subjects without grades - Grades color scheme - Subjects sorting - Language - Notifications - Show notifications - Show upcoming lesson notifications - 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. - Go to settings - Show debug notifications - Synchronization is disabled - Synchronization - Automatic update - Suspended on holidays - Updates interval - Wi-Fi only - Sync now - Synced! - Sync failed - Sync in progress - Value of the plus - Value of the minus - Reply with message history - Show arithmetic average when no weights provided - Advanced - Appearance & Behavior - Notifications - Synchronization - Grades - Attendance - Timetable - Grades - 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, licenses - - New grades - New homework - New exams - Lucky number - New messages - New notes - Push notifications - Upcoming lessons - Debug - - Black - Red - Blue - Green - Purple - No color - - Copied - Undo - - Download of updates has started… - An update has just been downloaded. - Restart - Update failed! Wulkanowy may not function properly. Consider updating - - No internet connection - 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 - An unexpected error occurred - Feature disabled by your school - Feature not available. Login in a mode other than Mobile API - From 55518cb0449f1073b056d0778a8f2618a924d7bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Sat, 28 Aug 2021 21:43:10 +0200 Subject: [PATCH 176/186] Add missing dashboard item in default view settings (#1450) --- .../repositories/PreferencesRepository.kt | 1 + .../wulkanowy/ui/modules/exam/ExamFragment.kt | 10 +----- .../ui/modules/exam/ExamPresenter.kt | 10 ------ .../wulkanowy/ui/modules/exam/ExamView.kt | 2 -- .../wulkanowy/ui/modules/main/MainActivity.kt | 6 ++-- .../ui/modules/main/MainPresenter.kt | 10 +++--- .../wulkanowy/ui/modules/main/MainView.kt | 36 ++++++++++--------- .../wulkanowy/utils/FragmentExtension.kt | 4 +-- app/src/main/res/values/preferences_keys.xml | 2 +- .../main/res/values/preferences_values.xml | 2 +- 10 files changed, 33 insertions(+), 50 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/PreferencesRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/PreferencesRepository.kt index e725c42a..7b3cd67b 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/PreferencesRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/PreferencesRepository.kt @@ -30,6 +30,7 @@ class PreferencesRepository @Inject constructor( @ApplicationContext val context: Context, moshi: Moshi ) { + @OptIn(ExperimentalStdlibApi::class) private val dashboardItemsPositionAdapter: JsonAdapter> = moshi.adapter() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamFragment.kt index 0940b0bd..fb7939bc 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamFragment.kt @@ -20,7 +20,7 @@ import javax.inject.Inject @AndroidEntryPoint class ExamFragment : BaseFragment(R.layout.fragment_exam), ExamView, - MainView.MainChildView, MainView.TitledView { + MainView.TitledView { @Inject lateinit var presenter: ExamPresenter @@ -90,14 +90,6 @@ class ExamFragment : BaseFragment(R.layout.fragment_exam), } } - override fun resetView() { - binding.examRecycler.scrollToPosition(0) - } - - override fun onFragmentReselected() { - if (::presenter.isInitialized) presenter.onViewReselected() - } - override fun showEmpty(show: Boolean) { binding.examEmpty.visibility = if (show) VISIBLE else GONE } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamPresenter.kt index b70a648f..582641fc 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamPresenter.kt @@ -82,16 +82,6 @@ class ExamPresenter @Inject constructor( view?.showExamDialog(exam) } - fun onViewReselected() { - Timber.i("Exam view is reselected") - baseDate.also { - if (currentDate != it) { - reloadView(it) - loadData() - } else if (view?.isViewEmpty == false) view?.resetView() - } - } - private fun setBaseDateOnHolidays() { flow { val student = studentRepository.getCurrentStudent() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamView.kt index ac1a87fe..45b9e788 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamView.kt @@ -17,8 +17,6 @@ interface ExamView : BaseView { fun showRefresh(show: Boolean) - fun resetView() - fun showEmpty(show: Boolean) fun showErrorView(show: Boolean) 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 b44bb4b2..cf31040a 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 @@ -34,6 +34,7 @@ import io.github.wulkanowy.ui.modules.account.accountquick.AccountQuickDialog import io.github.wulkanowy.ui.modules.attendance.AttendanceFragment import io.github.wulkanowy.ui.modules.conference.ConferenceFragment import io.github.wulkanowy.ui.modules.dashboard.DashboardFragment +import io.github.wulkanowy.ui.modules.exam.ExamFragment import io.github.wulkanowy.ui.modules.grade.GradeFragment import io.github.wulkanowy.ui.modules.homework.HomeworkFragment import io.github.wulkanowy.ui.modules.luckynumber.LuckyNumberFragment @@ -107,11 +108,12 @@ class MainActivity : BaseActivity(), MainVie private val moreMenuFragments = mapOf( MainView.Section.MESSAGE.id to MessageFragment.newInstance(), + MainView.Section.EXAM.id to ExamFragment.newInstance(), MainView.Section.HOMEWORK.id to HomeworkFragment.newInstance(), MainView.Section.NOTE.id to NoteFragment.newInstance(), - MainView.Section.LUCKY_NUMBER.id to LuckyNumberFragment.newInstance(), - MainView.Section.SCHOOL_ANNOUNCEMENT.id to SchoolAnnouncementFragment.newInstance(), MainView.Section.CONFERENCE.id to ConferenceFragment.newInstance(), + MainView.Section.SCHOOL_ANNOUNCEMENT.id to SchoolAnnouncementFragment.newInstance(), + MainView.Section.LUCKY_NUMBER.id to LuckyNumberFragment.newInstance(), ) @SuppressLint("NewApi") diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt index c0eb2b11..a2fb07f6 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt @@ -118,11 +118,9 @@ class MainPresenter @Inject constructor( view?.showStudentAvatar(currentStudent) } - private fun getProperViewIndexes(initMenu: MainView.Section?): Pair { - return when (initMenu?.id) { - in 0..3 -> initMenu!!.id to -1 - in 4..10 -> 4 to initMenu!!.id - else -> prefRepository.startMenuIndex to -1 - } + private fun getProperViewIndexes(initMenu: MainView.Section?) = when (initMenu?.id) { + in 0..3 -> initMenu!!.id to -1 + in 4..100 -> 4 to initMenu!!.id + else -> prefRepository.startMenuIndex to -1 } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainView.kt index 44f37d50..7402d37e 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainView.kt @@ -56,22 +56,24 @@ interface MainView : BaseView { set(_) {} } - enum class Section(val id: Int) { - GRADE(0), - ATTENDANCE(1), - EXAM(2), - TIMETABLE(3), - MORE(4), - MESSAGE(5), - HOMEWORK(6), - NOTE(7), - LUCKY_NUMBER(8), - SETTINGS(9), - ABOUT(10), - SCHOOL(11), - ACCOUNT(12), - STUDENT_INFO(13), - CONFERENCE(14), - SCHOOL_ANNOUNCEMENT(15) + enum class Section { + DASHBOARD, + GRADE, + ATTENDANCE, + TIMETABLE, + MORE, + MESSAGE, + EXAM, + HOMEWORK, + NOTE, + CONFERENCE, + SCHOOL_ANNOUNCEMENT, + SCHOOL, + LUCKY_NUMBER, + ACCOUNT, + STUDENT_INFO, + SETTINGS; + + val id get() = ordinal } } diff --git a/app/src/main/java/io/github/wulkanowy/utils/FragmentExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/FragmentExtension.kt index 4651dffd..210a6209 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/FragmentExtension.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/FragmentExtension.kt @@ -1,11 +1,11 @@ package io.github.wulkanowy.utils import androidx.fragment.app.Fragment -import io.github.wulkanowy.ui.modules.about.AboutFragment import io.github.wulkanowy.ui.modules.account.AccountFragment import io.github.wulkanowy.ui.modules.account.accountdetails.AccountDetailsFragment import io.github.wulkanowy.ui.modules.attendance.AttendanceFragment import io.github.wulkanowy.ui.modules.conference.ConferenceFragment +import io.github.wulkanowy.ui.modules.dashboard.DashboardFragment import io.github.wulkanowy.ui.modules.exam.ExamFragment import io.github.wulkanowy.ui.modules.grade.GradeFragment import io.github.wulkanowy.ui.modules.homework.HomeworkFragment @@ -32,13 +32,13 @@ fun Fragment.toSection(): MainView.Section? { is NoteFragment -> MainView.Section.NOTE is LuckyNumberFragment -> MainView.Section.LUCKY_NUMBER is SettingsFragment -> MainView.Section.SETTINGS - is AboutFragment -> MainView.Section.ABOUT is SchoolAndTeachersFragment -> MainView.Section.SCHOOL is AccountFragment -> MainView.Section.ACCOUNT is AccountDetailsFragment -> MainView.Section.ACCOUNT is StudentInfoFragment -> MainView.Section.STUDENT_INFO is ConferenceFragment -> MainView.Section.CONFERENCE is SchoolAnnouncementFragment -> MainView.Section.SCHOOL_ANNOUNCEMENT + is DashboardFragment -> MainView.Section.DASHBOARD else -> null } } diff --git a/app/src/main/res/values/preferences_keys.xml b/app/src/main/res/values/preferences_keys.xml index aa60bb7e..09dac700 100644 --- a/app/src/main/res/values/preferences_keys.xml +++ b/app/src/main/res/values/preferences_keys.xml @@ -1,6 +1,6 @@ - start_menu + default_menu_index attendance_present app_theme dashboard_tiles diff --git a/app/src/main/res/values/preferences_values.xml b/app/src/main/res/values/preferences_values.xml index f5a2b864..9c1a0421 100644 --- a/app/src/main/res/values/preferences_values.xml +++ b/app/src/main/res/values/preferences_values.xml @@ -1,9 +1,9 @@ + @string/dashboard_title @string/grade_title @string/attendance_title - @string/exam_title @string/timetable_title From 04c727a0c8a5a8a5ed5476a2439eceb0b23d7249 Mon Sep 17 00:00:00 2001 From: Tomasz F Date: Sun, 29 Aug 2021 00:41:58 +0200 Subject: [PATCH 177/186] Exams and homework notification fixes (#1292) --- .../wulkanowy/data/repositories/ExamRepository.kt | 6 ++++-- .../data/repositories/HomeworkRepository.kt | 6 ++++-- .../sync/notifications/NewConferenceNotification.kt | 11 ++++++++--- .../sync/notifications/NewExamNotification.kt | 13 +++++++++---- .../sync/notifications/NewHomeworkNotification.kt | 13 +++++++++---- .../modules/debug/notification/mock/conference.kt | 2 +- app/src/main/res/values/strings.xml | 8 ++++++++ 7 files changed, 43 insertions(+), 16 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 9406c77c..93d5a47c 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 @@ -40,8 +40,10 @@ class ExamRepository @Inject constructor( ) = networkBoundResource( mutex = saveFetchResultMutex, shouldFetch = { - it.isEmpty() || forceRefresh - || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester, start, end)) + val isShouldBeRefreshed = refreshHelper.isShouldBeRefreshed( + key = getRefreshKey(cacheKey, semester, start, end) + ) + it.isEmpty() || forceRefresh || isShouldBeRefreshed }, query = { examDb.loadAll( 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 476a810c..23dd74c2 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 @@ -36,8 +36,10 @@ class HomeworkRepository @Inject constructor( ) = networkBoundResource( mutex = saveFetchResultMutex, shouldFetch = { - it.isEmpty() || forceRefresh || - refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester, start, end)) + val isShouldBeRefreshed = refreshHelper.isShouldBeRefreshed( + key = getRefreshKey(cacheKey, semester, start, end) + ) + it.isEmpty() || forceRefresh || isShouldBeRefreshed }, query = { homeworkDb.loadAll( diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewConferenceNotification.kt b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewConferenceNotification.kt index 25648b93..fda2922f 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewConferenceNotification.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewConferenceNotification.kt @@ -8,6 +8,8 @@ import io.github.wulkanowy.data.db.entities.Conference import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.pojos.MultipleNotifications import io.github.wulkanowy.ui.modules.main.MainView +import io.github.wulkanowy.utils.toFormattedString +import java.time.LocalDateTime import javax.inject.Inject class NewConferenceNotification @Inject constructor( @@ -16,6 +18,11 @@ class NewConferenceNotification @Inject constructor( ) : BaseNotification(context, notificationManager) { fun notify(items: List, student: Student) { + val today = LocalDateTime.now() + val lines = items.filter { !it.date.isBefore(today) }.map { + "${it.date.toFormattedString("dd.MM")} - ${it.title}: ${it.subject}" + }.ifEmpty { return } + val notification = MultipleNotifications( type = NotificationType.NEW_CONFERENCE, icon = R.drawable.ic_more_conferences, @@ -23,9 +30,7 @@ class NewConferenceNotification @Inject constructor( contentStringRes = R.plurals.conference_notify_new_items, summaryStringRes = R.plurals.conference_number_item, startMenu = MainView.Section.CONFERENCE, - lines = items.map { - "${it.title}: ${it.subject}" - } + lines = lines ) sendNotification(notification, student) diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewExamNotification.kt b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewExamNotification.kt index a41c44e5..d493c4d2 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewExamNotification.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewExamNotification.kt @@ -8,6 +8,8 @@ import io.github.wulkanowy.data.db.entities.Exam import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.pojos.MultipleNotifications import io.github.wulkanowy.ui.modules.main.MainView +import io.github.wulkanowy.utils.toFormattedString +import java.time.LocalDate import javax.inject.Inject class NewExamNotification @Inject constructor( @@ -16,16 +18,19 @@ class NewExamNotification @Inject constructor( ) : BaseNotification(context, notificationManager) { fun notify(items: List, student: Student) { + val today = LocalDate.now() + val lines = items.filter { !it.date.isBefore(today) }.map { + "${it.date.toFormattedString("dd.MM")} - ${it.subject}: ${it.description}" + }.ifEmpty { return } + val notification = MultipleNotifications( type = NotificationType.NEW_EXAM, icon = R.drawable.ic_main_exam, titleStringRes = R.plurals.exam_notify_new_item_title, - contentStringRes = R.plurals.exam_notify_new_item_title, // TODO add missing string + contentStringRes = R.plurals.exam_notify_new_item_content, summaryStringRes = R.plurals.exam_number_item, startMenu = MainView.Section.EXAM, - lines = items.map { - "${it.subject}: ${it.description}" - } + lines = lines ) sendNotification(notification, student) diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewHomeworkNotification.kt b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewHomeworkNotification.kt index 844aed97..fe973cad 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewHomeworkNotification.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewHomeworkNotification.kt @@ -8,6 +8,8 @@ import io.github.wulkanowy.data.db.entities.Homework import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.pojos.MultipleNotifications import io.github.wulkanowy.ui.modules.main.MainView +import io.github.wulkanowy.utils.toFormattedString +import java.time.LocalDate import javax.inject.Inject class NewHomeworkNotification @Inject constructor( @@ -16,16 +18,19 @@ class NewHomeworkNotification @Inject constructor( ) : BaseNotification(context, notificationManager) { fun notify(items: List, student: Student) { + val today = LocalDate.now() + val lines = items.filter { !it.date.isBefore(today) }.map { + "${it.date.toFormattedString("dd.MM")} - ${it.subject}: ${it.content}" + }.ifEmpty { return } + val notification = MultipleNotifications( type = NotificationType.NEW_HOMEWORK, icon = R.drawable.ic_more_homework, titleStringRes = R.plurals.homework_notify_new_item_title, - contentStringRes = R.plurals.homework_notify_new_item_title, // todo: you received %d new homework + contentStringRes = R.plurals.homework_notify_new_item_content, summaryStringRes = R.plurals.homework_number_item, startMenu = MainView.Section.HOMEWORK, - lines = items.map { - "${it.subject}: ${it.content}" - } + lines = lines ) sendNotification(notification, student) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/conference.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/conference.kt index 969b1cf2..40af6bfb 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/conference.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/conference.kt @@ -53,6 +53,6 @@ private fun generateConference(title: String, subject: String) = Conference( diaryId = 0, agenda = "", conferenceId = 0, - date = LocalDateTime.now(), + date = LocalDateTime.now().plusMinutes(10), presentOnConference = "", ) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 39478a2e..ea1187c8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -206,6 +206,10 @@ New exam New exams + + You received %d new exam + You received %d new exams + %d exam %d exams @@ -312,6 +316,10 @@ New homework New homework + + You received %d new homework + You received %d new homework + %d homework %d homework From 765f8a2d1f0a18fb2cc2d48f9da7d3fb5d7602b6 Mon Sep 17 00:00:00 2001 From: Mateusz Idziejczak Date: Sun, 29 Aug 2021 00:43:58 +0200 Subject: [PATCH 178/186] Add in app review (#1435) --- app/build.gradle | 1 + .../wulkanowy/utils/InAppReviewHelper.kt | 17 +++++++++++ .../wulkanowy/utils/InAppReviewHelper.kt | 17 +++++++++++ .../repositories/PreferencesRepository.kt | 21 ++++++++++++++ .../wulkanowy/ui/modules/main/MainActivity.kt | 8 ++++++ .../ui/modules/main/MainPresenter.kt | 17 +++++++++++ .../wulkanowy/ui/modules/main/MainView.kt | 2 ++ .../wulkanowy/utils/InAppReviewHelper.kt | 28 +++++++++++++++++++ 8 files changed, 111 insertions(+) create mode 100644 app/src/fdroid/java/io/github/wulkanowy/utils/InAppReviewHelper.kt create mode 100644 app/src/hms/java/io/github/wulkanowy/utils/InAppReviewHelper.kt create mode 100644 app/src/play/java/io/github/wulkanowy/utils/InAppReviewHelper.kt diff --git a/app/build.gradle b/app/build.gradle index d5fccac6..c05aa034 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -215,6 +215,7 @@ dependencies { playImplementation 'com.google.firebase:firebase-analytics-ktx' playImplementation 'com.google.firebase:firebase-messaging:' playImplementation 'com.google.firebase:firebase-crashlytics:' + playImplementation 'com.google.android.play:core:1.10.0' playImplementation 'com.google.android.play:core-ktx:1.8.1' hmsImplementation 'com.huawei.hms:hianalytics:6.1.1.300' diff --git a/app/src/fdroid/java/io/github/wulkanowy/utils/InAppReviewHelper.kt b/app/src/fdroid/java/io/github/wulkanowy/utils/InAppReviewHelper.kt new file mode 100644 index 00000000..d052b54b --- /dev/null +++ b/app/src/fdroid/java/io/github/wulkanowy/utils/InAppReviewHelper.kt @@ -0,0 +1,17 @@ +package io.github.wulkanowy.utils + +import android.content.Context +import dagger.hilt.android.qualifiers.ApplicationContext +import io.github.wulkanowy.ui.modules.main.MainActivity +import javax.inject.Singleton +import javax.inject.Inject + +@Singleton +class InAppReviewHelper @Inject constructor( + @ApplicationContext private val context: Context +) { + + fun showInAppReview(activity: MainActivity) { + // do nothing + } +} \ No newline at end of file diff --git a/app/src/hms/java/io/github/wulkanowy/utils/InAppReviewHelper.kt b/app/src/hms/java/io/github/wulkanowy/utils/InAppReviewHelper.kt new file mode 100644 index 00000000..fb9bcae6 --- /dev/null +++ b/app/src/hms/java/io/github/wulkanowy/utils/InAppReviewHelper.kt @@ -0,0 +1,17 @@ +package io.github.wulkanowy.utils + +import android.content.Context +import dagger.hilt.android.qualifiers.ApplicationContext +import io.github.wulkanowy.ui.modules.main.MainActivity +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class InAppReviewHelper @Inject constructor( + @ApplicationContext private val context: Context +) { + + fun showInAppReview(activity: MainActivity) { + // do nothing + } +} \ No newline at end of file diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/PreferencesRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/PreferencesRepository.kt index 7b3cd67b..bc8100f2 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/PreferencesRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/PreferencesRepository.kt @@ -10,14 +10,17 @@ import com.squareup.moshi.Moshi import com.squareup.moshi.adapter import dagger.hilt.android.qualifiers.ApplicationContext import io.github.wulkanowy.R +import io.github.wulkanowy.sdk.toLocalDate import io.github.wulkanowy.ui.modules.dashboard.DashboardItem import io.github.wulkanowy.ui.modules.grade.GradeAverageMode import io.github.wulkanowy.ui.modules.grade.GradeSortingMode +import io.github.wulkanowy.utils.toTimestamp import io.github.wulkanowy.utils.toLocalDateTime import io.github.wulkanowy.utils.toTimestamp import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map +import java.time.LocalDate import java.time.LocalDateTime import javax.inject.Inject import javax.inject.Singleton @@ -222,6 +225,18 @@ class PreferencesRepository @Inject constructor( return flowSharedPref.getStringSet(prefKey, defaultSet) } + var inAppReviewCount: Int + get() = sharedPref.getInt(PREF_KEY_IN_APP_REVIEW_COUNT, 0) + set(value) = sharedPref.edit().putInt(PREF_KEY_IN_APP_REVIEW_COUNT, value).apply() + + var inAppReviewDate: LocalDate? + get() = sharedPref.getLong(PREF_KEY_IN_APP_REVIEW_DATE, 0).takeIf { it != 0L }?.toLocalDate() + set(value) = sharedPref.edit().putLong(PREF_KEY_IN_APP_REVIEW_DATE, value!!.toTimestamp()).apply() + + var isAppReviewDone: Boolean + get() = sharedPref.getBoolean(PREF_KEY_IN_APP_REVIEW_DONE, false) + set(value) = sharedPref.edit().putBoolean(PREF_KEY_IN_APP_REVIEW_DONE, value).apply() + private fun getLong(id: Int, default: Int) = getLong(context.getString(id), default) private fun getLong(id: String, default: Int) = @@ -240,5 +255,11 @@ class PreferencesRepository @Inject constructor( private companion object { private const val PREF_KEY_DASHBOARD_ITEMS_POSITION = "dashboard_items_position" + + private const val PREF_KEY_IN_APP_REVIEW_COUNT = "in_app_review_count" + + private const val PREF_KEY_IN_APP_REVIEW_DATE = "in_app_review_date" + + private const val PREF_KEY_IN_APP_REVIEW_DONE = "in_app_review_done" } } 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 cf31040a..d758ac0d 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 @@ -45,6 +45,7 @@ import io.github.wulkanowy.ui.modules.schoolannouncement.SchoolAnnouncementFragm import io.github.wulkanowy.ui.modules.timetable.TimetableFragment import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.AppInfo +import io.github.wulkanowy.utils.InAppReviewHelper import io.github.wulkanowy.utils.UpdateHelper import io.github.wulkanowy.utils.createNameInitialsDrawable import io.github.wulkanowy.utils.dpToPx @@ -68,6 +69,9 @@ class MainActivity : BaseActivity(), MainVie @Inject lateinit var updateHelper: UpdateHelper + @Inject + lateinit var inAppReviewHelper: InAppReviewHelper + @Inject lateinit var appInfo: AppInfo @@ -362,6 +366,10 @@ class MainActivity : BaseActivity(), MainVie } } + override fun showInAppReview() { + inAppReviewHelper.showInAppReview(this) + } + override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) navController.onSaveInstanceState(outState) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt index a2fb07f6..4805b5a1 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt @@ -14,6 +14,7 @@ import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.flowWithResource import kotlinx.coroutines.flow.onEach import timber.log.Timber +import java.time.LocalDate import javax.inject.Inject class MainPresenter @Inject constructor( @@ -106,11 +107,27 @@ class MainPresenter @Inject constructor( } else { notifyMenuViewChanged() switchMenuView(index) + checkInAppReview() true } } == true } + private fun checkInAppReview() { + prefRepository.inAppReviewCount++ + + if (prefRepository.inAppReviewDate == null) { + prefRepository.inAppReviewDate = LocalDate.now() + } + + if (!prefRepository.isAppReviewDone && prefRepository.inAppReviewCount >= 50 && + LocalDate.now().minusDays(14).isAfter(prefRepository.inAppReviewDate) + ) { + view?.showInAppReview() + prefRepository.isAppReviewDone = true + } + } + private fun showCurrentStudentAvatar() { val currentStudent = studentsWitSemesters?.singleOrNull { it.student.isCurrent }?.student ?: return diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainView.kt index 7402d37e..8851f587 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainView.kt @@ -40,6 +40,8 @@ interface MainView : BaseView { fun showStudentAvatar(student: Student) + fun showInAppReview() + interface MainChildView { fun onFragmentReselected() diff --git a/app/src/play/java/io/github/wulkanowy/utils/InAppReviewHelper.kt b/app/src/play/java/io/github/wulkanowy/utils/InAppReviewHelper.kt new file mode 100644 index 00000000..1dcece99 --- /dev/null +++ b/app/src/play/java/io/github/wulkanowy/utils/InAppReviewHelper.kt @@ -0,0 +1,28 @@ +package io.github.wulkanowy.utils + +import android.content.Context +import com.google.android.play.core.review.ReviewManagerFactory +import dagger.hilt.android.qualifiers.ApplicationContext +import io.github.wulkanowy.ui.modules.main.MainActivity +import timber.log.Timber +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class InAppReviewHelper @Inject constructor( + @ApplicationContext private val context: Context +) { + + fun showInAppReview(activity: MainActivity) { + val manager = ReviewManagerFactory.create(context) + val request = manager.requestReviewFlow() + request.addOnCompleteListener { task -> + if (task.isSuccessful) { + val reviewInfo = task.result + manager.launchReviewFlow(activity, reviewInfo) + } else { + Timber.e(task.exception) + } + } + } +} \ No newline at end of file From 2f43b6e55247167852e1f8f4c4396f63c4edec6d Mon Sep 17 00:00:00 2001 From: Daniel Olczyk <44818681+MRmlik12@users.noreply.github.com> Date: Sun, 29 Aug 2021 14:08:48 +0200 Subject: [PATCH 179/186] Change display name for MRmlik12 (#1451) --- app/src/main/assets/contributors.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/assets/contributors.json b/app/src/main/assets/contributors.json index c1e7e209..b2849931 100644 --- a/app/src/main/assets/contributors.json +++ b/app/src/main/assets/contributors.json @@ -36,7 +36,7 @@ "githubUsername": "Luncenok" }, { - "displayName": "MRmlik12", + "displayName": "Daniel Olczyk", "githubUsername": "MRmlik12" }, { From 57d11e825be154c8d40046add5d133e422f12c95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sun, 29 Aug 2021 15:40:28 +0200 Subject: [PATCH 180/186] Update readBy and unreadBy fields during message list fetch (#1452) --- .../data/repositories/MessageRepository.kt | 83 ++++++++-- .../repositories/MessageRepositoryTest.kt | 156 ++++++++++++++++-- 2 files changed, 205 insertions(+), 34 deletions(-) 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 2d70e26e..9977e1d5 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 @@ -4,10 +4,12 @@ import android.content.Context import com.squareup.moshi.Moshi import dagger.hilt.android.qualifiers.ApplicationContext import io.github.wulkanowy.R +import io.github.wulkanowy.data.Resource import io.github.wulkanowy.data.db.SharedPrefProvider import io.github.wulkanowy.data.db.dao.MessageAttachmentDao import io.github.wulkanowy.data.db.dao.MessagesDao import io.github.wulkanowy.data.db.entities.Message +import io.github.wulkanowy.data.db.entities.MessageWithAttachment import io.github.wulkanowy.data.db.entities.Recipient import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student @@ -48,22 +50,54 @@ class MessageRepository @Inject constructor( private val cacheKey = "message" @Suppress("UNUSED_PARAMETER") - fun getMessages(student: Student, semester: Semester, folder: MessageFolder, forceRefresh: Boolean, notify: Boolean = false) = networkBoundResource( + fun getMessages( + student: Student, semester: Semester, + folder: MessageFolder, forceRefresh: Boolean, notify: Boolean = false + ): Flow>> = networkBoundResource( mutex = saveFetchResultMutex, - shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, student, folder)) }, + shouldFetch = { + it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed( + getRefreshKey(cacheKey, student, folder) + ) + }, query = { messagesDb.loadAll(student.id.toInt(), folder.id) }, - fetch = { sdk.init(student).getMessages(Folder.valueOf(folder.name), now().minusMonths(3), now()).mapToEntities(student) }, + fetch = { + sdk.init(student).getMessages(Folder.valueOf(folder.name), now().minusMonths(3), now()) + .mapToEntities(student) + }, saveFetchResult = { old, new -> messagesDb.deleteAll(old uniqueSubtract new) messagesDb.insertAll((new uniqueSubtract old).onEach { it.isNotified = !notify }) + messagesDb.updateAll(getMessagesWithReadByChange(old, new, !notify)) refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student, folder)) } ) - fun getMessage(student: Student, message: Message, markAsRead: Boolean = false) = networkBoundResource( + private fun getMessagesWithReadByChange( + old: List, new: List, + setNotified: Boolean + ): List { + val oldMeta = old.map { Triple(it, it.readBy, it.unreadBy) } + val newMeta = new.map { Triple(it, it.readBy, it.unreadBy) } + + val updatedItems = newMeta uniqueSubtract oldMeta + + return updatedItems.map { + val oldItem = old.find { item -> item.messageId == it.first.messageId } + it.first.apply { + id = oldItem?.id ?: 0 + isNotified = oldItem?.isNotified ?: setNotified + content = oldItem?.content.orEmpty() + } + } + } + + fun getMessage( + student: Student, message: Message, markAsRead: Boolean = false + ): Flow> = networkBoundResource( shouldFetch = { checkNotNull(it, { "This message no longer exist!" }) Timber.d("Message content in db empty: ${it.message.content.isEmpty()}") @@ -71,7 +105,12 @@ class MessageRepository @Inject constructor( }, query = { messagesDb.loadMessageWithAttachment(student.id.toInt(), message.messageId) }, fetch = { - sdk.init(student).getMessageDetails(it!!.message.messageId, message.folderId, markAsRead, message.realId).let { details -> + sdk.init(student).getMessageDetails( + messageId = it!!.message.messageId, + folderId = message.folderId, + read = markAsRead, + id = message.realId + ).let { details -> details.content to details.attachments.mapToEntities() } }, @@ -95,26 +134,34 @@ class MessageRepository @Inject constructor( return messagesDb.updateAll(messages) } - suspend fun sendMessage(student: Student, subject: String, content: String, recipients: List): SentMessage { - return sdk.init(student).sendMessage( - subject = subject, - content = content, - recipients = recipients.mapFromEntities() - ) - } + suspend fun sendMessage( + student: Student, subject: String, content: String, + recipients: List + ): SentMessage = sdk.init(student).sendMessage( + subject = subject, + content = content, + recipients = recipients.mapFromEntities() + ) suspend fun deleteMessage(student: Student, message: Message) { - val isDeleted = sdk.init(student).deleteMessages(listOf(message.messageId), message.folderId) + val isDeleted = sdk.init(student).deleteMessages( + messages = listOf(message.messageId), message.folderId + ) - if (message.folderId != MessageFolder.TRASHED.id) { - if (isDeleted) messagesDb.updateAll(listOf(message.copy(folderId = MessageFolder.TRASHED.id).apply { + if (message.folderId != MessageFolder.TRASHED.id && isDeleted) { + val deletedMessage = message.copy(folderId = MessageFolder.TRASHED.id).apply { id = message.id content = message.content - })) + } + messagesDb.updateAll(listOf(deletedMessage)) } else messagesDb.deleteAll(listOf(message)) } var draftMessage: MessageDraft? - get() = sharedPrefProvider.getString(context.getString(R.string.pref_key_message_send_draft))?.let { MessageDraftJsonAdapter(moshi).fromJson(it) } - set(value) = sharedPrefProvider.putString(context.getString(R.string.pref_key_message_send_draft), value?.let { MessageDraftJsonAdapter(moshi).toJson(it) }) + get() = sharedPrefProvider.getString(context.getString(R.string.pref_key_message_send_draft)) + ?.let { MessageDraftJsonAdapter(moshi).fromJson(it) } + set(value) = sharedPrefProvider.putString( + context.getString(R.string.pref_key_message_send_draft), + value?.let { MessageDraftJsonAdapter(moshi).toJson(it) } + ) } 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 8b72479f..cadc4225 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 @@ -8,19 +8,25 @@ import io.github.wulkanowy.data.db.dao.MessageAttachmentDao import io.github.wulkanowy.data.db.dao.MessagesDao import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.MessageWithAttachment +import io.github.wulkanowy.data.enums.MessageFolder +import io.github.wulkanowy.getSemesterEntity import io.github.wulkanowy.getStudentEntity import io.github.wulkanowy.sdk.Sdk +import io.github.wulkanowy.sdk.pojo.Folder import io.github.wulkanowy.sdk.pojo.MessageDetails +import io.github.wulkanowy.sdk.pojo.Sender import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.toFirstResult 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 kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.toList import kotlinx.coroutines.runBlocking @@ -29,6 +35,7 @@ import org.junit.Before import org.junit.Test import java.net.UnknownHostException import java.time.LocalDateTime +import kotlin.test.assertTrue class MessageRepositoryTest { @@ -52,7 +59,9 @@ class MessageRepositoryTest { private val student = getStudentEntity() - private lateinit var messageRepository: MessageRepository + private val semester = getSemesterEntity() + + private lateinit var repository: MessageRepository @MockK private lateinit var moshi: Moshi @@ -62,15 +71,92 @@ class MessageRepositoryTest { MockKAnnotations.init(this) every { refreshHelper.isShouldBeRefreshed(any()) } returns false - messageRepository = MessageRepository(messageDb, messageAttachmentDao, sdk, context, refreshHelper, sharedPrefProvider, moshi) + repository = MessageRepository( + messagesDb = messageDb, + messageAttachmentDao = messageAttachmentDao, + sdk = sdk, + context = context, + refreshHelper = refreshHelper, + sharedPrefProvider = sharedPrefProvider, + moshi = moshi, + ) + } + + @Test + fun `get messages when read by values was changed on already read message`() = runBlocking { + every { messageDb.loadAll(any(), any()) } returns flow { + val dbMessage = getMessageEntity(3, "", false).apply { + unreadBy = 10 + readBy = 5 + isNotified = true + } + emit(listOf(dbMessage)) + } + coEvery { sdk.getMessages(Folder.RECEIVED, any(), any()) } returns listOf( + getMessageDto(messageId = 3, content = "", unread = false).copy( + unreadBy = 5, + readBy = 10, + ) + ) + coEvery { messageDb.deleteAll(any()) } just Runs + coEvery { messageDb.insertAll(any()) } returns listOf() + + repository.getMessages( + student = student, + semester = semester, + folder = MessageFolder.RECEIVED, + forceRefresh = true, + notify = true, // all new messages will be marked as not notified + ).toFirstResult().data.orEmpty() + + coVerify(exactly = 1) { messageDb.deleteAll(emptyList()) } + coVerify(exactly = 1) { messageDb.insertAll(emptyList()) } + coVerify(exactly = 1) { + messageDb.updateAll(withArg { + assertEquals(1, it.size) + assertEquals(5, it.single().unreadBy) + assertEquals(10, it.single().readBy) + }) + } + } + + @Test + fun `get messages when fetched completely new message without notify`() = runBlocking { + every { messageDb.loadAll(any(), any()) } returns flowOf(emptyList()) + coEvery { sdk.getMessages(Folder.RECEIVED, any(), any()) } returns listOf( + getMessageDto(messageId = 4, content = "Test", unread = true).copy( + unreadBy = 5, + readBy = 10, + ) + ) + coEvery { messageDb.deleteAll(any()) } just Runs + coEvery { messageDb.insertAll(any()) } returns listOf() + + repository.getMessages( + student = student, + semester = semester, + folder = MessageFolder.RECEIVED, + forceRefresh = true, + notify = false, + ).toFirstResult().data.orEmpty() + + coVerify(exactly = 1) { messageDb.deleteAll(withArg { checkEquals(emptyList()) }) } + coVerify { + messageDb.insertAll(withArg { + assertEquals(4, it.single().messageId) + assertTrue(it.single().isNotified) + }) + } } @Test(expected = NoSuchElementException::class) fun `throw error when message is not in the db`() { val testMessage = getMessageEntity(1, "", false) - coEvery { messageDb.loadMessageWithAttachment(1, 1) } throws NoSuchElementException("No message in database") + coEvery { + messageDb.loadMessageWithAttachment(1, 1) + } throws NoSuchElementException("No message in database") - runBlocking { messageRepository.getMessage(student, testMessage).toFirstResult() } + runBlocking { repository.getMessage(student, testMessage).toFirstResult() } } @Test @@ -78,9 +164,11 @@ class MessageRepositoryTest { val testMessage = getMessageEntity(123, "Test", false) val messageWithAttachment = MessageWithAttachment(testMessage, emptyList()) - coEvery { messageDb.loadMessageWithAttachment(1, testMessage.messageId) } returns flowOf(messageWithAttachment) + coEvery { messageDb.loadMessageWithAttachment(1, testMessage.messageId) } returns flowOf( + messageWithAttachment + ) - val res = runBlocking { messageRepository.getMessage(student, testMessage).toFirstResult() } + val res = runBlocking { repository.getMessage(student, testMessage).toFirstResult() } assertEquals(null, res.error) assertEquals(Status.SUCCESS, res.status) @@ -95,12 +183,24 @@ class MessageRepositoryTest { val mWa = MessageWithAttachment(testMessage, emptyList()) val mWaWithContent = MessageWithAttachment(testMessageWithContent, emptyList()) - coEvery { messageDb.loadMessageWithAttachment(1, testMessage.messageId) } returnsMany listOf(flowOf(mWa), flowOf(mWaWithContent)) - coEvery { sdk.getMessageDetails(testMessage.messageId, 1, false, testMessage.realId) } returns MessageDetails("Test", emptyList()) + coEvery { + messageDb.loadMessageWithAttachment( + 1, + testMessage.messageId + ) + } returnsMany listOf(flowOf(mWa), flowOf(mWaWithContent)) + coEvery { + sdk.getMessageDetails( + messageId = testMessage.messageId, + folderId = 1, + read = false, + id = testMessage.realId + ) + } returns MessageDetails("Test", emptyList()) coEvery { messageDb.updateAll(any()) } just Runs coEvery { messageAttachmentDao.insertAttachments(any()) } returns listOf(1) - val res = runBlocking { messageRepository.getMessage(student, testMessage).toFirstResult() } + val res = runBlocking { repository.getMessage(student, testMessage).toFirstResult() } assertEquals(null, res.error) assertEquals(Status.SUCCESS, res.status) @@ -112,18 +212,22 @@ class MessageRepositoryTest { fun `get message when content in db is empty and there is no internet connection`() { val testMessage = getMessageEntity(123, "", false) - coEvery { messageDb.loadMessageWithAttachment(1, testMessage.messageId) } throws UnknownHostException() + coEvery { + messageDb.loadMessageWithAttachment(1, testMessage.messageId) + } throws UnknownHostException() - runBlocking { messageRepository.getMessage(student, testMessage).toFirstResult() } + runBlocking { repository.getMessage(student, testMessage).toFirstResult() } } @Test(expected = UnknownHostException::class) fun `get message when content in db is empty, unread and there is no internet connection`() { val testMessage = getMessageEntity(123, "", true) - coEvery { messageDb.loadMessageWithAttachment(1, testMessage.messageId) } throws UnknownHostException() + coEvery { + messageDb.loadMessageWithAttachment(1, testMessage.messageId) + } throws UnknownHostException() - runBlocking { messageRepository.getMessage(student, testMessage).toList()[1] } + runBlocking { repository.getMessage(student, testMessage).toList()[1] } } private fun getMessageEntity( @@ -135,10 +239,10 @@ class MessageRepositoryTest { realId = 1, messageId = messageId, sender = "", - senderId = 1, - recipient = "", + senderId = 0, + recipient = "Wielu adresatów", subject = "", - date = LocalDateTime.now(), + date = LocalDateTime.MAX, folderId = 1, unread = unread, removed = false, @@ -148,4 +252,24 @@ class MessageRepositoryTest { unreadBy = 1 readBy = 1 } + + private fun getMessageDto( + messageId: Int, + content: String, + unread: Boolean, + ) = io.github.wulkanowy.sdk.pojo.Message( + id = 1, + messageId = messageId, + sender = Sender("", "", 0, 0, 0, ""), + recipients = listOf(), + subject = "", + content = content, + date = LocalDateTime.MAX, + folderId = 1, + unread = unread, + unreadBy = 0, + readBy = 0, + removed = false, + hasAttachments = false, + ) } From 4aa6b0b99519db7e16dfec03cd72a14b6776b475 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sun, 29 Aug 2021 19:00:30 +0200 Subject: [PATCH 181/186] Hide keyboard on opening login host dropdown (#1455) --- .../wulkanowy/ui/modules/login/form/LoginFormFragment.kt | 3 +++ 1 file changed, 3 insertions(+) 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 5250ceb6..e383072e 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 @@ -81,6 +81,9 @@ class LoginFormFragment : BaseFragment(R.layout.fragme loginFormRecoverLink.setOnClickListener { presenter.onRecoverClick() } loginFormRecoverLinkSecond.setOnClickListener { presenter.onRecoverClick() } loginFormPass.setOnEditorDoneSignIn { loginFormSignIn.callOnClick() } + loginFormHost.setOnFocusChangeListener { _, hasFocus -> + if (hasFocus) requireActivity().hideSoftInput() + } } with(binding.loginFormHost) { From ea0fb00bdebaa48e9c7ac6dfa01ee39bc67e6bdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sun, 29 Aug 2021 19:31:28 +0200 Subject: [PATCH 182/186] Fix crash on opening date pickers during holidays (#1456) --- .../modules/attendance/AttendanceFragment.kt | 30 ++++++++++--------- .../history/LuckyNumberHistoryFragment.kt | 20 ++++++------- .../history/LuckyNumberHistoryPresenter.kt | 18 +++++++++++ .../ui/modules/timetable/TimetableFragment.kt | 21 +++++++------ 4 files changed, 54 insertions(+), 35 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 10cab3df..7ae1e058 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 @@ -35,7 +35,8 @@ import java.time.LocalDate import javax.inject.Inject @AndroidEntryPoint -class AttendanceFragment : BaseFragment(R.layout.fragment_attendance), AttendanceView, MainView.MainChildView, +class AttendanceFragment : BaseFragment(R.layout.fragment_attendance), + AttendanceView, MainView.MainChildView, MainView.TitledView { @Inject @@ -118,7 +119,9 @@ class AttendanceFragment : BaseFragment(R.layout.frag with(binding) { attendanceSwipe.setOnRefreshListener(presenter::onSwipeRefresh) attendanceSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary)) - attendanceSwipe.setProgressBackgroundColorSchemeColor(requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh)) + attendanceSwipe.setProgressBackgroundColorSchemeColor( + requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh) + ) attendanceErrorRetry.setOnClickListener { presenter.onRetry() } attendanceErrorDetails.setOnClickListener { presenter.onDetailsClick() } @@ -208,7 +211,7 @@ class AttendanceFragment : BaseFragment(R.layout.frag } override fun showNextButton(show: Boolean) { - binding. attendanceNextButton.visibility = if (show) VISIBLE else INVISIBLE + binding.attendanceNextButton.visibility = if (show) VISIBLE else INVISIBLE } override fun showExcuseButton(show: Boolean) { @@ -220,20 +223,19 @@ class AttendanceFragment : BaseFragment(R.layout.frag } override fun showDatePickerDialog(currentDate: LocalDate) { - val now = LocalDate.now() - val startOfSchoolYear = now.schoolYearStart.toTimestamp() - val endWeek = now.plusWeeks(1).toTimestamp() + val baseDate = currentDate.schoolYearStart + val rangeStart = baseDate.toTimestamp() + val rangeEnd = LocalDate.now().plusWeeks(1).toTimestamp() val constraintsBuilder = CalendarConstraints.Builder().apply { - setValidator(SchoolDaysValidator(startOfSchoolYear, endWeek)) - setStart(startOfSchoolYear) - setEnd(endWeek) + setValidator(SchoolDaysValidator(rangeStart, rangeEnd)) + setStart(rangeStart) + setEnd(rangeEnd) } - val datePicker = - MaterialDatePicker.Builder.datePicker() - .setCalendarConstraints(constraintsBuilder.build()) - .setSelection(currentDate.toTimestamp()) - .build() + val datePicker = MaterialDatePicker.Builder.datePicker() + .setCalendarConstraints(constraintsBuilder.build()) + .setSelection(currentDate.toTimestamp()) + .build() datePicker.addOnPositiveButtonClickListener { val date = it.toLocalDateTime() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryFragment.kt index 4981aad2..dc141f8d 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryFragment.kt @@ -112,19 +112,19 @@ class LuckyNumberHistoryFragment : } override fun showDatePickerDialog(currentDate: LocalDate) { - val now = LocalDate.now() - val startOfSchoolYear = now.schoolYearStart.toTimestamp() + val baseDate = currentDate.schoolYearStart + val rangeStart = baseDate.toTimestamp() + val rangeEnd = LocalDate.now().plusWeeks(1).toTimestamp() val constraintsBuilder = CalendarConstraints.Builder().apply { - setValidator(SchoolDaysValidator(startOfSchoolYear, now.toTimestamp())) - setStart(startOfSchoolYear) - setEnd(now.toTimestamp()) + setValidator(SchoolDaysValidator(rangeStart, rangeEnd)) + setStart(rangeStart) + setEnd(rangeEnd) } - val datePicker = - MaterialDatePicker.Builder.datePicker() - .setCalendarConstraints(constraintsBuilder.build()) - .setSelection(currentDate.toTimestamp()) - .build() + val datePicker = MaterialDatePicker.Builder.datePicker() + .setCalendarConstraints(constraintsBuilder.build()) + .setSelection(currentDate.toTimestamp()) + .build() datePicker.addOnPositiveButtonClickListener { val date = it.toLocalDateTime() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryPresenter.kt index 556dda75..c45cb69a 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryPresenter.kt @@ -2,18 +2,22 @@ package io.github.wulkanowy.ui.modules.luckynumber.history import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.repositories.LuckyNumberRepository +import io.github.wulkanowy.data.repositories.SemesterRepository import io.github.wulkanowy.data.repositories.StudentRepository 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.afterLoading import io.github.wulkanowy.utils.flowWithResource +import io.github.wulkanowy.utils.getLastSchoolDayIfHoliday import io.github.wulkanowy.utils.isHolidays import io.github.wulkanowy.utils.monday import io.github.wulkanowy.utils.previousOrSameSchoolDay import io.github.wulkanowy.utils.sunday import io.github.wulkanowy.utils.toFormattedString +import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.onEach import timber.log.Timber import java.time.LocalDate @@ -22,6 +26,7 @@ import javax.inject.Inject class LuckyNumberHistoryPresenter @Inject constructor( errorHandler: ErrorHandler, studentRepository: StudentRepository, + private val semesterRepository: SemesterRepository, private val luckyNumberRepository: LuckyNumberRepository, private val analytics: AnalyticsHelper ) : BasePresenter(errorHandler, studentRepository) { @@ -40,6 +45,19 @@ class LuckyNumberHistoryPresenter @Inject constructor( Timber.i("Lucky number history view was initialized") errorHandler.showErrorMessage = ::showErrorViewOnError loadData() + if (currentDate.isHolidays) setBaseDateOnHolidays() + } + + private fun setBaseDateOnHolidays() { + flow { + val student = studentRepository.getCurrentStudent() + emit(semesterRepository.getCurrentSemester(student)) + }.catch { + Timber.i("Loading semester result: An exception occurred") + }.onEach { + currentDate = currentDate.getLastSchoolDayIfHoliday(it.schoolYear) + reloadNavigation() + }.launch("holidays") } private fun loadData() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt index a374e166..a65d6921 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt @@ -185,20 +185,19 @@ class TimetableFragment : BaseFragment(R.layout.fragme } override fun showDatePickerDialog(currentDate: LocalDate) { - val now = LocalDate.now() - val startOfSchoolYear = now.schoolYearStart.toTimestamp() - val endOfSchoolYear = now.schoolYearEnd.toTimestamp() + val baseDate = currentDate.schoolYearStart + val rangeStart = baseDate.toTimestamp() + val rangeEnd = LocalDate.now().schoolYearEnd.toTimestamp() val constraintsBuilder = CalendarConstraints.Builder().apply { - setValidator(SchoolDaysValidator(startOfSchoolYear, endOfSchoolYear)) - setStart(startOfSchoolYear) - setEnd(endOfSchoolYear) + setValidator(SchoolDaysValidator(rangeStart, rangeEnd)) + setStart(rangeStart) + setEnd(rangeEnd) } - val datePicker = - MaterialDatePicker.Builder.datePicker() - .setCalendarConstraints(constraintsBuilder.build()) - .setSelection(currentDate.toTimestamp()) - .build() + val datePicker = MaterialDatePicker.Builder.datePicker() + .setCalendarConstraints(constraintsBuilder.build()) + .setSelection(currentDate.toTimestamp()) + .build() datePicker.addOnPositiveButtonClickListener { val date = it.toLocalDateTime() From 98dcc62bb73de7b5eaef04dfe6ff1437eeb8fd2c Mon Sep 17 00:00:00 2001 From: Mateusz Idziejczak Date: Sun, 29 Aug 2021 19:47:14 +0200 Subject: [PATCH 183/186] Add excuse function to "not excusable" account (#1429) --- .../modules/attendance/AttendanceAdapter.kt | 3 +- .../modules/attendance/AttendanceFragment.kt | 25 ++++++++++++++-- .../modules/attendance/AttendancePresenter.kt | 30 +++++++++++++++++-- .../ui/modules/attendance/AttendanceView.kt | 2 ++ .../message/send/SendMessageActivity.kt | 8 +++++ .../message/send/SendMessagePresenter.kt | 6 +++- .../wulkanowy/utils/AttendanceExtension.kt | 3 ++ app/src/main/res/values/strings.xml | 2 ++ 8 files changed, 72 insertions(+), 7 deletions(-) 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 03ec1c84..6cee2396 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 @@ -10,6 +10,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.description +import io.github.wulkanowy.utils.isExcusableOrNotExcused import javax.inject.Inject class AttendanceAdapter @Inject constructor() : @@ -56,7 +57,7 @@ class AttendanceAdapter @Inject constructor() : attendanceItemExcuseInfo.visibility = View.VISIBLE } else -> { - if (item.excusable && excuseActionMode) { + if (item.isExcusableOrNotExcused && excuseActionMode) { attendanceItemNumber.visibility = View.GONE attendanceItemExcuseCheckbox.visibility = View.VISIBLE } else { 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 7ae1e058..05894679 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 @@ -24,6 +24,7 @@ import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.modules.attendance.summary.AttendanceSummaryFragment import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainView +import io.github.wulkanowy.ui.modules.message.send.SendMessageActivity import io.github.wulkanowy.ui.widgets.DividerItemDecoration import io.github.wulkanowy.utils.SchoolDaysValidator import io.github.wulkanowy.utils.dpToPx @@ -120,7 +121,9 @@ class AttendanceFragment : BaseFragment(R.layout.frag attendanceSwipe.setOnRefreshListener(presenter::onSwipeRefresh) attendanceSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary)) attendanceSwipe.setProgressBackgroundColorSchemeColor( - requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh) + requireContext().getThemeAttrColor( + R.attr.colorSwipeRefresh + ) ) attendanceErrorRetry.setOnClickListener { presenter.onRetry() } attendanceErrorDetails.setOnClickListener { presenter.onDetailsClick() } @@ -253,8 +256,13 @@ class AttendanceFragment : BaseFragment(R.layout.frag .setNegativeButton(android.R.string.cancel) { _, _ -> } .create() .apply { - setButton(BUTTON_POSITIVE, getString(R.string.attendance_excuse_dialog_submit)) { _, _ -> - presenter.onExcuseDialogSubmit(dialogBinding.excuseReason.text?.toString().orEmpty()) + setButton( + BUTTON_POSITIVE, + getString(R.string.attendance_excuse_dialog_submit) + ) { _, _ -> + presenter.onExcuseDialogSubmit( + dialogBinding.excuseReason.text?.toString().orEmpty() + ) } }.show() } @@ -267,6 +275,17 @@ 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, + date, + numbers, + if (reason.isNotBlank()) " ${getString(R.string.attendance_excuse_reason)} " else "", + reason.ifBlank { "" } + ) + startActivity(SendMessageActivity.getStartIntent(requireContext(), reasonFullText)) + } + override fun showExcuseCheckboxes(show: Boolean) { attendanceAdapter.apply { excuseActionMode = show 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 333e7103..9a159812 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 @@ -15,6 +15,7 @@ import io.github.wulkanowy.utils.capitalise import io.github.wulkanowy.utils.flowWithResource import io.github.wulkanowy.utils.flowWithResourceIn import io.github.wulkanowy.utils.getLastSchoolDayIfHoliday +import io.github.wulkanowy.utils.isExcusableOrNotExcused import io.github.wulkanowy.utils.isHolidays import io.github.wulkanowy.utils.nextSchoolDay import io.github.wulkanowy.utils.previousOrSameSchoolDay @@ -47,6 +48,8 @@ class AttendancePresenter @Inject constructor( private val attendanceToExcuseList = mutableListOf() + private var isVulcanExcusedFunctionEnabled = false + fun onAttachView(view: AttendanceView, date: Long?) { super.onAttachView(view) view.initView() @@ -148,7 +151,21 @@ class AttendancePresenter @Inject constructor( fun onExcuseDialogSubmit(reason: String) { view?.finishActionMode() - excuseAbsence(if (reason != "") reason else null, attendanceToExcuseList.toList()) + + if (isVulcanExcusedFunctionEnabled) { + excuseAbsence( + reason = reason.takeIf { it.isNotBlank() }, + toExcuseList = attendanceToExcuseList.toList() + ) + } else { + val attendanceToExcuseNumbers = attendanceToExcuseList.map { it.number } + + view?.startSendMessageIntent( + date = attendanceToExcuseList[0].date, + numbers = attendanceToExcuseNumbers.joinToString(", "), + reason = reason + ) + } } fun onPrepareActionMode(): Boolean { @@ -188,8 +205,12 @@ class AttendancePresenter @Inject constructor( private fun loadData(forceRefresh: Boolean = false) { Timber.i("Loading attendance data started") + var isParent = false + flowWithResourceIn { val student = studentRepository.getCurrentStudent() + isParent = student.isParent + val semester = semesterRepository.getCurrentSemester(student) attendanceRepository.getAttendance( student, @@ -227,12 +248,17 @@ class AttendancePresenter @Inject constructor( it.data?.filter { item -> !item.presence }.orEmpty() } + isVulcanExcusedFunctionEnabled = + filteredAttendance.any { item -> item.excusable } + view?.apply { updateData(filteredAttendance.sortedBy { item -> item.number }) showEmpty(filteredAttendance.isEmpty()) showErrorView(false) showContent(filteredAttendance.isNotEmpty()) - showExcuseButton(filteredAttendance.any { item -> item.excusable }) + showExcuseButton(filteredAttendance.any { item -> + (!isParent && isVulcanExcusedFunctionEnabled) || (isParent && item.isExcusableOrNotExcused) + }) } analytics.logEvent( "load_data", 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 0459dfcf..738f2ff8 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 @@ -54,6 +54,8 @@ interface AttendanceView : BaseView { fun openSummaryView() + fun startSendMessageIntent(date: LocalDate, numbers: String, reason: String) + fun startActionMode() fun showExcuseCheckboxes(show: Boolean) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessageActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessageActivity.kt index d49c9b83..1432a994 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessageActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessageActivity.kt @@ -35,6 +35,8 @@ class SendMessageActivity : BaseActivity() - fun onAttachView(view: SendMessageView, message: Message?, reply: Boolean?) { + fun onAttachView(view: SendMessageView, reason: String?, message: Message?, reply: Boolean?) { super.onAttachView(view) view.initView() initializeSubjectStream() @@ -50,6 +50,10 @@ class SendMessagePresenter @Inject constructor( if (messageRepository.draftMessage != null && reply == null) { view.showMessageBackupDialog() } + reason?.let { + setSubject("Usprawiedliwenie") + setContent(it) + } message?.let { setSubject(when (reply) { true -> "RE: " 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 8bccec62..479cc518 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/AttendanceExtension.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/AttendanceExtension.kt @@ -16,6 +16,9 @@ private inline val AttendanceSummary.allPresences: Double private inline val AttendanceSummary.allAbsences: Double get() = absence.toDouble() + absenceExcused +inline val Attendance.isExcusableOrNotExcused: Boolean + get() = excusable || ((absence || lateness) && !excused) + fun AttendanceSummary.calculatePercentage() = calculatePercentage(allPresences, allAbsences) fun List.calculatePercentage(): Double { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ea1187c8..08776c1a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -192,6 +192,8 @@ Absence excuse request sent successfully! You must select at least one absence! Excuse + z powodu + Dzień dobry,\nProszę o usprawiedliwienie mojego dziecka w dniu %s z lekcji %s%s%s.\n\nPozdrawiam. From 79e9e1a780c8c600d9380ffe3ad3ddd273955295 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Sun, 29 Aug 2021 20:01:36 +0200 Subject: [PATCH 184/186] New Crowdin updates (#1321) --- .../main/res/values-cs/preferences_values.xml | 11 + app/src/main/res/values-cs/strings.xml | 163 ++++++++++++++- .../main/res/values-de/preferences_values.xml | 11 + app/src/main/res/values-de/strings.xml | 147 +++++++++++-- .../main/res/values-pl/preferences_values.xml | 11 + app/src/main/res/values-pl/strings.xml | 153 +++++++++++++- .../main/res/values-ru/preferences_values.xml | 11 + app/src/main/res/values-ru/strings.xml | 183 ++++++++++++++-- .../main/res/values-sk/preferences_values.xml | 11 + app/src/main/res/values-sk/strings.xml | 163 ++++++++++++++- .../main/res/values-uk/preferences_values.xml | 11 + app/src/main/res/values-uk/strings.xml | 195 +++++++++++++++--- 12 files changed, 999 insertions(+), 71 deletions(-) diff --git a/app/src/main/res/values-cs/preferences_values.xml b/app/src/main/res/values-cs/preferences_values.xml index ce32f22b..fb938f09 100644 --- a/app/src/main/res/values-cs/preferences_values.xml +++ b/app/src/main/res/values-cs/preferences_values.xml @@ -45,4 +45,15 @@ Průměr známek z obou semestrů Průměr známek z celého roku + + Šťastné číslo + Nepřečtené zprávy + Docházka + Lekce + Známky + Domácí úkoly + Školní oznámení + Zkoušky + Setkání + diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 8929f6c2..3b847655 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -11,6 +11,8 @@ Více O aplikaci Prohlížeč protokolů + Ladění + Ladění oznámení Tvůrci Licence Zprávy @@ -21,6 +23,7 @@ Vyberte účet Podrobnosti účtu Informace o žáku + Domů Semestr %1$d, %2$d/%3$d @@ -88,6 +91,7 @@ Předpokládaná známka Vypočítaný průměr Konečný průměr + z %1$d z %2$d předmětů Shrnutí Třída Označit jako přečtené @@ -196,6 +200,12 @@ Nové zkoušky Nové zkoušky + + Máte %d novou zkoušku + Máte %d nové zkoušky + Máte %d nových zkoušek + Máte %d nových zkoušek + %d zkouška %d zkoušky @@ -203,7 +213,7 @@ %d zkoušek - Doručená pošta + Doručené Odesláno Koš (žádný předmět) @@ -225,6 +235,15 @@ Zpráva neexistuje Musíte vybrat alespoň 1 příjemce Obsah zprávy musí mít alespoň 3 znaky + Pouze nepřečtené + Pouze s přílohami + Přečtena: %s + + Přečtena přes: %1$d z %2$d osob + Přečtena přes: %1$d z %2$d osob + Přečtena přes: %1$d z %2$d osob + Přečtena přes: %1$d z %2$d osob + %d zpráva %d zprávy @@ -237,6 +256,8 @@ Nové zprávy Nové zprávy + Chcete obnovit koncept zprávy? + Chcete obnovit koncept zprávy s příjemci: %s? Máte %1$d novou zprávu Máte %1$d nové zprávy @@ -313,6 +334,12 @@ Nové domácí úkoly Nové domácí úkoly + + Máte %d nový domácí úkol + Máte %d nové domácí úkoly + Máte %d nových domácích úkolů + Máte %d nových domácích úkolů + %d domácí úkol %d domácí úkoly @@ -324,7 +351,7 @@ Dnešní šťastné číslo je Žádné informace o šťastném čísle Šťastné číslo pro dnešek - Dnes je šťastným číslem: %s + Dnešní šťastné číslo je: %s Zobrazit historii Historie šťastných čísel @@ -357,6 +384,45 @@ Setkání Žádné informace o setkáních + + %d setkání + %d setkání + %d setkání + %d setkání + + + Nové setkání + Nová setkání + Nová setkání + Nová setkání + + + Máte %1$d nové setkání + Máte %1$d nové setkání + Máte %1$d nových setkání + Máte %1$d nových setkání + + + Školní oznámení + Žádná školní oznámení + + %d školní oznámení + %d školní oznámení + %d školní oznámení + %d školní oznámení + + + Nové školní oznámení + Nová školní oznámení + Nová školní oznámení + Nová školní oznámení + + + Máte %1$d nové školní oznámení + Máte %1$d nové školní oznámení + Máte %1$d nových školních oznámení + Máte %1$d nových školních oznámení + Přidat účet Odhlásit @@ -382,6 +448,8 @@ Server Discord Připojte se ke komunitě Wulkanového Facebooková fanpage + Twitter stránka + Sledujte nás na Twitteru Stejně jako naše facebooková fanpage Zásady ochrany osobních údajů Pravidla pro shromažďování osobních údajů @@ -417,6 +485,7 @@ Muž Žena Příjmení + Opatrovník Přezdívka Přidat přezdívku @@ -424,6 +493,80 @@ Sdílet protokoly Obnovit + + Lekce + (Zítra) + %1$s (%2$s) + Za chvíli: + Brzy: + První: + Teď: + + za %1$d minutu + za %1$d minuty + za %1$d minut + za %1$d minut + + + ještě %1$d minutu + ještě %1$d minuty + ještě %1$d minut + ještě %1$d minut + + Konec lekcí + Další: + Později: + + ještě %1$d další lekce + ještě %1$d další lekce + ještě %1$d dalších lekcí + ještě %1$d dalších lekcí + + do %1$s + Žádné nadcházející lekce + Při načítání lekcí došlo k chybě + Domácí úkoly + Žádné domácí úkoly do vykonána + Při načítání domácích úkolů došlo k chybě + + Ještě %1$d další domácí úkol + Ještě %1$d další domácí úkoly + Ještě %1$d dalších domácích úkolů + Ještě %1$d dalších domácích úkolů + + do %1$s + Poslední známky + Žádné nové známky + Při načítání známek došlo k chybě + Školní oznámení + Žádná aktuální oznámení + Při načítání oznámení došlo k chybě + + Ještě %1$d další oznámení + Ještě %1$d další oznámení + Ještě %1$d dalších oznámení + Ještě %1$d dalších oznámení + + Zkoušky + Žádné nadcházející zkoušky + Při načítání zkoušek došlo k chybě + + Ještě %1$d další zkouška + Ještě %1$d další zkoušky + Ještě %1$d dalších zkoušek + Ještě %1$d dalších zkoušek + + Setkání + Žádná nadcházející setkání + Při načítání setkání došlo k chybě + + Ještě %1$d další setkání + Ještě %1$d další setkání + Ještě %1$d dalších setkání + Ještě %1$d dalších setkání + + Při načítání dat došlo k chybě + Žádné Zkontrolovat aktualizace Před hlášením chyby zkontrolujte, zda je k dispozici aktualizace s opravou chyb @@ -487,6 +630,7 @@ Synchronizováno! Synchronizace selhala Probíhá synchronizace + Poslední úplná synchronizace: %s Hodnota plusu Hodnota mínusu Odpovědět s historií zpráv @@ -496,6 +640,8 @@ Upozornění Synchronizace Známky + Domů + Viditelnost dlaždic Docházka Plán lekce Známky @@ -512,23 +658,26 @@ Nové známky Nové domácí úkoly + Nová setkání Nové zkoušky Šťastné číslo Nové zprávy Nové poznámky + Nové školní oznámení Push upozornění Nadcházející lekce Ladění - Černý - Červený - Modrý - Zelený - Fialový + Černá + Červená + Modrá + Zelená + Fialová Žádná barva Zkopírováno Vrátit + Změnit Stahování aktualizací začalo… Aktualizace byla stažena. diff --git a/app/src/main/res/values-de/preferences_values.xml b/app/src/main/res/values-de/preferences_values.xml index 35e93059..53faaf9b 100644 --- a/app/src/main/res/values-de/preferences_values.xml +++ b/app/src/main/res/values-de/preferences_values.xml @@ -45,4 +45,15 @@ Durchschnitt der Noten aus beiden Semestern Durchschnitt der Noten aus dem ganzen Jahr + + Lucky number + Unread messages + Attendance + Lessons + Grades + Homework + School announcements + Exams + Conferences + diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 7399e1de..4bbf7767 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -11,6 +11,8 @@ Mehr Über die Applikation Log Viewer + Debuggen + Benachrichtigungen debuggen Mitarbeiter Lizenzen Nachrichten @@ -21,6 +23,7 @@ Konto auswählen Kontodetails Schülerinfo + Übersicht Semester %1$d, %2$d/%3$d @@ -88,6 +91,7 @@ Vorhergesagte Note Berechnender Durchschnitt Finaler Durchschnitt + aus %1$d von %2$d Schulfächern Zusammenfassung Klasse Als gelesen markieren @@ -177,12 +181,16 @@ Form Eintrittsdatum - New exam - New exams + Neue prüfung + Neue prüfungen + + + You received %d new exam + Du hast %d neue Prüfungen bekommen - %d exam - %d exams + %d prüfung + %d prüfungen Posteingang @@ -207,6 +215,13 @@ Nachricht existiert nicht Sie müssen mindestens 1 Empfänger auswählen. Der Inhalt der Nachricht muss mindestens 3 Zeichen lang sein. + Nur ungelesen + Nur mit Anhängen + Lesen: %s + + Read by: %1$d of %2$d people + Read by: %1$d of %2$d people + %d nachricht %d nachrichten @@ -215,6 +230,8 @@ Neu nachricht Neue nachrichten + Do you want to restore draft message? + Do you want to restore draft message with recipients: %s? Du hast %1$d nachricht bekommen Du hast %1$d nachrichten bekommen @@ -266,19 +283,23 @@ Unvollständig Anhänge - New homework - New homework + Neue hausaufgaben + Neue hausaufgaben + + + You received %d new homework + Du hast %d neue Hausaufgaben bekommen - %d homework - %d homework + %d hausaufgaben + %d hausaufgaben Glückliche Nummer Die heutige Glücksnummer ist Keine Information über die Glücksnummer. Glücksnummer für heute - Die heutige Glücksnummer ist: %s + Heutige Glückszahl ist: %s Verlauf anzeigen Glücksnummerverlauf @@ -311,6 +332,33 @@ Sitzungen Keine Informationen über Sitzungen + + %d konferenz + %d konferenzen + + + Neue konferenz + Neue konferenzen + + + Sie haben %1$d neue konferenz + Sie haben %1$d neue konferenzen + + + Schulankündigungen + Keine schulankündigungen + + %d schulankündigung + %d schulankündigungen + + + Ankündigung der neuen schule + Neue schulankündigungen + + + Sie haben %1$d neue schulmitteilungen + Sie haben %1$d neue schulankündigungen + Konto hinzufügen Abmelden @@ -336,11 +384,13 @@ Discord server Treten Sie der Wulkanowy-Gemeinschaft bei Facebook fanpage + Twitter-Seite + Folgen Sie uns bei Twitter Gefällt unsere Facebook-Fanpage Datenschutzerklärung Regeln für die Sammlung persönlicher Daten - System settings - Open system settings + Systemeinstellungen + Systemeinstellungen öffnen Startseite Besuchen Sie die Website und helfen Sie bei der Entwicklung der Anwendung Lizenzen @@ -371,6 +421,7 @@ Mann Frau Nachname + Wächter Nick Nick hinzufügen @@ -378,6 +429,66 @@ Logs teilen Aktualisieren + + Lektionen + (Morgen) + %1$s (%2$s) + Gleich: + Bald: + Erstens: + Jetzt: + + in %1$d Minute + in %1$d Minuten + + + Noch %1$d Minute + Noch %1$d Minuten + + Ende der Lektion + Nächste: + Später: + + Noch %1$d Lektion + %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 Auf Updates prüfen Bevor Sie einen Fehler melden, prüfen Sie zuerst, ob ein Update mit der Fehlerbehebung verfügbar ist @@ -426,7 +537,7 @@ Benachrichtigungen Benachrichtigungen anzeigen Benachrichtigungen über bevorstehende Lektionen anzeigen - Open system notification settings + Systembenachrichtigungseinstellungen öffnen Synchronisierungs- und Benachrichtigungsprobleme reparieren Ihr Gerät hat möglicherweise Probleme mit der Datensynchronisierung und Benachrichtigungen.\n\nUm diese zu reparieren, fügen Sie Wulkanowy zum Autostart hinzu und deaktivieren Sie die Batterieoptimierung in den Systemeinstellungen des Geräts. Gehe zu den Einstellungen @@ -441,15 +552,18 @@ Synchronisiert! Synchronisierung fehlgeschlagen Synchronisierung läuft + Last full sync: %s Wert des Plus Wert des Minus Antwort mit Nachrichtenhistorie - Show arithmetic average when no weights provided + Arithmetisches Mittel anzeigen, wenn keine Gewichte angegeben sind Erweitert Aussehen & Verhalten Benachrichtigungen Synchronisierung Noten + Dashboard + Tiles visibility Schulbesuch Zeitplan Noten @@ -465,11 +579,13 @@ App-Version, Mitarbeiter, soziale Portale, Lizenzen Neue Noten - New homework - New exams + Neue Hausaufgaben + Neue Konferenzen + Neue Prüfungen Glückliche Nummer Neue Nachrichten Neue Eintragen + Neue Schulankündigungen Push-Benachrichtigungen Bevorstehende Lektionen Debuggen @@ -483,6 +599,7 @@ Kopiert lösen + Change Download der Updates wurde gestartet… Ein Update wurde gerade heruntergeladen. diff --git a/app/src/main/res/values-pl/preferences_values.xml b/app/src/main/res/values-pl/preferences_values.xml index 91869785..12cfda8c 100644 --- a/app/src/main/res/values-pl/preferences_values.xml +++ b/app/src/main/res/values-pl/preferences_values.xml @@ -45,4 +45,15 @@ Średnia średnich z obu semestrów Średnia wszystkich ocen z całego roku + + Szczęśliwy numerek + Nieprzeczytane wiadomości + Frekwencja + Lekcje + Oceny + Zadania domowe + Ogłoszenia szkolne + Sprawdziany + Zebrania + diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index a855ebd1..74627ed1 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -11,6 +11,8 @@ Więcej O aplikacji Przeglądarka logów + Debugowanie + Debugowanie powiadomień Twórcy Licencje Wiadomości @@ -21,6 +23,7 @@ Wybierz konto Szczegóły konta Informacje o uczniu + Start Semestr %1$d, %2$d/%3$d @@ -88,6 +91,7 @@ Przewidywana ocena Obliczona średnia Końcowa średnia + z %1$d na %2$d przedmiotów Podsumowanie Klasa Oznacz jako przeczytane @@ -196,6 +200,12 @@ Nowe sprawdziany Nowe sprawdziany + + Masz %d nowy sprawdzian + Masz %d nowe sprawdziany + Masz %d nowych sprawdzianów + Masz %d nowych sprawdzianów + %d sprawdzian %d sprawdziany @@ -225,6 +235,15 @@ Wiadomość nie istnieje Musisz wybrać co najmniej 1 adresata Treść wiadomości musi zawierać co najmniej 3 znaki + Tylko nieprzeczytane + Tylko z załącznikami + Przeczytana: %s + + Przeczytana przez: %1$d z %2$d osób + Przeczytana przez: %1$d z %2$d osób + Przeczytana przez: %1$d z %2$d osób + Przeczytana przez: %1$d z %2$d osób + %d wiadomość %d wiadomości @@ -237,6 +256,8 @@ Nowe wiadomości Nowe wiadomości + Czy chcesz przywrócić wersję roboczą wiadomości? + Czy chcesz przywrócić wersję roboczą wiadomości z adresatami: %s? Masz %1$d nową wiadomość Masz %1$d nowe wiadomości @@ -313,6 +334,12 @@ Nowe zadania domowe Nowe zadania domowe + + Masz %d nowe zadanie domowe + Masz %d nowe zadania domowe + Masz %d nowych zadań domowych + Masz %d nowych zadań domowych + %d zadanie domowe %d zadania domowe @@ -324,7 +351,7 @@ Dzisiejszym szczęśliwym numerkiem jest Brak informacji o szczęśliwym numerku Szczęśliwy numerek na dzisiaj - Dziś szczęśliwym numerkiem jest: %s + Dzisiejszym szczęśliwym numerkiem jest: %s Pokaż historię Historia numerków @@ -357,6 +384,45 @@ Zebrania Brak informacji o zebraniach + + %d zebranie + %d zebrania + %d zebrań + %d zebrań + + + Nowe zebranie + Nowe zebrania + Nowe zebrania + Nowe zebrania + + + Masz %1$d nowe zebranie + Masz %1$d nowe zebrania + Masz %1$d nowych zebrań + Masz %1$d nowych zebrań + + + Ogłoszenia szkolne + Brak ogłoszeń szkolnych + + %d ogłoszenie szkolne + %d ogłoszenia szkolne + %d ogłoszeń szkolnych + %d ogłoszeń szkolnych + + + Nowe ogłoszenie szkolne + Nowe ogłoszenia szkolne + Nowe ogłoszenia szkolne + Nowe ogłoszenia szkolne + + + Masz %1$d nowe ogłoszenie szkolne + Masz %1$d nowe ogłoszenia szkolne + Masz %1$d nowych ogłoszeń szkolnych + Masz %1$d nowych ogłoszeń szkolnych + Dodaj konto Wyloguj @@ -382,6 +448,8 @@ Serwer Discord Dołącz do społeczności Wulkanowego Fanpage na Facebooku + Strona na Twitterze + Śledź nas na Twitterze Polub nasz fanpage na Facebooku Polityka prywatności Zasady zbierania danych osobowych @@ -417,6 +485,7 @@ Mężczyzna Kobieta Nazwisko + Opiekun Pseudonim Dodaj pseudonim @@ -424,6 +493,80 @@ Udostępnij logi Odśwież + + Lekcje + (Jutro) + %1$s (%2$s) + Za chwilę: + Wkrótce: + Pierwsza: + Teraz: + + za %1$d minutę + za %1$d minuty + za %1$d minut + za %1$d minut + + + jeszcze %1$d minuta + jeszcze %1$d minuty + jeszcze %1$d minut + jeszcze %1$d minut + + Koniec lekcji + Następnie: + Później: + + jeszcze %1$d dodatkowa lekcja + jeszcze %1$d dodatkowe lekcje + jeszcze %1$d dodatkowych lekcji + jeszcze %1$d dodatkowych lekcji + + do %1$s + Brak nadchodzących lekcji + Wystąpił błąd podczas ładowania lekcji + Zadania domowe + Brak prac domowych do wykonania + Wystąpił błąd podczas ładowania zadań domowych + + Jeszcze %1$d dodatkowe zadanie domowe + Jeszcze %1$d dodatkowe zadania domowe + Jeszcze %1$d dodatkowych zadań domowych + Jeszcze %1$d dodatkowych zadań domowych + + do %1$s + Ostatnie oceny + Brak nowych ocen + Wystąpił błąd podczas ładowania ocen + Ogłoszenia szkolne + Brak aktualnych ogłoszeń + Wystąpił błąd podczas ładowania ogłoszeń + + Jeszcze %1$d dodatkowe ogłoszenie + Jeszcze %1$d dodatkowe ogłoszenia + Jeszcze %1$d dodatkowych ogłoszeń + Jeszcze %1$d dodatkowych ogłoszeń + + Sprawdziany + Brak nadchodzących sprawdzianów + Wystąpił błąd podczas ładowania sprawdzianów + + Jeszcze %1$d dodatkowy sprawdzian + Jeszcze %1$d dodatkowe sprawdziany + Jeszcze %1$d dodatkowych sprawdzianów + Jeszcze %1$d dodatkowych sprawdzianów + + Zebrania + Brak nadchodzących zebrań + Wystąpił błąd podczas ładowania zebrań + + Jeszcze %1$d dodatkowe zebranie + Jeszcze %1$d dodatkowe zebrania + Jeszcze %1$d dodatkowych zebrań + Jeszcze %1$d dodatkowych zebrań + + Wystąpił błąd podczas ładowania danych + Brak Sprawdź dostępność aktualizacji Przed zgłoszeniem błędu sprawdź wcześniej, czy dostępna jest już aktualizacja z poprawką błędu @@ -472,7 +615,7 @@ Powiadomienia Pokazuj powiadomienia Pokazuj powiadomienia o nadchodzących lekcjach - Otwórz ustawienia systemowe powiadomień + Otwórz systemowe ustawienia powiadomień Napraw problemy z synchronizacją i powiadomieniami Na twoim urządzeniu mogą występować problemy z synchronizacją danych i powiadomieniami.\n\nBy je naprawić, dodaj Wulkanowego do autostartu i wyłącz optymalizację/oszczędzanie baterii w ustawieniach systemowych telefonu. Przejdź do ustawień @@ -487,6 +630,7 @@ Zsynchronizowano! Synchronizacja nie powiodła się Synchronizacja w trakcie + Ostatnia pełna synchronizacja: %s Wartość plusa Wartość minusa Odpowiadaj z historią wiadomości @@ -496,6 +640,8 @@ Powiadomienia Synchronizacja Oceny + Start + Widoczność kafelków Frekwencja Plan lekcji Oceny @@ -512,10 +658,12 @@ Nowe oceny Nowe zadania domowe + Nowe zebrania Nowe sprawdziany Szczęśliwy numerek Nowe wiadomości Nowe uwagi + Nowe ogłoszenia szkolne Powiadomienia push Nadchodzące lekcje Debugowanie @@ -529,6 +677,7 @@ Skopiowano Cofnij + Zmień Rozpoczęto pobieranie aktualizacji… Aktualizacja została pobrana. diff --git a/app/src/main/res/values-ru/preferences_values.xml b/app/src/main/res/values-ru/preferences_values.xml index 4ea5a614..7c4d14df 100644 --- a/app/src/main/res/values-ru/preferences_values.xml +++ b/app/src/main/res/values-ru/preferences_values.xml @@ -45,4 +45,15 @@ Средняя оценка с двух семестров Средняя оценок со всего года + + Счастливый номер + Непрочитанные сообщения + Посещаемость + Уроки + Оценки + Домашняя работа + Объявления школ + Экзамены + Конференции + diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 1c67d284..e9349313 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -11,6 +11,8 @@ Ещё О приложении Просмотр журнала + Отладка + Отладка уведомления Разработчики Лицензии Сообщения @@ -21,6 +23,7 @@ Выбор учетной записи Данные аккаунта Информация о студенте + Панель %1$d семестр, %2$d/%3$d @@ -88,6 +91,7 @@ Ожидаемая оценка Рассчитанная средняя оценка Итоговая средняя оценка + от %1$d из %2$d субъектов Итоги Класс Пометить как \"прочитанное\" @@ -191,16 +195,22 @@ Тип Дата записи - New exam - New exams - New exams - New exams + Новый экзамен + Новый экзамен + Новый экзамен + Новые экзамены + + + Вы получили %d новый экзамен + Вы получили %d новый экзамен + Вы получили %d новый экзамен + Вы получили %d новых экзаменов - %d exam - %d exams - %d exams - %d exams + %d экзамен + %d экзамен + %d экзамен + %d экзаменов Полученные @@ -225,6 +235,15 @@ Сообщения не существует Вы должны выбрать как минимум одного получателя Текст сообщения должен содержать как минимум 3 знака + Только непрочитанные + Только с вложениями + Чтение: %s + + Прочитано: %1$d из %2$d человек + Прочитано: %1$d из %2$d человек + Прочитано: %1$d из %2$d человек + Прочитано: %1$d из %2$d человек + %d сообщение %d сообщения @@ -237,6 +256,8 @@ Новые сообщения Новые сообщения + Вы хотите восстановить черновичное сообщение? + Вы хотите восстановить черновик сообщения с получателями: %s? Вы получили %1$d новое сообщение Вы получили %1$d новых сообщения @@ -308,23 +329,29 @@ Отметить как невыполненное Вложения - New homework - New homework - New homework - New homework + Новая домашняя работа + Новая домашняя работа + Новая домашняя работа + Новая домашняя работа + + + Вы получили %d новых домашних заданий + Вы получили %d новых домашних заданий + Вы получили %d новых домашних заданий + Вы получили %d новых домашних заданий - %d homework - %d homework - %d homework - %d homework + %d домашних заданий + %d домашних заданий + %d домашних заданий + %d домашних заданий Счастливый номер Сегодняшний счастливый номер это Нет данных о счастливом номере Сегодняшний счастливый номер - Сегодняшний счастливый номер это: %s + Сегодняшний номер удачи: %s Показать историю История удачных чисел @@ -357,6 +384,45 @@ Встречи Нет информации о встречах + + %d конференция + %d конференция + %d конференция + %d конференций + + + Новая конференция + Новая конференция + Новая конференция + Новые конференции + + + У вас %1$d новая конференция + У вас %1$d новая конференция + У вас %1$d новая конференция + У вас %1$d новых конференций + + + Объявления школ + Нет объявлений о школе + + Объявление о школе %d + Объявление о школе %d + Объявление о школе %d + Объявления школы %d + + + Объявление о новой школе + New school announcements + New school announcements + New school announcements + + + You have %1$d new school announcement + You have %1$d new school announcements + You have %1$d new school announcements + You have %1$d new school announcements + Добавить аккаунт Выйти @@ -382,6 +448,8 @@ Сервер Discord Присоединиться к сообществу приложения Facebook фан-страница + Twitter page + Follow us on twitter Поставьте лайк на нашей странице в Facebook Политика приватности Правила хранения личных данных @@ -417,6 +485,7 @@ Муж Женская Фамилия + Guardian Ник Добавить ник @@ -424,6 +493,80 @@ Поделиться логами Обновить + + Lessons + (Tomorrow) + %1$s (%2$s) + In a moment: + Soon: + First: + Now: + + in %1$d minute + in %1$d minutes + in %1$d minutes + in %1$d minutes + + + %1$d more minute + %1$d more minutes + %1$d more minutes + %1$d more minutes + + End of lessons + Next: + Later: + + %1$d more lesson + %1$d more lessons + %1$d more lessons + %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 + %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 + %1$d more announcements + %1$d more announcements + + Exams + No upcoming exams + An error occurred while loading the exams + + %1$d more exam + %1$d more exams + %1$d more exams + %1$d more exams + + Conferences + No upcoming conferences + An error occurred while loading the conferences + + %1$d more conference + %1$d more conferences + %1$d more conferences + %1$d more conferences + + An error occurred while loading data + None Проверить наличие обновлений Прежде чем сообщать об ошибке, проверьте наличие обновлений @@ -487,6 +630,7 @@ Синхронизировано! Синхронизация не удалась Идёт синхронизация + Last full sync: %s Стоимость плюса Стоимость минуса Отвечать с историей сообщений @@ -496,6 +640,8 @@ Уведомления Синхронизация Оценки + Dashboard + Tiles visibility Посещаемость Расписание Оценки @@ -512,10 +658,12 @@ Новые оценки New homework + New conferences New exams Счастливый номер Новые сообщения Новые заметки + New school announcements Показывать push-уведомления Будущие уроки Дебаг @@ -529,6 +677,7 @@ Скопировано Отменить + Change Загрузка обновлений началась… Только что было скачано обновление. diff --git a/app/src/main/res/values-sk/preferences_values.xml b/app/src/main/res/values-sk/preferences_values.xml index b51bfa40..108af555 100644 --- a/app/src/main/res/values-sk/preferences_values.xml +++ b/app/src/main/res/values-sk/preferences_values.xml @@ -45,4 +45,15 @@ Priemer známok z oboch semestrov Priemer známok z celého roka + + Šťastné číslo + Neprečítané správy + Dochádzka + Lekcie + Známky + Domáce úlohy + Školské oznámenia + Skúšky + Stretnutie + diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index a9656eab..81a76cf3 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -11,6 +11,8 @@ Viac O aplikácii Prehliadač protokolov + Ladenie + Ladenie oznámení Prispievatelia Licencie Správy @@ -21,6 +23,7 @@ Vyberte účet Podrobnosti účtu Informácie o žiakovi + Domov Semester %1$d, %2$d/%3$d @@ -88,6 +91,7 @@ Predpokladaná známka Vypočítaný priemer Konečný priemer + z %1$d z %2$d predmetov Zhrnutie Trieda Označiť ako prečítané @@ -196,6 +200,12 @@ Nové skúšky Nové skúšky + + You received %d new exam + You received %d new exams + You received %d new exams + You received %d new exams + %d skúška %d skúšky @@ -203,7 +213,7 @@ %d skúšok - Doručená pošta + Doručené Odoslané Kôš (žiadny predmet) @@ -225,6 +235,15 @@ Správa neexistuje Musíte vybrať aspoň 1 príjemca Obsah správy musí mať aspoň 3 znaky + Iba neprečítané + Iba s prílohami + Prečítaná: %s + + Prečítaná cez: %1$d z %2$d osôb + Prečítaná cez: %1$d z %2$d osôb + Prečítaná cez: %1$d z %2$d osôb + Prečítaná cez: %1$d z %2$d osôb + %d správa %d správy @@ -237,6 +256,8 @@ Nové správy Nové správy + Chcete obnoviť koncept správy? + Chcete obnoviť koncept správy s príjemcami:%s? Máte %1$d novú správu Máte %1$d nové správy @@ -313,6 +334,12 @@ Nové domáce úlohy Nové domáce úlohy + + You received %d new homework + You received %d new homework + You received %d new homework + You received %d new homework + %d domáci úloh %d domáce úlohy @@ -324,7 +351,7 @@ Dnešné šťastné číslo je Žiadne informácie o šťastnom čísle Šťastné číslo pre dnešok - Dnes je šťastným číslom: %s + Dnešné šťastné číslo je: %s Zobraziť históriu História šťastných čísel @@ -357,6 +384,45 @@ Stretnutie Žiadne informácie o stretnutiach + + %d stretnutie + %d stretnutia + %d stretnutí + %d stretnutí + + + Nové stretnutie + Nová stretnutia + Nová stretnutia + Nová stretnutia + + + Máte %1$d nové stretnutie + Máte %1$d nové stretnutia + Máte %1$d nových stretnutí + Máte %1$d nových stretnutí + + + Školské oznámenia + Žiadne školské oznámenia + + %d školské oznámenie + %d školské oznámenia + %d školských oznámení + %d školských oznámení + + + Nové školské oznámenie + Nové školské oznámenia + Nové školské oznámenia + Nové školské oznámenia + + + Máte %1$d nové školské oznámenie + Máte %1$d nové školské oznámenia + Máte %1$d nových školských oznámení + Máte %1$d nových školských oznámení + Pridať účet Odhlásiť @@ -382,6 +448,8 @@ Server Discord Pripojte sa ku komunite Wulkanového Facebooková fanpage + Twitter stránka + Sledujte nás na Twitteri Rovnako ako naše facebooková fanpage Zásady ochrany osobných údajov Pravidlá pre zhromažďovanie osobných údajov @@ -417,6 +485,7 @@ Muž Žena Priezvisko + Opatrovník Prezývka Pridať prezývku @@ -424,6 +493,80 @@ Zdieľať protokoly Obnoviť + + Lekcie + (Zajtra) + %1$s (%2$s) + Za chvíľu: + Čoskoro: + Prvá: + Teraz: + + za %1$d minútu + za %1$d minúty + za %1$d minút + za %1$d minút + + + ešte %1$d minútu + ešte %1$d minúty + ešte %1$d minút + ešte %1$d minút + + Koniec lekcií + Ďalej: + Neskôr: + + ešte %1$d ďalší lekcia + ešte %1$d ďalšie lekcie + ešte %1$d ďalších lekcií + ešte %1$d ďalších lekcií + + do %1$s + Žiadne nadchádzajúce lekcie + Pri načítaní lekcií došlo k chybe + Domáce úlohy + Žiadne domáce úlohy do vykonaná + Pri načítaní domácich úloh došlo k chybe + + Ešte %1$d ďalšia domáca úloha + Ešte %1$d ďalšie domáce úlohy + Ešte %1$d ďalších domácich úloh + Ešte %1$d ďalších domácich úloh + + do %1$s + Posledné známky + Žiadne nové známky + Pri načítaní známok došlo k chybe + Školské oznámenia + Žiadne aktuálne oznámenia + Pri načítaní oznámení došlo k chybe + + Ešte %1$d ďalšie oznámenie + Ešte %1$d ďalšie oznámenia + Ešte %1$d ďalších oznámení + Ešte %1$d ďalších oznámení + + Skúšky + Žiadne nadchádzajúce skúšky + Pri načítaní skúšok došlo k chybe + + Ešte %1$d ďalšia skúška + Ešte %1$d ďalšie skúšky + Ešte %1$d ďalších skúšok + Ešte %1$d ďalších skúšok + + Stretnutie + Žiadna nadchádzajúce stretnutie + Pri načítaní stretnutí došlo k chybe + + Ešte %1$d ďalšie stretnutie + Ešte %1$d ďalšie stretnutia + Ešte %1$d ďalších stretnutí + Ešte %1$d ďalších stretnutí + + Pri načítaní dát došlo k chybe + Žiadne Skontrolovať aktualizácie Pred hlásením chyby skontrolujte, či je k dispozícii aktualizácia s opravou chýb @@ -487,6 +630,7 @@ Synchronizovano! Synchronizácia zlyhala Prebieha synchronizácia + Posledná úplná synchronizácia: %s Hodnota plusu Hodnota mínusu Odpovedať s históriou správ @@ -496,6 +640,8 @@ Upozornenia Synchronizácia Známky + Domov + Viditeľnosť dlaždíc Dochádzka Plán lekcie Známky @@ -512,23 +658,26 @@ Nové známky Nové domáce úlohy + Nová stretnutia Nové skúšky Šťastné číslo Nové správy Nové poznámky + Nové školské oznámenia Push upozornenia Nadchádzajúce lekcie Ladenie - Čierny - Červený - Modrý - Zelený - Fialový + Čierna + Červená + Modrá + Zelená + Fialová Žiadna farba Skopírované Vrátiť + Zmeniť Sťahovanie aktualizácií začalo… Aktualizácia bola stiahnutá. diff --git a/app/src/main/res/values-uk/preferences_values.xml b/app/src/main/res/values-uk/preferences_values.xml index 5c70bd53..f6f5b984 100644 --- a/app/src/main/res/values-uk/preferences_values.xml +++ b/app/src/main/res/values-uk/preferences_values.xml @@ -45,4 +45,15 @@ Середнє оцінювання за обидва семестри Середнє оцінювання за весь рік + + Щасливий номер + Непрочитані повідомлення + Відвідуваність + Уроки + Оцінки + Домашні завдання + Оголошення школи + Тести + Конференції + diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 8bc1b8e3..4850e239 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -11,6 +11,8 @@ Інше Про додаток Переглядач журналів + Відладка + Налагодження сповіщень Розробники Ліцензії Повідомлення @@ -21,6 +23,7 @@ Виберіть обліковий запис Деталі облікового запису Інформація про учня + Дошка %1$d семестр, %2$d/%3$d @@ -88,6 +91,7 @@ Очікувана оцінка Розрахована середня оцінка Підсумкова середня оцінка + з %1$d із %2$d тем Підсумок Клас Позначити як прочитане @@ -191,16 +195,22 @@ Тип Дата запису - New exam - New exams - New exams - New exams + Новий іспит + Новий іспит + Новий іспит + Нові іспити + + + Ви отримали %d новий іспит + Ви отримали %d новий іспит + Ви отримали %d новий іспит + Ви отримали %d нових іспитів - %d exam - %d exams - %d exams - %d exams + %d екзамен + %d екзамен + %d екзамен + %d іспити Отримані @@ -225,6 +235,15 @@ Такого повідомлення не існує Необхідно обрати принаймні 1 адресата Зміст повідомлення мусить складатися принаймні з 3 знаків + Лише непрочитані + Тільки з вкладеннями + Читання: %s + + Прочитані: %1$d з %2$d осіб + Прочитані: %1$d з %2$d осіб + Прочитані: %1$d з %2$d осіб + Прочитані: %1$d з %2$d осіб + %d повідомлення %d повідомлення @@ -237,6 +256,8 @@ Нові повідомлення Нові повідомлення + Ви хочете відновити повідомлення в чернетці? + Ви хочете відновити чернетку повідомлення з отримувачами: %s? Ви отримали %1$d нове повідомлення Ви отримали %1$d нових повідомлення @@ -308,23 +329,29 @@ Позначити як не зроблене Додатки - New homework - New homework - New homework - New homework + Нова домашня робота + Нова домашня робота + Нова домашня робота + Нова домашня робота + + + Ви отримали %d нове домашнє завдання + Ви отримали %d нове домашнє завдання + Ви отримали %d нове домашнє завдання + Ви отримали %d нове домашнє завдання - %d homework - %d homework - %d homework - %d homework + %d домашнє завдання + %d домашнє завдання + %d домашнє завдання + %d домашнє завдання Щасливий номер Сьогоднішній щасливий номер Брак інформації о щасливому номері Сьогоднішній щасливий номер - Сьогоднішнім щасливим номером є %s + Сьогоднішній щасливий номер: %s Показати історію Історія щасливих чисел @@ -357,6 +384,45 @@ Зустрічі Немає інформації про зустрічі + + %d конференція + %d конференція + %d конференція + %d конференцій + + + Нова конференція + Нова конференція + Нова конференція + Нові конференції + + + У вас є %1$d нова конференція + У вас є %1$d нова конференція + У вас є %1$d нова конференція + У вас є %1$d нових конференцій + + + Оголошення школи + Жодних навчальних оголошень + + %d оголошення про школу + %d оголошення про школу + %d оголошення про школу + Оголошення нової школи + + + Оголошення нової школи + Оголошення нової школи + Нові шкільні оголошення + Нові шкільні оголошення + + + У вас є %1$d нове оголошення про школу + У вас є %1$d нове оголошення про школу + У вас є %1$d нове оголошення про школу + У вас є %1$d нових оголошень про школи + Додати аккаунт Вийти @@ -382,11 +448,13 @@ Сервер Discord Приєднатися до спільноти додатка Фен-сторінка Facebook + Сторінка Twitter + Стежте за нами у Твіттері Вподобати нашу фансторінку у Facebook Політика конфіденційності Правила зберігання особистих даних - System settings - Open system settings + Налаштування системи + Відкрити налаштування системи Домашня сторінка Допомогти розвитку додатка Ліцензії @@ -417,6 +485,7 @@ Чоловіча Жінка Прізвище + Охоронець Псевдонім Додати псевдонім @@ -424,6 +493,80 @@ Поділитися логами Оновити + + Уроки + (Завтра) + %1$s (%2$s) + Через мить: + Незабаром: + Перше: + Зараз: + + через %1$d хвилину + через %1$d хвилину + через %1$d хвилину + через %1$d хвилин + + + %1$d більше хвилини + %1$d більше хвилини + %1$d більше хвилини + %1$d ще хвилин + + Кінець уроків + Далі: + Пізніше : + + %1$d більше уроку + %1$d більше уроку + %1$d більше уроку + %1$d більше уроків + + до %1$s + Немає майбутніх уроків + Сталася помилка під час завантаження уроків + Домашня робота + Немає домашнього завдання для виконання + Помилка при завантаженні домашньої роботи + + Ще %1$d домашнє завдання + Ще %1$d домашнє завдання + Ще %1$d домашнє завдання + Ще %1$d домашнє завдання + + до %1$s + Останні оцінки + Немає нових оцінок + Помилка при завантаженні класів + Оголошення школи + Немає поточних оголошень + Помилка при завантаженні анонсів + + %1$d нових оголошень + %1$d нових оголошень + %1$d нових оголошень + %1$d нових оголошень + + Іспити + Немає майбутніх іспитів + Помилка при завантаженні іспитів + + %1$d ще екзамен + %1$d ще екзамен + %1$d ще екзамен + %1$d ще іспитів + + Конференції + Немає майбутніх конференцій + Помилка при завантаженні конференцій + + Ще %1$d конференція + Ще %1$d конференція + Ще %1$d конференція + %1$d більше конференцій + + Помилка при завантаженні даних + Нічого Провірити наявність оновлень Перед тим, як повідомлювати о помілці, перевірте наявність оновлень @@ -472,7 +615,7 @@ Повідомлення Показувати повідомлення Показувати повідомлення о наступних уроках - Open system notification settings + Відкрити налаштування сповіщень системи Виправити помилки з синхронізацією і повідомленнями На вашому пристрої можуть бути помилки з синхронізацією і повідомленнями\n\nЩоб виправити іх, вам необхідно додати Wulkanowy в авто-старт и вимкнути оптимізацію/экономію батареї в налаштуваннях пристрою. Перейти до налаштувань @@ -487,15 +630,18 @@ Синхронізовано! Синхронізація не вдалася Триває синхронізація + Остання синхронізація: %s Вартість плюсу Вага мінуса Відповісти з історією повідомлень - Show arithmetic average when no weights provided + Показувати в середньому арифметику, якщо немає ваги Додатково Вигляд & Поведінка Повідомлення Синхронізація Оцінки + Дошка + Видимість плиток Відвідуваність Розклад Класи @@ -511,11 +657,13 @@ Версія програми, учасники, соціальні портали, ліцензії Нові оцінки - New homework - New exams + Нова домашня робота + Нові конференції + Нові іспити Щасливий номер Нові повідомлення Нові нотатки + Оголошення нової школи Показувати push-повідомлення Наступні уроки Дебаг @@ -529,6 +677,7 @@ Скопійовано Відмінити + Змінити Завантаження оновлень розпочато… Щойно завантажено оновлення. From 170b7c43798606aa1ef12cc0e7013788286bfe7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Sun, 29 Aug 2021 21:06:33 +0200 Subject: [PATCH 185/186] New Crowdin updates (#1459) --- .../main/res/values-de/preferences_values.xml | 18 +- app/src/main/res/values-de/strings.xml | 82 ++++----- app/src/main/res/values-ru/strings.xml | 160 +++++++++--------- app/src/main/res/values-sk/strings.xml | 16 +- 4 files changed, 138 insertions(+), 138 deletions(-) diff --git a/app/src/main/res/values-de/preferences_values.xml b/app/src/main/res/values-de/preferences_values.xml index 53faaf9b..1e0df8de 100644 --- a/app/src/main/res/values-de/preferences_values.xml +++ b/app/src/main/res/values-de/preferences_values.xml @@ -46,14 +46,14 @@ Durchschnitt der Noten aus dem ganzen Jahr - Lucky number - Unread messages - Attendance - Lessons - Grades - Homework - School announcements - Exams - Conferences + Glückszahl + Ungelesene Nachrichten + Schulbesuch + Lektionen + Noten + Hausaufgaben + Schulankündigungen + Prüfungen + Sitzungen diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 4bbf7767..29d5c763 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -185,8 +185,8 @@ Neue prüfungen - You received %d new exam - Du hast %d neue Prüfungen bekommen + Du hast %d neue Prüfung erhalten + Sie haben %d neue Prüfungen erhalten %d prüfung @@ -219,8 +219,8 @@ Nur mit Anhängen Lesen: %s - Read by: %1$d of %2$d people - Read by: %1$d of %2$d people + Lesen von: %1$d von %2$d Personen + Lesen von: %1$d von %2$d Personen %d nachricht @@ -230,8 +230,8 @@ Neu nachricht Neue nachrichten - Do you want to restore draft message? - Do you want to restore draft message with recipients: %s? + Möchten Sie den Entwurf der Nachricht wiederherstellen? + Möchten Sie die Entwurfsnachricht mit den Empfängern wiederherstellen: %s? Du hast %1$d nachricht bekommen Du hast %1$d nachrichten bekommen @@ -287,8 +287,8 @@ Neue hausaufgaben - You received %d new homework - Du hast %d neue Hausaufgaben bekommen + Du hast %d neue Hausaufgaben erhalten + Du hast %d neue Hausaufgaben erhalten %d hausaufgaben @@ -450,45 +450,45 @@ Später: Noch %1$d Lektion - %1$d more lessons + Noch %1$d Lektionen - 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 + bis %1$s + Keine anstehenden Lektionen + Fehler beim Laden des Lektionen + Hausaufgaben + Keine Hausaufgaben zu tun + Fehler beim Laden der Hausaufgaben - %1$d more homework - %1$d more homework + Noch %1$d Hausaufgabe + Noch %1$d Hausaufgaben - 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 + bis %1$s + Letzte Noten + Keine neuen Noten + Ein Fehler ist beim Laden der Noten + Schulankündigungen + Keine aktuellen Ankündigungen + Fehler beim Laden der Ankündigungen - %1$d more announcement - %1$d more announcements + Noch %1$d Schulankündigung + Noch %1$d Schulankündigungen - Exams - No upcoming exams - An error occurred while loading the exams + Prüfungen + Keine bevorstehenden Prüfungen + Fehler beim Laden der Prüfungen - %1$d more exam - %1$d more exams + Noch %1$d Prüfung + Noch %1$d Prüfungen - Conferences - No upcoming conferences - An error occurred while loading the conferences + Sitzungen + Keine bevorstehenden Ankündigungen + Beim Laden der Konferenzen trat ein Fehler auf - %1$d more conference - %1$d more conferences + %1$d weitere Konferenz + %1$d weitere Konferenzen - An error occurred while loading data - None + Fehler beim Laden der Daten + Keine Auf Updates prüfen Bevor Sie einen Fehler melden, prüfen Sie zuerst, ob ein Update mit der Fehlerbehebung verfügbar ist @@ -552,7 +552,7 @@ Synchronisiert! Synchronisierung fehlgeschlagen Synchronisierung läuft - Last full sync: %s + Letzte vollständige Synchronisierung: %s Wert des Plus Wert des Minus Antwort mit Nachrichtenhistorie @@ -563,7 +563,7 @@ Synchronisierung Noten Dashboard - Tiles visibility + Sichtbarkeit der Kacheln Schulbesuch Zeitplan Noten @@ -599,7 +599,7 @@ Kopiert lösen - Change + Ändern Download der Updates wurde gestartet… Ein Update wurde gerade heruntergeladen. diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index e9349313..db78f595 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -413,15 +413,15 @@ Объявление о новой школе - New school announcements - New school announcements - New school announcements + Объявление о новой школе + Объявление о новой школе + Объявления о новых школах - You have %1$d new school announcement - You have %1$d new school announcements - You have %1$d new school announcements - You have %1$d new school announcements + У вас %1$d объявление о новой школе + У вас %1$d объявление о новой школе + У тебя %1$d новых школьных объявлений + У тебя %1$d новых школьных объявлений Добавить аккаунт @@ -448,13 +448,13 @@ Сервер Discord Присоединиться к сообществу приложения Facebook фан-страница - Twitter page - Follow us on twitter + Странница в твиттере + Подпишись на нас в твиттере Поставьте лайк на нашей странице в Facebook Политика приватности Правила хранения личных данных - System settings - Open system settings + Параметры системы + Открыть системные настройки Домашняя страница Помочь в развитии приложения Лицензии @@ -485,7 +485,7 @@ Муж Женская Фамилия - Guardian + Опекун Ник Добавить ник @@ -494,79 +494,79 @@ Поделиться логами Обновить - Lessons - (Tomorrow) + Уроки + (Завтра) %1$s (%2$s) - In a moment: - Soon: - First: - Now: + Сейчас: + Скоро: + Первый: + Сейчас: - in %1$d minute - in %1$d minutes - in %1$d minutes - in %1$d minutes + через %1$d минуту + через %1$d минуту + через %1$d минуту + через %1$d минут - %1$d more minute - %1$d more minutes - %1$d more minutes - %1$d more minutes + Еще %1$d минута + Еще %1$d минута + Еще %1$d минута + Ещё %1$d минут - End of lessons - Next: - Later: + Окончание уроков + Далее: + Позднее: - %1$d more lesson - %1$d more lessons - %1$d more lessons - %1$d more lessons + Еще %1$d урок + Еще %1$d урок + Еще %1$d урок + Ещё %1$d уроков - 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$s + Нет предстоящих занятий + Произошла ошибка при загрузке уроков + Домашняя работа + Нет домашних заданий + Произошла ошибка при загрузке домашнего задания - %1$d more homework - %1$d more homework - %1$d more homework - %1$d more homework + Ещё %1$d домашних заданий + Ещё %1$d домашних заданий + Ещё %1$d домашних заданий + Ещё %1$d домашних заданий - 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$s + Последние оценки + Нет новых оценок + Произошла ошибка при загрузке оценок + Объявления школ + Нет текущих объявлений + Произошла ошибка при загрузке объявлений - %1$d more announcement - %1$d more announcements - %1$d more announcements - %1$d more announcements + Ещё %1$d объявление + Ещё %1$d объявление + Ещё %1$d объявление + Ещё %1$d объявлений - Exams - No upcoming exams - An error occurred while loading the exams + Экзамены + Нет предстоящих экзаменов + Произошла ошибка при загрузке экзамена - %1$d more exam - %1$d more exams - %1$d more exams - %1$d more exams + Еще %1$d экзамен + Еще %1$d экзамен + Еще %1$d экзамен + ещё %1$d экзаменов - Conferences - No upcoming conferences - An error occurred while loading the conferences + Конференции + Нет предстоящих конференций + Произошла ошибка при загрузке конференций - %1$d more conference - %1$d more conferences - %1$d more conferences - %1$d more conferences + Еще %1$d конференция + Еще %1$d конференция + Еще %1$d конференция + Еще %1$d конференций - An error occurred while loading data - None + Произошла ошибка при загрузке данных + Отсутствует Проверить наличие обновлений Прежде чем сообщать об ошибке, проверьте наличие обновлений @@ -615,7 +615,7 @@ Уведомления Показывать уведомления Показывать уведомления о будущих уроках - Open system notification settings + Открыть настройки уведомлений системы Исправить проблемы с синхронизацией и уведомлениями На вашем устройстве могут быть проблемы с синхронизацией данных и уведомлениями.\n\nЧтобы их исправить, вам необходимо добавить Wulkanowy в авто-старт и выключить оптимизацию/экономию батареи в настройках устройства. Перейти в настройски @@ -630,18 +630,18 @@ Синхронизировано! Синхронизация не удалась Идёт синхронизация - Last full sync: %s + Последняя полная синхронизация: %s Стоимость плюса Стоимость минуса Отвечать с историей сообщений - Show arithmetic average when no weights provided + Показывать среднее арифметическое при отсутствии весов Расширенные Внешний вид & Поведение Уведомления Синхронизация Оценки - Dashboard - Tiles visibility + Панель + Видимость плиток Посещаемость Расписание Оценки @@ -657,13 +657,13 @@ Версия приложения, участники, социальные порталы, лицензии Новые оценки - New homework - New conferences - New exams + Новая домашняя работа + Новые конференции + Новые экзамены Счастливый номер Новые сообщения Новые заметки - New school announcements + Объявления о новых школах Показывать push-уведомления Будущие уроки Дебаг @@ -677,7 +677,7 @@ Скопировано Отменить - Change + Изменить Загрузка обновлений началась… Только что было скачано обновление. diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 81a76cf3..72899b78 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -201,10 +201,10 @@ Nové skúšky - You received %d new exam - You received %d new exams - You received %d new exams - You received %d new exams + Máte %d novú skúšku + Máte %d nové skúšky + Máte %d nových skúšok + Máte %d nových skúšok %d skúška @@ -335,10 +335,10 @@ Nové domáce úlohy - You received %d new homework - You received %d new homework - You received %d new homework - You received %d new homework + Máte %d novú domácu úlohu + Máte %d nové domáce úlohy + Máte %d nových domácich úloh + Máte %d nových domácich úloh %d domáci úloh From 72d8b4aa8480a4d7c32b1ccb225b7a2498dbe24c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sun, 29 Aug 2021 21:08:08 +0200 Subject: [PATCH 186/186] Version 1.2.0 --- app/build.gradle | 10 +++++----- app/src/main/play/release-notes/pl-PL/default.txt | 10 +++++++--- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index c05aa034..61e47534 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -21,8 +21,8 @@ android { testApplicationId "io.github.tests.wulkanowy" minSdkVersion 21 targetSdkVersion 30 - versionCode 92 - versionName "1.1.6" + versionCode 93 + versionName "1.2.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary = true @@ -133,8 +133,8 @@ play { serviceAccountEmail = System.getenv("PLAY_SERVICE_ACCOUNT_EMAIL") ?: "jan@fakelog.cf" serviceAccountCredentials = file('key.p12') defaultToAppBundles = false - track = 'production' - updatePriority = 5 + track = 'beta' + updatePriority = 3 } huaweiPublish { @@ -157,7 +157,7 @@ ext { } dependencies { - implementation "io.github.wulkanowy:sdk:b991d0c" + implementation "io.github.wulkanowy:sdk:1.2.0" coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' 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 a6f3f44e..42fd2229 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,10 @@ -Wersja 1.1.6 +Wersja 1.2.0 -- naprawiliśmy błąd przy wysyłaniu wiadomości -- naprawiliśmy oznaczanie odwołanych lekcji jako zastępstwa +- dodaliśmy nowy ekran startowy 🎉 +- usprawniliśmy powiadomienia +- dodaliśmy wersje robocze, filtrowanie oraz informację o odczytaniu przez odbiorcę w wiadomościach +- dodaliśmy informacje o liczeniu średniej w podsumowaniu ocen +- dodaliśmy opcję generowania wiadomości z usprawiedliwieniem dni w szkołach pozbawionych funkcji usprawiedliwiania przez zakładkę frekwencja +- oraz wiele wiele innych ulepszeń i poprawek Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases