Compare commits

..

21 Commits
1.7.1 ... 1.7.4

Author SHA1 Message Date
1033be4503 Merge branch 'release/1.7.4' 2022-09-01 17:57:24 +02:00
e67066f3ae Version 1.7.4 2022-09-01 17:57:20 +02:00
bc22808b0e Add support for matching last kingergarten semester if there is no any current (#1962) 2022-09-01 17:48:46 +02:00
e05abb3539 Fix showing empty view in grade details when there is no grades (#1963) 2022-09-01 17:48:03 +02:00
6153c7b97d Add support for match mailboxes with more different names (#1961) 2022-09-01 14:55:00 +02:00
e574e5e2ec Merge branch 'release/1.7.3' into develop 2022-08-31 19:31:39 +02:00
e6571a1dfc Merge branch 'release/1.7.3' 2022-08-31 19:31:33 +02:00
d566de0282 Version 1.7.3 2022-08-31 19:31:28 +02:00
558db061f5 Fix marking message as read (#1960)
* Fix marking message as read

* Update sdk

* Update sdk

* Fix tests

* Use many recipients strings instead of first recipient
2022-08-31 18:31:12 +02:00
190f40ede8 Merge branch 'release/1.7.2' into develop 2022-08-30 13:34:28 +02:00
4b795d6ef5 Merge branch 'release/1.7.2' 2022-08-30 13:34:20 +02:00
d139bd5b14 Version 1.7.2 2022-08-30 13:34:10 +02:00
bf34cb0c1e New Crowdin updates (#1953)
Co-authored-by: Mikołaj Pich <m.pich@outlook.com>
2022-08-30 12:11:32 +02:00
eed091aad2 Update row in mailboxes table on primary key conflict (#1954) 2022-08-30 09:07:24 +02:00
535206056d Fix selecting student mailbox based on studentName field (#1958) 2022-08-30 09:07:07 +02:00
f2cb3b4f9e Change message draft key in shared preferences (#1959) 2022-08-30 09:06:29 +02:00
54372e0a55 Bump kotlinx-serialization-json from 1.3.3 to 1.4.0 (#1950) 2022-08-29 13:40:14 +00:00
5c17c38d1d Bump mockk from 1.12.5 to 1.12.7 (#1955) 2022-08-29 13:30:56 +00:00
f68a8e4215 Bump robolectric from 4.8.1 to 4.8.2 (#1956) 2022-08-29 13:30:28 +00:00
70c2cb7dbf Bump desugar_jdk_libs from 1.1.6 to 1.1.8 (#1957) 2022-08-29 13:30:00 +00:00
7f6fd60821 Merge branch 'release/1.7.1' into develop 2022-08-23 00:56:29 +02:00
12 changed files with 329 additions and 34 deletions

View File

@ -23,8 +23,8 @@ android {
testApplicationId "io.github.tests.wulkanowy"
minSdkVersion 21
targetSdkVersion 32
versionCode 110
versionName "1.7.1"
versionCode 113
versionName "1.7.4"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
resValue "string", "app_name", "Wulkanowy"
@ -160,8 +160,8 @@ kapt {
play {
defaultToAppBundles = false
track = 'production'
releaseStatus = com.github.triplet.gradle.androidpublisher.ReleaseStatus.IN_PROGRESS
userFraction = 0.95d
// releaseStatus = com.github.triplet.gradle.androidpublisher.ReleaseStatus.IN_PROGRESS
// userFraction = 0.05d
updatePriority = 5
enabled.set(false)
}
@ -181,16 +181,16 @@ ext {
android_hilt = "1.0.0"
room = "2.4.3"
chucker = "3.5.2"
mockk = "1.12.5"
mockk = "1.12.7"
coroutines = "1.6.4"
}
dependencies {
implementation "io.github.wulkanowy:sdk:1.7.0"
implementation "io.github.wulkanowy:sdk:1.7.4"
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.6'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.8'
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.3"
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.0"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines"
implementation "androidx.core:core-ktx:1.8.0"
@ -263,7 +263,7 @@ dependencies {
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines"
testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
testImplementation 'org.robolectric:robolectric:4.8.1'
testImplementation 'org.robolectric:robolectric:4.8.2'
testImplementation "androidx.test:runner:1.4.0"
testImplementation "androidx.test.ext:junit:1.1.3"
testImplementation "androidx.test:core:1.4.0"

View File

@ -2,11 +2,12 @@ package io.github.wulkanowy.data.db.dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Update
interface BaseDao<T> {
@Insert
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAll(items: List<T>): List<Long>
@Update

View File

@ -11,7 +11,4 @@ interface MailboxDao : BaseDao<Mailbox> {
@Query("SELECT * FROM Mailboxes WHERE userLoginId = :userLoginId ")
suspend fun loadAll(userLoginId: Int): List<Mailbox>
@Query("SELECT * FROM Mailboxes WHERE userLoginId = :userLoginId AND studentName = :studentName ")
suspend fun load(userLoginId: Int, studentName: String): Mailbox?
}

View File

@ -32,17 +32,52 @@ class MailboxRepository @Inject constructor(
suspend fun getMailbox(student: Student): Mailbox {
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student))
val mailbox = mailboxDao.load(student.userLoginId, student.studentName)
val mailboxes = mailboxDao.loadAll(student.userLoginId)
val mailbox = mailboxes.filterByStudent(student)
return if (isExpired || mailbox == null) {
refreshMailboxes(student)
val newMailbox = mailboxDao.load(student.userLoginId, student.studentName)
val newMailbox = mailboxDao.loadAll(student.userLoginId).filterByStudent(student)
requireNotNull(newMailbox) {
"Mailbox for ${student.userName} - ${student.studentName} not found!"
"Mailbox for ${student.userName} - ${student.studentName} not found! Saved mailboxes: $mailboxes"
}
newMailbox
} else mailbox
}
private fun List<Mailbox>.filterByStudent(student: Student): Mailbox? {
val normalizedStudentName = student.studentName.normalizeStudentName()
return find {
it.studentName.normalizeStudentName() == normalizedStudentName
} ?: singleOrNull {
it.studentName.getFirstAndLastPart() == normalizedStudentName.getFirstAndLastPart()
} ?: singleOrNull {
it.studentName.getUnauthorizedVersion() == normalizedStudentName
}
}
private fun String.normalizeStudentName(): String {
return trim().split(" ").joinToString(" ") { part ->
part.lowercase().replaceFirstChar { it.uppercase() }
}
}
private fun String.getFirstAndLastPart(): String {
val parts = normalizeStudentName().split(" ")
val endParts = parts.filterIndexed { i, _ ->
i == 0 || parts.size == i - 1
}
return endParts.joinToString(" ")
}
private fun String.getUnauthorizedVersion(): String {
return normalizeStudentName().split(" ")
.joinToString(" ") {
it.first() + "*".repeat(it.length - 1)
}
}
}

View File

@ -64,7 +64,10 @@ class MessageRepository @Inject constructor(
},
query = { messagesDb.loadAll(mailbox.globalKey, folder.id) },
fetch = {
sdk.init(student).getMessages(Folder.valueOf(folder.name)).mapToEntities(mailbox)
sdk.init(student).getMessages(
folder = Folder.valueOf(folder.name),
mailboxKey = mailbox.globalKey,
).mapToEntities(mailbox)
},
saveFetchResult = { old, new ->
messagesDb.deleteAll(old uniqueSubtract new)
@ -89,7 +92,7 @@ class MessageRepository @Inject constructor(
},
query = { messagesDb.loadMessageWithAttachment(message.messageGlobalKey) },
fetch = {
sdk.init(student).getMessageDetails(it!!.message.messageGlobalKey)
sdk.init(student).getMessageDetails(it!!.message.messageGlobalKey, markAsRead)
},
saveFetchResult = { old, new ->
checkNotNull(old) { "Fetched message no longer exist!" }
@ -98,7 +101,7 @@ class MessageRepository @Inject constructor(
id = message.id
unread = !markAsRead
sender = new.sender
recipients = new.recipients.firstOrNull() ?: "Wielu adresoatów"
recipients = new.recipients.singleOrNull() ?: "Wielu adresatów"
content = content.ifBlank { new.content }
})
)
@ -167,10 +170,10 @@ class MessageRepository @Inject constructor(
}
var draftMessage: MessageDraft?
get() = sharedPrefProvider.getString(context.getString(R.string.pref_key_message_send_draft))
get() = sharedPrefProvider.getString(context.getString(R.string.pref_key_message_draft))
?.let { json.decodeFromString(it) }
set(value) = sharedPrefProvider.putString(
context.getString(R.string.pref_key_message_send_draft),
context.getString(R.string.pref_key_message_draft),
value?.let { json.encodeToString(it) }
)
}

View File

@ -3,7 +3,6 @@ package io.github.wulkanowy.ui.modules.grade.details
import io.github.wulkanowy.data.*
import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.data.enums.GradeExpandMode
import io.github.wulkanowy.data.enums.GradeSortingMode
import io.github.wulkanowy.data.enums.GradeSortingMode.*
import io.github.wulkanowy.data.repositories.GradeRepository
import io.github.wulkanowy.data.repositories.PreferencesRepository
@ -132,16 +131,17 @@ class GradeDetailsPresenter @Inject constructor(
}
.logResourceStatus("load grade details")
.onResourceData {
val gradeItems = createGradeItems(it)
view?.run {
enableSwipe(true)
showProgress(false)
showErrorView(false)
showContent(it.isNotEmpty())
showEmpty(it.isEmpty())
showContent(gradeItems.isNotEmpty())
showEmpty(gradeItems.isEmpty())
updateNewGradesAmount(it)
updateMarkAsDoneButton()
updateData(
data = createGradeItems(it),
data = gradeItems,
expandMode = preferencesRepository.gradeExpandMode,
preferencesRepository.gradeColorTheme
)

View File

@ -15,5 +15,8 @@ fun List<Semester>.getCurrentOrLast(): Semester {
// when there is more than one current semester - find one with higher id
singleOrNull { semester -> semester.semesterId == maxByOrNull { it.semesterId }?.semesterId }?.let { return it }
// when there is no active kindergarten semester - get one from last year
singleOrNull { semester -> semester.schoolYear == maxByOrNull { it.schoolYear }?.schoolYear }?.let { return it }
throw IllegalArgumentException("Duplicated last semester! Semesters: ${joinToString(separator = "\n")}")
}

View File

@ -1,9 +1,6 @@
Wersja 1.7.1
Wersja 1.7.4
- naprawiliśmy logowanie do aplikacji
- dodaliśmy wsparcie nowego modułu Wiadomości Plus
- dodaliśmy nową możliwość wsparcia naszego projektu przez opcjonalne reklamy
- dodaliśmy sortowanie po średniej
- naprawiliśmy też kilka usterek wpływających na komfort używania aplikacji
- naprawiliśmy kilka błędów w obsłudze nowego modułu wiadomości
- naprawiliśmy wyświetlanie napisu "Brak ocen", jesli uczeń nie zdobył w danym semestrze jeszcze żadnych ocen
Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases

View File

@ -31,8 +31,7 @@
<string name="pref_key_homework_fullscreen">homework_fullscreen</string>
<string name="pref_key_subjects_without_grades">subjects_without_grades</string>
<string name="pref_key_optional_arithmetic_average">optional_arithmetic_average</string>
<string name="pref_key_message_send_is_draft">message_send_is_draft</string>
<string name="pref_key_message_send_draft">message_send_recipients</string>
<string name="pref_key_message_draft">message_draft</string>
<string name="pref_key_last_sync_date">last_sync_date</string>
<string name="pref_key_notifications_piggyback">notifications_piggyback</string>
<string name="pref_key_notifications_piggyback_cancel_original">notifications_piggyback_cancel_original</string>

View File

@ -0,0 +1,188 @@
package io.github.wulkanowy.data.repositories
import io.github.wulkanowy.data.db.dao.MailboxDao
import io.github.wulkanowy.data.db.entities.Mailbox
import io.github.wulkanowy.data.db.entities.MailboxType
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.AutoRefreshHelper
import io.mockk.MockKAnnotations
import io.mockk.Runs
import io.mockk.coEvery
import io.mockk.impl.annotations.MockK
import io.mockk.impl.annotations.SpyK
import io.mockk.just
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import java.time.Instant
@OptIn(ExperimentalCoroutinesApi::class)
class MailboxRepositoryTest {
@SpyK
private var sdk = Sdk()
@MockK
private lateinit var mailboxDao: MailboxDao
@MockK
private lateinit var refreshHelper: AutoRefreshHelper
private lateinit var systemUnderTest: MailboxRepository
@Before
fun setUp() {
MockKAnnotations.init(this)
coEvery { refreshHelper.shouldBeRefreshed(any()) } returns false
coEvery { refreshHelper.updateLastRefreshTimestamp(any()) } just Runs
coEvery { mailboxDao.deleteAll(any()) } just Runs
coEvery { mailboxDao.insertAll(any()) } returns emptyList()
coEvery { mailboxDao.loadAll(any()) } returns emptyList()
coEvery { sdk.getMailboxes() } returns emptyList()
systemUnderTest = MailboxRepository(
mailboxDao = mailboxDao,
sdk = sdk,
refreshHelper = refreshHelper,
)
}
@Test(expected = IllegalArgumentException::class)
fun `get mailbox that doesn't exist`() = runTest {
val student = getStudentEntity(
userName = "Stanisław Kowalski",
studentName = "Jan Kowalski",
)
coEvery { sdk.getMailboxes() } returns emptyList()
systemUnderTest.getMailbox(student)
}
@Test
fun `get mailbox for user with additional spaces`() = runTest {
val student = getStudentEntity(
userName = " Stanisław Kowalski ",
studentName = " Jan Kowalski ",
)
val expectedMailbox = getMailboxEntity("Jan Kowalski ")
coEvery { mailboxDao.loadAll(any()) } returns listOf(
expectedMailbox,
)
val selectedMailbox = systemUnderTest.getMailbox(student)
assertEquals(expectedMailbox, selectedMailbox)
}
@Test
fun `get mailbox for unique non-authorized student`() = runTest {
val student = getStudentEntity(
userName = "Stanisław Kowalski",
studentName = "J** K*******",
)
val expectedMailbox = getMailboxEntity("Jan Kowalski")
coEvery { mailboxDao.loadAll(any()) } returns listOf(
expectedMailbox,
)
assertEquals(expectedMailbox, systemUnderTest.getMailbox(student))
}
@Test(expected = IllegalArgumentException::class)
fun `get mailbox for not-unique non-authorized student`() = runTest {
val student = getStudentEntity(
userName = "Stanisław Kowalski",
studentName = "J** K*******",
)
coEvery { mailboxDao.loadAll(any()) } returns listOf(
getMailboxEntity("Jan Kowalski"),
getMailboxEntity("Jan Kurowski"),
)
systemUnderTest.getMailbox(student)
}
@Test
fun `get mailbox for student with uppercase name`() = runTest {
val student = getStudentEntity(
userName = "Mochoń Julia",
studentName = "KLAUDIA MOCHOŃ",
)
val expectedMailbox = getMailboxEntity("Klaudia Mochoń")
coEvery { mailboxDao.loadAll(any()) } returns listOf(
expectedMailbox,
)
assertEquals(expectedMailbox, systemUnderTest.getMailbox(student))
}
@Test
fun `get mailbox for student with second name`() = runTest {
val student = getStudentEntity(
userName = "Fistaszek Karolina",
studentName = "Julia Fistaszek",
)
val expectedMailbox = getMailboxEntity("Julia Maria Fistaszek")
coEvery { mailboxDao.loadAll(any()) } returns listOf(
expectedMailbox,
)
assertEquals(expectedMailbox, systemUnderTest.getMailbox(student))
}
@Test
fun `get mailbox for student with second name and uppercase`() = runTest {
val student = getStudentEntity(
userName = "BEDNAREK KAMIL",
studentName = "ALEKSANDRA BEDNAREK",
)
val expectedMailbox = getMailboxEntity("Aleksandra Anna Bednarek")
coEvery { mailboxDao.loadAll(any()) } returns listOf(
expectedMailbox,
)
assertEquals(expectedMailbox, systemUnderTest.getMailbox(student))
}
private fun getMailboxEntity(
studentName: String,
) = Mailbox(
globalKey = "",
fullName = "",
userName = "",
userLoginId = 123,
studentName = studentName,
schoolNameShort = "",
type = MailboxType.STUDENT,
)
private fun getStudentEntity(
studentName: String,
userName: String,
) = Student(
scrapperBaseUrl = "http://fakelog.cf",
email = "jan@fakelog.cf",
certificateKey = "",
classId = 0,
className = "",
isCurrent = false,
isParent = false,
loginMode = Sdk.Mode.API.name,
loginType = Sdk.ScrapperLoginType.STANDARD.name,
mobileBaseUrl = "",
password = "",
privateKey = "",
registrationDate = Instant.now(),
schoolName = "",
schoolShortName = "test",
schoolSymbol = "",
studentId = 1,
studentName = studentName,
symbol = "",
userLoginId = 1,
userName = userName,
)
}

View File

@ -141,7 +141,7 @@ class MessageRepositoryTest {
messageDb.loadMessageWithAttachment("v4")
} returnsMany listOf(flowOf(mWa), flowOf(mWaWithContent))
coEvery {
sdk.getMessageDetails("v4")
sdk.getMessageDetails("v4", any())
} returns mockk {
every { sender } returns ""
every { recipients } returns listOf("")

View File

@ -0,0 +1,72 @@
package io.github.wulkanowy.utils
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.getSemesterEntity
import org.junit.Test
import java.time.LocalDate
import kotlin.test.assertEquals
class SemesterExtensionKtTest {
@Test(expected = IllegalArgumentException::class)
fun `get current semester when current is doubled`() {
val semesters = listOf(
getSemesterEntity(1, 1, LocalDate.now(), LocalDate.now()),
getSemesterEntity(1, 1, LocalDate.now(), LocalDate.now())
)
semesters.getCurrentOrLast()
}
@Test(expected = RuntimeException::class)
fun `get current semester when there is empty list`() {
val semesters = listOf<Semester>()
semesters.getCurrentOrLast()
}
@Test
fun `get current kindergarten semester when there is no any current`() {
val semesters = listOf(
createSemesterEntity(
kindergartenDiaryId = 281,
schoolYear = 2020,
semesterId = 0,
start = LocalDate.of(2020, 9, 1),
end = LocalDate.of(2021, 8, 31),
),
createSemesterEntity(
kindergartenDiaryId = 342,
schoolYear = 2021,
semesterId = 0,
start = LocalDate.of(2021, 9, 1),
end = LocalDate.of(2022, 8, 31),
),
)
val res = semesters.getCurrentOrLast()
assertEquals(2021, res.schoolYear)
}
private fun createSemesterEntity(
diaryId: Int = 0,
kindergartenDiaryId: Int = 0,
semesterId: Int = 0,
schoolYear: Int = 0,
start: LocalDate = LocalDate.now(),
end: LocalDate = LocalDate.now().plusMonths(6),
) = Semester(
studentId = 1,
diaryId = diaryId,
kindergartenDiaryId = kindergartenDiaryId,
semesterId = semesterId,
diaryName = "$semesterId",
schoolYear = schoolYear,
classId = 0,
semesterName = semesterId,
unitId = 1,
start = start,
end = end
)
}