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 0e92447974
20 changed files with 99 additions and 73 deletions

View File

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

View File

@ -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)

View File

@ -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)

View File

@ -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"

View File

@ -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?>
} }

View File

@ -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)
}
} }

View File

@ -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) {

View File

@ -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)
} }
) )
} }

View File

@ -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 })
} }

View File

@ -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

View File

@ -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 -> {

View File

@ -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!!)

View File

@ -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)

View File

@ -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()
}
} }

View File

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

View File

@ -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

View File

@ -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)
} }

View File

@ -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

View File

@ -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>

View File

@ -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)