Merge branch 'release/0.20.1' into master

This commit is contained in:
Mikołaj Pich 2020-09-02 00:14:23 +02:00
commit ebe865da0f
20 changed files with 99 additions and 73 deletions
.travis.ymlREADME.en.mdREADME.md
app
build.gradle
src
main
test/java/io/github/wulkanowy/data/repositories/semester

@ -14,7 +14,7 @@ cache:
branches: branches:
only: only:
- develop - develop
- 0.20.0 - 0.20.1
android: android:
licenses: licenses:

@ -47,7 +47,6 @@ You can also download a [development version](https://wulkanowy.github.io/#downl
* [Wulkanowy SDK](https://github.com/wulkanowy/sdk) * [Wulkanowy SDK](https://github.com/wulkanowy/sdk)
* [RxJava 2](https://github.com/ReactiveX/RxJava)
* [Dagger 2](https://github.com/google/dagger) * [Dagger 2](https://github.com/google/dagger)
* [Room](https://developer.android.com/topic/libraries/architecture/room) * [Room](https://developer.android.com/topic/libraries/architecture/room)
* [WorkManager](https://developer.android.com/topic/libraries/architecture/workmanager) * [WorkManager](https://developer.android.com/topic/libraries/architecture/workmanager)

@ -48,7 +48,6 @@ Możesz także pobrać [wersję rozwojową](https://wulkanowy.github.io/#downloa
## Zbudowana za pomocą ## Zbudowana za pomocą
* [Wulkanowy SDK](https://github.com/wulkanowy/SDK) * [Wulkanowy SDK](https://github.com/wulkanowy/SDK)
* [RxJava 2](https://github.com/ReactiveX/RxJava)
* [Dagger 2](https://github.com/google/dagger) * [Dagger 2](https://github.com/google/dagger)
* [Room](https://developer.android.com/topic/libraries/architecture/room) * [Room](https://developer.android.com/topic/libraries/architecture/room)
* [WorkManager](https://developer.android.com/topic/libraries/architecture/workmanager) * [WorkManager](https://developer.android.com/topic/libraries/architecture/workmanager)

@ -126,7 +126,7 @@ configurations.all {
} }
dependencies { dependencies {
implementation "io.github.wulkanowy:sdk:0.20.0" implementation "io.github.wulkanowy:sdk:0.20.1"
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.10' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.10'
@ -145,9 +145,9 @@ dependencies {
implementation "androidx.recyclerview:recyclerview:1.1.0" implementation "androidx.recyclerview:recyclerview:1.1.0"
implementation "androidx.viewpager:viewpager:1.0.0" implementation "androidx.viewpager:viewpager:1.0.0"
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0" implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
implementation "androidx.constraintlayout:constraintlayout:1.1.3" implementation "androidx.constraintlayout:constraintlayout:2.0.1"
implementation "androidx.coordinatorlayout:coordinatorlayout:1.1.0" implementation "androidx.coordinatorlayout:coordinatorlayout:1.1.0"
implementation "com.google.android.material:material:1.1.0" implementation "com.google.android.material:material:1.2.0"
implementation "com.github.wulkanowy:material-chips-input:2.1.1" implementation "com.github.wulkanowy:material-chips-input:2.1.1"
implementation "com.github.PhilJay:MPAndroidChart:v3.1.0" implementation "com.github.PhilJay:MPAndroidChart:v3.1.0"
implementation "me.zhanghai.android.materialprogressbar:library:1.6.1" implementation "me.zhanghai.android.materialprogressbar:library:1.6.1"
@ -181,10 +181,10 @@ dependencies {
implementation 'me.xdrop:fuzzywuzzy:1.3.1' implementation 'me.xdrop:fuzzywuzzy:1.3.1'
playImplementation 'com.google.firebase:firebase-analytics:17.5.0' playImplementation 'com.google.firebase:firebase-analytics:17.5.0'
playImplementation 'com.google.firebase:firebase-inappmessaging-display-ktx:19.1.0' playImplementation 'com.google.firebase:firebase-inappmessaging-display-ktx:19.1.1'
playImplementation "com.google.firebase:firebase-inappmessaging-ktx:19.1.1" playImplementation "com.google.firebase:firebase-inappmessaging-ktx:19.1.1"
playImplementation 'com.google.firebase:firebase-messaging:20.2.4' playImplementation 'com.google.firebase:firebase-messaging:20.2.4'
playImplementation 'com.google.firebase:firebase-crashlytics:17.1.1' playImplementation 'com.google.firebase:firebase-crashlytics:17.2.1'
playImplementation 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava' playImplementation 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava'
releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker" releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker"
@ -196,8 +196,8 @@ dependencies {
testImplementation "io.mockk:mockk:$mockk" testImplementation "io.mockk:mockk:$mockk"
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.3.9' testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.3.9'
androidTestImplementation "androidx.test:core:1.2.0" androidTestImplementation "androidx.test:core:1.3.0"
androidTestImplementation "androidx.test:runner:1.2.0" androidTestImplementation "androidx.test:runner:1.3.0"
androidTestImplementation "androidx.test.ext:junit:1.1.2" androidTestImplementation "androidx.test.ext:junit:1.1.2"
androidTestImplementation "io.mockk:mockk-android:$mockk" androidTestImplementation "io.mockk:mockk-android:$mockk"
androidTestImplementation "androidx.room:room-testing:$room" androidTestImplementation "androidx.room:room-testing:$room"

@ -12,5 +12,5 @@ import javax.inject.Singleton
interface LuckyNumberDao : BaseDao<LuckyNumber> { interface LuckyNumberDao : BaseDao<LuckyNumber> {
@Query("SELECT * FROM LuckyNumbers WHERE student_id = :studentId AND date = :date") @Query("SELECT * FROM LuckyNumbers WHERE student_id = :studentId AND date = :date")
fun load(studentId: Int, date: LocalDate): Flow<LuckyNumber> fun load(studentId: Int, date: LocalDate): Flow<LuckyNumber?>
} }

@ -3,7 +3,8 @@ package io.github.wulkanowy.data.repositories.luckynumber
import io.github.wulkanowy.data.db.entities.LuckyNumber import io.github.wulkanowy.data.db.entities.LuckyNumber
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.utils.networkBoundResource import io.github.wulkanowy.utils.networkBoundResource
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import java.time.LocalDate.now import java.time.LocalDate.now
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -28,11 +29,8 @@ class LuckyNumberRepository @Inject constructor(
} }
) )
fun getNotNotifiedLuckyNumber(student: Student): Flow<LuckyNumber?> { suspend fun getNotNotifiedLuckyNumber(student: Student) =
return local.getLuckyNumber(student, now()) local.getLuckyNumber(student, now()).map { if (it?.isNotified == false) it else null }.first()
}
suspend fun updateLuckyNumber(luckyNumber: LuckyNumber?) { suspend fun updateLuckyNumber(luckyNumber: LuckyNumber?) = local.updateLuckyNumber(luckyNumber)
local.updateLuckyNumber(luckyNumber)
}
} }

@ -1,5 +1,6 @@
package io.github.wulkanowy.data.repositories.semester package io.github.wulkanowy.data.repositories.semester
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.DispatchersProvider import io.github.wulkanowy.utils.DispatchersProvider
@ -18,24 +19,33 @@ class SemesterRepository @Inject constructor(
) { ) {
suspend fun getSemesters(student: Student, forceRefresh: Boolean = false, refreshOnNoCurrent: Boolean = false) = withContext(dispatchers.backgroundThread) { suspend fun getSemesters(student: Student, forceRefresh: Boolean = false, refreshOnNoCurrent: Boolean = false) = withContext(dispatchers.backgroundThread) {
local.getSemesters(student).let { semesters -> val semesters = local.getSemesters(student)
semesters.filter {
!forceRefresh && when { if (isShouldFetch(student, semesters, forceRefresh, refreshOnNoCurrent)) {
Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API -> semesters.firstOrNull { it.isCurrent }?.diaryId != 0 refreshSemesters(student)
refreshOnNoCurrent -> semesters.any { semester -> semester.isCurrent } local.getSemesters(student)
else -> true } else semesters
} }
private fun isShouldFetch(student: Student, semesters: List<Semester>, forceRefresh: Boolean, refreshOnNoCurrent: Boolean): Boolean {
val isNoSemesters = semesters.isEmpty()
val isRefreshOnModeChangeRequired = if (Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) {
semesters.firstOrNull { it.isCurrent }?.diaryId == 0
} else false
val isRefreshOnNoCurrentAppropriate = refreshOnNoCurrent && !semesters.any { semester -> semester.isCurrent }
return forceRefresh || isNoSemesters || isRefreshOnModeChangeRequired || isRefreshOnNoCurrentAppropriate
} }
}.ifEmpty {
private suspend fun refreshSemesters(student: Student) {
val new = remote.getSemesters(student) val new = remote.getSemesters(student)
if (new.isEmpty()) throw IllegalArgumentException("Empty semester list!") if (new.isEmpty()) throw IllegalArgumentException("Empty semester list!")
val old = local.getSemesters(student) val old = local.getSemesters(student)
local.deleteSemesters(old.uniqueSubtract(new)) local.deleteSemesters(old.uniqueSubtract(new))
local.saveSemesters(new.uniqueSubtract(old)) local.saveSemesters(new.uniqueSubtract(old))
local.getSemesters(student)
}
} }
suspend fun getCurrentSemester(student: Student, forceRefresh: Boolean = false) = withContext(dispatchers.backgroundThread) { suspend fun getCurrentSemester(student: Student, forceRefresh: Boolean = false) = withContext(dispatchers.backgroundThread) {

@ -26,6 +26,7 @@ class UpcomingLessonsChannel @Inject constructor(
lockscreenVisibility = VISIBILITY_PUBLIC lockscreenVisibility = VISIBILITY_PUBLIC
setShowBadge(false) setShowBadge(false)
enableVibration(false) enableVibration(false)
setSound(null, null)
} }
) )
} }

@ -19,7 +19,6 @@ import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.getCompatColor import io.github.wulkanowy.utils.getCompatColor
import io.github.wulkanowy.utils.waitForResult import io.github.wulkanowy.utils.waitForResult
import kotlinx.coroutines.flow.first
import javax.inject.Inject import javax.inject.Inject
import kotlin.random.Random import kotlin.random.Random
@ -33,7 +32,7 @@ class LuckyNumberWork @Inject constructor(
override suspend fun doWork(student: Student, semester: Semester) { override suspend fun doWork(student: Student, semester: Semester) {
luckyNumberRepository.getLuckyNumber(student, true, preferencesRepository.isNotificationsEnable).waitForResult() luckyNumberRepository.getLuckyNumber(student, true, preferencesRepository.isNotificationsEnable).waitForResult()
luckyNumberRepository.getNotNotifiedLuckyNumber(student).first()?.let { luckyNumberRepository.getNotNotifiedLuckyNumber(student)?.let {
notify(it) notify(it)
luckyNumberRepository.updateLuckyNumber(it.apply { isNotified = true }) luckyNumberRepository.updateLuckyNumber(it.apply { isNotified = true })
} }

@ -21,7 +21,7 @@ class GradeSummaryAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerV
var items = emptyList<GradeSummary>() var items = emptyList<GradeSummary>()
override fun getItemCount() = items.size + 1 override fun getItemCount() = items.size + if (items.isNotEmpty()) 1 else 0
override fun getItemViewType(position: Int) = when (position) { override fun getItemViewType(position: Int) = when (position) {
0 -> ViewType.HEADER.id 0 -> ViewType.HEADER.id

@ -45,16 +45,17 @@ class GradeSummaryPresenter @Inject constructor(
Status.LOADING -> Timber.i("Loading grade summary started") Status.LOADING -> Timber.i("Loading grade summary started")
Status.SUCCESS -> { Status.SUCCESS -> {
Timber.i("Loading grade summary result: Success") Timber.i("Loading grade summary result: Success")
val items = createGradeSummaryItems(it.data!!)
view?.run { view?.run {
showEmpty(it.data!!.isEmpty()) showEmpty(items.isEmpty())
showContent(it.data.isNotEmpty()) showContent(items.isNotEmpty())
showErrorView(false) showErrorView(false)
updateData(createGradeSummaryItems(it.data)) updateData(items)
} }
analytics.logEvent( analytics.logEvent(
"load_data", "load_data",
"type" to "grade_summary", "type" to "grade_summary",
"items" to it.data!!.size "items" to it.data.size
) )
} }
Status.ERROR -> { Status.ERROR -> {

@ -66,7 +66,12 @@ class LoginRecoverPresenter @Inject constructor(
showErrorView(false) showErrorView(false)
showCaptcha(false) showCaptcha(false)
} }
Status.SUCCESS -> view?.loadReCaptcha(siteKey = it.data!!.first, url = it.data.second) Status.SUCCESS -> view?.run {
loadReCaptcha(url = it.data!!.first, siteKey = it.data.second)
showProgress(false)
showErrorView(false)
showCaptcha(true)
}
Status.ERROR -> { Status.ERROR -> {
Timber.i("Obtain captcha site key result: An exception occurred") Timber.i("Obtain captcha site key result: An exception occurred")
errorHandler.dispatch(it.error!!) errorHandler.dispatch(it.error!!)

@ -120,15 +120,6 @@ class SettingsFragment : PreferenceFragmentCompat(),
ErrorDialog.newInstance(error).show(childFragmentManager, error.toString()) ErrorDialog.newInstance(error).show(childFragmentManager, error.toString())
} }
override fun showForceSyncDialog() {
AlertDialog.Builder(requireContext())
.setTitle(R.string.pref_services_dialog_force_sync_title)
.setMessage(R.string.pref_services_dialog_force_sync_summary)
.setPositiveButton(android.R.string.ok) { _, _ -> presenter.onForceSyncDialogSubmit() }
.setNegativeButton(android.R.string.cancel) { _, _ -> }
.show()
}
override fun showFixSyncDialog() { override fun showFixSyncDialog() {
AlertDialog.Builder(requireContext()) AlertDialog.Builder(requireContext())
.setTitle(R.string.pref_notify_fix_sync_issues) .setTitle(R.string.pref_notify_fix_sync_issues)

@ -55,14 +55,6 @@ class SettingsPresenter @Inject constructor(
} }
fun onSyncNowClicked() { fun onSyncNowClicked() {
view?.showForceSyncDialog()
}
fun onFixSyncIssuesClicked() {
view?.showFixSyncDialog()
}
fun onForceSyncDialogSubmit() {
view?.run { view?.run {
syncManager.startOneTimeSyncWorker().onEach { workInfo -> syncManager.startOneTimeSyncWorker().onEach { workInfo ->
when (workInfo.state) { when (workInfo.state) {
@ -87,4 +79,8 @@ class SettingsPresenter @Inject constructor(
}.launch("sync") }.launch("sync")
} }
} }
fun onFixSyncIssuesClicked() {
view?.showFixSyncDialog()
}
} }

@ -18,6 +18,5 @@ interface SettingsView : BaseView {
fun setSyncInProgress(inProgress: Boolean) fun setSyncInProgress(inProgress: Boolean)
fun showForceSyncDialog()
fun showFixSyncDialog() fun showFixSyncDialog()
} }

@ -22,6 +22,7 @@ import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import timber.log.Timber import timber.log.Timber
import java.lang.NullPointerException
import java.time.LocalDate import java.time.LocalDate
import java.time.LocalDate.now import java.time.LocalDate.now
import java.time.LocalDate.of import java.time.LocalDate.of

@ -3,6 +3,7 @@ package io.github.wulkanowy.utils
import io.github.wulkanowy.data.Resource import io.github.wulkanowy.data.Resource
import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.Status
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.emitAll import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filter
@ -69,18 +70,20 @@ inline fun <ResultType, RequestType, T> networkBoundResource(
fun <T> flowWithResource(block: suspend () -> T) = flow { fun <T> flowWithResource(block: suspend () -> T) = flow {
emit(Resource.loading()) emit(Resource.loading())
try { emit(try {
emit(Resource.success(block())) Resource.success(block())
} catch (e: Throwable) { } catch (e: Throwable) {
emit(Resource.error(e)) Resource.error(e)
} })
} }
fun <T> flowWithResourceIn(block: suspend () -> Flow<Resource<T>>) = flow { fun <T> flowWithResourceIn(block: suspend () -> Flow<Resource<T>>) = flow {
emit(Resource.loading()) emit(Resource.loading())
try { try {
block().collect { block()
.catch { emit(Resource.error(it)) }
.collect {
if (it.status != Status.LOADING) { // LOADING is already emitted if (it.status != Status.LOADING) { // LOADING is already emitted
emit(it) emit(it)
} }

@ -1,5 +1,7 @@
Wersja 0.20.0 Wersja 0.20.1
- naprawiliśmy obsługę wiadomości - naprawiliśmy powiadomienie o szczęśliwym numerku
- naprawiliśmy wyświetlanie oznaczenia klasy ucznia w menadżerze kont - naprawiliśmy przełączanie na nowy rok szkolny
- naprawiliśmy funkcję resetowania hasła
- dodaliśmy dziennik e-Skarżysko
Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases

@ -10,6 +10,7 @@
<item>Rawa Mazowiecka - Platforma vEdukacja</item> <item>Rawa Mazowiecka - Platforma vEdukacja</item>
<item>Zduńska Wola - e-Urząd</item> <item>Zduńska Wola - e-Urząd</item>
<item>Sieradz - Portal oświatowy</item> <item>Sieradz - Portal oświatowy</item>
<item>Skarżysko-Kamienna - e-Skarżysko</item>
<item>Łask - Platforma vEdukacja</item> <item>Łask - Platforma vEdukacja</item>
<item>Powiat łaski - Platforma edukacyjna</item> <item>Powiat łaski - Platforma edukacyjna</item>
<item>Powiat Krasnostawski - Platforma oświatowa</item> <item>Powiat Krasnostawski - Platforma oświatowa</item>
@ -36,6 +37,7 @@
<item>https://vulcan.net.pl/</item> <item>https://vulcan.net.pl/</item>
<item>https://vulcan.net.pl/</item> <item>https://vulcan.net.pl/</item>
<item>https://vulcan.net.pl/</item> <item>https://vulcan.net.pl/</item>
<item>https://vulcan.net.pl/</item>
<item>http://fakelog.tk/?standard</item> <item>http://fakelog.tk/?standard</item>
</string-array> </string-array>
<string-array name="hosts_symbols"> <string-array name="hosts_symbols">
@ -48,6 +50,7 @@
<item>rawamazowiecka</item> <item>rawamazowiecka</item>
<item>zdunskawola</item> <item>zdunskawola</item>
<item>sieradz</item> <item>sieradz</item>
<item>skarzyskokamienna</item>
<item>lask</item> <item>lask</item>
<item>powiatlaski</item> <item>powiatlaski</item>
<item>powiatkrasnostawski</item> <item>powiatkrasnostawski</item>

@ -63,7 +63,15 @@ class SemesterRepositoryTest {
createSemesterEntity(0, 2, now().minusMonths(3), now()) createSemesterEntity(0, 2, now().minusMonths(3), now())
) )
val goodSemesters = listOf(
createSemesterEntity(122, 1, now().minusMonths(6), now().minusMonths(3)),
createSemesterEntity(123, 2, now().minusMonths(3), now())
)
coEvery { semesterLocal.getSemesters(student) } returns badSemesters coEvery { semesterLocal.getSemesters(student) } returns badSemesters
coEvery { semesterRemote.getSemesters(student) } returns goodSemesters
coEvery { semesterLocal.deleteSemesters(any()) } just Runs
coEvery { semesterLocal.saveSemesters(any()) } just Runs
val items = runBlocking { semesterRepository.getSemesters(student) } val items = runBlocking { semesterRepository.getSemesters(student) }
assertEquals(2, items.size) assertEquals(2, items.size)
@ -152,12 +160,23 @@ class SemesterRepositoryTest {
@Test @Test
fun getSemesters_noCurrent_refreshOnNoCurrent() { fun getSemesters_noCurrent_refreshOnNoCurrent() {
val semesters = listOf( val semestersWithNoCurrent = listOf(
createSemesterEntity(1, 1, now().minusMonths(12), now().minusMonths(6)), createSemesterEntity(1, 1, now().minusMonths(12), now().minusMonths(6)),
createSemesterEntity(1, 2, now().minusMonths(6), now().minusMonths(1)) createSemesterEntity(1, 2, now().minusMonths(6), now().minusMonths(1))
) )
coEvery { semesterLocal.getSemesters(student) } returns semesters val newSemesters = listOf(
createSemesterEntity(1, 1, now().minusMonths(12), now().minusMonths(6)),
createSemesterEntity(1, 2, now().minusMonths(6), now().minusMonths(1)),
createSemesterEntity(2, 1, now().minusMonths(1), now().plusMonths(5)),
createSemesterEntity(2, 2, now().plusMonths(5), now().plusMonths(11)),
)
coEvery { semesterLocal.getSemesters(student) } returns semestersWithNoCurrent
coEvery { semesterRemote.getSemesters(student) } returns newSemesters
coEvery { semesterLocal.deleteSemesters(any()) } just Runs
coEvery { semesterLocal.saveSemesters(any()) } just Runs
val items = runBlocking { semesterRepository.getSemesters(student, refreshOnNoCurrent = true) } val items = runBlocking { semesterRepository.getSemesters(student, refreshOnNoCurrent = true) }
assertEquals(2, items.size) assertEquals(2, items.size)