Compare commits
59 commits
2.3.3
...
feature/at
Author | SHA1 | Date | |
---|---|---|---|
![]() |
1591d494ab | ||
![]() |
ea28fc783c | ||
![]() |
1af44cf60a | ||
![]() |
c04752ed39 | ||
![]() |
c198e6a2f7 | ||
![]() |
28c234a8fd | ||
![]() |
f8c9122686 | ||
![]() |
2c1337bb51 | ||
![]() |
7a4032dda4 | ||
![]() |
1ab300d74f | ||
![]() |
1b8c389984 | ||
![]() |
74a20b2f65 | ||
![]() |
7effb7aca2 | ||
![]() |
d5c17285c1 | ||
![]() |
e378b4c70a | ||
![]() |
31854fc4b8 | ||
![]() |
f52fe8306f | ||
![]() |
b613b84469 | ||
![]() |
2776d019b9 | ||
![]() |
729e72cddb | ||
![]() |
ec101c1f52 | ||
![]() |
cfec79405f | ||
![]() |
7d8be1b9fc | ||
![]() |
c781159e75 | ||
![]() |
3cf6c295b0 | ||
![]() |
e757585bd3 | ||
![]() |
736d16a7ab | ||
![]() |
6f4a8d5534 | ||
![]() |
b5e17c4ff7 | ||
![]() |
cc01525f16 | ||
![]() |
c2ec05662b | ||
![]() |
2d4a1bff83 | ||
![]() |
cd853e4d57 | ||
![]() |
8183d7d5a0 | ||
![]() |
3f199cb610 | ||
![]() |
22f72981cb | ||
![]() |
bce92b7347 | ||
![]() |
ed5166333a | ||
![]() |
a05f1f70f7 | ||
![]() |
e58a60410c | ||
![]() |
fc91936884 | ||
![]() |
88043569ac | ||
![]() |
10add8a70e | ||
![]() |
098af9884a | ||
![]() |
554c1b1261 | ||
![]() |
dc59f4ffa3 | ||
![]() |
e0f4cad7fb | ||
![]() |
a51a54dc7a | ||
![]() |
7cdac6ede1 | ||
![]() |
9dfb282e88 | ||
![]() |
725668f855 | ||
![]() |
e58c155961 | ||
![]() |
05a5047a70 | ||
![]() |
497acf9d68 | ||
![]() |
976eb5a772 | ||
![]() |
9ececeb4e9 | ||
![]() |
096fe359e7 | ||
![]() |
a98e8398fd | ||
![]() |
d8c4926a97 |
174 changed files with 10117 additions and 2959 deletions
4
.github/FUNDING.yml
vendored
Normal file
4
.github/FUNDING.yml
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
# These are supported funding model platforms
|
||||||
|
|
||||||
|
github: wulkanowy
|
||||||
|
custom: https://www.paypal.com/paypalme/wulkanowy
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -65,6 +65,8 @@ captures/
|
||||||
.idea/uiDesigner.xml
|
.idea/uiDesigner.xml
|
||||||
.idea/runConfigurations.xml
|
.idea/runConfigurations.xml
|
||||||
.idea/discord.xml
|
.idea/discord.xml
|
||||||
|
.idea/migrations.xml
|
||||||
|
.idea/androidTestResultsUserPreferences.xml
|
||||||
|
|
||||||
# Keystore files
|
# Keystore files
|
||||||
*.jks
|
*.jks
|
||||||
|
|
10
.idea/migrations.xml
generated
10
.idea/migrations.xml
generated
|
@ -1,10 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="ProjectMigrations">
|
|
||||||
<option name="MigrateToGradleLocalJavaHome">
|
|
||||||
<set>
|
|
||||||
<option value="$PROJECT_DIR$" />
|
|
||||||
</set>
|
|
||||||
</option>
|
|
||||||
</component>
|
|
||||||
</project>
|
|
|
@ -27,8 +27,8 @@ android {
|
||||||
testApplicationId "io.github.tests.wulkanowy"
|
testApplicationId "io.github.tests.wulkanowy"
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 34
|
targetSdkVersion 34
|
||||||
versionCode 143
|
versionCode 148
|
||||||
versionName "2.3.3"
|
versionName "2.4.2"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|
||||||
resValue "string", "app_name", "Wulkanowy"
|
resValue "string", "app_name", "Wulkanowy"
|
||||||
|
@ -142,7 +142,9 @@ android {
|
||||||
packagingOptions {
|
packagingOptions {
|
||||||
resources {
|
resources {
|
||||||
excludes += ['META-INF/library_release.kotlin_module',
|
excludes += ['META-INF/library_release.kotlin_module',
|
||||||
'META-INF/library-core_release.kotlin_module']
|
'META-INF/library-core_release.kotlin_module',
|
||||||
|
'META-INF/LICENSE.md',
|
||||||
|
'META-INF/LICENSE-notice.md']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,7 +165,7 @@ play {
|
||||||
track = 'production'
|
track = 'production'
|
||||||
releaseStatus = ReleaseStatus.IN_PROGRESS
|
releaseStatus = ReleaseStatus.IN_PROGRESS
|
||||||
userFraction = 0.99d
|
userFraction = 0.99d
|
||||||
updatePriority = 3
|
updatePriority = 2
|
||||||
enabled.set(false)
|
enabled.set(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -185,19 +187,19 @@ huaweiPublish {
|
||||||
|
|
||||||
ext {
|
ext {
|
||||||
work_manager = "2.9.0"
|
work_manager = "2.9.0"
|
||||||
android_hilt = "1.1.0"
|
android_hilt = "1.2.0"
|
||||||
room = "2.6.1"
|
room = "2.6.1"
|
||||||
chucker = "4.0.0"
|
chucker = "4.0.0"
|
||||||
mockk = "1.13.9"
|
mockk = "1.13.9"
|
||||||
coroutines = "1.7.3"
|
coroutines = "1.8.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'io.github.wulkanowy:sdk:2.3.5'
|
implementation 'io.github.wulkanowy:sdk:2.4.2-SNAPSHOT'
|
||||||
|
|
||||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4'
|
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4'
|
||||||
|
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2"
|
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3"
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines"
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines"
|
||||||
|
|
||||||
implementation 'androidx.core:core-ktx:1.12.0'
|
implementation 'androidx.core:core-ktx:1.12.0'
|
||||||
|
@ -221,7 +223,7 @@ dependencies {
|
||||||
implementation "androidx.work:work-runtime:$work_manager"
|
implementation "androidx.work:work-runtime:$work_manager"
|
||||||
playImplementation "androidx.work:work-gcm:$work_manager"
|
playImplementation "androidx.work:work-gcm:$work_manager"
|
||||||
|
|
||||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.6.2"
|
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.7.0"
|
||||||
|
|
||||||
implementation "androidx.room:room-runtime:$room"
|
implementation "androidx.room:room-runtime:$room"
|
||||||
implementation "androidx.room:room-ktx:$room"
|
implementation "androidx.room:room-ktx:$room"
|
||||||
|
@ -238,18 +240,19 @@ dependencies {
|
||||||
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
|
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
|
||||||
implementation "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0"
|
implementation "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0"
|
||||||
implementation "com.squareup.okhttp3:logging-interceptor:4.12.0"
|
implementation "com.squareup.okhttp3:logging-interceptor:4.12.0"
|
||||||
|
implementation "com.squareup.okhttp3:okhttp-urlconnection:4.12.0"
|
||||||
|
|
||||||
implementation "com.jakewharton.timber:timber:5.0.1"
|
implementation "com.jakewharton.timber:timber:5.0.1"
|
||||||
implementation 'com.github.Faierbel:slf4j-timber:2.0'
|
implementation 'com.github.Faierbel:slf4j-timber:2.0'
|
||||||
implementation 'com.github.bastienpaulfr:Treessence:1.1.2'
|
implementation 'com.github.bastienpaulfr:Treessence:1.1.2'
|
||||||
implementation "com.mikepenz:aboutlibraries-core:$about_libraries"
|
implementation "com.mikepenz:aboutlibraries-core:$about_libraries"
|
||||||
implementation 'io.coil-kt:coil:2.5.0'
|
implementation 'io.coil-kt:coil:2.6.0'
|
||||||
implementation "io.github.wulkanowy:AppKillerManager:3.0.1"
|
implementation "io.github.wulkanowy:AppKillerManager:3.0.1"
|
||||||
implementation 'me.xdrop:fuzzywuzzy:1.4.0'
|
implementation 'me.xdrop:fuzzywuzzy:1.4.0'
|
||||||
implementation 'com.fredporciuncula:flow-preferences:1.9.1'
|
implementation 'com.fredporciuncula:flow-preferences:1.9.1'
|
||||||
implementation 'org.apache.commons:commons-text:1.11.0'
|
implementation 'org.apache.commons:commons-text:1.11.0'
|
||||||
|
|
||||||
playImplementation platform('com.google.firebase:firebase-bom:32.7.0')
|
playImplementation platform('com.google.firebase:firebase-bom:32.7.2')
|
||||||
playImplementation 'com.google.firebase:firebase-analytics'
|
playImplementation 'com.google.firebase:firebase-analytics'
|
||||||
playImplementation 'com.google.firebase:firebase-messaging'
|
playImplementation 'com.google.firebase:firebase-messaging'
|
||||||
playImplementation 'com.google.firebase:firebase-crashlytics:'
|
playImplementation 'com.google.firebase:firebase-crashlytics:'
|
||||||
|
@ -261,7 +264,7 @@ dependencies {
|
||||||
playImplementation 'com.google.android.play:review-ktx:2.0.1'
|
playImplementation 'com.google.android.play:review-ktx:2.0.1'
|
||||||
playImplementation "com.google.android.ump:user-messaging-platform:2.1.0"
|
playImplementation "com.google.android.ump:user-messaging-platform:2.1.0"
|
||||||
|
|
||||||
hmsImplementation 'com.huawei.hms:hianalytics:6.12.0.300'
|
hmsImplementation 'com.huawei.hms:hianalytics:6.12.0.301'
|
||||||
hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.9.1.303'
|
hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.9.1.303'
|
||||||
|
|
||||||
releaseImplementation "com.github.chuckerteam.chucker:library-no-op:$chucker"
|
releaseImplementation "com.github.chuckerteam.chucker:library-no-op:$chucker"
|
||||||
|
|
2451
app/schemas/io.github.wulkanowy.data.db.AppDatabase/58.json
Normal file
2451
app/schemas/io.github.wulkanowy.data.db.AppDatabase/58.json
Normal file
File diff suppressed because it is too large
Load diff
2501
app/schemas/io.github.wulkanowy.data.db.AppDatabase/59.json
Normal file
2501
app/schemas/io.github.wulkanowy.data.db.AppDatabase/59.json
Normal file
File diff suppressed because it is too large
Load diff
2527
app/schemas/io.github.wulkanowy.data.db.AppDatabase/60.json
Normal file
2527
app/schemas/io.github.wulkanowy.data.db.AppDatabase/60.json
Normal file
File diff suppressed because it is too large
Load diff
|
@ -14,34 +14,37 @@ import kotlin.test.assertFailsWith
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class ScramblerTest {
|
class ScramblerTest {
|
||||||
|
|
||||||
|
private val scrambler = Scrambler(ApplicationProvider.getApplicationContext())
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun encryptDecryptTest() {
|
fun encryptDecryptTest() {
|
||||||
assertEquals("TEST", decrypt(encrypt("TEST",
|
assertEquals(
|
||||||
ApplicationProvider.getApplicationContext())))
|
"TEST", scrambler.decrypt(scrambler.encrypt("TEST"))
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun emptyTextEncryptTest() {
|
fun emptyTextEncryptTest() {
|
||||||
assertFailsWith<ScramblerException> {
|
assertFailsWith<ScramblerException> {
|
||||||
decrypt("")
|
scrambler.decrypt("")
|
||||||
}
|
}
|
||||||
|
|
||||||
assertFailsWith<ScramblerException> {
|
assertFailsWith<ScramblerException> {
|
||||||
encrypt("", ApplicationProvider.getApplicationContext())
|
scrambler.encrypt("")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@SdkSuppress(minSdkVersion = 18)
|
@SdkSuppress(minSdkVersion = 18)
|
||||||
fun emptyKeyStoreTest() {
|
fun emptyKeyStoreTest() {
|
||||||
val text = encrypt("test", ApplicationProvider.getApplicationContext())
|
val text = scrambler.encrypt("test")
|
||||||
|
|
||||||
val keyStore = KeyStore.getInstance("AndroidKeyStore")
|
val keyStore = KeyStore.getInstance("AndroidKeyStore")
|
||||||
keyStore.load(null)
|
keyStore.load(null)
|
||||||
keyStore.deleteEntry("wulkanowy_password")
|
keyStore.deleteEntry("wulkanowy_password")
|
||||||
|
|
||||||
assertFailsWith<ScramblerException> {
|
assertFailsWith<ScramblerException> {
|
||||||
decrypt(text)
|
scrambler.decrypt(text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,6 +44,7 @@
|
||||||
android:networkSecurityConfig="@xml/network_security_config"
|
android:networkSecurityConfig="@xml/network_security_config"
|
||||||
android:supportsRtl="false"
|
android:supportsRtl="false"
|
||||||
android:theme="@style/WulkanowyTheme"
|
android:theme="@style/WulkanowyTheme"
|
||||||
|
android:resizeableActivity="true"
|
||||||
tools:ignore="DataExtractionRules,UnusedAttribute">
|
tools:ignore="DataExtractionRules,UnusedAttribute">
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.modules.splash.SplashActivity"
|
android:name=".ui.modules.splash.SplashActivity"
|
||||||
|
|
|
@ -54,5 +54,9 @@
|
||||||
{
|
{
|
||||||
"displayName": "Antoni Paduch",
|
"displayName": "Antoni Paduch",
|
||||||
"githubUsername": "janAte1"
|
"githubUsername": "janAte1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"displayName": "Kamil Wąsik",
|
||||||
|
"githubUsername": "JestemKamil"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -21,6 +21,7 @@ import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||||
import io.github.wulkanowy.sdk.Sdk
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
import io.github.wulkanowy.utils.AppInfo
|
import io.github.wulkanowy.utils.AppInfo
|
||||||
import io.github.wulkanowy.utils.RemoteConfigHelper
|
import io.github.wulkanowy.utils.RemoteConfigHelper
|
||||||
|
import io.github.wulkanowy.utils.WebkitCookieManagerProxy
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import okhttp3.MediaType.Companion.toMediaType
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
|
@ -43,6 +44,7 @@ internal class DataModule {
|
||||||
buildTag = android.os.Build.MODEL
|
buildTag = android.os.Build.MODEL
|
||||||
userAgentTemplate = remoteConfig.userAgentTemplate
|
userAgentTemplate = remoteConfig.userAgentTemplate
|
||||||
setSimpleHttpLogger { Timber.d(it) }
|
setSimpleHttpLogger { Timber.d(it) }
|
||||||
|
setAdditionalCookieManager(WebkitCookieManagerProxy())
|
||||||
|
|
||||||
// for debug only
|
// for debug only
|
||||||
addInterceptor(chuckerInterceptor, network = true)
|
addInterceptor(chuckerInterceptor, network = true)
|
||||||
|
@ -251,4 +253,12 @@ internal class DataModule {
|
||||||
@Singleton
|
@Singleton
|
||||||
@Provides
|
@Provides
|
||||||
fun provideAdminMessageDao(database: AppDatabase) = database.adminMessagesDao
|
fun provideAdminMessageDao(database: AppDatabase) = database.adminMessagesDao
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
@Provides
|
||||||
|
fun provideMutesDao(database: AppDatabase) = database.mutedMessageSendersDao
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
@Provides
|
||||||
|
fun provideGradeDescriptiveDao(database: AppDatabase) = database.gradeDescriptiveDao
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,16 @@
|
||||||
package io.github.wulkanowy.data
|
package io.github.wulkanowy.data
|
||||||
|
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.catch
|
||||||
|
import kotlinx.coroutines.flow.collect
|
||||||
|
import kotlinx.coroutines.flow.emitAll
|
||||||
|
import kotlinx.coroutines.flow.filter
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
|
import kotlinx.coroutines.flow.flowOf
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import kotlinx.coroutines.flow.takeWhile
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
@ -20,8 +30,15 @@ val <T> Resource<T>.dataOrNull: T?
|
||||||
get() = when (this) {
|
get() = when (this) {
|
||||||
is Resource.Success -> this.data
|
is Resource.Success -> this.data
|
||||||
is Resource.Intermediate -> this.data
|
is Resource.Intermediate -> this.data
|
||||||
is Resource.Loading -> null
|
else -> null
|
||||||
is Resource.Error -> null
|
}
|
||||||
|
|
||||||
|
val <T> Resource<T>.dataOrThrow: T
|
||||||
|
get() = when (this) {
|
||||||
|
is Resource.Success -> this.data
|
||||||
|
is Resource.Intermediate -> this.data
|
||||||
|
is Resource.Loading -> throw IllegalStateException("Resource is in loading state")
|
||||||
|
is Resource.Error -> throw this.error
|
||||||
}
|
}
|
||||||
|
|
||||||
val <T> Resource<T>.errorOrNull: Throwable?
|
val <T> Resource<T>.errorOrNull: Throwable?
|
||||||
|
@ -131,7 +148,7 @@ inline fun <ResultType, RequestType> networkBoundResource(
|
||||||
query().map { Resource.Success(filterResult(it)) }
|
query().map { Resource.Success(filterResult(it)) }
|
||||||
} catch (throwable: Throwable) {
|
} catch (throwable: Throwable) {
|
||||||
onFetchFailed(throwable)
|
onFetchFailed(throwable)
|
||||||
query().map { Resource.Error(throwable) }
|
flowOf(Resource.Error(throwable))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
query().map { Resource.Success(filterResult(it)) }
|
query().map { Resource.Success(filterResult(it)) }
|
||||||
|
@ -165,7 +182,7 @@ inline fun <ResultType, RequestType, T> networkBoundResource(
|
||||||
query().map { Resource.Success(mapResult(it)) }
|
query().map { Resource.Success(mapResult(it)) }
|
||||||
} catch (throwable: Throwable) {
|
} catch (throwable: Throwable) {
|
||||||
onFetchFailed(throwable)
|
onFetchFailed(throwable)
|
||||||
query().map { Resource.Error(throwable) }
|
flowOf(Resource.Error(throwable))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
query().map { Resource.Success(mapResult(it)) }
|
query().map { Resource.Success(mapResult(it)) }
|
||||||
|
|
|
@ -1,11 +1,128 @@
|
||||||
package io.github.wulkanowy.data.db
|
package io.github.wulkanowy.data.db
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.room.*
|
import androidx.room.AutoMigration
|
||||||
|
import androidx.room.Database
|
||||||
|
import androidx.room.Room
|
||||||
|
import androidx.room.RoomDatabase
|
||||||
import androidx.room.RoomDatabase.JournalMode.TRUNCATE
|
import androidx.room.RoomDatabase.JournalMode.TRUNCATE
|
||||||
import io.github.wulkanowy.data.db.dao.*
|
import androidx.room.TypeConverters
|
||||||
import io.github.wulkanowy.data.db.entities.*
|
import io.github.wulkanowy.data.db.dao.AdminMessageDao
|
||||||
import io.github.wulkanowy.data.db.migrations.*
|
import io.github.wulkanowy.data.db.dao.AttendanceDao
|
||||||
|
import io.github.wulkanowy.data.db.dao.AttendanceSummaryDao
|
||||||
|
import io.github.wulkanowy.data.db.dao.CompletedLessonsDao
|
||||||
|
import io.github.wulkanowy.data.db.dao.ConferenceDao
|
||||||
|
import io.github.wulkanowy.data.db.dao.ExamDao
|
||||||
|
import io.github.wulkanowy.data.db.dao.GradeDao
|
||||||
|
import io.github.wulkanowy.data.db.dao.GradeDescriptiveDao
|
||||||
|
import io.github.wulkanowy.data.db.dao.GradePartialStatisticsDao
|
||||||
|
import io.github.wulkanowy.data.db.dao.GradePointsStatisticsDao
|
||||||
|
import io.github.wulkanowy.data.db.dao.GradeSemesterStatisticsDao
|
||||||
|
import io.github.wulkanowy.data.db.dao.GradeSummaryDao
|
||||||
|
import io.github.wulkanowy.data.db.dao.HomeworkDao
|
||||||
|
import io.github.wulkanowy.data.db.dao.LuckyNumberDao
|
||||||
|
import io.github.wulkanowy.data.db.dao.MailboxDao
|
||||||
|
import io.github.wulkanowy.data.db.dao.MessageAttachmentDao
|
||||||
|
import io.github.wulkanowy.data.db.dao.MessagesDao
|
||||||
|
import io.github.wulkanowy.data.db.dao.MobileDeviceDao
|
||||||
|
import io.github.wulkanowy.data.db.dao.MutedMessageSendersDao
|
||||||
|
import io.github.wulkanowy.data.db.dao.NoteDao
|
||||||
|
import io.github.wulkanowy.data.db.dao.NotificationDao
|
||||||
|
import io.github.wulkanowy.data.db.dao.RecipientDao
|
||||||
|
import io.github.wulkanowy.data.db.dao.SchoolAnnouncementDao
|
||||||
|
import io.github.wulkanowy.data.db.dao.SchoolDao
|
||||||
|
import io.github.wulkanowy.data.db.dao.SemesterDao
|
||||||
|
import io.github.wulkanowy.data.db.dao.StudentDao
|
||||||
|
import io.github.wulkanowy.data.db.dao.StudentInfoDao
|
||||||
|
import io.github.wulkanowy.data.db.dao.SubjectDao
|
||||||
|
import io.github.wulkanowy.data.db.dao.TeacherDao
|
||||||
|
import io.github.wulkanowy.data.db.dao.TimetableAdditionalDao
|
||||||
|
import io.github.wulkanowy.data.db.dao.TimetableDao
|
||||||
|
import io.github.wulkanowy.data.db.dao.TimetableHeaderDao
|
||||||
|
import io.github.wulkanowy.data.db.entities.AdminMessage
|
||||||
|
import io.github.wulkanowy.data.db.entities.Attendance
|
||||||
|
import io.github.wulkanowy.data.db.entities.AttendanceSummary
|
||||||
|
import io.github.wulkanowy.data.db.entities.CompletedLesson
|
||||||
|
import io.github.wulkanowy.data.db.entities.Conference
|
||||||
|
import io.github.wulkanowy.data.db.entities.Exam
|
||||||
|
import io.github.wulkanowy.data.db.entities.Grade
|
||||||
|
import io.github.wulkanowy.data.db.entities.GradeDescriptive
|
||||||
|
import io.github.wulkanowy.data.db.entities.GradePartialStatistics
|
||||||
|
import io.github.wulkanowy.data.db.entities.GradePointsStatistics
|
||||||
|
import io.github.wulkanowy.data.db.entities.GradeSemesterStatistics
|
||||||
|
import io.github.wulkanowy.data.db.entities.GradeSummary
|
||||||
|
import io.github.wulkanowy.data.db.entities.Homework
|
||||||
|
import io.github.wulkanowy.data.db.entities.LuckyNumber
|
||||||
|
import io.github.wulkanowy.data.db.entities.Mailbox
|
||||||
|
import io.github.wulkanowy.data.db.entities.Message
|
||||||
|
import io.github.wulkanowy.data.db.entities.MessageAttachment
|
||||||
|
import io.github.wulkanowy.data.db.entities.MobileDevice
|
||||||
|
import io.github.wulkanowy.data.db.entities.MutedMessageSender
|
||||||
|
import io.github.wulkanowy.data.db.entities.Note
|
||||||
|
import io.github.wulkanowy.data.db.entities.Notification
|
||||||
|
import io.github.wulkanowy.data.db.entities.Recipient
|
||||||
|
import io.github.wulkanowy.data.db.entities.School
|
||||||
|
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
|
||||||
|
import io.github.wulkanowy.data.db.entities.Semester
|
||||||
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
|
import io.github.wulkanowy.data.db.entities.StudentInfo
|
||||||
|
import io.github.wulkanowy.data.db.entities.Subject
|
||||||
|
import io.github.wulkanowy.data.db.entities.Teacher
|
||||||
|
import io.github.wulkanowy.data.db.entities.Timetable
|
||||||
|
import io.github.wulkanowy.data.db.entities.TimetableAdditional
|
||||||
|
import io.github.wulkanowy.data.db.entities.TimetableHeader
|
||||||
|
import io.github.wulkanowy.data.db.migrations.Migration10
|
||||||
|
import io.github.wulkanowy.data.db.migrations.Migration11
|
||||||
|
import io.github.wulkanowy.data.db.migrations.Migration12
|
||||||
|
import io.github.wulkanowy.data.db.migrations.Migration13
|
||||||
|
import io.github.wulkanowy.data.db.migrations.Migration14
|
||||||
|
import io.github.wulkanowy.data.db.migrations.Migration15
|
||||||
|
import io.github.wulkanowy.data.db.migrations.Migration16
|
||||||
|
import io.github.wulkanowy.data.db.migrations.Migration17
|
||||||
|
import io.github.wulkanowy.data.db.migrations.Migration18
|
||||||
|
import io.github.wulkanowy.data.db.migrations.Migration19
|
||||||
|
import io.github.wulkanowy.data.db.migrations.Migration2
|
||||||
|
import io.github.wulkanowy.data.db.migrations.Migration20
|
||||||
|
import io.github.wulkanowy.data.db.migrations.Migration21
|
||||||
|
import io.github.wulkanowy.data.db.migrations.Migration22
|
||||||
|
import io.github.wulkanowy.data.db.migrations.Migration23
|
||||||
|
import io.github.wulkanowy.data.db.migrations.Migration24
|
||||||
|
import io.github.wulkanowy.data.db.migrations.Migration25
|
||||||
|
import io.github.wulkanowy.data.db.migrations.Migration26
|
||||||
|
import io.github.wulkanowy.data.db.migrations.Migration27
|
||||||
|
import io.github.wulkanowy.data.db.migrations.Migration28
|
||||||
|
import io.github.wulkanowy.data.db.migrations.Migration29
|
||||||
|
import io.github.wulkanowy.data.db.migrations.Migration3
|
||||||
|
import io.github.wulkanowy.data.db.migrations.Migration30
|
||||||
|
import io.github.wulkanowy.data.db.migrations.Migration31
|
||||||
|
import io.github.wulkanowy.data.db.migrations.Migration32
|
||||||
|
import io.github.wulkanowy.data.db.migrations.Migration33
|
||||||
|
import io.github.wulkanowy.data.db.migrations.Migration34
|
||||||
|
import io.github.wulkanowy.data.db.migrations.Migration35
|
||||||
|
import io.github.wulkanowy.data.db.migrations.Migration36
|
||||||
|
import io.github.wulkanowy.data.db.migrations.Migration37
|
||||||
|
import io.github.wulkanowy.data.db.migrations.Migration38
|
||||||
|
import io.github.wulkanowy.data.db.migrations.Migration39
|
||||||
|
import io.github.wulkanowy.data.db.migrations.Migration4
|
||||||
|
import io.github.wulkanowy.data.db.migrations.Migration40
|
||||||
|
import io.github.wulkanowy.data.db.migrations.Migration41
|
||||||
|
import io.github.wulkanowy.data.db.migrations.Migration42
|
||||||
|
import io.github.wulkanowy.data.db.migrations.Migration43
|
||||||
|
import io.github.wulkanowy.data.db.migrations.Migration44
|
||||||
|
import io.github.wulkanowy.data.db.migrations.Migration46
|
||||||
|
import io.github.wulkanowy.data.db.migrations.Migration49
|
||||||
|
import io.github.wulkanowy.data.db.migrations.Migration5
|
||||||
|
import io.github.wulkanowy.data.db.migrations.Migration50
|
||||||
|
import io.github.wulkanowy.data.db.migrations.Migration51
|
||||||
|
import io.github.wulkanowy.data.db.migrations.Migration53
|
||||||
|
import io.github.wulkanowy.data.db.migrations.Migration54
|
||||||
|
import io.github.wulkanowy.data.db.migrations.Migration55
|
||||||
|
import io.github.wulkanowy.data.db.migrations.Migration57
|
||||||
|
import io.github.wulkanowy.data.db.migrations.Migration58
|
||||||
|
import io.github.wulkanowy.data.db.migrations.Migration6
|
||||||
|
import io.github.wulkanowy.data.db.migrations.Migration7
|
||||||
|
import io.github.wulkanowy.data.db.migrations.Migration8
|
||||||
|
import io.github.wulkanowy.data.db.migrations.Migration9
|
||||||
import io.github.wulkanowy.utils.AppInfo
|
import io.github.wulkanowy.utils.AppInfo
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@ -41,7 +158,9 @@ import javax.inject.Singleton
|
||||||
TimetableHeader::class,
|
TimetableHeader::class,
|
||||||
SchoolAnnouncement::class,
|
SchoolAnnouncement::class,
|
||||||
Notification::class,
|
Notification::class,
|
||||||
AdminMessage::class
|
AdminMessage::class,
|
||||||
|
MutedMessageSender::class,
|
||||||
|
GradeDescriptive::class,
|
||||||
],
|
],
|
||||||
autoMigrations = [
|
autoMigrations = [
|
||||||
AutoMigration(from = 44, to = 45),
|
AutoMigration(from = 44, to = 45),
|
||||||
|
@ -51,6 +170,9 @@ import javax.inject.Singleton
|
||||||
AutoMigration(from = 54, to = 55, spec = Migration55::class),
|
AutoMigration(from = 54, to = 55, spec = Migration55::class),
|
||||||
AutoMigration(from = 55, to = 56),
|
AutoMigration(from = 55, to = 56),
|
||||||
AutoMigration(from = 56, to = 57, spec = Migration57::class),
|
AutoMigration(from = 56, to = 57, spec = Migration57::class),
|
||||||
|
AutoMigration(from = 57, to = 58, spec = Migration58::class),
|
||||||
|
AutoMigration(from = 58, to = 59),
|
||||||
|
AutoMigration(from = 59, to = 60),
|
||||||
],
|
],
|
||||||
version = AppDatabase.VERSION_SCHEMA,
|
version = AppDatabase.VERSION_SCHEMA,
|
||||||
exportSchema = true
|
exportSchema = true
|
||||||
|
@ -59,7 +181,7 @@ import javax.inject.Singleton
|
||||||
abstract class AppDatabase : RoomDatabase() {
|
abstract class AppDatabase : RoomDatabase() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val VERSION_SCHEMA = 57
|
const val VERSION_SCHEMA = 60
|
||||||
|
|
||||||
fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf(
|
fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf(
|
||||||
Migration2(),
|
Migration2(),
|
||||||
|
@ -184,4 +306,8 @@ abstract class AppDatabase : RoomDatabase() {
|
||||||
abstract val notificationDao: NotificationDao
|
abstract val notificationDao: NotificationDao
|
||||||
|
|
||||||
abstract val adminMessagesDao: AdminMessageDao
|
abstract val adminMessagesDao: AdminMessageDao
|
||||||
|
|
||||||
|
abstract val mutedMessageSendersDao: MutedMessageSendersDao
|
||||||
|
|
||||||
|
abstract val gradeDescriptiveDao: GradeDescriptiveDao
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
package io.github.wulkanowy.data.db.dao
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Query
|
||||||
|
import io.github.wulkanowy.data.db.entities.GradeDescriptive
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
@Dao
|
||||||
|
interface GradeDescriptiveDao : BaseDao<GradeDescriptive> {
|
||||||
|
|
||||||
|
@Query("SELECT * FROM GradesDescriptive WHERE semester_id = :semesterId AND student_id = :studentId")
|
||||||
|
fun loadAll(semesterId: Int, studentId: Int): Flow<List<GradeDescriptive>>
|
||||||
|
}
|
|
@ -5,15 +5,23 @@ import androidx.room.Query
|
||||||
import androidx.room.Transaction
|
import androidx.room.Transaction
|
||||||
import io.github.wulkanowy.data.db.entities.Message
|
import io.github.wulkanowy.data.db.entities.Message
|
||||||
import io.github.wulkanowy.data.db.entities.MessageWithAttachment
|
import io.github.wulkanowy.data.db.entities.MessageWithAttachment
|
||||||
|
import io.github.wulkanowy.data.db.entities.MessageWithMutedAuthor
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
interface MessagesDao : BaseDao<Message> {
|
interface MessagesDao : BaseDao<Message> {
|
||||||
|
|
||||||
@Transaction
|
@Transaction
|
||||||
@Query("SELECT * FROM Messages WHERE message_global_key = :messageGlobalKey")
|
@Query("SELECT * FROM Messages WHERE message_global_key = :messageGlobalKey")
|
||||||
fun loadMessageWithAttachment(messageGlobalKey: String): Flow<MessageWithAttachment?>
|
fun loadMessageWithAttachment(messageGlobalKey: String): Flow<MessageWithAttachment?>
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
@Query("SELECT * FROM Messages WHERE mailbox_key = :mailboxKey AND folder_id = :folder ORDER BY date DESC")
|
||||||
|
fun loadMessagesWithMutedAuthor(mailboxKey: String, folder: Int): Flow<List<MessageWithMutedAuthor>>
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
@Query("SELECT * FROM Messages WHERE email = :email AND folder_id = :folder ORDER BY date DESC")
|
||||||
|
fun loadMessagesWithMutedAuthor(folder: Int, email: String): Flow<List<MessageWithMutedAuthor>>
|
||||||
|
|
||||||
@Query("SELECT * FROM Messages WHERE mailbox_key = :mailboxKey AND folder_id = :folder ORDER BY date DESC")
|
@Query("SELECT * FROM Messages WHERE mailbox_key = :mailboxKey AND folder_id = :folder ORDER BY date DESC")
|
||||||
fun loadAll(mailboxKey: String, folder: Int): Flow<List<Message>>
|
fun loadAll(mailboxKey: String, folder: Int): Flow<List<Message>>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
package io.github.wulkanowy.data.db.dao
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Insert
|
||||||
|
import androidx.room.OnConflictStrategy
|
||||||
|
import androidx.room.Query
|
||||||
|
import io.github.wulkanowy.data.db.entities.MutedMessageSender
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface MutedMessageSendersDao : BaseDao<MutedMessageSender> {
|
||||||
|
|
||||||
|
@Query("SELECT COUNT(*) FROM MutedMessageSenders WHERE author = :author")
|
||||||
|
suspend fun checkMute(author: String): Boolean
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||||
|
suspend fun insertMute(mute: MutedMessageSender): Long
|
||||||
|
|
||||||
|
@Query("DELETE FROM MutedMessageSenders WHERE author = :author")
|
||||||
|
suspend fun deleteMute(author: String)
|
||||||
|
}
|
|
@ -15,5 +15,5 @@ interface TimetableDao : BaseDao<Timetable> {
|
||||||
fun loadAll(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): Flow<List<Timetable>>
|
fun loadAll(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): Flow<List<Timetable>>
|
||||||
|
|
||||||
@Query("SELECT * FROM Timetable WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :from AND date <= :end")
|
@Query("SELECT * FROM Timetable WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :from AND date <= :end")
|
||||||
fun load(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): List<Timetable>
|
suspend fun load(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): List<Timetable>
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,9 @@ data class AdminMessage(
|
||||||
@ColumnInfo(name = "types", defaultValue = "[]")
|
@ColumnInfo(name = "types", defaultValue = "[]")
|
||||||
val types: List<MessageType> = emptyList(),
|
val types: List<MessageType> = emptyList(),
|
||||||
|
|
||||||
@ColumnInfo(name = "is_dismissible")
|
@ColumnInfo(name = "is_ok_visible", defaultValue = "0")
|
||||||
val isDismissible: Boolean = false
|
val isOkVisible: Boolean = false,
|
||||||
|
|
||||||
|
@ColumnInfo(name = "is_x_visible", defaultValue = "0")
|
||||||
|
val isXVisible: Boolean = false
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
package io.github.wulkanowy.data.db.entities
|
||||||
|
|
||||||
|
import androidx.room.ColumnInfo
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
import java.io.Serializable
|
||||||
|
|
||||||
|
@Entity(tableName = "GradesDescriptive")
|
||||||
|
data class GradeDescriptive(
|
||||||
|
|
||||||
|
@ColumnInfo(name = "semester_id")
|
||||||
|
val semesterId: Int,
|
||||||
|
|
||||||
|
@ColumnInfo(name = "student_id")
|
||||||
|
val studentId: Int,
|
||||||
|
|
||||||
|
val subject: String,
|
||||||
|
|
||||||
|
val description: String,
|
||||||
|
) : Serializable {
|
||||||
|
|
||||||
|
@PrimaryKey(autoGenerate = true)
|
||||||
|
var id: Long = 0
|
||||||
|
|
||||||
|
@ColumnInfo(name = "is_notified")
|
||||||
|
var isNotified: Boolean = true
|
||||||
|
}
|
|
@ -2,11 +2,15 @@ package io.github.wulkanowy.data.db.entities
|
||||||
|
|
||||||
import androidx.room.Embedded
|
import androidx.room.Embedded
|
||||||
import androidx.room.Relation
|
import androidx.room.Relation
|
||||||
|
import java.io.Serializable
|
||||||
|
|
||||||
data class MessageWithAttachment(
|
data class MessageWithAttachment(
|
||||||
@Embedded
|
@Embedded
|
||||||
val message: Message,
|
val message: Message,
|
||||||
|
|
||||||
@Relation(parentColumn = "message_global_key", entityColumn = "message_global_key")
|
@Relation(parentColumn = "message_global_key", entityColumn = "message_global_key")
|
||||||
val attachments: List<MessageAttachment>
|
val attachments: List<MessageAttachment>,
|
||||||
)
|
|
||||||
|
@Relation(parentColumn = "correspondents", entityColumn = "author")
|
||||||
|
val mutedMessageSender: MutedMessageSender?,
|
||||||
|
) : Serializable
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
package io.github.wulkanowy.data.db.entities
|
||||||
|
|
||||||
|
import androidx.room.Embedded
|
||||||
|
import androidx.room.Relation
|
||||||
|
|
||||||
|
data class MessageWithMutedAuthor(
|
||||||
|
@Embedded
|
||||||
|
val message: Message,
|
||||||
|
|
||||||
|
@Relation(parentColumn = "correspondents", entityColumn = "author")
|
||||||
|
val mutedMessageSender: MutedMessageSender?,
|
||||||
|
)
|
|
@ -0,0 +1,15 @@
|
||||||
|
package io.github.wulkanowy.data.db.entities
|
||||||
|
|
||||||
|
import androidx.room.ColumnInfo
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
import java.io.Serializable
|
||||||
|
|
||||||
|
@Entity(tableName = "MutedMessageSenders")
|
||||||
|
data class MutedMessageSender(
|
||||||
|
@ColumnInfo(name = "author")
|
||||||
|
val author: String,
|
||||||
|
) : Serializable {
|
||||||
|
@PrimaryKey(autoGenerate = true)
|
||||||
|
var id: Long = 0
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package io.github.wulkanowy.data.db.migrations
|
||||||
|
|
||||||
|
import androidx.room.DeleteColumn
|
||||||
|
import androidx.room.migration.AutoMigrationSpec
|
||||||
|
|
||||||
|
@DeleteColumn(
|
||||||
|
tableName = "AdminMessages",
|
||||||
|
columnName = "is_dismissible",
|
||||||
|
)
|
||||||
|
class Migration58 : AutoMigrationSpec
|
|
@ -3,5 +3,10 @@ package io.github.wulkanowy.data.enums
|
||||||
enum class MessageFolder(val id: Int = 1) {
|
enum class MessageFolder(val id: Int = 1) {
|
||||||
RECEIVED(1),
|
RECEIVED(1),
|
||||||
SENT(2),
|
SENT(2),
|
||||||
TRASHED(3)
|
TRASHED(3),
|
||||||
|
;
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun byId(id: Int) = entries.first { it.id == id }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
package io.github.wulkanowy.data.mappers
|
package io.github.wulkanowy.data.mappers
|
||||||
|
|
||||||
import io.github.wulkanowy.data.db.entities.Grade
|
import io.github.wulkanowy.data.db.entities.Grade
|
||||||
|
import io.github.wulkanowy.data.db.entities.GradeDescriptive
|
||||||
import io.github.wulkanowy.data.db.entities.GradeSummary
|
import io.github.wulkanowy.data.db.entities.GradeSummary
|
||||||
import io.github.wulkanowy.data.db.entities.Semester
|
import io.github.wulkanowy.data.db.entities.Semester
|
||||||
import io.github.wulkanowy.sdk.pojo.GradeSummary as SdkGradeSummary
|
|
||||||
import io.github.wulkanowy.sdk.pojo.Grade as SdkGrade
|
import io.github.wulkanowy.sdk.pojo.Grade as SdkGrade
|
||||||
|
import io.github.wulkanowy.sdk.pojo.GradeDescriptive as SdkGradeDescriptive
|
||||||
|
import io.github.wulkanowy.sdk.pojo.GradeSummary as SdkGradeSummary
|
||||||
|
|
||||||
fun List<SdkGrade>.mapToEntities(semester: Semester) = map {
|
fun List<SdkGrade>.mapToEntities(semester: Semester) = map {
|
||||||
Grade(
|
Grade(
|
||||||
|
@ -40,3 +42,15 @@ fun List<SdkGradeSummary>.mapToEntities(semester: Semester) = map {
|
||||||
average = it.average
|
average = it.average
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JvmName("mapGradeDescriptiveToEntities")
|
||||||
|
fun List<SdkGradeDescriptive>.mapToEntities(semester: Semester) = map {
|
||||||
|
GradeDescriptive(
|
||||||
|
semesterId = semester.semesterId,
|
||||||
|
studentId = semester.studentId,
|
||||||
|
subject = it.subject,
|
||||||
|
description = it.description
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -9,11 +9,15 @@ import io.github.wulkanowy.data.mappers.mapToEntities
|
||||||
import io.github.wulkanowy.data.networkBoundResource
|
import io.github.wulkanowy.data.networkBoundResource
|
||||||
import io.github.wulkanowy.sdk.Sdk
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
import io.github.wulkanowy.sdk.pojo.Absent
|
import io.github.wulkanowy.sdk.pojo.Absent
|
||||||
import io.github.wulkanowy.utils.*
|
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||||
import kotlinx.coroutines.Dispatchers
|
import io.github.wulkanowy.utils.getRefreshKey
|
||||||
|
import io.github.wulkanowy.utils.init
|
||||||
|
import io.github.wulkanowy.utils.monday
|
||||||
|
import io.github.wulkanowy.utils.sunday
|
||||||
|
import io.github.wulkanowy.utils.switchSemester
|
||||||
|
import io.github.wulkanowy.utils.uniqueSubtract
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import java.time.LocalTime
|
import java.time.LocalTime
|
||||||
|
@ -52,13 +56,11 @@ class AttendanceRepository @Inject constructor(
|
||||||
attendanceDb.loadAll(semester.diaryId, semester.studentId, start.monday, end.sunday)
|
attendanceDb.loadAll(semester.diaryId, semester.studentId, start.monday, end.sunday)
|
||||||
},
|
},
|
||||||
fetch = {
|
fetch = {
|
||||||
val lessons = withContext(Dispatchers.IO) {
|
val lessons = timetableDb.load(
|
||||||
timetableDb.load(
|
semester.diaryId, semester.studentId, start.monday, end.sunday
|
||||||
semester.diaryId, semester.studentId, start.monday, end.sunday
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
sdk.init(student)
|
sdk.init(student)
|
||||||
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
|
.switchSemester(semester)
|
||||||
.getAttendance(start.monday, end.sunday)
|
.getAttendance(start.monday, end.sunday)
|
||||||
.mapToEntities(semester, lessons)
|
.mapToEntities(semester, lessons)
|
||||||
},
|
},
|
||||||
|
@ -86,9 +88,12 @@ class AttendanceRepository @Inject constructor(
|
||||||
return attendanceDb.updateAll(timetable)
|
return attendanceDb.updateAll(timetable)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JvmName("excuseForAbsenceLessons")
|
||||||
suspend fun excuseForAbsence(
|
suspend fun excuseForAbsence(
|
||||||
student: Student, semester: Semester,
|
student: Student,
|
||||||
absenceList: List<Attendance>, reason: String? = null
|
semester: Semester,
|
||||||
|
absenceList: List<Attendance>,
|
||||||
|
reason: String? = null
|
||||||
) {
|
) {
|
||||||
val items = absenceList.map { attendance ->
|
val items = absenceList.map { attendance ->
|
||||||
Absent(
|
Absent(
|
||||||
|
@ -97,7 +102,25 @@ class AttendanceRepository @Inject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
sdk.init(student)
|
sdk.init(student)
|
||||||
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
|
.switchSemester(semester)
|
||||||
|
.excuseForAbsence(items, reason)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmName("excuseForAbsenceDays")
|
||||||
|
suspend fun excuseForAbsence(
|
||||||
|
student: Student,
|
||||||
|
semester: Semester,
|
||||||
|
days: List<LocalDate>,
|
||||||
|
reason: String? = null
|
||||||
|
) {
|
||||||
|
val items = days.map { day ->
|
||||||
|
Absent(
|
||||||
|
date = LocalDateTime.of(day, LocalTime.of(0, 0)),
|
||||||
|
timeId = null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
sdk.init(student)
|
||||||
|
.switchSemester(semester)
|
||||||
.excuseForAbsence(items, reason)
|
.excuseForAbsence(items, reason)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import io.github.wulkanowy.sdk.Sdk
|
||||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||||
import io.github.wulkanowy.utils.getRefreshKey
|
import io.github.wulkanowy.utils.getRefreshKey
|
||||||
import io.github.wulkanowy.utils.init
|
import io.github.wulkanowy.utils.init
|
||||||
|
import io.github.wulkanowy.utils.switchSemester
|
||||||
import io.github.wulkanowy.utils.uniqueSubtract
|
import io.github.wulkanowy.utils.uniqueSubtract
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -40,7 +41,7 @@ class AttendanceSummaryRepository @Inject constructor(
|
||||||
query = { attendanceDb.loadAll(semester.diaryId, semester.studentId, subjectId) },
|
query = { attendanceDb.loadAll(semester.diaryId, semester.studentId, subjectId) },
|
||||||
fetch = {
|
fetch = {
|
||||||
sdk.init(student)
|
sdk.init(student)
|
||||||
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
|
.switchSemester(semester)
|
||||||
.getAttendanceSummary(subjectId)
|
.getAttendanceSummary(subjectId)
|
||||||
.mapToEntities(semester, subjectId)
|
.mapToEntities(semester, subjectId)
|
||||||
},
|
},
|
||||||
|
|
|
@ -48,7 +48,7 @@ class CompletedLessonsRepository @Inject constructor(
|
||||||
},
|
},
|
||||||
fetch = {
|
fetch = {
|
||||||
sdk.init(student)
|
sdk.init(student)
|
||||||
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
|
.switchSemester(semester)
|
||||||
.getCompletedLessons(start.monday, end.sunday)
|
.getCompletedLessons(start.monday, end.sunday)
|
||||||
.mapToEntities(semester)
|
.mapToEntities(semester)
|
||||||
},
|
},
|
||||||
|
|
|
@ -10,6 +10,7 @@ import io.github.wulkanowy.sdk.Sdk
|
||||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||||
import io.github.wulkanowy.utils.getRefreshKey
|
import io.github.wulkanowy.utils.getRefreshKey
|
||||||
import io.github.wulkanowy.utils.init
|
import io.github.wulkanowy.utils.init
|
||||||
|
import io.github.wulkanowy.utils.switchSemester
|
||||||
import io.github.wulkanowy.utils.uniqueSubtract
|
import io.github.wulkanowy.utils.uniqueSubtract
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
@ -46,7 +47,7 @@ class ConferenceRepository @Inject constructor(
|
||||||
},
|
},
|
||||||
fetch = {
|
fetch = {
|
||||||
sdk.init(student)
|
sdk.init(student)
|
||||||
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
|
.switchSemester(semester)
|
||||||
.getConferences()
|
.getConferences()
|
||||||
.mapToEntities(semester)
|
.mapToEntities(semester)
|
||||||
.filter { it.date >= startDate }
|
.filter { it.date >= startDate }
|
||||||
|
|
|
@ -7,7 +7,13 @@ import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||||
import io.github.wulkanowy.data.networkBoundResource
|
import io.github.wulkanowy.data.networkBoundResource
|
||||||
import io.github.wulkanowy.sdk.Sdk
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
import io.github.wulkanowy.utils.*
|
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||||
|
import io.github.wulkanowy.utils.endExamsDay
|
||||||
|
import io.github.wulkanowy.utils.getRefreshKey
|
||||||
|
import io.github.wulkanowy.utils.init
|
||||||
|
import io.github.wulkanowy.utils.startExamsDay
|
||||||
|
import io.github.wulkanowy.utils.switchSemester
|
||||||
|
import io.github.wulkanowy.utils.uniqueSubtract
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
|
@ -51,7 +57,7 @@ class ExamRepository @Inject constructor(
|
||||||
},
|
},
|
||||||
fetch = {
|
fetch = {
|
||||||
sdk.init(student)
|
sdk.init(student)
|
||||||
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
|
.switchSemester(semester)
|
||||||
.getExams(start.startExamsDay, start.endExamsDay)
|
.getExams(start.startExamsDay, start.endExamsDay)
|
||||||
.mapToEntities(semester)
|
.mapToEntities(semester)
|
||||||
},
|
},
|
||||||
|
@ -67,14 +73,16 @@ class ExamRepository @Inject constructor(
|
||||||
filterResult = { it.filter { item -> item.date in start..end } }
|
filterResult = { it.filter { item -> item.date in start..end } }
|
||||||
)
|
)
|
||||||
|
|
||||||
fun getExamsFromDatabase(semester: Semester, start: LocalDate): Flow<List<Exam>> {
|
fun getExamsFromDatabase(
|
||||||
return examDb.loadAll(
|
semester: Semester,
|
||||||
diaryId = semester.diaryId,
|
start: LocalDate,
|
||||||
studentId = semester.studentId,
|
end: LocalDate
|
||||||
from = start.startExamsDay,
|
): Flow<List<Exam>> = examDb.loadAll(
|
||||||
end = start.endExamsDay
|
diaryId = semester.diaryId,
|
||||||
)
|
studentId = semester.studentId,
|
||||||
}
|
from = start,
|
||||||
|
end = end,
|
||||||
|
)
|
||||||
|
|
||||||
suspend fun updateExam(exam: List<Exam>) = examDb.updateAll(exam)
|
suspend fun updateExam(exam: List<Exam>) = examDb.updateAll(exam)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,22 @@
|
||||||
package io.github.wulkanowy.data.repositories
|
package io.github.wulkanowy.data.repositories
|
||||||
|
|
||||||
import io.github.wulkanowy.data.db.dao.GradeDao
|
import io.github.wulkanowy.data.db.dao.GradeDao
|
||||||
|
import io.github.wulkanowy.data.db.dao.GradeDescriptiveDao
|
||||||
import io.github.wulkanowy.data.db.dao.GradeSummaryDao
|
import io.github.wulkanowy.data.db.dao.GradeSummaryDao
|
||||||
import io.github.wulkanowy.data.db.entities.Grade
|
import io.github.wulkanowy.data.db.entities.Grade
|
||||||
|
import io.github.wulkanowy.data.db.entities.GradeDescriptive
|
||||||
import io.github.wulkanowy.data.db.entities.GradeSummary
|
import io.github.wulkanowy.data.db.entities.GradeSummary
|
||||||
import io.github.wulkanowy.data.db.entities.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.data.mappers.mapToEntities
|
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||||
import io.github.wulkanowy.data.networkBoundResource
|
import io.github.wulkanowy.data.networkBoundResource
|
||||||
import io.github.wulkanowy.sdk.Sdk
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
import io.github.wulkanowy.utils.*
|
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||||
|
import io.github.wulkanowy.utils.getRefreshKey
|
||||||
|
import io.github.wulkanowy.utils.init
|
||||||
|
import io.github.wulkanowy.utils.switchSemester
|
||||||
|
import io.github.wulkanowy.utils.toLocalDate
|
||||||
|
import io.github.wulkanowy.utils.uniqueSubtract
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
@ -22,14 +29,13 @@ import javax.inject.Singleton
|
||||||
class GradeRepository @Inject constructor(
|
class GradeRepository @Inject constructor(
|
||||||
private val gradeDb: GradeDao,
|
private val gradeDb: GradeDao,
|
||||||
private val gradeSummaryDb: GradeSummaryDao,
|
private val gradeSummaryDb: GradeSummaryDao,
|
||||||
|
private val gradeDescriptiveDb: GradeDescriptiveDao,
|
||||||
private val sdk: Sdk,
|
private val sdk: Sdk,
|
||||||
private val refreshHelper: AutoRefreshHelper,
|
private val refreshHelper: AutoRefreshHelper,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val saveFetchResultMutex = Mutex()
|
private val saveFetchResultMutex = Mutex()
|
||||||
|
|
||||||
private val cacheKey = "grade"
|
|
||||||
|
|
||||||
fun getGrades(
|
fun getGrades(
|
||||||
student: Student,
|
student: Student,
|
||||||
semester: Semester,
|
semester: Semester,
|
||||||
|
@ -41,30 +47,52 @@ class GradeRepository @Inject constructor(
|
||||||
//When details is empty and summary is not, app will not use summary cache - edge case
|
//When details is empty and summary is not, app will not use summary cache - edge case
|
||||||
it.first.isEmpty()
|
it.first.isEmpty()
|
||||||
},
|
},
|
||||||
shouldFetch = { (details, summaries) ->
|
shouldFetch = { (details, summaries, descriptive) ->
|
||||||
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
val isExpired =
|
||||||
details.isEmpty() || summaries.isEmpty() || forceRefresh || isExpired
|
refreshHelper.shouldBeRefreshed(getRefreshKey(GRADE_CACHE_KEY, semester))
|
||||||
|
details.isEmpty() || (summaries.isEmpty() && descriptive.isEmpty()) || forceRefresh || isExpired
|
||||||
},
|
},
|
||||||
query = {
|
query = {
|
||||||
val detailsFlow = gradeDb.loadAll(semester.semesterId, semester.studentId)
|
val detailsFlow = gradeDb.loadAll(semester.semesterId, semester.studentId)
|
||||||
val summaryFlow = gradeSummaryDb.loadAll(semester.semesterId, semester.studentId)
|
val summaryFlow = gradeSummaryDb.loadAll(semester.semesterId, semester.studentId)
|
||||||
detailsFlow.combine(summaryFlow) { details, summaries -> details to summaries }
|
val descriptiveFlow =
|
||||||
|
gradeDescriptiveDb.loadAll(semester.semesterId, semester.studentId)
|
||||||
|
|
||||||
|
combine(detailsFlow, summaryFlow, descriptiveFlow) { details, summaries, descriptive ->
|
||||||
|
Triple(details, summaries, descriptive)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
fetch = {
|
fetch = {
|
||||||
val (details, summary) = sdk.init(student)
|
val (details, summary, descriptive) = sdk.init(student)
|
||||||
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
|
.switchSemester(semester)
|
||||||
.getGrades(semester.semesterId)
|
.getGrades(semester.semesterId)
|
||||||
|
|
||||||
details.mapToEntities(semester) to summary.mapToEntities(semester)
|
Triple(
|
||||||
|
details.mapToEntities(semester),
|
||||||
|
summary.mapToEntities(semester),
|
||||||
|
descriptive.mapToEntities(semester)
|
||||||
|
)
|
||||||
},
|
},
|
||||||
saveFetchResult = { (oldDetails, oldSummary), (newDetails, newSummary) ->
|
saveFetchResult = { (oldDetails, oldSummary, oldDescriptive), (newDetails, newSummary, newDescriptive) ->
|
||||||
refreshGradeDetails(student, oldDetails, newDetails, notify)
|
refreshGradeDetails(student, oldDetails, newDetails, notify)
|
||||||
refreshGradeSummaries(oldSummary, newSummary, notify)
|
refreshGradeSummaries(oldSummary, newSummary, notify)
|
||||||
|
refreshGradeDescriptions(oldDescriptive, newDescriptive, notify)
|
||||||
|
|
||||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester))
|
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(GRADE_CACHE_KEY, semester))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private suspend fun refreshGradeDescriptions(
|
||||||
|
old: List<GradeDescriptive>,
|
||||||
|
new: List<GradeDescriptive>,
|
||||||
|
notify: Boolean
|
||||||
|
) {
|
||||||
|
gradeDescriptiveDb.deleteAll(old uniqueSubtract new)
|
||||||
|
gradeDescriptiveDb.insertAll((new uniqueSubtract old).onEach {
|
||||||
|
if (notify) it.isNotified = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun refreshGradeDetails(
|
private suspend fun refreshGradeDetails(
|
||||||
student: Student,
|
student: Student,
|
||||||
oldGrades: List<Grade>,
|
oldGrades: List<Grade>,
|
||||||
|
@ -132,6 +160,10 @@ class GradeRepository @Inject constructor(
|
||||||
return gradeSummaryDb.loadAll(semester.semesterId, semester.studentId)
|
return gradeSummaryDb.loadAll(semester.semesterId, semester.studentId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getGradesDescriptiveFromDatabase(semester: Semester): Flow<List<GradeDescriptive>> {
|
||||||
|
return gradeDescriptiveDb.loadAll(semester.semesterId, semester.studentId)
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun updateGrade(grade: Grade) {
|
suspend fun updateGrade(grade: Grade) {
|
||||||
return gradeDb.updateAll(listOf(grade))
|
return gradeDb.updateAll(listOf(grade))
|
||||||
}
|
}
|
||||||
|
@ -143,4 +175,13 @@ class GradeRepository @Inject constructor(
|
||||||
suspend fun updateGradesSummary(gradesSummary: List<GradeSummary>) {
|
suspend fun updateGradesSummary(gradesSummary: List<GradeSummary>) {
|
||||||
return gradeSummaryDb.updateAll(gradesSummary)
|
return gradeSummaryDb.updateAll(gradesSummary)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun updateGradesDescriptive(gradesDescriptive: List<GradeDescriptive>) {
|
||||||
|
return gradeDescriptiveDb.updateAll(gradesDescriptive)
|
||||||
|
}
|
||||||
|
|
||||||
|
private companion object {
|
||||||
|
|
||||||
|
private const val GRADE_CACHE_KEY = "grade"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ import io.github.wulkanowy.sdk.Sdk
|
||||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||||
import io.github.wulkanowy.utils.getRefreshKey
|
import io.github.wulkanowy.utils.getRefreshKey
|
||||||
import io.github.wulkanowy.utils.init
|
import io.github.wulkanowy.utils.init
|
||||||
|
import io.github.wulkanowy.utils.switchSemester
|
||||||
import io.github.wulkanowy.utils.uniqueSubtract
|
import io.github.wulkanowy.utils.uniqueSubtract
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
@ -56,7 +57,7 @@ class GradeStatisticsRepository @Inject constructor(
|
||||||
query = { gradePartialStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
|
query = { gradePartialStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
|
||||||
fetch = {
|
fetch = {
|
||||||
sdk.init(student)
|
sdk.init(student)
|
||||||
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
|
.switchSemester(semester)
|
||||||
.getGradesPartialStatistics(semester.semesterId)
|
.getGradesPartialStatistics(semester.semesterId)
|
||||||
.mapToEntities(semester)
|
.mapToEntities(semester)
|
||||||
},
|
},
|
||||||
|
@ -101,7 +102,7 @@ class GradeStatisticsRepository @Inject constructor(
|
||||||
query = { gradeSemesterStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
|
query = { gradeSemesterStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
|
||||||
fetch = {
|
fetch = {
|
||||||
sdk.init(student)
|
sdk.init(student)
|
||||||
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
|
.switchSemester(semester)
|
||||||
.getGradesSemesterStatistics(semester.semesterId)
|
.getGradesSemesterStatistics(semester.semesterId)
|
||||||
.mapToEntities(semester)
|
.mapToEntities(semester)
|
||||||
},
|
},
|
||||||
|
@ -157,7 +158,7 @@ class GradeStatisticsRepository @Inject constructor(
|
||||||
query = { gradePointsStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
|
query = { gradePointsStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
|
||||||
fetch = {
|
fetch = {
|
||||||
sdk.init(student)
|
sdk.init(student)
|
||||||
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
|
.switchSemester(semester)
|
||||||
.getGradesPointsStatistics(semester.semesterId)
|
.getGradesPointsStatistics(semester.semesterId)
|
||||||
.mapToEntities(semester)
|
.mapToEntities(semester)
|
||||||
},
|
},
|
||||||
|
|
|
@ -7,7 +7,13 @@ import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||||
import io.github.wulkanowy.data.networkBoundResource
|
import io.github.wulkanowy.data.networkBoundResource
|
||||||
import io.github.wulkanowy.sdk.Sdk
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
import io.github.wulkanowy.utils.*
|
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||||
|
import io.github.wulkanowy.utils.getRefreshKey
|
||||||
|
import io.github.wulkanowy.utils.init
|
||||||
|
import io.github.wulkanowy.utils.monday
|
||||||
|
import io.github.wulkanowy.utils.sunday
|
||||||
|
import io.github.wulkanowy.utils.switchSemester
|
||||||
|
import io.github.wulkanowy.utils.uniqueSubtract
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -50,7 +56,7 @@ class HomeworkRepository @Inject constructor(
|
||||||
},
|
},
|
||||||
fetch = {
|
fetch = {
|
||||||
sdk.init(student)
|
sdk.init(student)
|
||||||
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
|
.switchSemester(semester)
|
||||||
.getHomework(start.monday, end.sunday)
|
.getHomework(start.monday, end.sunday)
|
||||||
.mapToEntities(semester)
|
.mapToEntities(semester)
|
||||||
},
|
},
|
||||||
|
|
|
@ -8,13 +8,17 @@ import io.github.wulkanowy.data.db.SharedPrefProvider
|
||||||
import io.github.wulkanowy.data.db.dao.MailboxDao
|
import io.github.wulkanowy.data.db.dao.MailboxDao
|
||||||
import io.github.wulkanowy.data.db.dao.MessageAttachmentDao
|
import io.github.wulkanowy.data.db.dao.MessageAttachmentDao
|
||||||
import io.github.wulkanowy.data.db.dao.MessagesDao
|
import io.github.wulkanowy.data.db.dao.MessagesDao
|
||||||
|
import io.github.wulkanowy.data.db.dao.MutedMessageSendersDao
|
||||||
import io.github.wulkanowy.data.db.entities.Mailbox
|
import io.github.wulkanowy.data.db.entities.Mailbox
|
||||||
import io.github.wulkanowy.data.db.entities.Message
|
import io.github.wulkanowy.data.db.entities.Message
|
||||||
import io.github.wulkanowy.data.db.entities.MessageWithAttachment
|
import io.github.wulkanowy.data.db.entities.MessageWithAttachment
|
||||||
|
import io.github.wulkanowy.data.db.entities.MessageWithMutedAuthor
|
||||||
|
import io.github.wulkanowy.data.db.entities.MutedMessageSender
|
||||||
import io.github.wulkanowy.data.db.entities.Recipient
|
import io.github.wulkanowy.data.db.entities.Recipient
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.enums.MessageFolder
|
import io.github.wulkanowy.data.enums.MessageFolder
|
||||||
import io.github.wulkanowy.data.enums.MessageFolder.RECEIVED
|
import io.github.wulkanowy.data.enums.MessageFolder.RECEIVED
|
||||||
|
import io.github.wulkanowy.data.enums.MessageFolder.SENT
|
||||||
import io.github.wulkanowy.data.enums.MessageFolder.TRASHED
|
import io.github.wulkanowy.data.enums.MessageFolder.TRASHED
|
||||||
import io.github.wulkanowy.data.mappers.mapFromEntities
|
import io.github.wulkanowy.data.mappers.mapFromEntities
|
||||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||||
|
@ -22,6 +26,7 @@ import io.github.wulkanowy.data.networkBoundResource
|
||||||
import io.github.wulkanowy.data.onResourceError
|
import io.github.wulkanowy.data.onResourceError
|
||||||
import io.github.wulkanowy.data.onResourceSuccess
|
import io.github.wulkanowy.data.onResourceSuccess
|
||||||
import io.github.wulkanowy.data.pojos.MessageDraft
|
import io.github.wulkanowy.data.pojos.MessageDraft
|
||||||
|
import io.github.wulkanowy.data.toFirstResult
|
||||||
import io.github.wulkanowy.data.waitForResult
|
import io.github.wulkanowy.data.waitForResult
|
||||||
import io.github.wulkanowy.domain.messages.GetMailboxByStudentUseCase
|
import io.github.wulkanowy.domain.messages.GetMailboxByStudentUseCase
|
||||||
import io.github.wulkanowy.sdk.Sdk
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
|
@ -31,7 +36,6 @@ import io.github.wulkanowy.utils.getRefreshKey
|
||||||
import io.github.wulkanowy.utils.init
|
import io.github.wulkanowy.utils.init
|
||||||
import io.github.wulkanowy.utils.uniqueSubtract
|
import io.github.wulkanowy.utils.uniqueSubtract
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.first
|
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
|
@ -42,6 +46,7 @@ import javax.inject.Singleton
|
||||||
@Singleton
|
@Singleton
|
||||||
class MessageRepository @Inject constructor(
|
class MessageRepository @Inject constructor(
|
||||||
private val messagesDb: MessagesDao,
|
private val messagesDb: MessagesDao,
|
||||||
|
private val mutedMessageSendersDao: MutedMessageSendersDao,
|
||||||
private val messageAttachmentDao: MessageAttachmentDao,
|
private val messageAttachmentDao: MessageAttachmentDao,
|
||||||
private val sdk: Sdk,
|
private val sdk: Sdk,
|
||||||
@ApplicationContext private val context: Context,
|
@ApplicationContext private val context: Context,
|
||||||
|
@ -51,7 +56,6 @@ class MessageRepository @Inject constructor(
|
||||||
private val mailboxDao: MailboxDao,
|
private val mailboxDao: MailboxDao,
|
||||||
private val getMailboxByStudentUseCase: GetMailboxByStudentUseCase,
|
private val getMailboxByStudentUseCase: GetMailboxByStudentUseCase,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val saveFetchResultMutex = Mutex()
|
private val saveFetchResultMutex = Mutex()
|
||||||
|
|
||||||
private val messagesCacheKey = "message"
|
private val messagesCacheKey = "message"
|
||||||
|
@ -63,7 +67,7 @@ class MessageRepository @Inject constructor(
|
||||||
folder: MessageFolder,
|
folder: MessageFolder,
|
||||||
forceRefresh: Boolean,
|
forceRefresh: Boolean,
|
||||||
notify: Boolean = false,
|
notify: Boolean = false,
|
||||||
): Flow<Resource<List<Message>>> = networkBoundResource(
|
): Flow<Resource<List<MessageWithMutedAuthor>>> = networkBoundResource(
|
||||||
mutex = saveFetchResultMutex,
|
mutex = saveFetchResultMutex,
|
||||||
isResultEmpty = { it.isEmpty() },
|
isResultEmpty = { it.isEmpty() },
|
||||||
shouldFetch = {
|
shouldFetch = {
|
||||||
|
@ -74,8 +78,8 @@ class MessageRepository @Inject constructor(
|
||||||
},
|
},
|
||||||
query = {
|
query = {
|
||||||
if (mailbox == null) {
|
if (mailbox == null) {
|
||||||
messagesDb.loadAll(folder.id, student.email)
|
messagesDb.loadMessagesWithMutedAuthor(folder.id, student.email)
|
||||||
} else messagesDb.loadAll(mailbox.globalKey, folder.id)
|
} else messagesDb.loadMessagesWithMutedAuthor(mailbox.globalKey, folder.id)
|
||||||
},
|
},
|
||||||
fetch = {
|
fetch = {
|
||||||
sdk.init(student).getMessages(
|
sdk.init(student).getMessages(
|
||||||
|
@ -83,10 +87,12 @@ class MessageRepository @Inject constructor(
|
||||||
mailboxKey = mailbox?.globalKey,
|
mailboxKey = mailbox?.globalKey,
|
||||||
).mapToEntities(student, mailbox, mailboxDao.loadAll(student.email))
|
).mapToEntities(student, mailbox, mailboxDao.loadAll(student.email))
|
||||||
},
|
},
|
||||||
saveFetchResult = { old, new ->
|
saveFetchResult = { oldWithAuthors, new ->
|
||||||
|
val old = oldWithAuthors.map { it.message }
|
||||||
messagesDb.deleteAll(old uniqueSubtract new)
|
messagesDb.deleteAll(old uniqueSubtract new)
|
||||||
messagesDb.insertAll((new uniqueSubtract old).onEach {
|
messagesDb.insertAll((new uniqueSubtract old).onEach {
|
||||||
it.isNotified = !notify
|
val muted = isMuted(it.correspondents)
|
||||||
|
it.isNotified = !notify || muted
|
||||||
})
|
})
|
||||||
|
|
||||||
refreshHelper.updateLastRefreshTimestamp(
|
refreshHelper.updateLastRefreshTimestamp(
|
||||||
|
@ -106,9 +112,7 @@ class MessageRepository @Inject constructor(
|
||||||
Timber.d("Message content in db empty: ${it.message.content.isBlank()}")
|
Timber.d("Message content in db empty: ${it.message.content.isBlank()}")
|
||||||
(it.message.unread && markAsRead) || it.message.content.isBlank()
|
(it.message.unread && markAsRead) || it.message.content.isBlank()
|
||||||
},
|
},
|
||||||
query = {
|
query = { messagesDb.loadMessageWithAttachment(message.messageGlobalKey) },
|
||||||
messagesDb.loadMessageWithAttachment(message.messageGlobalKey)
|
|
||||||
},
|
|
||||||
fetch = {
|
fetch = {
|
||||||
sdk.init(student).getMessageDetails(
|
sdk.init(student).getMessageDetails(
|
||||||
messageKey = it!!.message.messageGlobalKey,
|
messageKey = it!!.message.messageGlobalKey,
|
||||||
|
@ -152,17 +156,30 @@ class MessageRepository @Inject constructor(
|
||||||
subject: String,
|
subject: String,
|
||||||
content: String,
|
content: String,
|
||||||
recipients: List<Recipient>,
|
recipients: List<Recipient>,
|
||||||
mailboxId: String,
|
mailbox: Mailbox,
|
||||||
) {
|
) {
|
||||||
sdk.init(student).sendMessage(
|
sdk.init(student).sendMessage(
|
||||||
subject = subject,
|
subject = subject,
|
||||||
content = content,
|
content = content,
|
||||||
recipients = recipients.mapFromEntities(),
|
recipients = recipients.mapFromEntities(),
|
||||||
mailboxId = mailboxId,
|
mailboxId = mailbox.globalKey,
|
||||||
)
|
)
|
||||||
|
refreshFolders(student, mailbox, listOf(SENT))
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun deleteMessages(student: Student, mailbox: Mailbox?, messages: List<Message>) {
|
suspend fun restoreMessages(student: Student, mailbox: Mailbox?, messages: List<Message>) {
|
||||||
|
sdk.init(student).restoreMessages(
|
||||||
|
messages = messages.map { it.messageGlobalKey },
|
||||||
|
)
|
||||||
|
|
||||||
|
refreshFolders(student, mailbox)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun deleteMessage(student: Student, message: Message) {
|
||||||
|
deleteMessages(student, listOf(message))
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun deleteMessages(student: Student, messages: List<Message>) {
|
||||||
val firstMessage = messages.first()
|
val firstMessage = messages.first()
|
||||||
sdk.init(student).deleteMessages(
|
sdk.init(student).deleteMessages(
|
||||||
messages = messages.map { it.messageGlobalKey },
|
messages = messages.map { it.messageGlobalKey },
|
||||||
|
@ -181,18 +198,24 @@ class MessageRepository @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
messagesDb.updateAll(deletedMessages)
|
messagesDb.updateAll(deletedMessages)
|
||||||
} else messagesDb.deleteAll(messages)
|
} else {
|
||||||
|
messagesDb.deleteAll(messages)
|
||||||
getMessages(
|
}
|
||||||
student = student,
|
|
||||||
mailbox = mailbox,
|
|
||||||
folder = TRASHED,
|
|
||||||
forceRefresh = true,
|
|
||||||
).first()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun deleteMessage(student: Student, mailbox: Mailbox?, message: Message) {
|
private suspend fun refreshFolders(
|
||||||
deleteMessages(student, mailbox, listOf(message))
|
student: Student,
|
||||||
|
mailbox: Mailbox?,
|
||||||
|
folders: List<MessageFolder> = MessageFolder.entries
|
||||||
|
) {
|
||||||
|
folders.forEach {
|
||||||
|
getMessages(
|
||||||
|
student = student,
|
||||||
|
mailbox = mailbox,
|
||||||
|
folder = it,
|
||||||
|
forceRefresh = true,
|
||||||
|
).toFirstResult()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getMailboxes(student: Student, forceRefresh: Boolean) = networkBoundResource(
|
suspend fun getMailboxes(student: Student, forceRefresh: Boolean) = networkBoundResource(
|
||||||
|
@ -236,4 +259,18 @@ class MessageRepository @Inject constructor(
|
||||||
context.getString(R.string.pref_key_message_draft),
|
context.getString(R.string.pref_key_message_draft),
|
||||||
value?.let { json.encodeToString(it) }
|
value?.let { json.encodeToString(it) }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private suspend fun isMuted(author: String): Boolean {
|
||||||
|
return mutedMessageSendersDao.checkMute(author)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun muteMessage(author: String) {
|
||||||
|
if (isMuted(author)) return
|
||||||
|
mutedMessageSendersDao.insertMute(MutedMessageSender(author))
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun unmuteMessage(author: String) {
|
||||||
|
if (!isMuted(author)) return
|
||||||
|
mutedMessageSendersDao.deleteMute(author)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import io.github.wulkanowy.sdk.Sdk
|
||||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||||
import io.github.wulkanowy.utils.getRefreshKey
|
import io.github.wulkanowy.utils.getRefreshKey
|
||||||
import io.github.wulkanowy.utils.init
|
import io.github.wulkanowy.utils.init
|
||||||
|
import io.github.wulkanowy.utils.switchSemester
|
||||||
import io.github.wulkanowy.utils.uniqueSubtract
|
import io.github.wulkanowy.utils.uniqueSubtract
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -42,7 +43,7 @@ class MobileDeviceRepository @Inject constructor(
|
||||||
query = { mobileDb.loadAll(student.userLoginId) },
|
query = { mobileDb.loadAll(student.userLoginId) },
|
||||||
fetch = {
|
fetch = {
|
||||||
sdk.init(student)
|
sdk.init(student)
|
||||||
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
|
.switchSemester(semester)
|
||||||
.getRegisteredDevices()
|
.getRegisteredDevices()
|
||||||
.mapToEntities(student)
|
.mapToEntities(student)
|
||||||
},
|
},
|
||||||
|
@ -56,7 +57,7 @@ class MobileDeviceRepository @Inject constructor(
|
||||||
|
|
||||||
suspend fun unregisterDevice(student: Student, semester: Semester, device: MobileDevice) {
|
suspend fun unregisterDevice(student: Student, semester: Semester, device: MobileDevice) {
|
||||||
sdk.init(student)
|
sdk.init(student)
|
||||||
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
|
.switchSemester(semester)
|
||||||
.unregisterDevice(device.deviceId)
|
.unregisterDevice(device.deviceId)
|
||||||
|
|
||||||
mobileDb.deleteAll(listOf(device))
|
mobileDb.deleteAll(listOf(device))
|
||||||
|
@ -64,7 +65,7 @@ class MobileDeviceRepository @Inject constructor(
|
||||||
|
|
||||||
suspend fun getToken(student: Student, semester: Semester): MobileDeviceToken {
|
suspend fun getToken(student: Student, semester: Semester): MobileDeviceToken {
|
||||||
return sdk.init(student)
|
return sdk.init(student)
|
||||||
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
|
.switchSemester(semester)
|
||||||
.getToken()
|
.getToken()
|
||||||
.mapToMobileDeviceToken()
|
.mapToMobileDeviceToken()
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,7 +41,7 @@ class NoteRepository @Inject constructor(
|
||||||
query = { noteDb.loadAll(student.studentId) },
|
query = { noteDb.loadAll(student.studentId) },
|
||||||
fetch = {
|
fetch = {
|
||||||
sdk.init(student)
|
sdk.init(student)
|
||||||
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
|
.switchSemester(semester)
|
||||||
.getNotes()
|
.getNotes()
|
||||||
.mapToEntities(semester)
|
.mapToEntities(semester)
|
||||||
},
|
},
|
||||||
|
|
|
@ -9,6 +9,7 @@ import io.github.wulkanowy.sdk.Sdk
|
||||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||||
import io.github.wulkanowy.utils.getRefreshKey
|
import io.github.wulkanowy.utils.getRefreshKey
|
||||||
import io.github.wulkanowy.utils.init
|
import io.github.wulkanowy.utils.init
|
||||||
|
import io.github.wulkanowy.utils.switchSemester
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
@ -40,7 +41,7 @@ class SchoolRepository @Inject constructor(
|
||||||
query = { schoolDb.load(semester.studentId, semester.classId) },
|
query = { schoolDb.load(semester.studentId, semester.classId) },
|
||||||
fetch = {
|
fetch = {
|
||||||
sdk.init(student)
|
sdk.init(student)
|
||||||
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
|
.switchSemester(semester)
|
||||||
.getSchool()
|
.getSchool()
|
||||||
.mapToEntity(semester)
|
.mapToEntity(semester)
|
||||||
},
|
},
|
||||||
|
|
|
@ -11,6 +11,7 @@ import io.github.wulkanowy.ui.modules.login.LoginData
|
||||||
import io.github.wulkanowy.utils.IntegrityHelper
|
import io.github.wulkanowy.utils.IntegrityHelper
|
||||||
import io.github.wulkanowy.utils.getCurrentOrLast
|
import io.github.wulkanowy.utils.getCurrentOrLast
|
||||||
import io.github.wulkanowy.utils.init
|
import io.github.wulkanowy.utils.init
|
||||||
|
import io.github.wulkanowy.utils.switchSemester
|
||||||
import kotlinx.coroutines.withTimeout
|
import kotlinx.coroutines.withTimeout
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
@ -42,11 +43,7 @@ class SchoolsRepository @Inject constructor(
|
||||||
|
|
||||||
val schoolInfo = sdk
|
val schoolInfo = sdk
|
||||||
.init(student.copy(password = loginData.password))
|
.init(student.copy(password = loginData.password))
|
||||||
.switchDiary(
|
.switchSemester(semester)
|
||||||
diaryId = semester.diaryId,
|
|
||||||
kindergartenDiaryId = semester.kindergartenDiaryId,
|
|
||||||
schoolYear = semester.schoolYear
|
|
||||||
)
|
|
||||||
.getSchool()
|
.getSchool()
|
||||||
|
|
||||||
schoolsService.logLoginEvent(
|
schoolsService.logLoginEvent(
|
||||||
|
|
|
@ -7,6 +7,7 @@ import io.github.wulkanowy.data.mappers.mapToEntity
|
||||||
import io.github.wulkanowy.data.networkBoundResource
|
import io.github.wulkanowy.data.networkBoundResource
|
||||||
import io.github.wulkanowy.sdk.Sdk
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
import io.github.wulkanowy.utils.init
|
import io.github.wulkanowy.utils.init
|
||||||
|
import io.github.wulkanowy.utils.switchSemester
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
@ -30,7 +31,7 @@ class StudentInfoRepository @Inject constructor(
|
||||||
query = { studentInfoDao.loadStudentInfo(student.studentId) },
|
query = { studentInfoDao.loadStudentInfo(student.studentId) },
|
||||||
fetch = {
|
fetch = {
|
||||||
sdk.init(student)
|
sdk.init(student)
|
||||||
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
|
.switchSemester(semester)
|
||||||
.getStudentInfo().mapToEntity(semester)
|
.getStudentInfo().mapToEntity(semester)
|
||||||
},
|
},
|
||||||
saveFetchResult = { old, new ->
|
saveFetchResult = { old, new ->
|
||||||
|
|
|
@ -16,6 +16,7 @@ import io.github.wulkanowy.sdk.Sdk
|
||||||
import io.github.wulkanowy.utils.DispatchersProvider
|
import io.github.wulkanowy.utils.DispatchersProvider
|
||||||
import io.github.wulkanowy.utils.init
|
import io.github.wulkanowy.utils.init
|
||||||
import io.github.wulkanowy.utils.security.Scrambler
|
import io.github.wulkanowy.utils.security.Scrambler
|
||||||
|
import io.github.wulkanowy.utils.switchSemester
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
@ -149,12 +150,12 @@ class StudentRepository @Inject constructor(
|
||||||
|
|
||||||
suspend fun authorizePermission(student: Student, semester: Semester, pesel: String) =
|
suspend fun authorizePermission(student: Student, semester: Semester, pesel: String) =
|
||||||
sdk.init(student)
|
sdk.init(student)
|
||||||
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
|
.switchSemester(semester)
|
||||||
.authorizePermission(pesel)
|
.authorizePermission(pesel)
|
||||||
|
|
||||||
suspend fun refreshStudentName(student: Student, semester: Semester) {
|
suspend fun refreshStudentName(student: Student, semester: Semester) {
|
||||||
val newCurrentApiStudent = sdk.init(student)
|
val newCurrentApiStudent = sdk.init(student)
|
||||||
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
|
.switchSemester(semester)
|
||||||
.getCurrentStudent() ?: return
|
.getCurrentStudent() ?: return
|
||||||
|
|
||||||
val studentName = StudentName(
|
val studentName = StudentName(
|
||||||
|
|
|
@ -9,6 +9,7 @@ import io.github.wulkanowy.sdk.Sdk
|
||||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||||
import io.github.wulkanowy.utils.getRefreshKey
|
import io.github.wulkanowy.utils.getRefreshKey
|
||||||
import io.github.wulkanowy.utils.init
|
import io.github.wulkanowy.utils.init
|
||||||
|
import io.github.wulkanowy.utils.switchSemester
|
||||||
import io.github.wulkanowy.utils.uniqueSubtract
|
import io.github.wulkanowy.utils.uniqueSubtract
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -39,8 +40,9 @@ class SubjectRepository @Inject constructor(
|
||||||
query = { subjectDao.loadAll(semester.diaryId, semester.studentId) },
|
query = { subjectDao.loadAll(semester.diaryId, semester.studentId) },
|
||||||
fetch = {
|
fetch = {
|
||||||
sdk.init(student)
|
sdk.init(student)
|
||||||
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
|
.switchSemester(semester)
|
||||||
.getSubjects().mapToEntities(semester)
|
.getSubjects()
|
||||||
|
.mapToEntities(semester)
|
||||||
},
|
},
|
||||||
saveFetchResult = { old, new ->
|
saveFetchResult = { old, new ->
|
||||||
subjectDao.deleteAll(old uniqueSubtract new)
|
subjectDao.deleteAll(old uniqueSubtract new)
|
||||||
|
|
|
@ -9,6 +9,7 @@ import io.github.wulkanowy.sdk.Sdk
|
||||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||||
import io.github.wulkanowy.utils.getRefreshKey
|
import io.github.wulkanowy.utils.getRefreshKey
|
||||||
import io.github.wulkanowy.utils.init
|
import io.github.wulkanowy.utils.init
|
||||||
|
import io.github.wulkanowy.utils.switchSemester
|
||||||
import io.github.wulkanowy.utils.uniqueSubtract
|
import io.github.wulkanowy.utils.uniqueSubtract
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -39,7 +40,7 @@ class TeacherRepository @Inject constructor(
|
||||||
query = { teacherDb.loadAll(semester.studentId, semester.classId) },
|
query = { teacherDb.loadAll(semester.studentId, semester.classId) },
|
||||||
fetch = {
|
fetch = {
|
||||||
sdk.init(student)
|
sdk.init(student)
|
||||||
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
|
.switchSemester(semester)
|
||||||
.getTeachers()
|
.getTeachers()
|
||||||
.mapToEntities(semester)
|
.mapToEntities(semester)
|
||||||
},
|
},
|
||||||
|
|
|
@ -3,13 +3,23 @@ package io.github.wulkanowy.data.repositories
|
||||||
import io.github.wulkanowy.data.db.dao.TimetableAdditionalDao
|
import io.github.wulkanowy.data.db.dao.TimetableAdditionalDao
|
||||||
import io.github.wulkanowy.data.db.dao.TimetableDao
|
import io.github.wulkanowy.data.db.dao.TimetableDao
|
||||||
import io.github.wulkanowy.data.db.dao.TimetableHeaderDao
|
import io.github.wulkanowy.data.db.dao.TimetableHeaderDao
|
||||||
import io.github.wulkanowy.data.db.entities.*
|
import io.github.wulkanowy.data.db.entities.Semester
|
||||||
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
|
import io.github.wulkanowy.data.db.entities.Timetable
|
||||||
|
import io.github.wulkanowy.data.db.entities.TimetableAdditional
|
||||||
|
import io.github.wulkanowy.data.db.entities.TimetableHeader
|
||||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||||
import io.github.wulkanowy.data.networkBoundResource
|
import io.github.wulkanowy.data.networkBoundResource
|
||||||
import io.github.wulkanowy.data.pojos.TimetableFull
|
import io.github.wulkanowy.data.pojos.TimetableFull
|
||||||
import io.github.wulkanowy.sdk.Sdk
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
import io.github.wulkanowy.services.alarm.TimetableNotificationSchedulerHelper
|
import io.github.wulkanowy.services.alarm.TimetableNotificationSchedulerHelper
|
||||||
import io.github.wulkanowy.utils.*
|
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||||
|
import io.github.wulkanowy.utils.getRefreshKey
|
||||||
|
import io.github.wulkanowy.utils.init
|
||||||
|
import io.github.wulkanowy.utils.monday
|
||||||
|
import io.github.wulkanowy.utils.sunday
|
||||||
|
import io.github.wulkanowy.utils.switchSemester
|
||||||
|
import io.github.wulkanowy.utils.uniqueSubtract
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
@ -65,7 +75,7 @@ class TimetableRepository @Inject constructor(
|
||||||
query = { getFullTimetableFromDatabase(student, semester, start, end) },
|
query = { getFullTimetableFromDatabase(student, semester, start, end) },
|
||||||
fetch = {
|
fetch = {
|
||||||
val timetableFull = sdk.init(student)
|
val timetableFull = sdk.init(student)
|
||||||
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
|
.switchSemester(semester)
|
||||||
.getTimetable(start.monday, end.sunday)
|
.getTimetable(start.monday, end.sunday)
|
||||||
|
|
||||||
timetableFull.mapToEntities(semester)
|
timetableFull.mapToEntities(semester)
|
||||||
|
@ -121,12 +131,12 @@ class TimetableRepository @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getTimetableFromDatabase(
|
suspend fun getTimetableFromDatabase(
|
||||||
semester: Semester,
|
semester: Semester,
|
||||||
from: LocalDate,
|
start: LocalDate,
|
||||||
end: LocalDate
|
end: LocalDate
|
||||||
): Flow<List<Timetable>> {
|
): List<Timetable> {
|
||||||
return timetableDb.loadAll(semester.diaryId, semester.studentId, from, end)
|
return timetableDb.load(semester.diaryId, semester.studentId, start, end)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun updateTimetable(timetable: List<Timetable>) {
|
suspend fun updateTimetable(timetable: List<Timetable>) {
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
package io.github.wulkanowy.domain.timetable
|
||||||
|
|
||||||
|
import io.github.wulkanowy.data.db.entities.Semester
|
||||||
|
import io.github.wulkanowy.data.repositories.TimetableRepository
|
||||||
|
import io.github.wulkanowy.utils.monday
|
||||||
|
import io.github.wulkanowy.utils.sunday
|
||||||
|
import java.time.LocalDate
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class IsStudentHasLessonsOnWeekendUseCase @Inject constructor(
|
||||||
|
private val timetableRepository: TimetableRepository,
|
||||||
|
private val isWeekendHasLessonsUseCase: IsWeekendHasLessonsUseCase,
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend operator fun invoke(
|
||||||
|
semester: Semester,
|
||||||
|
currentDate: LocalDate = LocalDate.now(),
|
||||||
|
): Boolean {
|
||||||
|
val lessons = timetableRepository.getTimetableFromDatabase(
|
||||||
|
semester = semester,
|
||||||
|
start = currentDate.monday,
|
||||||
|
end = currentDate.sunday,
|
||||||
|
)
|
||||||
|
return isWeekendHasLessonsUseCase(lessons)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package io.github.wulkanowy.domain.timetable
|
||||||
|
|
||||||
|
import io.github.wulkanowy.data.db.entities.Timetable
|
||||||
|
import java.time.DayOfWeek
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class IsWeekendHasLessonsUseCase @Inject constructor() {
|
||||||
|
|
||||||
|
operator fun invoke(
|
||||||
|
lessons: List<Timetable>,
|
||||||
|
): Boolean = lessons.any {
|
||||||
|
it.date.dayOfWeek in listOf(
|
||||||
|
DayOfWeek.SATURDAY,
|
||||||
|
DayOfWeek.SUNDAY,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -65,8 +65,6 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
|
||||||
range = lesson.start..lesson.end,
|
range = lesson.start..lesson.end,
|
||||||
requestCode = getRequestCode(lesson.start, studentId)
|
requestCode = getRequestCode(lesson.start, studentId)
|
||||||
)
|
)
|
||||||
|
|
||||||
Timber.d("TimetableNotification canceled: type 1 & 2, subject: ${lesson.subject}, start: ${lesson.start}, student: $studentId")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,12 +4,12 @@ import android.content.Context
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
import io.github.wulkanowy.data.db.entities.Grade
|
import io.github.wulkanowy.data.db.entities.Grade
|
||||||
|
import io.github.wulkanowy.data.db.entities.GradeDescriptive
|
||||||
import io.github.wulkanowy.data.db.entities.GradeSummary
|
import io.github.wulkanowy.data.db.entities.GradeSummary
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.pojos.GroupNotificationData
|
import io.github.wulkanowy.data.pojos.GroupNotificationData
|
||||||
import io.github.wulkanowy.data.pojos.NotificationData
|
import io.github.wulkanowy.data.pojos.NotificationData
|
||||||
import io.github.wulkanowy.ui.modules.Destination
|
import io.github.wulkanowy.ui.modules.Destination
|
||||||
import io.github.wulkanowy.ui.modules.splash.SplashActivity
|
|
||||||
import io.github.wulkanowy.utils.getPlural
|
import io.github.wulkanowy.utils.getPlural
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -88,4 +88,28 @@ class NewGradeNotification @Inject constructor(
|
||||||
|
|
||||||
appNotificationManager.sendMultipleNotifications(groupNotificationData, student)
|
appNotificationManager.sendMultipleNotifications(groupNotificationData, student)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun notifyDescriptive(items: List<GradeDescriptive>, student: Student) {
|
||||||
|
val notificationDataList = items.map {
|
||||||
|
NotificationData(
|
||||||
|
title = context.getPlural(R.plurals.grade_new_items_descriptive, 1),
|
||||||
|
content = "${it.subject}: ${it.description}",
|
||||||
|
destination = Destination.Grade,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val groupNotificationData = GroupNotificationData(
|
||||||
|
notificationDataList = notificationDataList,
|
||||||
|
title = context.getPlural(R.plurals.grade_new_items_descriptive, items.size),
|
||||||
|
content = context.getPlural(
|
||||||
|
R.plurals.grade_notify_new_items_descriptive,
|
||||||
|
items.size,
|
||||||
|
items.size
|
||||||
|
),
|
||||||
|
destination = Destination.Grade,
|
||||||
|
type = NotificationType.NEW_GRADE_DESCRIPTIVE
|
||||||
|
)
|
||||||
|
|
||||||
|
appNotificationManager.sendMultipleNotifications(groupNotificationData, student)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,10 @@ enum class NotificationType(
|
||||||
channel = NewGradesChannel.CHANNEL_ID,
|
channel = NewGradesChannel.CHANNEL_ID,
|
||||||
icon = R.drawable.ic_stat_grade,
|
icon = R.drawable.ic_stat_grade,
|
||||||
),
|
),
|
||||||
|
NEW_GRADE_DESCRIPTIVE(
|
||||||
|
channel = NewGradesChannel.CHANNEL_ID,
|
||||||
|
icon = R.drawable.ic_stat_grade,
|
||||||
|
),
|
||||||
NEW_HOMEWORK(
|
NEW_HOMEWORK(
|
||||||
channel = NewHomeworkChannel.CHANNEL_ID,
|
channel = NewHomeworkChannel.CHANNEL_ID,
|
||||||
icon = R.drawable.ic_more_homework,
|
icon = R.drawable.ic_more_homework,
|
||||||
|
|
|
@ -16,17 +16,24 @@ class AttendanceWork @Inject constructor(
|
||||||
) : Work {
|
) : Work {
|
||||||
|
|
||||||
override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) {
|
override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) {
|
||||||
|
val startDate = now().previousOrSameSchoolDay
|
||||||
|
val endDate = startDate.plusDays(7)
|
||||||
|
|
||||||
attendanceRepository.getAttendance(
|
attendanceRepository.getAttendance(
|
||||||
student = student,
|
student = student,
|
||||||
semester = semester,
|
semester = semester,
|
||||||
start = now().previousOrSameSchoolDay,
|
start = startDate,
|
||||||
end = now().previousOrSameSchoolDay,
|
end = endDate,
|
||||||
forceRefresh = true,
|
forceRefresh = true,
|
||||||
notify = notify,
|
notify = notify,
|
||||||
)
|
)
|
||||||
.waitForResult()
|
.waitForResult()
|
||||||
|
|
||||||
attendanceRepository.getAttendanceFromDatabase(semester, now().minusDays(7), now())
|
attendanceRepository.getAttendanceFromDatabase(
|
||||||
|
semester = semester,
|
||||||
|
start = startDate,
|
||||||
|
end = endDate,
|
||||||
|
)
|
||||||
.first()
|
.first()
|
||||||
.filterNot { it.isNotified }
|
.filterNot { it.isNotified }
|
||||||
.let {
|
.let {
|
||||||
|
|
|
@ -5,6 +5,8 @@ import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.repositories.ExamRepository
|
import io.github.wulkanowy.data.repositories.ExamRepository
|
||||||
import io.github.wulkanowy.data.waitForResult
|
import io.github.wulkanowy.data.waitForResult
|
||||||
import io.github.wulkanowy.services.sync.notifications.NewExamNotification
|
import io.github.wulkanowy.services.sync.notifications.NewExamNotification
|
||||||
|
import io.github.wulkanowy.utils.endExamsDay
|
||||||
|
import io.github.wulkanowy.utils.startExamsDay
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import java.time.LocalDate.now
|
import java.time.LocalDate.now
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -15,16 +17,24 @@ class ExamWork @Inject constructor(
|
||||||
) : Work {
|
) : Work {
|
||||||
|
|
||||||
override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) {
|
override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) {
|
||||||
|
val startDate = now().startExamsDay
|
||||||
|
val endDate = startDate.endExamsDay
|
||||||
|
|
||||||
examRepository.getExams(
|
examRepository.getExams(
|
||||||
student = student,
|
student = student,
|
||||||
semester = semester,
|
semester = semester,
|
||||||
start = now(),
|
start = startDate,
|
||||||
end = now(),
|
end = endDate,
|
||||||
forceRefresh = true,
|
forceRefresh = true,
|
||||||
notify = notify,
|
notify = notify,
|
||||||
).waitForResult()
|
).waitForResult()
|
||||||
|
|
||||||
examRepository.getExamsFromDatabase(semester, now()).first()
|
examRepository.getExamsFromDatabase(
|
||||||
|
semester = semester,
|
||||||
|
start = startDate,
|
||||||
|
end = endDate,
|
||||||
|
)
|
||||||
|
.first()
|
||||||
.filter { !it.isNotified }.let {
|
.filter { !it.isNotified }.let {
|
||||||
if (it.isNotEmpty()) newExamNotification.notify(it, student)
|
if (it.isNotEmpty()) newExamNotification.notify(it, student)
|
||||||
|
|
||||||
|
|
|
@ -45,5 +45,15 @@ class GradeWork @Inject constructor(
|
||||||
grade.isFinalGradeNotified = true
|
grade.isFinalGradeNotified = true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gradeRepository.getGradesDescriptiveFromDatabase(semester).first()
|
||||||
|
.filter { !it.isNotified }
|
||||||
|
.let {
|
||||||
|
if (it.isNotEmpty()) newGradeNotification.notifyDescriptive(it, student)
|
||||||
|
|
||||||
|
gradeRepository.updateGradesDescriptive(it.onEach { grade ->
|
||||||
|
grade.isNotified = true
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,9 @@ import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.repositories.HomeworkRepository
|
import io.github.wulkanowy.data.repositories.HomeworkRepository
|
||||||
import io.github.wulkanowy.data.waitForResult
|
import io.github.wulkanowy.data.waitForResult
|
||||||
import io.github.wulkanowy.services.sync.notifications.NewHomeworkNotification
|
import io.github.wulkanowy.services.sync.notifications.NewHomeworkNotification
|
||||||
|
import io.github.wulkanowy.utils.monday
|
||||||
import io.github.wulkanowy.utils.nextOrSameSchoolDay
|
import io.github.wulkanowy.utils.nextOrSameSchoolDay
|
||||||
|
import io.github.wulkanowy.utils.sunday
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import java.time.LocalDate.now
|
import java.time.LocalDate.now
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -16,16 +18,24 @@ class HomeworkWork @Inject constructor(
|
||||||
) : Work {
|
) : Work {
|
||||||
|
|
||||||
override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) {
|
override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) {
|
||||||
|
val startDate = now().nextOrSameSchoolDay.monday
|
||||||
|
val endDate = startDate.sunday
|
||||||
|
|
||||||
homeworkRepository.getHomework(
|
homeworkRepository.getHomework(
|
||||||
student = student,
|
student = student,
|
||||||
semester = semester,
|
semester = semester,
|
||||||
start = now().nextOrSameSchoolDay,
|
start = startDate,
|
||||||
end = now().nextOrSameSchoolDay,
|
end = endDate,
|
||||||
forceRefresh = true,
|
forceRefresh = true,
|
||||||
notify = notify,
|
notify = notify,
|
||||||
).waitForResult()
|
).waitForResult()
|
||||||
|
|
||||||
homeworkRepository.getHomeworkFromDatabase(semester, now(), now().plusDays(7)).first()
|
homeworkRepository.getHomeworkFromDatabase(
|
||||||
|
semester = semester,
|
||||||
|
start = startDate,
|
||||||
|
end = endDate
|
||||||
|
)
|
||||||
|
.first()
|
||||||
.filter { !it.isNotified }.let {
|
.filter { !it.isNotified }.let {
|
||||||
if (it.isNotEmpty()) newHomeworkNotification.notify(it, student)
|
if (it.isNotEmpty()) newHomeworkNotification.notify(it, student)
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,6 @@ import io.github.wulkanowy.data.repositories.TimetableRepository
|
||||||
import io.github.wulkanowy.data.waitForResult
|
import io.github.wulkanowy.data.waitForResult
|
||||||
import io.github.wulkanowy.services.sync.notifications.ChangeTimetableNotification
|
import io.github.wulkanowy.services.sync.notifications.ChangeTimetableNotification
|
||||||
import io.github.wulkanowy.utils.nextOrSameSchoolDay
|
import io.github.wulkanowy.utils.nextOrSameSchoolDay
|
||||||
import kotlinx.coroutines.flow.first
|
|
||||||
import java.time.LocalDate.now
|
import java.time.LocalDate.now
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -16,18 +15,24 @@ class TimetableWork @Inject constructor(
|
||||||
) : Work {
|
) : Work {
|
||||||
|
|
||||||
override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) {
|
override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) {
|
||||||
|
val startDate = now().nextOrSameSchoolDay
|
||||||
|
val endDate = startDate.plusDays(7)
|
||||||
|
|
||||||
timetableRepository.getTimetable(
|
timetableRepository.getTimetable(
|
||||||
student = student,
|
student = student,
|
||||||
semester = semester,
|
semester = semester,
|
||||||
start = now().nextOrSameSchoolDay,
|
start = startDate,
|
||||||
end = now().nextOrSameSchoolDay,
|
end = endDate,
|
||||||
forceRefresh = true,
|
forceRefresh = true,
|
||||||
notify = notify,
|
notify = notify,
|
||||||
)
|
)
|
||||||
.waitForResult()
|
.waitForResult()
|
||||||
|
|
||||||
timetableRepository.getTimetableFromDatabase(semester, now(), now().plusDays(7))
|
timetableRepository.getTimetableFromDatabase(
|
||||||
.first()
|
semester = semester,
|
||||||
|
start = startDate,
|
||||||
|
end = endDate,
|
||||||
|
)
|
||||||
.filterNot { it.isNotified }
|
.filterNot { it.isNotified }
|
||||||
.let {
|
.let {
|
||||||
if (it.isNotEmpty()) changeTimetableNotification.notify(it, student)
|
if (it.isNotEmpty()) changeTimetableNotification.notify(it, student)
|
||||||
|
|
|
@ -11,6 +11,7 @@ import com.google.android.material.snackbar.Snackbar
|
||||||
import com.google.android.material.snackbar.Snackbar.LENGTH_LONG
|
import com.google.android.material.snackbar.Snackbar.LENGTH_LONG
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
import io.github.wulkanowy.ui.modules.auth.AuthDialog
|
import io.github.wulkanowy.ui.modules.auth.AuthDialog
|
||||||
|
import io.github.wulkanowy.ui.modules.captcha.CaptchaDialog
|
||||||
import io.github.wulkanowy.ui.modules.login.LoginActivity
|
import io.github.wulkanowy.ui.modules.login.LoginActivity
|
||||||
import io.github.wulkanowy.utils.FragmentLifecycleLogger
|
import io.github.wulkanowy.utils.FragmentLifecycleLogger
|
||||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||||
|
@ -77,6 +78,10 @@ abstract class BaseActivity<T : BasePresenter<out BaseView>, VB : ViewBinding> :
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onCaptchaVerificationRequired(url: String?) {
|
||||||
|
CaptchaDialog.newInstance(url).show(supportFragmentManager, "captcha_dialog")
|
||||||
|
}
|
||||||
|
|
||||||
override fun showDecryptionFailedDialog() {
|
override fun showDecryptionFailedDialog() {
|
||||||
MaterialAlertDialogBuilder(this)
|
MaterialAlertDialogBuilder(this)
|
||||||
.setTitle(R.string.main_session_expired)
|
.setTitle(R.string.main_session_expired)
|
||||||
|
|
|
@ -8,7 +8,6 @@ import android.widget.Toast
|
||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
import androidx.viewbinding.ViewBinding
|
import androidx.viewbinding.ViewBinding
|
||||||
import com.google.android.material.elevation.SurfaceColors
|
import com.google.android.material.elevation.SurfaceColors
|
||||||
import io.github.wulkanowy.ui.modules.auth.AuthDialog
|
|
||||||
import io.github.wulkanowy.utils.AnalyticsHelper
|
import io.github.wulkanowy.utils.AnalyticsHelper
|
||||||
import io.github.wulkanowy.utils.lifecycleAwareVariable
|
import io.github.wulkanowy.utils.lifecycleAwareVariable
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -32,6 +31,10 @@ abstract class BaseDialogFragment<VB : ViewBinding> : DialogFragment(), BaseView
|
||||||
(activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog()
|
(activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onCaptchaVerificationRequired(url: String?) {
|
||||||
|
(activity as? BaseActivity<*, *>)?.onCaptchaVerificationRequired(url)
|
||||||
|
}
|
||||||
|
|
||||||
override fun showDecryptionFailedDialog() {
|
override fun showDecryptionFailedDialog() {
|
||||||
(activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog()
|
(activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog()
|
||||||
}
|
}
|
||||||
|
@ -45,7 +48,7 @@ abstract class BaseDialogFragment<VB : ViewBinding> : DialogFragment(), BaseView
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showAuthDialog() {
|
override fun showAuthDialog() {
|
||||||
AuthDialog.newInstance().show(childFragmentManager, "auth_dialog")
|
(activity as? BaseActivity<*, *>)?.showAuthDialog()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showErrorDetailsDialog(error: Throwable) {
|
override fun showErrorDetailsDialog(error: Throwable) {
|
||||||
|
|
|
@ -7,7 +7,6 @@ import androidx.viewbinding.ViewBinding
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import com.google.android.material.snackbar.Snackbar.LENGTH_LONG
|
import com.google.android.material.snackbar.Snackbar.LENGTH_LONG
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
import io.github.wulkanowy.ui.modules.auth.AuthDialog
|
|
||||||
import io.github.wulkanowy.utils.lifecycleAwareVariable
|
import io.github.wulkanowy.utils.lifecycleAwareVariable
|
||||||
|
|
||||||
abstract class BaseFragment<VB : ViewBinding>(@LayoutRes layoutId: Int) : Fragment(layoutId),
|
abstract class BaseFragment<VB : ViewBinding>(@LayoutRes layoutId: Int) : Fragment(layoutId),
|
||||||
|
@ -43,12 +42,16 @@ abstract class BaseFragment<VB : ViewBinding>(@LayoutRes layoutId: Int) : Fragme
|
||||||
(activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog()
|
(activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onCaptchaVerificationRequired(url: String?) {
|
||||||
|
(activity as? BaseActivity<*, *>)?.onCaptchaVerificationRequired(url)
|
||||||
|
}
|
||||||
|
|
||||||
override fun showDecryptionFailedDialog() {
|
override fun showDecryptionFailedDialog() {
|
||||||
(activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog()
|
(activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showAuthDialog() {
|
override fun showAuthDialog() {
|
||||||
AuthDialog.newInstance().show(childFragmentManager, "auth_dialog")
|
(activity as? BaseActivity<*, *>)?.showAuthDialog()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun openClearLoginView() {
|
override fun openClearLoginView() {
|
||||||
|
|
|
@ -29,6 +29,7 @@ open class BasePresenter<T : BaseView>(
|
||||||
errorHandler.apply {
|
errorHandler.apply {
|
||||||
showErrorMessage = view::showError
|
showErrorMessage = view::showError
|
||||||
onExpiredCredentials = view::showExpiredCredentialsDialog
|
onExpiredCredentials = view::showExpiredCredentialsDialog
|
||||||
|
onCaptchaVerificationRequired = view::onCaptchaVerificationRequired
|
||||||
onDecryptionFailed = view::showDecryptionFailedDialog
|
onDecryptionFailed = view::showDecryptionFailedDialog
|
||||||
onNoCurrentStudent = view::openClearLoginView
|
onNoCurrentStudent = view::openClearLoginView
|
||||||
onPasswordChangeRequired = view::showChangePasswordSnackbar
|
onPasswordChangeRequired = view::showChangePasswordSnackbar
|
||||||
|
|
|
@ -8,6 +8,8 @@ interface BaseView {
|
||||||
|
|
||||||
fun showExpiredCredentialsDialog()
|
fun showExpiredCredentialsDialog()
|
||||||
|
|
||||||
|
fun onCaptchaVerificationRequired(url: String?)
|
||||||
|
|
||||||
fun showDecryptionFailedDialog()
|
fun showDecryptionFailedDialog()
|
||||||
|
|
||||||
fun showAuthDialog()
|
fun showAuthDialog()
|
||||||
|
|
|
@ -4,6 +4,7 @@ import android.content.Context
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import io.github.wulkanowy.data.exceptions.NoCurrentStudentException
|
import io.github.wulkanowy.data.exceptions.NoCurrentStudentException
|
||||||
import io.github.wulkanowy.sdk.scrapper.exception.AuthorizationRequiredException
|
import io.github.wulkanowy.sdk.scrapper.exception.AuthorizationRequiredException
|
||||||
|
import io.github.wulkanowy.sdk.scrapper.exception.CloudflareVerificationException
|
||||||
import io.github.wulkanowy.sdk.scrapper.login.BadCredentialsException
|
import io.github.wulkanowy.sdk.scrapper.login.BadCredentialsException
|
||||||
import io.github.wulkanowy.sdk.scrapper.login.PasswordChangeRequiredException
|
import io.github.wulkanowy.sdk.scrapper.login.PasswordChangeRequiredException
|
||||||
import io.github.wulkanowy.utils.getErrorString
|
import io.github.wulkanowy.utils.getErrorString
|
||||||
|
@ -25,22 +26,29 @@ open class ErrorHandler @Inject constructor(@ApplicationContext protected val co
|
||||||
|
|
||||||
var onAuthorizationRequired: () -> Unit = {}
|
var onAuthorizationRequired: () -> Unit = {}
|
||||||
|
|
||||||
|
var onCaptchaVerificationRequired: (url: String?) -> Unit = {}
|
||||||
|
|
||||||
fun dispatch(error: Throwable) {
|
fun dispatch(error: Throwable) {
|
||||||
Timber.e(error, "An exception occurred while the Wulkanowy was running")
|
Timber.e(error, "An exception occurred while the Wulkanowy was running")
|
||||||
proceed(error)
|
proceed(error)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun proceed(error: Throwable) {
|
protected open fun proceed(error: Throwable) {
|
||||||
showErrorMessage(context.resources.getErrorString(error), error)
|
showDefaultMessage(error)
|
||||||
when (error) {
|
when (error) {
|
||||||
is PasswordChangeRequiredException -> onPasswordChangeRequired(error.redirectUrl)
|
is PasswordChangeRequiredException -> onPasswordChangeRequired(error.redirectUrl)
|
||||||
is ScramblerException -> onDecryptionFailed()
|
is ScramblerException -> onDecryptionFailed()
|
||||||
is BadCredentialsException -> onExpiredCredentials()
|
is BadCredentialsException -> onExpiredCredentials()
|
||||||
is NoCurrentStudentException -> onNoCurrentStudent()
|
is NoCurrentStudentException -> onNoCurrentStudent()
|
||||||
is AuthorizationRequiredException -> onAuthorizationRequired()
|
is AuthorizationRequiredException -> onAuthorizationRequired()
|
||||||
|
is CloudflareVerificationException -> onCaptchaVerificationRequired(error.originalUrl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun showDefaultMessage(error: Throwable) {
|
||||||
|
showErrorMessage(context.resources.getErrorString(error), error)
|
||||||
|
}
|
||||||
|
|
||||||
open fun clear() {
|
open fun clear() {
|
||||||
showErrorMessage = { _, _ -> }
|
showErrorMessage = { _, _ -> }
|
||||||
onExpiredCredentials = {}
|
onExpiredCredentials = {}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package io.github.wulkanowy.ui.modules.attendance
|
package io.github.wulkanowy.ui.modules.attendance
|
||||||
|
|
||||||
|
import android.graphics.Typeface
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
@ -10,6 +11,7 @@ import io.github.wulkanowy.data.db.entities.Attendance
|
||||||
import io.github.wulkanowy.data.enums.SentExcuseStatus
|
import io.github.wulkanowy.data.enums.SentExcuseStatus
|
||||||
import io.github.wulkanowy.databinding.ItemAttendanceBinding
|
import io.github.wulkanowy.databinding.ItemAttendanceBinding
|
||||||
import io.github.wulkanowy.utils.descriptionRes
|
import io.github.wulkanowy.utils.descriptionRes
|
||||||
|
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||||
import io.github.wulkanowy.utils.isExcusableOrNotExcused
|
import io.github.wulkanowy.utils.isExcusableOrNotExcused
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -39,7 +41,33 @@ class AttendanceAdapter @Inject constructor() :
|
||||||
root.context.getString(R.string.all_no_data)
|
root.context.getString(R.string.all_no_data)
|
||||||
}
|
}
|
||||||
attendanceItemDescription.setText(item.descriptionRes)
|
attendanceItemDescription.setText(item.descriptionRes)
|
||||||
attendanceItemAlert.isVisible = item.let { it.absence && !it.excused }
|
|
||||||
|
attendanceItemDescription.setTextColor(
|
||||||
|
root.context.getThemeAttrColor(
|
||||||
|
when {
|
||||||
|
item.absence && !item.excused -> R.attr.colorAttendanceAbsence
|
||||||
|
item.lateness && !item.excused -> R.attr.colorAttendanceLateness
|
||||||
|
else -> android.R.attr.textColorSecondary
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (item.exemption || item.excused) {
|
||||||
|
attendanceItemDescription.setTypeface(null, Typeface.BOLD)
|
||||||
|
} else {
|
||||||
|
attendanceItemDescription.setTypeface(null, Typeface.NORMAL)
|
||||||
|
}
|
||||||
|
|
||||||
|
attendanceItemAlert.isVisible =
|
||||||
|
item.let { (it.absence && !it.excused) || (it.lateness && !it.excused) }
|
||||||
|
|
||||||
|
attendanceItemAlert.setColorFilter(root.context.getThemeAttrColor(
|
||||||
|
when{
|
||||||
|
item.absence && !item.excused -> R.attr.colorAttendanceAbsence
|
||||||
|
item.lateness && !item.excused -> R.attr.colorAttendanceLateness
|
||||||
|
else -> android.R.attr.colorPrimary
|
||||||
|
}
|
||||||
|
))
|
||||||
attendanceItemNumber.visibility = View.GONE
|
attendanceItemNumber.visibility = View.GONE
|
||||||
attendanceItemExcuseInfo.visibility = View.GONE
|
attendanceItemExcuseInfo.visibility = View.GONE
|
||||||
attendanceItemExcuseCheckbox.visibility = View.GONE
|
attendanceItemExcuseCheckbox.visibility = View.GONE
|
||||||
|
@ -54,10 +82,12 @@ class AttendanceAdapter @Inject constructor() :
|
||||||
attendanceItemExcuseInfo.visibility = View.VISIBLE
|
attendanceItemExcuseInfo.visibility = View.VISIBLE
|
||||||
attendanceItemAlert.visibility = View.INVISIBLE
|
attendanceItemAlert.visibility = View.INVISIBLE
|
||||||
}
|
}
|
||||||
|
|
||||||
SentExcuseStatus.DENIED -> {
|
SentExcuseStatus.DENIED -> {
|
||||||
attendanceItemExcuseInfo.setImageResource(R.drawable.ic_excuse_denied)
|
attendanceItemExcuseInfo.setImageResource(R.drawable.ic_excuse_denied)
|
||||||
attendanceItemExcuseInfo.visibility = View.VISIBLE
|
attendanceItemExcuseInfo.visibility = View.VISIBLE
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
if (item.isExcusableOrNotExcused && excuseActionMode) {
|
if (item.isExcusableOrNotExcused && excuseActionMode) {
|
||||||
attendanceItemNumber.visibility = View.GONE
|
attendanceItemNumber.visibility = View.GONE
|
||||||
|
|
|
@ -6,10 +6,12 @@ import android.view.View
|
||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import io.github.wulkanowy.R
|
||||||
import io.github.wulkanowy.data.db.entities.Attendance
|
import io.github.wulkanowy.data.db.entities.Attendance
|
||||||
import io.github.wulkanowy.databinding.DialogAttendanceBinding
|
import io.github.wulkanowy.databinding.DialogAttendanceBinding
|
||||||
import io.github.wulkanowy.ui.base.BaseDialogFragment
|
import io.github.wulkanowy.ui.base.BaseDialogFragment
|
||||||
import io.github.wulkanowy.utils.descriptionRes
|
import io.github.wulkanowy.utils.descriptionRes
|
||||||
|
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||||
import io.github.wulkanowy.utils.serializable
|
import io.github.wulkanowy.utils.serializable
|
||||||
import io.github.wulkanowy.utils.toFormattedString
|
import io.github.wulkanowy.utils.toFormattedString
|
||||||
|
|
||||||
|
@ -44,6 +46,16 @@ class AttendanceDialog : BaseDialogFragment<DialogAttendanceBinding>() {
|
||||||
with(binding) {
|
with(binding) {
|
||||||
attendanceDialogSubjectValue.text = attendance.subject
|
attendanceDialogSubjectValue.text = attendance.subject
|
||||||
attendanceDialogDescriptionValue.setText(attendance.descriptionRes)
|
attendanceDialogDescriptionValue.setText(attendance.descriptionRes)
|
||||||
|
attendanceDialogDescriptionValue.setTextColor(
|
||||||
|
root.context.getThemeAttrColor(
|
||||||
|
when {
|
||||||
|
attendance.absence && !attendance.excused -> R.attr.colorAttendanceAbsence
|
||||||
|
attendance.lateness && !attendance.excused -> R.attr.colorAttendanceLateness
|
||||||
|
else -> android.R.attr.textColorSecondary
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
attendanceDialogDateValue.text = attendance.date.toFormattedString()
|
attendanceDialogDateValue.text = attendance.date.toFormattedString()
|
||||||
attendanceDialogNumberValue.text = attendance.number.toString()
|
attendanceDialogNumberValue.text = attendance.number.toString()
|
||||||
attendanceDialogClose.setOnClickListener { dismiss() }
|
attendanceDialogClose.setOnClickListener { dismiss() }
|
||||||
|
|
|
@ -2,8 +2,14 @@ package io.github.wulkanowy.ui.modules.attendance
|
||||||
|
|
||||||
import android.content.DialogInterface.BUTTON_POSITIVE
|
import android.content.DialogInterface.BUTTON_POSITIVE
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.*
|
import android.view.LayoutInflater
|
||||||
import android.view.View.*
|
import android.view.Menu
|
||||||
|
import android.view.MenuInflater
|
||||||
|
import android.view.MenuItem
|
||||||
|
import android.view.View
|
||||||
|
import android.view.View.GONE
|
||||||
|
import android.view.View.INVISIBLE
|
||||||
|
import android.view.View.VISIBLE
|
||||||
import androidx.appcompat.view.ActionMode
|
import androidx.appcompat.view.ActionMode
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
@ -63,6 +69,8 @@ class AttendanceFragment : BaseFragment<FragmentAttendanceBinding>(R.layout.frag
|
||||||
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
|
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
|
||||||
val inflater = mode.menuInflater
|
val inflater = mode.menuInflater
|
||||||
inflater.inflate(R.menu.context_menu_attendance, menu)
|
inflater.inflate(R.menu.context_menu_attendance, menu)
|
||||||
|
menu.findItem(R.id.excuseMenuDaySubmit).setVisible(presenter.isWholeDayExcusable)
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,6 +86,7 @@ class AttendanceFragment : BaseFragment<FragmentAttendanceBinding>(R.layout.frag
|
||||||
|
|
||||||
override fun onActionItemClicked(mode: ActionMode, menu: MenuItem): Boolean {
|
override fun onActionItemClicked(mode: ActionMode, menu: MenuItem): Boolean {
|
||||||
return when (menu.itemId) {
|
return when (menu.itemId) {
|
||||||
|
R.id.excuseMenuDaySubmit -> presenter.onExcuseDayButtonClick()
|
||||||
R.id.excuseMenuSubmit -> presenter.onExcuseSubmitButtonClick()
|
R.id.excuseMenuSubmit -> presenter.onExcuseSubmitButtonClick()
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
|
@ -257,11 +266,18 @@ class AttendanceFragment : BaseFragment<FragmentAttendanceBinding>(R.layout.frag
|
||||||
actionMode = (activity as MainActivity?)?.startSupportActionMode(actionModeCallback)
|
actionMode = (activity as MainActivity?)?.startSupportActionMode(actionModeCallback)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun startSendMessageIntent(date: LocalDate, numbers: String, reason: String) {
|
override fun startSendMessageIntent(date: LocalDate, lessons: String, reason: String) {
|
||||||
val reasonFullText = getString(
|
val reasonFullText = if (lessons.isEmpty()) {
|
||||||
R.string.attendance_excuse_formula,
|
getString(
|
||||||
|
R.string.attendance_excuse_day_formula,
|
||||||
|
date,
|
||||||
|
if (reason.isNotBlank()) " ${getString(R.string.attendance_excuse_reason)} " else "",
|
||||||
|
reason.ifBlank { "" }
|
||||||
|
)
|
||||||
|
} else getString(
|
||||||
|
R.string.attendance_excuse_lessons_formula,
|
||||||
date,
|
date,
|
||||||
numbers,
|
lessons,
|
||||||
if (reason.isNotBlank()) " ${getString(R.string.attendance_excuse_reason)} " else "",
|
if (reason.isNotBlank()) " ${getString(R.string.attendance_excuse_reason)} " else "",
|
||||||
reason.ifBlank { "" }
|
reason.ifBlank { "" }
|
||||||
)
|
)
|
||||||
|
|
|
@ -4,18 +4,14 @@ import android.annotation.SuppressLint
|
||||||
import io.github.wulkanowy.data.*
|
import io.github.wulkanowy.data.*
|
||||||
import io.github.wulkanowy.data.db.entities.Attendance
|
import io.github.wulkanowy.data.db.entities.Attendance
|
||||||
import io.github.wulkanowy.data.db.entities.Semester
|
import io.github.wulkanowy.data.db.entities.Semester
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
|
||||||
import io.github.wulkanowy.data.db.entities.Timetable
|
|
||||||
import io.github.wulkanowy.data.repositories.AttendanceRepository
|
import io.github.wulkanowy.data.repositories.AttendanceRepository
|
||||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||||
import io.github.wulkanowy.data.repositories.SemesterRepository
|
import io.github.wulkanowy.data.repositories.SemesterRepository
|
||||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||||
import io.github.wulkanowy.data.repositories.TimetableRepository
|
|
||||||
import io.github.wulkanowy.ui.base.BasePresenter
|
import io.github.wulkanowy.ui.base.BasePresenter
|
||||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||||
import io.github.wulkanowy.utils.*
|
import io.github.wulkanowy.utils.*
|
||||||
import kotlinx.coroutines.flow.catch
|
import kotlinx.coroutines.flow.firstOrNull
|
||||||
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.time.DayOfWeek
|
import java.time.DayOfWeek
|
||||||
|
@ -42,6 +38,7 @@ class AttendancePresenter @Inject constructor(
|
||||||
private lateinit var lastError: Throwable
|
private lateinit var lastError: Throwable
|
||||||
|
|
||||||
private val attendanceToExcuseList = mutableListOf<Attendance>()
|
private val attendanceToExcuseList = mutableListOf<Attendance>()
|
||||||
|
var isWholeDayExcusable = false
|
||||||
|
|
||||||
private var isVulcanExcusedFunctionEnabled = false
|
private var isVulcanExcusedFunctionEnabled = false
|
||||||
|
|
||||||
|
@ -135,6 +132,12 @@ class AttendancePresenter @Inject constructor(
|
||||||
view?.startActionMode()
|
view?.startActionMode()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onExcuseDayButtonClick(): Boolean {
|
||||||
|
view?.showExcuseDialog()
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
fun onExcuseCheckboxSelect(attendanceItem: Attendance, checked: Boolean) {
|
fun onExcuseCheckboxSelect(attendanceItem: Attendance, checked: Boolean) {
|
||||||
if (checked) attendanceToExcuseList.add(attendanceItem)
|
if (checked) attendanceToExcuseList.add(attendanceItem)
|
||||||
else attendanceToExcuseList.remove(attendanceItem)
|
else attendanceToExcuseList.remove(attendanceItem)
|
||||||
|
@ -156,7 +159,7 @@ class AttendancePresenter @Inject constructor(
|
||||||
fun onExcuseDialogSubmit(reason: String) {
|
fun onExcuseDialogSubmit(reason: String) {
|
||||||
view?.finishActionMode()
|
view?.finishActionMode()
|
||||||
|
|
||||||
if (attendanceToExcuseList.isEmpty()) return
|
if (attendanceToExcuseList.isEmpty() && !isWholeDayExcusable) return
|
||||||
|
|
||||||
if (isVulcanExcusedFunctionEnabled) {
|
if (isVulcanExcusedFunctionEnabled) {
|
||||||
excuseAbsence(
|
excuseAbsence(
|
||||||
|
@ -167,8 +170,8 @@ class AttendancePresenter @Inject constructor(
|
||||||
val attendanceToExcuseNumbers = attendanceToExcuseList.map { it.number }
|
val attendanceToExcuseNumbers = attendanceToExcuseList.map { it.number }
|
||||||
|
|
||||||
view?.startSendMessageIntent(
|
view?.startSendMessageIntent(
|
||||||
date = attendanceToExcuseList[0].date,
|
date = currentDate ?: attendanceToExcuseList[0].date,
|
||||||
numbers = attendanceToExcuseNumbers.joinToString(", "),
|
lessons = attendanceToExcuseNumbers.joinToString(", "),
|
||||||
reason = reason
|
reason = reason
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -210,7 +213,7 @@ class AttendancePresenter @Inject constructor(
|
||||||
|
|
||||||
val semester = semesterRepository.getCurrentSemester(student)
|
val semester = semesterRepository.getCurrentSemester(student)
|
||||||
|
|
||||||
checkInitialAndCurrentDate(student, semester)
|
checkInitialAndCurrentDate(semester)
|
||||||
attendanceRepository.getAttendance(
|
attendanceRepository.getAttendance(
|
||||||
student = student,
|
student = student,
|
||||||
semester = semester,
|
semester = semester,
|
||||||
|
@ -221,7 +224,9 @@ class AttendancePresenter @Inject constructor(
|
||||||
}
|
}
|
||||||
.logResourceStatus("load attendance")
|
.logResourceStatus("load attendance")
|
||||||
.onResourceLoading {
|
.onResourceLoading {
|
||||||
view?.showExcuseButton(false)
|
view?.apply {
|
||||||
|
showExcuseButton(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.mapResourceData {
|
.mapResourceData {
|
||||||
if (prefRepository.isShowPresent) {
|
if (prefRepository.isShowPresent) {
|
||||||
|
@ -244,15 +249,16 @@ class AttendancePresenter @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onResourceIntermediate { view?.showRefresh(true) }
|
.onResourceIntermediate { view?.showRefresh(true) }
|
||||||
.onResourceSuccess {
|
.onResourceSuccess { items ->
|
||||||
isVulcanExcusedFunctionEnabled = it.any { item -> item.excusable }
|
isVulcanExcusedFunctionEnabled = items.any { item -> item.excusable }
|
||||||
val anyExcusables = it.any { it.isExcusableOrNotExcused }
|
isWholeDayExcusable = items.all { it.isAbsenceExcusable }
|
||||||
|
val anyExcusables = items.any { it.isExcusableOrNotExcused }
|
||||||
view?.showExcuseButton(anyExcusables && (isParent || isVulcanExcusedFunctionEnabled))
|
view?.showExcuseButton(anyExcusables && (isParent || isVulcanExcusedFunctionEnabled))
|
||||||
|
|
||||||
analytics.logEvent(
|
analytics.logEvent(
|
||||||
"load_data",
|
"load_data",
|
||||||
"type" to "attendance",
|
"type" to "attendance",
|
||||||
"items" to it.size
|
"items" to items.size
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.onResourceNotLoading {
|
.onResourceNotLoading {
|
||||||
|
@ -266,15 +272,13 @@ class AttendancePresenter @Inject constructor(
|
||||||
.launch()
|
.launch()
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun checkInitialAndCurrentDate(student: Student, semester: Semester) {
|
private suspend fun checkInitialAndCurrentDate(semester: Semester) {
|
||||||
if (initialDate == null) {
|
if (initialDate == null) {
|
||||||
val lessons = attendanceRepository.getAttendance(
|
val lessons = attendanceRepository.getAttendanceFromDatabase(
|
||||||
student = student,
|
|
||||||
semester = semester,
|
semester = semester,
|
||||||
start = now().monday,
|
start = now().monday,
|
||||||
end = now().sunday,
|
end = now().sunday,
|
||||||
forceRefresh = false,
|
).firstOrNull().orEmpty()
|
||||||
).toFirstResult().dataOrNull.orEmpty()
|
|
||||||
isWeekendHasLessons = isWeekendHasLessons(lessons)
|
isWeekendHasLessons = isWeekendHasLessons(lessons)
|
||||||
initialDate = getInitialDate(semester)
|
initialDate = getInitialDate(semester)
|
||||||
}
|
}
|
||||||
|
@ -307,7 +311,19 @@ class AttendancePresenter @Inject constructor(
|
||||||
resourceFlow {
|
resourceFlow {
|
||||||
val student = studentRepository.getCurrentStudent()
|
val student = studentRepository.getCurrentStudent()
|
||||||
val semester = semesterRepository.getCurrentSemester(student)
|
val semester = semesterRepository.getCurrentSemester(student)
|
||||||
attendanceRepository.excuseForAbsence(student, semester, toExcuseList, reason)
|
if (toExcuseList.isEmpty()) {
|
||||||
|
attendanceRepository.excuseForAbsence(
|
||||||
|
student = student,
|
||||||
|
semester = semester,
|
||||||
|
days = listOfNotNull(currentDate),
|
||||||
|
reason = reason
|
||||||
|
)
|
||||||
|
} else attendanceRepository.excuseForAbsence(
|
||||||
|
student = student,
|
||||||
|
semester = semester,
|
||||||
|
absenceList = toExcuseList,
|
||||||
|
reason = reason
|
||||||
|
)
|
||||||
}.onEach {
|
}.onEach {
|
||||||
when (it) {
|
when (it) {
|
||||||
is Resource.Loading -> view?.run {
|
is Resource.Loading -> view?.run {
|
||||||
|
@ -316,6 +332,7 @@ class AttendancePresenter @Inject constructor(
|
||||||
showContent(false)
|
showContent(false)
|
||||||
showExcuseButton(false)
|
showExcuseButton(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
is Resource.Success -> {
|
is Resource.Success -> {
|
||||||
Timber.i("Excusing for absence result: Success")
|
Timber.i("Excusing for absence result: Success")
|
||||||
analytics.logEvent("excuse_absence", "items" to attendanceToExcuseList.size)
|
analytics.logEvent("excuse_absence", "items" to attendanceToExcuseList.size)
|
||||||
|
@ -328,6 +345,7 @@ class AttendancePresenter @Inject constructor(
|
||||||
}
|
}
|
||||||
loadData(forceRefresh = true)
|
loadData(forceRefresh = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
is Resource.Error -> {
|
is Resource.Error -> {
|
||||||
Timber.i("Excusing for absence result: An exception occurred")
|
Timber.i("Excusing for absence result: An exception occurred")
|
||||||
errorHandler.dispatch(it.error)
|
errorHandler.dispatch(it.error)
|
||||||
|
|
|
@ -56,7 +56,7 @@ interface AttendanceView : BaseView {
|
||||||
|
|
||||||
fun openSummaryView()
|
fun openSummaryView()
|
||||||
|
|
||||||
fun startSendMessageIntent(date: LocalDate, numbers: String, reason: String)
|
fun startSendMessageIntent(date: LocalDate, lessons: String, reason: String)
|
||||||
|
|
||||||
fun startActionMode()
|
fun startActionMode()
|
||||||
|
|
||||||
|
|
|
@ -62,7 +62,11 @@ class AuthPresenter @Inject constructor(
|
||||||
}
|
}
|
||||||
isSuccess
|
isSuccess
|
||||||
}
|
}
|
||||||
.onFailure { errorHandler.dispatch(it) }
|
.onFailure {
|
||||||
|
errorHandler.dispatch(it)
|
||||||
|
view?.showProgress(false)
|
||||||
|
view?.showContent(true)
|
||||||
|
}
|
||||||
.onSuccess {
|
.onSuccess {
|
||||||
if (it) {
|
if (it) {
|
||||||
view?.showSuccess(true)
|
view?.showSuccess(true)
|
||||||
|
|
|
@ -0,0 +1,86 @@
|
||||||
|
package io.github.wulkanowy.ui.modules.captcha
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.webkit.WebView
|
||||||
|
import android.webkit.WebViewClient
|
||||||
|
import androidx.core.os.bundleOf
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import io.github.wulkanowy.R
|
||||||
|
import io.github.wulkanowy.databinding.DialogCaptchaBinding
|
||||||
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
|
import io.github.wulkanowy.ui.base.BaseDialogFragment
|
||||||
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
|
class CaptchaDialog : BaseDialogFragment<DialogCaptchaBinding>() {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var sdk: Sdk
|
||||||
|
|
||||||
|
private var webView: WebView? = null
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val CAPTCHA_SUCCESS = "captcha_success"
|
||||||
|
private const val CAPTCHA_URL = "captcha_url"
|
||||||
|
private const val CAPTCHA_CHECK_JS = "document.getElementById('challenge-running') == null"
|
||||||
|
|
||||||
|
fun newInstance(url: String?): CaptchaDialog {
|
||||||
|
return CaptchaDialog().apply {
|
||||||
|
arguments = bundleOf(CAPTCHA_URL to url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View = DialogCaptchaBinding.inflate(inflater).apply { binding = this }.root
|
||||||
|
|
||||||
|
@SuppressLint("SetJavaScriptEnabled")
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
isCancelable = false
|
||||||
|
binding.captchaRefresh.setOnClickListener {
|
||||||
|
binding.captchaWebview.loadUrl(arguments?.getString(CAPTCHA_URL).orEmpty())
|
||||||
|
}
|
||||||
|
binding.captchaClose.setOnClickListener { dismiss() }
|
||||||
|
|
||||||
|
with(binding.captchaWebview) {
|
||||||
|
webView = this
|
||||||
|
with(settings) {
|
||||||
|
javaScriptEnabled = true
|
||||||
|
userAgentString = sdk.userAgent
|
||||||
|
}
|
||||||
|
|
||||||
|
webViewClient = object : WebViewClient() {
|
||||||
|
override fun onPageFinished(view: WebView?, url: String?) {
|
||||||
|
view?.evaluateJavascript(CAPTCHA_CHECK_JS) {
|
||||||
|
if (it == "true") {
|
||||||
|
onChallengeAccepted()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadUrl(arguments?.getString(CAPTCHA_URL).orEmpty())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onChallengeAccepted() {
|
||||||
|
runCatching { parentFragmentManager.setFragmentResult(CAPTCHA_SUCCESS, bundleOf()) }
|
||||||
|
.onFailure { Timber.e(it) }
|
||||||
|
showMessage(getString(R.string.captcha_verified_message))
|
||||||
|
dismissAllowingStateLoss()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
webView?.destroy()
|
||||||
|
super.onDestroy()
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,8 +18,10 @@ import io.github.wulkanowy.databinding.FragmentDashboardBinding
|
||||||
import io.github.wulkanowy.ui.base.BaseFragment
|
import io.github.wulkanowy.ui.base.BaseFragment
|
||||||
import io.github.wulkanowy.ui.modules.account.accountdetails.AccountDetailsFragment
|
import io.github.wulkanowy.ui.modules.account.accountdetails.AccountDetailsFragment
|
||||||
import io.github.wulkanowy.ui.modules.attendance.summary.AttendanceSummaryFragment
|
import io.github.wulkanowy.ui.modules.attendance.summary.AttendanceSummaryFragment
|
||||||
|
import io.github.wulkanowy.ui.modules.captcha.CaptchaDialog.Companion.CAPTCHA_SUCCESS
|
||||||
import io.github.wulkanowy.ui.modules.conference.ConferenceFragment
|
import io.github.wulkanowy.ui.modules.conference.ConferenceFragment
|
||||||
import io.github.wulkanowy.ui.modules.dashboard.adapters.DashboardAdapter
|
import io.github.wulkanowy.ui.modules.dashboard.adapters.DashboardAdapter
|
||||||
|
import io.github.wulkanowy.ui.modules.dashboard.viewholders.AdminMessageViewHolder
|
||||||
import io.github.wulkanowy.ui.modules.exam.ExamFragment
|
import io.github.wulkanowy.ui.modules.exam.ExamFragment
|
||||||
import io.github.wulkanowy.ui.modules.grade.GradeFragment
|
import io.github.wulkanowy.ui.modules.grade.GradeFragment
|
||||||
import io.github.wulkanowy.ui.modules.homework.HomeworkFragment
|
import io.github.wulkanowy.ui.modules.homework.HomeworkFragment
|
||||||
|
@ -62,6 +64,9 @@ class DashboardFragment : BaseFragment<FragmentDashboardBinding>(R.layout.fragme
|
||||||
return ((recyclerWidth - margin) / resources.displayMetrics.density).toInt()
|
return ((recyclerWidth - margin) / resources.displayMetrics.density).toInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override val isViewEmpty
|
||||||
|
get() = dashboardAdapter.itemCount == 0
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
fun newInstance() = DashboardFragment()
|
fun newInstance() = DashboardFragment()
|
||||||
|
@ -77,6 +82,13 @@ class DashboardFragment : BaseFragment<FragmentDashboardBinding>(R.layout.fragme
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
binding = FragmentDashboardBinding.bind(view)
|
binding = FragmentDashboardBinding.bind(view)
|
||||||
presenter.onAttachView(this)
|
presenter.onAttachView(this)
|
||||||
|
initializeCaptchaResultObserver()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initializeCaptchaResultObserver() {
|
||||||
|
childFragmentManager.setFragmentResultListener(CAPTCHA_SUCCESS, this) { _, _ ->
|
||||||
|
presenter.onRetryAfterCaptcha()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||||
|
@ -187,8 +199,17 @@ class DashboardFragment : BaseFragment<FragmentDashboardBinding>(R.layout.fragme
|
||||||
binding.dashboardRecycler.isVisible = show
|
binding.dashboardRecycler.isVisible = show
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showErrorView(show: Boolean) {
|
override fun showErrorView(show: Boolean, adminMessageItem: DashboardItem.AdminMessages?) {
|
||||||
binding.dashboardErrorContainer.isVisible = show
|
binding.dashboardErrorContainer.isVisible = show
|
||||||
|
binding.dashboardErrorAdminMessage.root.isVisible = adminMessageItem != null
|
||||||
|
|
||||||
|
if (adminMessageItem != null) {
|
||||||
|
AdminMessageViewHolder(
|
||||||
|
binding = binding.dashboardErrorAdminMessage,
|
||||||
|
onAdminMessageDismissClickListener = presenter::onAdminMessageDismissed,
|
||||||
|
onAdminMessageClickListener = presenter::onAdminMessageSelected,
|
||||||
|
).bind(adminMessageItem.adminMessage)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setErrorDetails(error: Throwable) {
|
override fun setErrorDetails(error: Throwable) {
|
||||||
|
|
|
@ -24,11 +24,13 @@ import io.github.wulkanowy.data.repositories.SemesterRepository
|
||||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||||
import io.github.wulkanowy.data.repositories.TimetableRepository
|
import io.github.wulkanowy.data.repositories.TimetableRepository
|
||||||
import io.github.wulkanowy.domain.adminmessage.GetAppropriateAdminMessageUseCase
|
import io.github.wulkanowy.domain.adminmessage.GetAppropriateAdminMessageUseCase
|
||||||
|
import io.github.wulkanowy.domain.timetable.IsStudentHasLessonsOnWeekendUseCase
|
||||||
import io.github.wulkanowy.ui.base.BasePresenter
|
import io.github.wulkanowy.ui.base.BasePresenter
|
||||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||||
import io.github.wulkanowy.utils.AdsHelper
|
import io.github.wulkanowy.utils.AdsHelper
|
||||||
import io.github.wulkanowy.utils.calculatePercentage
|
import io.github.wulkanowy.utils.calculatePercentage
|
||||||
import io.github.wulkanowy.utils.nextOrSameSchoolDay
|
import io.github.wulkanowy.utils.nextOrSameSchoolDay
|
||||||
|
import io.github.wulkanowy.utils.sunday
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.catch
|
import kotlinx.coroutines.flow.catch
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
|
@ -56,6 +58,7 @@ class DashboardPresenter @Inject constructor(
|
||||||
private val messageRepository: MessageRepository,
|
private val messageRepository: MessageRepository,
|
||||||
private val attendanceSummaryRepository: AttendanceSummaryRepository,
|
private val attendanceSummaryRepository: AttendanceSummaryRepository,
|
||||||
private val timetableRepository: TimetableRepository,
|
private val timetableRepository: TimetableRepository,
|
||||||
|
private val isStudentHasLessonsOnWeekendUseCase: IsStudentHasLessonsOnWeekendUseCase,
|
||||||
private val homeworkRepository: HomeworkRepository,
|
private val homeworkRepository: HomeworkRepository,
|
||||||
private val examRepository: ExamRepository,
|
private val examRepository: ExamRepository,
|
||||||
private val conferenceRepository: ConferenceRepository,
|
private val conferenceRepository: ConferenceRepository,
|
||||||
|
@ -239,6 +242,14 @@ class DashboardPresenter @Inject constructor(
|
||||||
loadData(selectedDashboardTiles, forceRefresh = true)
|
loadData(selectedDashboardTiles, forceRefresh = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onRetryAfterCaptcha() {
|
||||||
|
view?.run {
|
||||||
|
showErrorView(false)
|
||||||
|
showProgress(true)
|
||||||
|
}
|
||||||
|
loadData(selectedDashboardTiles, forceRefresh = true)
|
||||||
|
}
|
||||||
|
|
||||||
fun onViewReselected() {
|
fun onViewReselected() {
|
||||||
Timber.i("Dashboard view is reselected")
|
Timber.i("Dashboard view is reselected")
|
||||||
view?.run {
|
view?.run {
|
||||||
|
@ -293,6 +304,7 @@ class DashboardPresenter @Inject constructor(
|
||||||
forceRefresh = forceRefresh
|
forceRefresh = forceRefresh
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
.mapResourceData { it.map { messageWithAuthor -> messageWithAuthor.message } }
|
||||||
.onResourceError { errorHandler.dispatch(it) }
|
.onResourceError { errorHandler.dispatch(it) }
|
||||||
.takeIf { DashboardItem.Tile.MESSAGES in selectedTiles } ?: flowSuccess
|
.takeIf { DashboardItem.Tile.MESSAGES in selectedTiles } ?: flowSuccess
|
||||||
|
|
||||||
|
@ -316,7 +328,7 @@ class DashboardPresenter @Inject constructor(
|
||||||
) { luckyNumberResource, messageResource, attendanceResource ->
|
) { luckyNumberResource, messageResource, attendanceResource ->
|
||||||
val resList = listOf(luckyNumberResource, messageResource, attendanceResource)
|
val resList = listOf(luckyNumberResource, messageResource, attendanceResource)
|
||||||
|
|
||||||
DashboardItem.HorizontalGroup(
|
resList to DashboardItem.HorizontalGroup(
|
||||||
isLoading = resList.any { it is Resource.Loading },
|
isLoading = resList.any { it is Resource.Loading },
|
||||||
error = resList.map { it.errorOrNull }.let { errors ->
|
error = resList.map { it.errorOrNull }.let { errors ->
|
||||||
if (errors.all { it != null }) {
|
if (errors.all { it != null }) {
|
||||||
|
@ -341,9 +353,9 @@ class DashboardPresenter @Inject constructor(
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
.filterNot { it.isLoading && forceRefresh }
|
.filterNot { (_, it) -> it.isLoading && forceRefresh }
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
.onEach {
|
.onEach { (_, it) ->
|
||||||
updateData(it, forceRefresh)
|
updateData(it, forceRefresh)
|
||||||
|
|
||||||
if (it.isLoading) {
|
if (it.isLoading) {
|
||||||
|
@ -361,7 +373,7 @@ class DashboardPresenter @Inject constructor(
|
||||||
)
|
)
|
||||||
errorHandler.dispatch(it)
|
errorHandler.dispatch(it)
|
||||||
}
|
}
|
||||||
.launch("horizontal_group ${if (forceRefresh) "-forceRefresh" else ""}")
|
.launchWithUniqueRefreshJob("horizontal_group", forceRefresh)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadGrades(student: Student, forceRefresh: Boolean) {
|
private fun loadGrades(student: Student, forceRefresh: Boolean) {
|
||||||
|
@ -395,7 +407,7 @@ class DashboardPresenter @Inject constructor(
|
||||||
subjectWithGrades = it.dataOrNull,
|
subjectWithGrades = it.dataOrNull,
|
||||||
gradeTheme = preferencesRepository.gradeColorTheme,
|
gradeTheme = preferencesRepository.gradeColorTheme,
|
||||||
isLoading = true
|
isLoading = true
|
||||||
), forceRefresh
|
), false
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!it.dataOrNull.isNullOrEmpty()) {
|
if (!it.dataOrNull.isNullOrEmpty()) {
|
||||||
|
@ -427,14 +439,17 @@ class DashboardPresenter @Inject constructor(
|
||||||
private fun loadLessons(student: Student, forceRefresh: Boolean) {
|
private fun loadLessons(student: Student, forceRefresh: Boolean) {
|
||||||
flatResourceFlow {
|
flatResourceFlow {
|
||||||
val semester = semesterRepository.getCurrentSemester(student)
|
val semester = semesterRepository.getCurrentSemester(student)
|
||||||
val date = LocalDate.now()
|
val date = when (isStudentHasLessonsOnWeekendUseCase(semester)) {
|
||||||
|
true -> LocalDate.now()
|
||||||
|
else -> LocalDate.now().nextOrSameSchoolDay
|
||||||
|
}
|
||||||
|
|
||||||
timetableRepository.getTimetable(
|
timetableRepository.getTimetable(
|
||||||
student = student,
|
student = student,
|
||||||
semester = semester,
|
semester = semester,
|
||||||
start = date,
|
start = date,
|
||||||
end = date.plusDays(1),
|
end = date.sunday,
|
||||||
forceRefresh = forceRefresh
|
forceRefresh = forceRefresh,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.onEach {
|
.onEach {
|
||||||
|
@ -444,7 +459,7 @@ class DashboardPresenter @Inject constructor(
|
||||||
if (forceRefresh) return@onEach
|
if (forceRefresh) return@onEach
|
||||||
updateData(
|
updateData(
|
||||||
DashboardItem.Lessons(it.dataOrNull, isLoading = true),
|
DashboardItem.Lessons(it.dataOrNull, isLoading = true),
|
||||||
forceRefresh
|
false
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!it.dataOrNull?.lessons.isNullOrEmpty()) {
|
if (!it.dataOrNull?.lessons.isNullOrEmpty()) {
|
||||||
|
@ -501,7 +516,7 @@ class DashboardPresenter @Inject constructor(
|
||||||
val data = it.dataOrNull.orEmpty()
|
val data = it.dataOrNull.orEmpty()
|
||||||
updateData(
|
updateData(
|
||||||
DashboardItem.Homework(data, isLoading = true),
|
DashboardItem.Homework(data, isLoading = true),
|
||||||
forceRefresh
|
false
|
||||||
)
|
)
|
||||||
|
|
||||||
if (data.isNotEmpty()) {
|
if (data.isNotEmpty()) {
|
||||||
|
@ -535,7 +550,7 @@ class DashboardPresenter @Inject constructor(
|
||||||
if (forceRefresh) return@onEach
|
if (forceRefresh) return@onEach
|
||||||
updateData(
|
updateData(
|
||||||
DashboardItem.Announcements(it.dataOrNull.orEmpty(), isLoading = true),
|
DashboardItem.Announcements(it.dataOrNull.orEmpty(), isLoading = true),
|
||||||
forceRefresh
|
false
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!it.dataOrNull.isNullOrEmpty()) {
|
if (!it.dataOrNull.isNullOrEmpty()) {
|
||||||
|
@ -578,7 +593,7 @@ class DashboardPresenter @Inject constructor(
|
||||||
if (forceRefresh) return@onEach
|
if (forceRefresh) return@onEach
|
||||||
updateData(
|
updateData(
|
||||||
DashboardItem.Exams(it.dataOrNull.orEmpty(), isLoading = true),
|
DashboardItem.Exams(it.dataOrNull.orEmpty(), isLoading = true),
|
||||||
forceRefresh
|
false
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!it.dataOrNull.isNullOrEmpty()) {
|
if (!it.dataOrNull.isNullOrEmpty()) {
|
||||||
|
@ -619,7 +634,7 @@ class DashboardPresenter @Inject constructor(
|
||||||
if (forceRefresh) return@onEach
|
if (forceRefresh) return@onEach
|
||||||
updateData(
|
updateData(
|
||||||
DashboardItem.Conferences(it.dataOrNull.orEmpty(), isLoading = true),
|
DashboardItem.Conferences(it.dataOrNull.orEmpty(), isLoading = true),
|
||||||
forceRefresh
|
false
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!it.dataOrNull.isNullOrEmpty()) {
|
if (!it.dataOrNull.isNullOrEmpty()) {
|
||||||
|
@ -654,7 +669,7 @@ class DashboardPresenter @Inject constructor(
|
||||||
is Resource.Loading -> {
|
is Resource.Loading -> {
|
||||||
Timber.i("Loading dashboard admin message data started")
|
Timber.i("Loading dashboard admin message data started")
|
||||||
if (forceRefresh) return@onEach
|
if (forceRefresh) return@onEach
|
||||||
updateData(DashboardItem.AdminMessages(), forceRefresh)
|
updateData(DashboardItem.AdminMessages(), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
is Resource.Success -> {
|
is Resource.Success -> {
|
||||||
|
@ -684,7 +699,7 @@ class DashboardPresenter @Inject constructor(
|
||||||
private fun loadAds(forceRefresh: Boolean) {
|
private fun loadAds(forceRefresh: Boolean) {
|
||||||
presenterScope.launch {
|
presenterScope.launch {
|
||||||
if (!forceRefresh) {
|
if (!forceRefresh) {
|
||||||
updateData(DashboardItem.Ads(), forceRefresh)
|
updateData(DashboardItem.Ads(), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
val dashboardAdItem =
|
val dashboardAdItem =
|
||||||
|
@ -805,6 +820,8 @@ class DashboardPresenter @Inject constructor(
|
||||||
val filteredItems = itemsLoadedList.filterNot {
|
val filteredItems = itemsLoadedList.filterNot {
|
||||||
it.type == DashboardItem.Type.ACCOUNT || it.type == DashboardItem.Type.ADMIN_MESSAGE
|
it.type == DashboardItem.Type.ACCOUNT || it.type == DashboardItem.Type.ADMIN_MESSAGE
|
||||||
}
|
}
|
||||||
|
val dataLoadedAdminMessageItem =
|
||||||
|
itemsLoadedList.find { it.type == DashboardItem.Type.ADMIN_MESSAGE && it.isDataLoaded } as DashboardItem.AdminMessages?
|
||||||
val isAccountItemError =
|
val isAccountItemError =
|
||||||
itemsLoadedList.find { it.type == DashboardItem.Type.ACCOUNT }?.error != null
|
itemsLoadedList.find { it.type == DashboardItem.Type.ACCOUNT }?.error != null
|
||||||
val isGeneralError =
|
val isGeneralError =
|
||||||
|
@ -826,7 +843,7 @@ class DashboardPresenter @Inject constructor(
|
||||||
showRefresh(false)
|
showRefresh(false)
|
||||||
if ((forceRefresh && wasGeneralError) || !forceRefresh) {
|
if ((forceRefresh && wasGeneralError) || !forceRefresh) {
|
||||||
showContent(false)
|
showContent(false)
|
||||||
showErrorView(true)
|
showErrorView(true, dataLoadedAdminMessageItem)
|
||||||
setErrorDetails(lastError)
|
setErrorDetails(lastError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -854,6 +871,28 @@ class DashboardPresenter @Inject constructor(
|
||||||
onEach {
|
onEach {
|
||||||
if (it is Resource.Success) {
|
if (it is Resource.Success) {
|
||||||
cancelJobs(jobName)
|
cancelJobs(jobName)
|
||||||
|
} else if (it is Resource.Error) {
|
||||||
|
cancelJobs(jobName)
|
||||||
|
}
|
||||||
|
}.launch(jobName)
|
||||||
|
} else {
|
||||||
|
launch(jobName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmName("launchWithUniqueRefreshJobHorizontalGroup")
|
||||||
|
private fun Flow<Pair<List<Resource<*>>, *>>.launchWithUniqueRefreshJob(
|
||||||
|
name: String,
|
||||||
|
forceRefresh: Boolean
|
||||||
|
) {
|
||||||
|
val jobName = if (forceRefresh) "$name-forceRefresh" else name
|
||||||
|
|
||||||
|
if (forceRefresh) {
|
||||||
|
onEach { (resources, _) ->
|
||||||
|
if (resources.all { it is Resource.Success<*> }) {
|
||||||
|
cancelJobs(jobName)
|
||||||
|
} else if (resources.any { it is Resource.Error<*> }) {
|
||||||
|
cancelJobs(jobName)
|
||||||
}
|
}
|
||||||
}.launch(jobName)
|
}.launch(jobName)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -6,6 +6,8 @@ interface DashboardView : BaseView {
|
||||||
|
|
||||||
val tileWidth: Int
|
val tileWidth: Int
|
||||||
|
|
||||||
|
val isViewEmpty: Boolean
|
||||||
|
|
||||||
fun initView()
|
fun initView()
|
||||||
|
|
||||||
fun updateData(data: List<DashboardItem>)
|
fun updateData(data: List<DashboardItem>)
|
||||||
|
@ -18,7 +20,7 @@ interface DashboardView : BaseView {
|
||||||
|
|
||||||
fun showRefresh(show: Boolean)
|
fun showRefresh(show: Boolean)
|
||||||
|
|
||||||
fun showErrorView(show: Boolean)
|
fun showErrorView(show: Boolean, adminMessageItem: DashboardItem.AdminMessages? = null)
|
||||||
|
|
||||||
fun setErrorDetails(error: Throwable)
|
fun setErrorDetails(error: Throwable)
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,6 @@ import androidx.recyclerview.widget.RecyclerView
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
import io.github.wulkanowy.data.db.entities.AdminMessage
|
import io.github.wulkanowy.data.db.entities.AdminMessage
|
||||||
import io.github.wulkanowy.databinding.ItemDashboardAdminMessageBinding
|
import io.github.wulkanowy.databinding.ItemDashboardAdminMessageBinding
|
||||||
import io.github.wulkanowy.ui.modules.dashboard.DashboardItem
|
|
||||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||||
|
|
||||||
class AdminMessageViewHolder(
|
class AdminMessageViewHolder(
|
||||||
|
@ -25,9 +24,11 @@ class AdminMessageViewHolder(
|
||||||
context.getThemeAttrColor(R.attr.colorMessageHigh) to
|
context.getThemeAttrColor(R.attr.colorMessageHigh) to
|
||||||
context.getThemeAttrColor(R.attr.colorOnMessageHigh)
|
context.getThemeAttrColor(R.attr.colorOnMessageHigh)
|
||||||
}
|
}
|
||||||
|
|
||||||
"MEDIUM" -> {
|
"MEDIUM" -> {
|
||||||
context.getThemeAttrColor(R.attr.colorMessageMedium) to Color.BLACK
|
context.getThemeAttrColor(R.attr.colorMessageMedium) to Color.BLACK
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> null to context.getThemeAttrColor(R.attr.colorOnSurface)
|
else -> null to context.getThemeAttrColor(R.attr.colorOnSurface)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,11 +38,16 @@ class AdminMessageViewHolder(
|
||||||
dashboardAdminMessageItemDescription.text = item.content
|
dashboardAdminMessageItemDescription.text = item.content
|
||||||
dashboardAdminMessageItemDescription.setTextColor(textColor)
|
dashboardAdminMessageItemDescription.setTextColor(textColor)
|
||||||
dashboardAdminMessageItemIcon.setColorFilter(textColor)
|
dashboardAdminMessageItemIcon.setColorFilter(textColor)
|
||||||
dashboardAdminMessageItemDismiss.isVisible = item.isDismissible
|
dashboardAdminMessageItemDismiss.isVisible = item.isOkVisible
|
||||||
|
dashboardAdminMessageItemClose.isVisible = item.isXVisible
|
||||||
dashboardAdminMessageItemDismiss.setTextColor(textColor)
|
dashboardAdminMessageItemDismiss.setTextColor(textColor)
|
||||||
|
dashboardAdminMessageItemClose.imageTintList = ColorStateList.valueOf(textColor)
|
||||||
dashboardAdminMessageItemDismiss.setOnClickListener {
|
dashboardAdminMessageItemDismiss.setOnClickListener {
|
||||||
onAdminMessageDismissClickListener(item)
|
onAdminMessageDismissClickListener(item)
|
||||||
}
|
}
|
||||||
|
dashboardAdminMessageItemClose.setOnClickListener {
|
||||||
|
onAdminMessageDismissClickListener(item)
|
||||||
|
}
|
||||||
|
|
||||||
root.setCardBackgroundColor(backgroundColor?.let { ColorStateList.valueOf(it) })
|
root.setCardBackgroundColor(backgroundColor?.let { ColorStateList.valueOf(it) })
|
||||||
item.destinationUrl?.let { url ->
|
item.destinationUrl?.let { url ->
|
||||||
|
|
|
@ -2,6 +2,7 @@ package io.github.wulkanowy.ui.modules.debug
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.webkit.CookieManager
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
|
@ -58,6 +59,10 @@ class DebugFragment : BaseFragment<FragmentDebugBinding>(R.layout.fragment_debug
|
||||||
(activity as? MainActivity)?.pushView(NotificationDebugFragment.newInstance())
|
(activity as? MainActivity)?.pushView(NotificationDebugFragment.newInstance())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun clearWebkitCookies() {
|
||||||
|
CookieManager.getInstance().removeAllCookies(null)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
presenter.onDetachView()
|
presenter.onDetachView()
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
|
|
|
@ -15,6 +15,7 @@ class DebugPresenter @Inject constructor(
|
||||||
val items = listOf(
|
val items = listOf(
|
||||||
DebugItem(R.string.logviewer_title),
|
DebugItem(R.string.logviewer_title),
|
||||||
DebugItem(R.string.notification_debug_title),
|
DebugItem(R.string.notification_debug_title),
|
||||||
|
DebugItem(R.string.debug_cookies_clear),
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun onAttachView(view: DebugView) {
|
override fun onAttachView(view: DebugView) {
|
||||||
|
@ -31,6 +32,7 @@ class DebugPresenter @Inject constructor(
|
||||||
when (item.title) {
|
when (item.title) {
|
||||||
R.string.logviewer_title -> view?.openLogViewer()
|
R.string.logviewer_title -> view?.openLogViewer()
|
||||||
R.string.notification_debug_title -> view?.openNotificationsDebug()
|
R.string.notification_debug_title -> view?.openNotificationsDebug()
|
||||||
|
R.string.debug_cookies_clear -> view?.clearWebkitCookies()
|
||||||
else -> Timber.d("Unknown debug item: $item")
|
else -> Timber.d("Unknown debug item: $item")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,4 +11,6 @@ interface DebugView : BaseView {
|
||||||
fun openLogViewer()
|
fun openLogViewer()
|
||||||
|
|
||||||
fun openNotificationsDebug()
|
fun openNotificationsDebug()
|
||||||
|
|
||||||
|
fun clearWebkitCookies()
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ import io.github.wulkanowy.ui.base.ErrorHandler
|
||||||
import io.github.wulkanowy.ui.modules.debug.notification.mock.debugAttendanceItems
|
import io.github.wulkanowy.ui.modules.debug.notification.mock.debugAttendanceItems
|
||||||
import io.github.wulkanowy.ui.modules.debug.notification.mock.debugConferenceItems
|
import io.github.wulkanowy.ui.modules.debug.notification.mock.debugConferenceItems
|
||||||
import io.github.wulkanowy.ui.modules.debug.notification.mock.debugExamItems
|
import io.github.wulkanowy.ui.modules.debug.notification.mock.debugExamItems
|
||||||
|
import io.github.wulkanowy.ui.modules.debug.notification.mock.debugGradeDescriptiveItems
|
||||||
import io.github.wulkanowy.ui.modules.debug.notification.mock.debugGradeDetailsItems
|
import io.github.wulkanowy.ui.modules.debug.notification.mock.debugGradeDetailsItems
|
||||||
import io.github.wulkanowy.ui.modules.debug.notification.mock.debugGradeSummaryItems
|
import io.github.wulkanowy.ui.modules.debug.notification.mock.debugGradeSummaryItems
|
||||||
import io.github.wulkanowy.ui.modules.debug.notification.mock.debugHomeworkItems
|
import io.github.wulkanowy.ui.modules.debug.notification.mock.debugHomeworkItems
|
||||||
|
@ -55,6 +56,14 @@ class NotificationDebugPresenter @Inject constructor(
|
||||||
NotificationDebugItem(R.string.grade_summary_final_grade) { n ->
|
NotificationDebugItem(R.string.grade_summary_final_grade) { n ->
|
||||||
withStudent { newGradeNotification.notifyFinal(debugGradeSummaryItems.take(n), it) }
|
withStudent { newGradeNotification.notifyFinal(debugGradeSummaryItems.take(n), it) }
|
||||||
},
|
},
|
||||||
|
NotificationDebugItem(R.string.grade_summary_descriptive) { n ->
|
||||||
|
withStudent {
|
||||||
|
newGradeNotification.notifyDescriptive(
|
||||||
|
debugGradeDescriptiveItems.take(n),
|
||||||
|
it
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
NotificationDebugItem(R.string.homework_title) { n ->
|
NotificationDebugItem(R.string.homework_title) { n ->
|
||||||
withStudent { newHomeworkNotification.notify(debugHomeworkItems.take(n), it) }
|
withStudent { newHomeworkNotification.notify(debugHomeworkItems.take(n), it) }
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
package io.github.wulkanowy.ui.modules.debug.notification.mock
|
||||||
|
|
||||||
|
import io.github.wulkanowy.data.db.entities.GradeDescriptive
|
||||||
|
|
||||||
|
val debugGradeDescriptiveItems = listOf(
|
||||||
|
generateGradeDescriptive(
|
||||||
|
"Matematyka",
|
||||||
|
"Lorem ipsum dolor sit amet, consectetur adipiscing elit."
|
||||||
|
),
|
||||||
|
generateGradeDescriptive("Fizyka", "Lorem ipsum dolor sit amet, consectetur adipiscing elit."),
|
||||||
|
generateGradeDescriptive(
|
||||||
|
"Geografia",
|
||||||
|
"Lorem ipsum dolor sit amet, consectetur adipiscing elit."
|
||||||
|
),
|
||||||
|
generateGradeDescriptive(
|
||||||
|
"Sieci komputerowe",
|
||||||
|
"Lorem ipsum dolor sit amet, consectetur adipiscing elit."
|
||||||
|
),
|
||||||
|
generateGradeDescriptive(
|
||||||
|
"Systemy operacyjne",
|
||||||
|
"Lorem ipsum dolor sit amet, consectetur adipiscing elit."
|
||||||
|
),
|
||||||
|
generateGradeDescriptive(
|
||||||
|
"Język polski",
|
||||||
|
"Lorem ipsum dolor sit amet, consectetur adipiscing elit."
|
||||||
|
),
|
||||||
|
generateGradeDescriptive(
|
||||||
|
"Język angielski",
|
||||||
|
"Lorem ipsum dolor sit amet, consectetur adipiscing elit."
|
||||||
|
),
|
||||||
|
generateGradeDescriptive("Religia", "Lorem ipsum dolor sit amet, consectetur adipiscing elit."),
|
||||||
|
generateGradeDescriptive(
|
||||||
|
"Język niemiecki",
|
||||||
|
"Lorem ipsum dolor sit amet, consectetur adipiscing elit."
|
||||||
|
),
|
||||||
|
generateGradeDescriptive(
|
||||||
|
"Wychowanie fizyczne",
|
||||||
|
"Lorem ipsum dolor sit amet, consectetur adipiscing elit."
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun generateGradeDescriptive(subject: String, description: String) =
|
||||||
|
GradeDescriptive(
|
||||||
|
semesterId = 0,
|
||||||
|
studentId = 0,
|
||||||
|
subject = subject,
|
||||||
|
description = description
|
||||||
|
)
|
|
@ -1,15 +1,23 @@
|
||||||
package io.github.wulkanowy.ui.modules.grade
|
package io.github.wulkanowy.ui.modules.grade
|
||||||
|
|
||||||
import io.github.wulkanowy.data.*
|
import io.github.wulkanowy.data.Resource
|
||||||
|
import io.github.wulkanowy.data.dataOrNull
|
||||||
import io.github.wulkanowy.data.db.entities.Grade
|
import io.github.wulkanowy.data.db.entities.Grade
|
||||||
|
import io.github.wulkanowy.data.db.entities.GradeDescriptive
|
||||||
import io.github.wulkanowy.data.db.entities.GradeSummary
|
import io.github.wulkanowy.data.db.entities.GradeSummary
|
||||||
import io.github.wulkanowy.data.db.entities.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.data.errorOrNull
|
||||||
|
import io.github.wulkanowy.data.flatResourceFlow
|
||||||
|
import io.github.wulkanowy.data.mapData
|
||||||
|
import io.github.wulkanowy.data.mapResourceData
|
||||||
import io.github.wulkanowy.data.repositories.GradeRepository
|
import io.github.wulkanowy.data.repositories.GradeRepository
|
||||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||||
import io.github.wulkanowy.data.repositories.SemesterRepository
|
import io.github.wulkanowy.data.repositories.SemesterRepository
|
||||||
import io.github.wulkanowy.sdk.Sdk
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
import io.github.wulkanowy.ui.modules.grade.GradeAverageMode.*
|
import io.github.wulkanowy.ui.modules.grade.GradeAverageMode.ALL_YEAR
|
||||||
|
import io.github.wulkanowy.ui.modules.grade.GradeAverageMode.BOTH_SEMESTERS
|
||||||
|
import io.github.wulkanowy.ui.modules.grade.GradeAverageMode.ONE_SEMESTER
|
||||||
import io.github.wulkanowy.utils.calcAverage
|
import io.github.wulkanowy.utils.calcAverage
|
||||||
import io.github.wulkanowy.utils.changeModifier
|
import io.github.wulkanowy.utils.changeModifier
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
@ -62,6 +70,7 @@ class GradeAverageProvider @Inject constructor(
|
||||||
forceRefresh = forceRefresh,
|
forceRefresh = forceRefresh,
|
||||||
params = params,
|
params = params,
|
||||||
)
|
)
|
||||||
|
|
||||||
BOTH_SEMESTERS -> calculateCombinedAverage(
|
BOTH_SEMESTERS -> calculateCombinedAverage(
|
||||||
student = student,
|
student = student,
|
||||||
semesters = semesters,
|
semesters = semesters,
|
||||||
|
@ -69,6 +78,7 @@ class GradeAverageProvider @Inject constructor(
|
||||||
forceRefresh = forceRefresh,
|
forceRefresh = forceRefresh,
|
||||||
config = params,
|
config = params,
|
||||||
)
|
)
|
||||||
|
|
||||||
ALL_YEAR -> calculateCombinedAverage(
|
ALL_YEAR -> calculateCombinedAverage(
|
||||||
student = student,
|
student = student,
|
||||||
semesters = semesters,
|
semesters = semesters,
|
||||||
|
@ -189,36 +199,73 @@ class GradeAverageProvider @Inject constructor(
|
||||||
): Flow<Resource<List<GradeSubject>>> {
|
): Flow<Resource<List<GradeSubject>>> {
|
||||||
return gradeRepository.getGrades(student, semester, forceRefresh = forceRefresh)
|
return gradeRepository.getGrades(student, semester, forceRefresh = forceRefresh)
|
||||||
.mapResourceData { res ->
|
.mapResourceData { res ->
|
||||||
val (details, summaries) = res
|
val (details, summaries, descriptives) = res
|
||||||
val isAnyAverage = summaries.any { it.average != .0 }
|
val isAnyAverage = summaries.any { it.average != .0 }
|
||||||
val allGrades = details.groupBy { it.subject }
|
val allGrades = details.groupBy { it.subject }
|
||||||
|
val descriptiveGradesBySubject = descriptives.associateBy { it.subject }
|
||||||
|
|
||||||
val items = summaries.emulateEmptySummaries(
|
val items = summaries
|
||||||
student = student,
|
.createEmptySummariesByGradesIfNeeded(
|
||||||
semester = semester,
|
student = student,
|
||||||
grades = allGrades.toList(),
|
semester = semester,
|
||||||
calcAverage = isAnyAverage,
|
grades = allGrades.toList(),
|
||||||
params = params,
|
calcAverage = isAnyAverage,
|
||||||
).map { summary ->
|
params = params,
|
||||||
val grades = allGrades[summary.subject].orEmpty()
|
|
||||||
GradeSubject(
|
|
||||||
subject = summary.subject,
|
|
||||||
average = if (!isAnyAverage || params.forceAverageCalc) {
|
|
||||||
grades.updateModifiers(student, params)
|
|
||||||
.calcAverage(params.isOptionalArithmeticAverage)
|
|
||||||
} else summary.average,
|
|
||||||
points = summary.pointsSum,
|
|
||||||
summary = summary,
|
|
||||||
grades = grades,
|
|
||||||
isVulcanAverage = isAnyAverage
|
|
||||||
)
|
)
|
||||||
}
|
.createEmptySummariesByDescriptiveGradesIfNeeded(
|
||||||
|
student = student,
|
||||||
|
semester = semester,
|
||||||
|
descriptives = descriptives,
|
||||||
|
)
|
||||||
|
.map { summary ->
|
||||||
|
val grades = allGrades[summary.subject].orEmpty()
|
||||||
|
val descriptiveGrade = descriptiveGradesBySubject[summary.subject]
|
||||||
|
|
||||||
|
GradeSubject(
|
||||||
|
subject = summary.subject,
|
||||||
|
average = if (!isAnyAverage || params.forceAverageCalc) {
|
||||||
|
grades.updateModifiers(student, params)
|
||||||
|
.calcAverage(params.isOptionalArithmeticAverage)
|
||||||
|
} else summary.average,
|
||||||
|
points = summary.pointsSum,
|
||||||
|
summary = summary,
|
||||||
|
grades = grades,
|
||||||
|
descriptive = descriptiveGrade,
|
||||||
|
isVulcanAverage = isAnyAverage
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
items
|
items
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun List<GradeSummary>.emulateEmptySummaries(
|
private fun List<GradeSummary>.createEmptySummariesByDescriptiveGradesIfNeeded(
|
||||||
|
student: Student,
|
||||||
|
semester: Semester,
|
||||||
|
descriptives: List<GradeDescriptive>
|
||||||
|
): List<GradeSummary> {
|
||||||
|
val summarySubjects = this.map { it.subject }
|
||||||
|
val gradeSummaryToAdd = descriptives.mapNotNull { gradeDescriptive ->
|
||||||
|
if (gradeDescriptive.subject in summarySubjects) return@mapNotNull null
|
||||||
|
|
||||||
|
GradeSummary(
|
||||||
|
studentId = student.studentId,
|
||||||
|
semesterId = semester.semesterId,
|
||||||
|
position = 0,
|
||||||
|
subject = gradeDescriptive.subject,
|
||||||
|
predictedGrade = "",
|
||||||
|
finalGrade = "",
|
||||||
|
proposedPoints = "",
|
||||||
|
finalPoints = "",
|
||||||
|
pointsSum = "",
|
||||||
|
average = .0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return this + gradeSummaryToAdd
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun List<GradeSummary>.createEmptySummariesByGradesIfNeeded(
|
||||||
student: Student,
|
student: Student,
|
||||||
semester: Semester,
|
semester: Semester,
|
||||||
grades: List<Pair<String, List<Grade>>>,
|
grades: List<Pair<String, List<Grade>>>,
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package io.github.wulkanowy.ui.modules.grade
|
package io.github.wulkanowy.ui.modules.grade
|
||||||
|
|
||||||
import io.github.wulkanowy.data.db.entities.Grade
|
import io.github.wulkanowy.data.db.entities.Grade
|
||||||
|
import io.github.wulkanowy.data.db.entities.GradeDescriptive
|
||||||
import io.github.wulkanowy.data.db.entities.GradeSummary
|
import io.github.wulkanowy.data.db.entities.GradeSummary
|
||||||
|
|
||||||
data class GradeSubject(
|
data class GradeSubject(
|
||||||
|
@ -8,6 +9,7 @@ data class GradeSubject(
|
||||||
val average: Double,
|
val average: Double,
|
||||||
val points: String,
|
val points: String,
|
||||||
val summary: GradeSummary,
|
val summary: GradeSummary,
|
||||||
|
val descriptive: GradeDescriptive?,
|
||||||
val grades: List<Grade>,
|
val grades: List<Grade>,
|
||||||
val isVulcanAverage: Boolean
|
val isVulcanAverage: Boolean
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,13 +1,22 @@
|
||||||
package io.github.wulkanowy.ui.modules.grade.details
|
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.db.entities.Grade
|
||||||
import io.github.wulkanowy.data.enums.GradeExpandMode
|
import io.github.wulkanowy.data.enums.GradeExpandMode
|
||||||
import io.github.wulkanowy.data.enums.GradeSortingMode.*
|
import io.github.wulkanowy.data.enums.GradeSortingMode.ALPHABETIC
|
||||||
|
import io.github.wulkanowy.data.enums.GradeSortingMode.AVERAGE
|
||||||
|
import io.github.wulkanowy.data.enums.GradeSortingMode.DATE
|
||||||
|
import io.github.wulkanowy.data.flatResourceFlow
|
||||||
|
import io.github.wulkanowy.data.logResourceStatus
|
||||||
|
import io.github.wulkanowy.data.onResourceData
|
||||||
|
import io.github.wulkanowy.data.onResourceError
|
||||||
|
import io.github.wulkanowy.data.onResourceIntermediate
|
||||||
|
import io.github.wulkanowy.data.onResourceNotLoading
|
||||||
|
import io.github.wulkanowy.data.onResourceSuccess
|
||||||
import io.github.wulkanowy.data.repositories.GradeRepository
|
import io.github.wulkanowy.data.repositories.GradeRepository
|
||||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||||
import io.github.wulkanowy.data.repositories.SemesterRepository
|
import io.github.wulkanowy.data.repositories.SemesterRepository
|
||||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||||
|
import io.github.wulkanowy.data.resourceFlow
|
||||||
import io.github.wulkanowy.ui.base.BasePresenter
|
import io.github.wulkanowy.ui.base.BasePresenter
|
||||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||||
import io.github.wulkanowy.ui.modules.grade.GradeAverageProvider
|
import io.github.wulkanowy.ui.modules.grade.GradeAverageProvider
|
||||||
|
@ -207,20 +216,20 @@ class GradeDetailsPresenter @Inject constructor(
|
||||||
AVERAGE -> gradeSubjects.sortedByDescending { it.average }
|
AVERAGE -> gradeSubjects.sortedByDescending { it.average }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.map { (subject, average, points, _, grades) ->
|
.map { gradeSubject ->
|
||||||
val subItems = grades
|
val subItems = gradeSubject.grades
|
||||||
.sortedByDescending { it.date }
|
.sortedByDescending { it.date }
|
||||||
.map { GradeDetailsItem(it, ViewType.ITEM) }
|
.map { GradeDetailsItem(it, ViewType.ITEM) }
|
||||||
|
|
||||||
val gradeDetailsItems = listOf(
|
val gradeDetailsItems = listOf(
|
||||||
GradeDetailsItem(
|
GradeDetailsItem(
|
||||||
GradeDetailsHeader(
|
GradeDetailsHeader(
|
||||||
subject = subject,
|
subject = gradeSubject.subject,
|
||||||
average = average,
|
average = gradeSubject.average,
|
||||||
pointsSum = points,
|
pointsSum = gradeSubject.points,
|
||||||
grades = subItems
|
grades = subItems
|
||||||
).apply {
|
).apply {
|
||||||
newGrades = grades.filter { grade -> !grade.isRead }.size
|
newGrades = gradeSubject.grades.filter { grade -> !grade.isRead }.size
|
||||||
}, ViewType.HEADER
|
}, ViewType.HEADER
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
|
@ -2,16 +2,16 @@ package io.github.wulkanowy.ui.modules.grade.summary
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import androidx.core.view.isVisible
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
import io.github.wulkanowy.data.db.entities.GradeSummary
|
|
||||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||||
import io.github.wulkanowy.databinding.ItemGradeSummaryBinding
|
import io.github.wulkanowy.databinding.ItemGradeSummaryBinding
|
||||||
import io.github.wulkanowy.databinding.ScrollableHeaderGradeSummaryBinding
|
import io.github.wulkanowy.databinding.ScrollableHeaderGradeSummaryBinding
|
||||||
import io.github.wulkanowy.sdk.scrapper.grades.isGradeValid
|
import io.github.wulkanowy.sdk.scrapper.grades.isGradeValid
|
||||||
import io.github.wulkanowy.utils.calcFinalAverage
|
import io.github.wulkanowy.utils.calcFinalAverage
|
||||||
|
import io.github.wulkanowy.utils.ifNullOrBlank
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ class GradeSummaryAdapter @Inject constructor(
|
||||||
ITEM(2)
|
ITEM(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
var items = emptyList<GradeSummary>()
|
var items = emptyList<GradeSummaryItem>()
|
||||||
|
|
||||||
var onCalculatedHelpClickListener: () -> Unit = {}
|
var onCalculatedHelpClickListener: () -> Unit = {}
|
||||||
|
|
||||||
|
@ -44,9 +44,11 @@ class GradeSummaryAdapter @Inject constructor(
|
||||||
ViewType.HEADER.id -> HeaderViewHolder(
|
ViewType.HEADER.id -> HeaderViewHolder(
|
||||||
ScrollableHeaderGradeSummaryBinding.inflate(inflater, parent, false)
|
ScrollableHeaderGradeSummaryBinding.inflate(inflater, parent, false)
|
||||||
)
|
)
|
||||||
|
|
||||||
ViewType.ITEM.id -> ItemViewHolder(
|
ViewType.ITEM.id -> ItemViewHolder(
|
||||||
ItemGradeSummaryBinding.inflate(inflater, parent, false)
|
ItemGradeSummaryBinding.inflate(inflater, parent, false)
|
||||||
)
|
)
|
||||||
|
|
||||||
else -> throw IllegalStateException()
|
else -> throw IllegalStateException()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -60,19 +62,23 @@ class GradeSummaryAdapter @Inject constructor(
|
||||||
|
|
||||||
private fun bindHeaderViewHolder(binding: ScrollableHeaderGradeSummaryBinding) {
|
private fun bindHeaderViewHolder(binding: ScrollableHeaderGradeSummaryBinding) {
|
||||||
if (items.isEmpty()) return
|
if (items.isEmpty()) return
|
||||||
|
val gradeSummaries = items
|
||||||
|
.filter { it.gradeDescriptive == null }
|
||||||
|
.map { it.gradeSummary }
|
||||||
|
|
||||||
val context = binding.root.context
|
val context = binding.root.context
|
||||||
val finalItemsCount = items.count { isGradeValid(it.finalGrade) }
|
val finalItemsCount = gradeSummaries.count { isGradeValid(it.finalGrade) }
|
||||||
val calculatedItemsCount = items.count { value -> value.average != 0.0 }
|
val calculatedItemsCount = gradeSummaries.count { value -> value.average != 0.0 }
|
||||||
val allItemsCount = items.count { !it.subject.equals("zachowanie", true) }
|
val allItemsCount = gradeSummaries.count { !it.subject.equals("zachowanie", true) }
|
||||||
val finalAverage = items.calcFinalAverage(
|
val finalAverage = gradeSummaries.calcFinalAverage(
|
||||||
preferencesRepository.gradePlusModifier,
|
preferencesRepository.gradePlusModifier,
|
||||||
preferencesRepository.gradeMinusModifier
|
preferencesRepository.gradeMinusModifier
|
||||||
)
|
)
|
||||||
val calculatedAverage = items.filter { value -> value.average != 0.0 }
|
val calculatedAverage = gradeSummaries.filter { value -> value.average != 0.0 }
|
||||||
.map { values -> values.average }
|
.map { values -> values.average }
|
||||||
.reversed() // fix average precision
|
.reversed() // fix average precision
|
||||||
.average()
|
.average()
|
||||||
|
.let { if (it.isNaN()) 0.0 else it }
|
||||||
|
|
||||||
with(binding) {
|
with(binding) {
|
||||||
gradeSummaryScrollableHeaderFinal.text = formatAverage(finalAverage)
|
gradeSummaryScrollableHeaderFinal.text = formatAverage(finalAverage)
|
||||||
|
@ -95,16 +101,28 @@ class GradeSummaryAdapter @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
private fun bindItemViewHolder(binding: ItemGradeSummaryBinding, item: GradeSummary) {
|
private fun bindItemViewHolder(binding: ItemGradeSummaryBinding, item: GradeSummaryItem) {
|
||||||
with(binding) {
|
val (gradeSummary, gradeDescriptive) = item
|
||||||
gradeSummaryItemTitle.text = item.subject
|
|
||||||
gradeSummaryItemPoints.text = item.pointsSum
|
|
||||||
gradeSummaryItemAverage.text = formatAverage(item.average, "")
|
|
||||||
gradeSummaryItemPredicted.text = "${item.predictedGrade} ${item.proposedPoints}".trim()
|
|
||||||
gradeSummaryItemFinal.text = "${item.finalGrade} ${item.finalPoints}".trim()
|
|
||||||
|
|
||||||
gradeSummaryItemPointsContainer.visibility =
|
with(binding) {
|
||||||
if (item.pointsSum.isBlank()) View.GONE else View.VISIBLE
|
gradeSummaryItemTitle.text = gradeSummary.subject
|
||||||
|
gradeSummaryItemPoints.text = gradeSummary.pointsSum
|
||||||
|
gradeSummaryItemAverage.text = formatAverage(gradeSummary.average, "")
|
||||||
|
gradeSummaryItemPredicted.text =
|
||||||
|
"${gradeSummary.predictedGrade} ${gradeSummary.proposedPoints}".trim()
|
||||||
|
gradeSummaryItemFinal.text =
|
||||||
|
"${gradeSummary.finalGrade} ${gradeSummary.finalPoints}".trim()
|
||||||
|
gradeSummaryItemDescriptive.text = gradeDescriptive?.description.ifNullOrBlank {
|
||||||
|
root.context.getString(R.string.all_no_data)
|
||||||
|
}
|
||||||
|
|
||||||
|
gradeSummaryItemFinalDivider.isVisible = gradeDescriptive == null
|
||||||
|
gradeSummaryItemPredictedDivider.isVisible = gradeDescriptive == null
|
||||||
|
gradeSummaryItemPointsDivider.isVisible = gradeDescriptive == null
|
||||||
|
gradeSummaryItemPredictedContainer.isVisible = gradeDescriptive == null
|
||||||
|
gradeSummaryItemFinalContainer.isVisible = gradeDescriptive == null
|
||||||
|
gradeSummaryItemDescriptiveContainer.isVisible = gradeDescriptive != null
|
||||||
|
gradeSummaryItemPointsContainer.isVisible = gradeSummary.pointsSum.isNotBlank()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,12 +5,10 @@ import android.view.View
|
||||||
import android.view.View.GONE
|
import android.view.View.GONE
|
||||||
import android.view.View.INVISIBLE
|
import android.view.View.INVISIBLE
|
||||||
import android.view.View.VISIBLE
|
import android.view.View.VISIBLE
|
||||||
import androidx.appcompat.app.AlertDialog
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
import io.github.wulkanowy.data.db.entities.GradeSummary
|
|
||||||
import io.github.wulkanowy.databinding.FragmentGradeSummaryBinding
|
import io.github.wulkanowy.databinding.FragmentGradeSummaryBinding
|
||||||
import io.github.wulkanowy.ui.base.BaseFragment
|
import io.github.wulkanowy.ui.base.BaseFragment
|
||||||
import io.github.wulkanowy.ui.modules.grade.GradeFragment
|
import io.github.wulkanowy.ui.modules.grade.GradeFragment
|
||||||
|
@ -72,7 +70,7 @@ class GradeSummaryFragment :
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun updateData(data: List<GradeSummary>) {
|
override fun updateData(data: List<GradeSummaryItem>) {
|
||||||
with(gradeSummaryAdapter) {
|
with(gradeSummaryAdapter) {
|
||||||
items = data
|
items = data
|
||||||
notifyDataSetChanged()
|
notifyDataSetChanged()
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
package io.github.wulkanowy.ui.modules.grade.summary
|
||||||
|
|
||||||
|
import io.github.wulkanowy.data.db.entities.GradeDescriptive
|
||||||
|
import io.github.wulkanowy.data.db.entities.GradeSummary
|
||||||
|
|
||||||
|
data class GradeSummaryItem(
|
||||||
|
val gradeSummary: GradeSummary,
|
||||||
|
val gradeDescriptive: GradeDescriptive?
|
||||||
|
)
|
|
@ -1,9 +1,16 @@
|
||||||
package io.github.wulkanowy.ui.modules.grade.summary
|
package io.github.wulkanowy.ui.modules.grade.summary
|
||||||
|
|
||||||
import io.github.wulkanowy.data.*
|
import io.github.wulkanowy.data.enums.GradeSortingMode.ALPHABETIC
|
||||||
import io.github.wulkanowy.data.db.entities.GradeSummary
|
import io.github.wulkanowy.data.enums.GradeSortingMode.AVERAGE
|
||||||
import io.github.wulkanowy.data.enums.GradeSortingMode
|
import io.github.wulkanowy.data.enums.GradeSortingMode.DATE
|
||||||
import io.github.wulkanowy.data.enums.GradeSortingMode.*
|
import io.github.wulkanowy.data.flatResourceFlow
|
||||||
|
import io.github.wulkanowy.data.logResourceStatus
|
||||||
|
import io.github.wulkanowy.data.mapResourceData
|
||||||
|
import io.github.wulkanowy.data.onResourceData
|
||||||
|
import io.github.wulkanowy.data.onResourceError
|
||||||
|
import io.github.wulkanowy.data.onResourceIntermediate
|
||||||
|
import io.github.wulkanowy.data.onResourceNotLoading
|
||||||
|
import io.github.wulkanowy.data.onResourceSuccess
|
||||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||||
import io.github.wulkanowy.ui.base.BasePresenter
|
import io.github.wulkanowy.ui.base.BasePresenter
|
||||||
|
@ -128,7 +135,7 @@ class GradeSummaryPresenter @Inject constructor(
|
||||||
view?.showFinalAverageHelpDialog()
|
view?.showFinalAverageHelpDialog()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createGradeSummaryItems(items: List<GradeSubject>): List<GradeSummary> {
|
private fun createGradeSummaryItems(items: List<GradeSubject>): List<GradeSummaryItem> {
|
||||||
return items
|
return items
|
||||||
.filter { !checkEmpty(it) }
|
.filter { !checkEmpty(it) }
|
||||||
.let { gradeSubjects ->
|
.let { gradeSubjects ->
|
||||||
|
@ -136,21 +143,32 @@ class GradeSummaryPresenter @Inject constructor(
|
||||||
DATE -> gradeSubjects.sortedByDescending { gradeDetailsWithAverage ->
|
DATE -> gradeSubjects.sortedByDescending { gradeDetailsWithAverage ->
|
||||||
gradeDetailsWithAverage.grades.maxByOrNull { it.date }?.date
|
gradeDetailsWithAverage.grades.maxByOrNull { it.date }?.date
|
||||||
}
|
}
|
||||||
|
|
||||||
ALPHABETIC -> gradeSubjects.sortedBy { gradeDetailsWithAverage ->
|
ALPHABETIC -> gradeSubjects.sortedBy { gradeDetailsWithAverage ->
|
||||||
gradeDetailsWithAverage.subject.lowercase()
|
gradeDetailsWithAverage.subject.lowercase()
|
||||||
}
|
}
|
||||||
|
|
||||||
AVERAGE -> gradeSubjects.sortedByDescending { it.average }
|
AVERAGE -> gradeSubjects.sortedByDescending { it.average }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.map { it.summary.copy(average = it.average) }
|
.map {
|
||||||
|
val gradeSummary = it.summary.copy(average = it.average)
|
||||||
|
val descriptive = it.descriptive
|
||||||
|
GradeSummaryItem(
|
||||||
|
gradeSummary = gradeSummary,
|
||||||
|
gradeDescriptive = descriptive,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun checkEmpty(gradeSummary: GradeSubject): Boolean {
|
private fun checkEmpty(gradeSummary: GradeSubject): Boolean {
|
||||||
return gradeSummary.run {
|
return gradeSummary.run {
|
||||||
summary.finalGrade.isBlank()
|
summary.finalGrade.isBlank()
|
||||||
&& summary.predictedGrade.isBlank()
|
&& summary.predictedGrade.isBlank()
|
||||||
&& average == .0
|
&& average == .0
|
||||||
&& points.isBlank()
|
&& points.isBlank()
|
||||||
|
&& descriptive == null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package io.github.wulkanowy.ui.modules.grade.summary
|
package io.github.wulkanowy.ui.modules.grade.summary
|
||||||
|
|
||||||
import io.github.wulkanowy.data.db.entities.GradeSummary
|
|
||||||
import io.github.wulkanowy.ui.base.BaseView
|
import io.github.wulkanowy.ui.base.BaseView
|
||||||
|
|
||||||
interface GradeSummaryView : BaseView {
|
interface GradeSummaryView : BaseView {
|
||||||
|
@ -13,7 +12,7 @@ interface GradeSummaryView : BaseView {
|
||||||
|
|
||||||
fun initView()
|
fun initView()
|
||||||
|
|
||||||
fun updateData(data: List<GradeSummary>)
|
fun updateData(data: List<GradeSummaryItem>)
|
||||||
|
|
||||||
fun resetView()
|
fun resetView()
|
||||||
|
|
||||||
|
|
|
@ -7,5 +7,6 @@ data class LoginData(
|
||||||
val password: String,
|
val password: String,
|
||||||
val baseUrl: String,
|
val baseUrl: String,
|
||||||
val domainSuffix: String,
|
val domainSuffix: String,
|
||||||
val symbol: String?,
|
val defaultSymbol: String,
|
||||||
|
val userEnteredSymbol: String? = null,
|
||||||
) : Serializable
|
) : Serializable
|
||||||
|
|
|
@ -71,7 +71,7 @@ class LoginAdvancedPresenter @Inject constructor(
|
||||||
|
|
||||||
fun updateUsernameLabel() {
|
fun updateUsernameLabel() {
|
||||||
view?.apply {
|
view?.apply {
|
||||||
setUsernameLabel(if ("vulcan" in formHostValue || "fakelog" in formHostValue) emailLabel else nicknameLabel)
|
setUsernameLabel(if ("vulcan" in formHostValue || "wulkanowy" in formHostValue) emailLabel else nicknameLabel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,7 +79,7 @@ class LoginAdvancedPresenter @Inject constructor(
|
||||||
view?.apply {
|
view?.apply {
|
||||||
clearPassError()
|
clearPassError()
|
||||||
clearUsernameError()
|
clearUsernameError()
|
||||||
if (formHostValue.contains("fakelog")) {
|
if (formHostValue.contains("wulkanowy")) {
|
||||||
setDefaultCredentials(
|
setDefaultCredentials(
|
||||||
"jan@fakelog.cf", "jan123", "powiatwulkanowy", "FK100000", "999999"
|
"jan@fakelog.cf", "jan123", "powiatwulkanowy", "FK100000", "999999"
|
||||||
)
|
)
|
||||||
|
@ -155,7 +155,7 @@ class LoginAdvancedPresenter @Inject constructor(
|
||||||
password = view?.formPassValue.orEmpty().trim(),
|
password = view?.formPassValue.orEmpty().trim(),
|
||||||
baseUrl = view?.formHostValue.orEmpty().trim(),
|
baseUrl = view?.formHostValue.orEmpty().trim(),
|
||||||
domainSuffix = view?.formDomainSuffix.orEmpty().trim(),
|
domainSuffix = view?.formDomainSuffix.orEmpty().trim(),
|
||||||
symbol = view?.formSymbolValue.orEmpty().trim().getNormalizedSymbol(),
|
defaultSymbol = view?.formSymbolValue.orEmpty().trim().getNormalizedSymbol(),
|
||||||
)
|
)
|
||||||
when (it.data.symbols.size) {
|
when (it.data.symbols.size) {
|
||||||
0 -> view?.navigateToSymbol(loginData)
|
0 -> view?.navigateToSymbol(loginData)
|
||||||
|
|
|
@ -7,6 +7,7 @@ import android.view.View.GONE
|
||||||
import android.view.View.VISIBLE
|
import android.view.View.VISIBLE
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.core.widget.doOnTextChanged
|
import androidx.core.widget.doOnTextChanged
|
||||||
|
import androidx.fragment.app.setFragmentResultListener
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
import io.github.wulkanowy.data.db.entities.AdminMessage
|
import io.github.wulkanowy.data.db.entities.AdminMessage
|
||||||
|
@ -14,6 +15,7 @@ import io.github.wulkanowy.data.pojos.RegisterUser
|
||||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||||
import io.github.wulkanowy.databinding.FragmentLoginFormBinding
|
import io.github.wulkanowy.databinding.FragmentLoginFormBinding
|
||||||
import io.github.wulkanowy.ui.base.BaseFragment
|
import io.github.wulkanowy.ui.base.BaseFragment
|
||||||
|
import io.github.wulkanowy.ui.modules.captcha.CaptchaDialog
|
||||||
import io.github.wulkanowy.ui.modules.dashboard.viewholders.AdminMessageViewHolder
|
import io.github.wulkanowy.ui.modules.dashboard.viewholders.AdminMessageViewHolder
|
||||||
import io.github.wulkanowy.ui.modules.login.LoginActivity
|
import io.github.wulkanowy.ui.modules.login.LoginActivity
|
||||||
import io.github.wulkanowy.ui.modules.login.LoginData
|
import io.github.wulkanowy.ui.modules.login.LoginData
|
||||||
|
@ -72,6 +74,13 @@ class LoginFormFragment : BaseFragment<FragmentLoginFormBinding>(R.layout.fragme
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
binding = FragmentLoginFormBinding.bind(view)
|
binding = FragmentLoginFormBinding.bind(view)
|
||||||
presenter.onAttachView(this)
|
presenter.onAttachView(this)
|
||||||
|
initializeCaptchaResultObserver()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initializeCaptchaResultObserver() {
|
||||||
|
setFragmentResultListener(CaptchaDialog.CAPTCHA_SUCCESS) { _, _ ->
|
||||||
|
presenter.onRetryAfterCaptcha()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun initView() {
|
override fun initView() {
|
||||||
|
@ -85,6 +94,7 @@ class LoginFormFragment : BaseFragment<FragmentLoginFormBinding>(R.layout.fragme
|
||||||
loginFormUsername.doOnTextChanged { _, _, _, _ -> presenter.onUsernameTextChanged() }
|
loginFormUsername.doOnTextChanged { _, _, _, _ -> presenter.onUsernameTextChanged() }
|
||||||
loginFormPass.doOnTextChanged { _, _, _, _ -> presenter.onPassTextChanged() }
|
loginFormPass.doOnTextChanged { _, _, _, _ -> presenter.onPassTextChanged() }
|
||||||
loginFormHost.setOnItemClickListener { _, _, _, _ -> presenter.onHostSelected() }
|
loginFormHost.setOnItemClickListener { _, _, _, _ -> presenter.onHostSelected() }
|
||||||
|
loginFormDomainSuffix.doOnTextChanged { _, _, _, _ -> presenter.onDomainSuffixChanged() }
|
||||||
loginFormSignIn.setOnClickListener { presenter.onSignInClick() }
|
loginFormSignIn.setOnClickListener { presenter.onSignInClick() }
|
||||||
loginFormAdvancedButton.setOnClickListener { presenter.onAdvancedLoginClick() }
|
loginFormAdvancedButton.setOnClickListener { presenter.onAdvancedLoginClick() }
|
||||||
loginFormPrivacyLink.setOnClickListener { presenter.onPrivacyLinkClick() }
|
loginFormPrivacyLink.setOnClickListener { presenter.onPrivacyLinkClick() }
|
||||||
|
@ -179,6 +189,12 @@ class LoginFormFragment : BaseFragment<FragmentLoginFormBinding>(R.layout.fragme
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun setDomainSuffixInvalid() {
|
||||||
|
with(binding.loginFormDomainSuffixLayout) {
|
||||||
|
error = getString(R.string.login_invalid_domain_suffix)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun clearUsernameError() {
|
override fun clearUsernameError() {
|
||||||
binding.loginFormUsernameLayout.error = null
|
binding.loginFormUsernameLayout.error = null
|
||||||
binding.loginFormErrorBox.isVisible = false
|
binding.loginFormErrorBox.isVisible = false
|
||||||
|
@ -197,6 +213,10 @@ class LoginFormFragment : BaseFragment<FragmentLoginFormBinding>(R.layout.fragme
|
||||||
binding.loginFormErrorBox.isVisible = false
|
binding.loginFormErrorBox.isVisible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun clearDomainSuffixError() {
|
||||||
|
binding.loginFormDomainSuffixLayout.error = null
|
||||||
|
}
|
||||||
|
|
||||||
override fun showSoftKeyboard() {
|
override fun showSoftKeyboard() {
|
||||||
activity?.showSoftInput()
|
activity?.showSoftInput()
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||||
import io.github.wulkanowy.data.resourceFlow
|
import io.github.wulkanowy.data.resourceFlow
|
||||||
import io.github.wulkanowy.domain.adminmessage.GetAppropriateAdminMessageUseCase
|
import io.github.wulkanowy.domain.adminmessage.GetAppropriateAdminMessageUseCase
|
||||||
|
import io.github.wulkanowy.sdk.scrapper.login.InvalidSymbolException
|
||||||
import io.github.wulkanowy.ui.base.BasePresenter
|
import io.github.wulkanowy.ui.base.BasePresenter
|
||||||
import io.github.wulkanowy.ui.modules.login.LoginData
|
import io.github.wulkanowy.ui.modules.login.LoginData
|
||||||
import io.github.wulkanowy.ui.modules.login.LoginErrorHandler
|
import io.github.wulkanowy.ui.modules.login.LoginErrorHandler
|
||||||
|
@ -90,7 +91,7 @@ class LoginFormPresenter @Inject constructor(
|
||||||
clearPassError()
|
clearPassError()
|
||||||
clearUsernameError()
|
clearUsernameError()
|
||||||
clearHostError()
|
clearHostError()
|
||||||
if (formHostValue.contains("fakelog")) {
|
if (formHostValue.contains("wulkanowy")) {
|
||||||
setCredentials("jan@fakelog.cf", "jan123")
|
setCredentials("jan@fakelog.cf", "jan123")
|
||||||
} else if (formUsernameValue == "jan@fakelog.cf" && formPassValue == "jan123") {
|
} else if (formUsernameValue == "jan@fakelog.cf" && formPassValue == "jan123") {
|
||||||
setCredentials("", "")
|
setCredentials("", "")
|
||||||
|
@ -101,6 +102,12 @@ class LoginFormPresenter @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onDomainSuffixChanged() {
|
||||||
|
view?.apply {
|
||||||
|
clearDomainSuffixError()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun updateCustomDomainSuffixVisibility() {
|
fun updateCustomDomainSuffixVisibility() {
|
||||||
view?.run {
|
view?.run {
|
||||||
showDomainSuffixInput("customSuffix" in formHostValue)
|
showDomainSuffixInput("customSuffix" in formHostValue)
|
||||||
|
@ -148,14 +155,18 @@ class LoginFormPresenter @Inject constructor(
|
||||||
password = password,
|
password = password,
|
||||||
baseUrl = host,
|
baseUrl = host,
|
||||||
domainSuffix = domainSuffix,
|
domainSuffix = domainSuffix,
|
||||||
symbol = symbol
|
defaultSymbol = symbol
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onRetryAfterCaptcha() {
|
||||||
|
onSignInClick()
|
||||||
|
}
|
||||||
|
|
||||||
fun onSignInClick() {
|
fun onSignInClick() {
|
||||||
val loginData = getLoginData()
|
val loginData = getLoginData()
|
||||||
|
|
||||||
if (!validateCredentials(loginData.login, loginData.password, loginData.baseUrl)) return
|
if (!validateCredentials(loginData)) return
|
||||||
|
|
||||||
resourceFlow {
|
resourceFlow {
|
||||||
studentRepository.getUserSubjectsFromScrapper(
|
studentRepository.getUserSubjectsFromScrapper(
|
||||||
|
@ -163,7 +174,7 @@ class LoginFormPresenter @Inject constructor(
|
||||||
password = loginData.password,
|
password = loginData.password,
|
||||||
scrapperBaseUrl = loginData.baseUrl,
|
scrapperBaseUrl = loginData.baseUrl,
|
||||||
domainSuffix = loginData.domainSuffix,
|
domainSuffix = loginData.domainSuffix,
|
||||||
symbol = loginData.symbol.orEmpty(),
|
symbol = loginData.defaultSymbol,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.logResourceStatus("login")
|
.logResourceStatus("login")
|
||||||
|
@ -194,6 +205,9 @@ class LoginFormPresenter @Inject constructor(
|
||||||
}
|
}
|
||||||
.onResourceError {
|
.onResourceError {
|
||||||
loginErrorHandler.dispatch(it)
|
loginErrorHandler.dispatch(it)
|
||||||
|
if (it is InvalidSymbolException) {
|
||||||
|
loginErrorHandler.showDefaultMessage(it)
|
||||||
|
}
|
||||||
lastError = it
|
lastError = it
|
||||||
view?.showContact(true)
|
view?.showContact(true)
|
||||||
analytics.logEvent(
|
analytics.logEvent(
|
||||||
|
@ -225,24 +239,29 @@ class LoginFormPresenter @Inject constructor(
|
||||||
view?.onRecoverClick()
|
view?.onRecoverClick()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun validateCredentials(login: String, password: String, host: String): Boolean {
|
private fun validateCredentials(loginData: LoginData): Boolean {
|
||||||
var isCorrect = true
|
var isCorrect = true
|
||||||
|
|
||||||
if (login.isEmpty()) {
|
if (loginData.login.isEmpty()) {
|
||||||
view?.setErrorUsernameRequired()
|
view?.setErrorUsernameRequired()
|
||||||
isCorrect = false
|
isCorrect = false
|
||||||
} else {
|
} else {
|
||||||
if ("@" in login && "login" in host) {
|
if ("@" in loginData.login && "login" in loginData.baseUrl) {
|
||||||
view?.setErrorLoginRequired()
|
view?.setErrorLoginRequired()
|
||||||
isCorrect = false
|
isCorrect = false
|
||||||
}
|
}
|
||||||
if ("@" !in login && "email" in host) {
|
if ("@" !in loginData.login && "email" in loginData.baseUrl) {
|
||||||
view?.setErrorEmailRequired()
|
view?.setErrorEmailRequired()
|
||||||
isCorrect = false
|
isCorrect = false
|
||||||
}
|
}
|
||||||
if ("@" in login && "||" !in login && "login" !in host && "email" !in host) {
|
|
||||||
val emailHost = login.substringAfter("@")
|
val isEmailLogin = "@" in loginData.login
|
||||||
val emailDomain = URL(host).host
|
val isEmailWithLogin = "||" !in loginData.login
|
||||||
|
val isLoginNotRequired = "login" !in loginData.baseUrl
|
||||||
|
val isEmailNotRequired = "email" !in loginData.baseUrl
|
||||||
|
if (isEmailLogin && isEmailWithLogin && isLoginNotRequired && isEmailNotRequired) {
|
||||||
|
val emailHost = loginData.login.substringAfter("@")
|
||||||
|
val emailDomain = URL(loginData.baseUrl).host
|
||||||
if (!emailHost.equals(emailDomain, true)) {
|
if (!emailHost.equals(emailDomain, true)) {
|
||||||
view?.setErrorEmailInvalid(domain = emailDomain)
|
view?.setErrorEmailInvalid(domain = emailDomain)
|
||||||
isCorrect = false
|
isCorrect = false
|
||||||
|
@ -250,16 +269,21 @@ class LoginFormPresenter @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (password.isEmpty()) {
|
if (loginData.password.isEmpty()) {
|
||||||
view?.setErrorPassRequired(focus = isCorrect)
|
view?.setErrorPassRequired(focus = isCorrect)
|
||||||
isCorrect = false
|
isCorrect = false
|
||||||
}
|
}
|
||||||
|
|
||||||
if (password.length < 6 && password.isNotEmpty()) {
|
if (loginData.password.length < 6 && loginData.password.isNotEmpty()) {
|
||||||
view?.setErrorPassInvalid(focus = isCorrect)
|
view?.setErrorPassInvalid(focus = isCorrect)
|
||||||
isCorrect = false
|
isCorrect = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (loginData.domainSuffix !in listOf("", "rc", "kurs")) {
|
||||||
|
view?.setDomainSuffixInvalid()
|
||||||
|
isCorrect = false
|
||||||
|
}
|
||||||
|
|
||||||
return isCorrect
|
return isCorrect
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,12 +46,16 @@ interface LoginFormView : BaseView {
|
||||||
|
|
||||||
fun setErrorEmailInvalid(domain: String)
|
fun setErrorEmailInvalid(domain: String)
|
||||||
|
|
||||||
|
fun setDomainSuffixInvalid()
|
||||||
|
|
||||||
fun clearUsernameError()
|
fun clearUsernameError()
|
||||||
|
|
||||||
fun clearPassError()
|
fun clearPassError()
|
||||||
|
|
||||||
fun clearHostError()
|
fun clearHostError()
|
||||||
|
|
||||||
|
fun clearDomainSuffixError()
|
||||||
|
|
||||||
fun showSoftKeyboard()
|
fun showSoftKeyboard()
|
||||||
|
|
||||||
fun hideSoftKeyboard()
|
fun hideSoftKeyboard()
|
||||||
|
|
|
@ -38,7 +38,7 @@ class LoginRecoverPresenter @Inject constructor(
|
||||||
|
|
||||||
fun onHostSelected() {
|
fun onHostSelected() {
|
||||||
view?.run {
|
view?.run {
|
||||||
if ("fakelog" in recoverHostValue) setDefaultCredentials("jan@fakelog.cf")
|
if ("wulkanowy" in recoverHostValue) setDefaultCredentials("jan@fakelog.cf")
|
||||||
clearUsernameError()
|
clearUsernameError()
|
||||||
updateFields()
|
updateFields()
|
||||||
}
|
}
|
||||||
|
@ -60,7 +60,7 @@ class LoginRecoverPresenter @Inject constructor(
|
||||||
resourceFlow {
|
resourceFlow {
|
||||||
recoverRepository.getReCaptchaSiteKey(
|
recoverRepository.getReCaptchaSiteKey(
|
||||||
host,
|
host,
|
||||||
symbol.ifBlank { "Default" })
|
symbol.ifBlank { "default" })
|
||||||
}.onEach {
|
}.onEach {
|
||||||
when (it) {
|
when (it) {
|
||||||
is Resource.Loading -> view?.run {
|
is Resource.Loading -> view?.run {
|
||||||
|
@ -103,7 +103,7 @@ class LoginRecoverPresenter @Inject constructor(
|
||||||
fun onReCaptchaVerified(reCaptchaResponse: String) {
|
fun onReCaptchaVerified(reCaptchaResponse: String) {
|
||||||
val username = view?.recoverNameValue.orEmpty()
|
val username = view?.recoverNameValue.orEmpty()
|
||||||
val host = view?.recoverHostValue.orEmpty()
|
val host = view?.recoverHostValue.orEmpty()
|
||||||
val symbol = view?.formHostSymbol.ifNullOrBlank { "Default" }
|
val symbol = view?.formHostSymbol.ifNullOrBlank { "default" }
|
||||||
|
|
||||||
resourceFlow {
|
resourceFlow {
|
||||||
recoverRepository.sendRecoverRequest(
|
recoverRepository.sendRecoverRequest(
|
||||||
|
|
|
@ -10,13 +10,11 @@ import io.github.wulkanowy.data.pojos.RegisterUser
|
||||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||||
import io.github.wulkanowy.databinding.FragmentLoginStudentSelectBinding
|
import io.github.wulkanowy.databinding.FragmentLoginStudentSelectBinding
|
||||||
import io.github.wulkanowy.ui.base.BaseFragment
|
import io.github.wulkanowy.ui.base.BaseFragment
|
||||||
import io.github.wulkanowy.ui.modules.auth.AuthDialog
|
|
||||||
import io.github.wulkanowy.ui.modules.login.LoginActivity
|
import io.github.wulkanowy.ui.modules.login.LoginActivity
|
||||||
import io.github.wulkanowy.ui.modules.login.LoginData
|
import io.github.wulkanowy.ui.modules.login.LoginData
|
||||||
import io.github.wulkanowy.ui.modules.login.support.LoginSupportDialog
|
import io.github.wulkanowy.ui.modules.login.support.LoginSupportDialog
|
||||||
import io.github.wulkanowy.ui.modules.login.support.LoginSupportInfo
|
import io.github.wulkanowy.ui.modules.login.support.LoginSupportInfo
|
||||||
import io.github.wulkanowy.utils.AppInfo
|
import io.github.wulkanowy.utils.AppInfo
|
||||||
import io.github.wulkanowy.utils.openEmailClient
|
|
||||||
import io.github.wulkanowy.utils.openInternetBrowser
|
import io.github.wulkanowy.utils.openInternetBrowser
|
||||||
import io.github.wulkanowy.utils.serializable
|
import io.github.wulkanowy.utils.serializable
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
|
@ -111,8 +111,8 @@ class LoginStudentSelectPresenter @Inject constructor(
|
||||||
val notEmptySymbols = registerUser.symbols.filter { it.schools.isNotEmpty() }
|
val notEmptySymbols = registerUser.symbols.filter { it.schools.isNotEmpty() }
|
||||||
val emptySymbols = registerUser.symbols.filter { it.schools.isEmpty() }
|
val emptySymbols = registerUser.symbols.filter { it.schools.isEmpty() }
|
||||||
|
|
||||||
if (emptySymbols.isNotEmpty() && notEmptySymbols.isNotEmpty() && emptySymbols.any { it.symbol == loginData.symbol }) {
|
if (emptySymbols.isNotEmpty() && notEmptySymbols.isNotEmpty() && emptySymbols.any { it.symbol == loginData.userEnteredSymbol }) {
|
||||||
add(createEmptySymbolItem(emptySymbols.first { it.symbol == loginData.symbol }))
|
add(createEmptySymbolItem(emptySymbols.first { it.symbol == loginData.userEnteredSymbol }))
|
||||||
}
|
}
|
||||||
|
|
||||||
addAll(createNotEmptySymbolItems(notEmptySymbols, students))
|
addAll(createNotEmptySymbolItems(notEmptySymbols, students))
|
||||||
|
@ -317,7 +317,7 @@ class LoginStudentSelectPresenter @Inject constructor(
|
||||||
loginData = loginData,
|
loginData = loginData,
|
||||||
registerUser = registerUser,
|
registerUser = registerUser,
|
||||||
lastErrorMessage = lastError?.message,
|
lastErrorMessage = lastError?.message,
|
||||||
enteredSymbol = loginData.symbol,
|
enteredSymbol = loginData.userEnteredSymbol,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,7 +105,7 @@ class LoginSupportDialog : BaseDialogFragment<DialogLoginSupportBinding>() {
|
||||||
"${appInfo.systemManufacturer} ${appInfo.systemModel}",
|
"${appInfo.systemManufacturer} ${appInfo.systemModel}",
|
||||||
appInfo.systemVersion.toString(),
|
appInfo.systemVersion.toString(),
|
||||||
"${appInfo.versionName}-${appInfo.buildFlavor}",
|
"${appInfo.versionName}-${appInfo.buildFlavor}",
|
||||||
supportInfo.loginData.baseUrl + "/" + supportInfo.loginData.symbol,
|
supportInfo.loginData.let { "${it.baseUrl}/${it.defaultSymbol}/${it.userEnteredSymbol}" },
|
||||||
preferencesRepository.installationId,
|
preferencesRepository.installationId,
|
||||||
getLastErrorFromStudentSelectScreen(),
|
getLastErrorFromStudentSelectScreen(),
|
||||||
dialogLoginSupportSchoolInput.text.takeIf { !it.isNullOrBlank() }
|
dialogLoginSupportSchoolInput.text.takeIf { !it.isNullOrBlank() }
|
||||||
|
|
|
@ -60,7 +60,7 @@ class LoginSymbolPresenter @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
loginData = loginData.copy(
|
loginData = loginData.copy(
|
||||||
symbol = view?.symbolValue?.getNormalizedSymbol(),
|
userEnteredSymbol = view?.symbolValue?.getNormalizedSymbol(),
|
||||||
)
|
)
|
||||||
resourceFlow {
|
resourceFlow {
|
||||||
studentRepository.getUserSubjectsFromScrapper(
|
studentRepository.getUserSubjectsFromScrapper(
|
||||||
|
@ -68,7 +68,7 @@ class LoginSymbolPresenter @Inject constructor(
|
||||||
password = loginData.password,
|
password = loginData.password,
|
||||||
scrapperBaseUrl = loginData.baseUrl,
|
scrapperBaseUrl = loginData.baseUrl,
|
||||||
domainSuffix = loginData.domainSuffix,
|
domainSuffix = loginData.domainSuffix,
|
||||||
symbol = loginData.symbol.orEmpty(),
|
symbol = loginData.userEnteredSymbol.orEmpty(),
|
||||||
)
|
)
|
||||||
}.onEach { user ->
|
}.onEach { user ->
|
||||||
registerUser = user.dataOrNull
|
registerUser = user.dataOrNull
|
||||||
|
@ -93,13 +93,10 @@ class LoginSymbolPresenter @Inject constructor(
|
||||||
else -> {
|
else -> {
|
||||||
val enteredSymbolDetails = user.data.symbols
|
val enteredSymbolDetails = user.data.symbols
|
||||||
.firstOrNull()
|
.firstOrNull()
|
||||||
?.takeIf { it.symbol == loginData.symbol }
|
?.takeIf { it.symbol == loginData.userEnteredSymbol }
|
||||||
|
|
||||||
if (enteredSymbolDetails?.error is InvalidSymbolException) {
|
if (enteredSymbolDetails?.error is InvalidSymbolException) {
|
||||||
view?.run {
|
showInvalidSymbolError()
|
||||||
setErrorSymbolInvalid()
|
|
||||||
showContact(true)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
Timber.i("Login with symbol result: Success")
|
Timber.i("Login with symbol result: Success")
|
||||||
view?.navigateToStudentSelect(loginData, requireNotNull(user.data))
|
view?.navigateToStudentSelect(loginData, requireNotNull(user.data))
|
||||||
|
@ -128,6 +125,9 @@ class LoginSymbolPresenter @Inject constructor(
|
||||||
loginErrorHandler.dispatch(user.error)
|
loginErrorHandler.dispatch(user.error)
|
||||||
lastError = user.error
|
lastError = user.error
|
||||||
view?.showContact(true)
|
view?.showContact(true)
|
||||||
|
if (user.error is InvalidSymbolException) {
|
||||||
|
showInvalidSymbolError()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.onResourceNotLoading {
|
}.onResourceNotLoading {
|
||||||
|
@ -145,6 +145,13 @@ class LoginSymbolPresenter @Inject constructor(
|
||||||
return normalizedSymbol in definitelyInvalidSymbols
|
return normalizedSymbol in definitelyInvalidSymbols
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun showInvalidSymbolError() {
|
||||||
|
view?.run {
|
||||||
|
setErrorSymbolInvalid()
|
||||||
|
showContact(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun onFaqClick() {
|
fun onFaqClick() {
|
||||||
view?.openFaqPage()
|
view?.openFaqPage()
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,10 +11,8 @@ import android.view.View
|
||||||
import android.widget.RemoteViews
|
import android.widget.RemoteViews
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
import io.github.wulkanowy.data.Resource
|
import io.github.wulkanowy.data.dataOrThrow
|
||||||
import io.github.wulkanowy.data.dataOrNull
|
|
||||||
import io.github.wulkanowy.data.db.SharedPrefProvider
|
import io.github.wulkanowy.data.db.SharedPrefProvider
|
||||||
import io.github.wulkanowy.data.db.entities.LuckyNumber
|
|
||||||
import io.github.wulkanowy.data.repositories.LuckyNumberRepository
|
import io.github.wulkanowy.data.repositories.LuckyNumberRepository
|
||||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||||
import io.github.wulkanowy.data.toFirstResult
|
import io.github.wulkanowy.data.toFirstResult
|
||||||
|
@ -69,8 +67,7 @@ class LuckyNumberWidgetProvider : AppWidgetProvider() {
|
||||||
|
|
||||||
appWidgetIds?.forEach { widgetId ->
|
appWidgetIds?.forEach { widgetId ->
|
||||||
val studentId = sharedPref.getLong(getStudentWidgetKey(widgetId), 0)
|
val studentId = sharedPref.getLong(getStudentWidgetKey(widgetId), 0)
|
||||||
val luckyNumberResource = getLuckyNumber(studentId, widgetId)
|
val luckyNumber = getLuckyNumber(studentId, widgetId)?.luckyNumber?.toString()
|
||||||
val luckyNumber = luckyNumberResource.dataOrNull?.luckyNumber?.toString()
|
|
||||||
val remoteView = RemoteViews(context.packageName, R.layout.widget_luckynumber)
|
val remoteView = RemoteViews(context.packageName, R.layout.widget_luckynumber)
|
||||||
.apply {
|
.apply {
|
||||||
setTextViewText(R.id.luckyNumberWidgetValue, luckyNumber ?: "-")
|
setTextViewText(R.id.luckyNumberWidgetValue, luckyNumber ?: "-")
|
||||||
|
@ -143,18 +140,18 @@ class LuckyNumberWidgetProvider : AppWidgetProvider() {
|
||||||
sharedPref.putLong(getStudentWidgetKey(appWidgetId), it.id)
|
sharedPref.putLong(getStudentWidgetKey(appWidgetId), it.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentStudent != null) {
|
if (currentStudent != null) {
|
||||||
luckyNumberRepository.getLuckyNumber(currentStudent, forceRefresh = false)
|
luckyNumberRepository.getLuckyNumber(currentStudent, forceRefresh = false)
|
||||||
.toFirstResult()
|
.toFirstResult()
|
||||||
} else {
|
.dataOrThrow
|
||||||
Resource.Success<LuckyNumber?>(null)
|
} else null
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Timber.e(e, "An error has occurred in lucky number provider")
|
Timber.e(e, "An error has occurred in lucky number provider")
|
||||||
Resource.Error(e)
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@ import androidx.core.view.isVisible
|
||||||
import androidx.core.view.updateLayoutParams
|
import androidx.core.view.updateLayoutParams
|
||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
import androidx.preference.PreferenceFragmentCompat
|
import androidx.preference.PreferenceFragmentCompat
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
@ -30,6 +31,8 @@ import io.github.wulkanowy.databinding.ActivityMainBinding
|
||||||
import io.github.wulkanowy.ui.base.BaseActivity
|
import io.github.wulkanowy.ui.base.BaseActivity
|
||||||
import io.github.wulkanowy.ui.modules.Destination
|
import io.github.wulkanowy.ui.modules.Destination
|
||||||
import io.github.wulkanowy.ui.modules.account.accountquick.AccountQuickDialog
|
import io.github.wulkanowy.ui.modules.account.accountquick.AccountQuickDialog
|
||||||
|
import io.github.wulkanowy.ui.modules.auth.AuthDialog
|
||||||
|
import io.github.wulkanowy.ui.modules.captcha.CaptchaDialog
|
||||||
import io.github.wulkanowy.ui.modules.settings.appearance.menuorder.AppMenuItem
|
import io.github.wulkanowy.ui.modules.settings.appearance.menuorder.AppMenuItem
|
||||||
import io.github.wulkanowy.utils.AnalyticsHelper
|
import io.github.wulkanowy.utils.AnalyticsHelper
|
||||||
import io.github.wulkanowy.utils.AppInfo
|
import io.github.wulkanowy.utils.AppInfo
|
||||||
|
@ -40,10 +43,17 @@ import io.github.wulkanowy.utils.dpToPx
|
||||||
import io.github.wulkanowy.utils.nickOrName
|
import io.github.wulkanowy.utils.nickOrName
|
||||||
import io.github.wulkanowy.utils.safelyPopFragments
|
import io.github.wulkanowy.utils.safelyPopFragments
|
||||||
import io.github.wulkanowy.utils.setOnViewChangeListener
|
import io.github.wulkanowy.utils.setOnViewChangeListener
|
||||||
|
import kotlinx.coroutines.FlowPreview
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
import kotlinx.coroutines.flow.debounce
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainView,
|
class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainView,
|
||||||
|
@ -73,6 +83,8 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
|
||||||
private val navController =
|
private val navController =
|
||||||
FragNavController(supportFragmentManager, R.id.main_fragment_container)
|
FragNavController(supportFragmentManager, R.id.main_fragment_container)
|
||||||
|
|
||||||
|
private val captchaVerificationEvent = MutableSharedFlow<String?>()
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
private const val EXTRA_START_DESTINATION = "start_destination_json"
|
private const val EXTRA_START_DESTINATION = "start_destination_json"
|
||||||
|
@ -144,6 +156,7 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
|
||||||
initializeToolbar()
|
initializeToolbar()
|
||||||
initializeBottomNavigation(startMenuIndex, rootAppMenuItems)
|
initializeBottomNavigation(startMenuIndex, rootAppMenuItems)
|
||||||
initializeNavController(startMenuIndex, rootUpdatedDestinations)
|
initializeNavController(startMenuIndex, rootUpdatedDestinations)
|
||||||
|
initializeCaptchaVerificationEvent()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initializeNavController(
|
private fun initializeNavController(
|
||||||
|
@ -323,6 +336,27 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(FlowPreview::class)
|
||||||
|
private fun initializeCaptchaVerificationEvent() {
|
||||||
|
captchaVerificationEvent
|
||||||
|
.debounce(1.seconds)
|
||||||
|
.onEach { url ->
|
||||||
|
Timber.d("Showing captcha dialog for: $url")
|
||||||
|
showDialogFragment(CaptchaDialog.newInstance(url))
|
||||||
|
}
|
||||||
|
.launchIn(lifecycleScope)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCaptchaVerificationRequired(url: String?) {
|
||||||
|
lifecycleScope.launch {
|
||||||
|
captchaVerificationEvent.emit(url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun showAuthDialog() {
|
||||||
|
showDialogFragment(AuthDialog.newInstance())
|
||||||
|
}
|
||||||
|
|
||||||
override fun onSaveInstanceState(outState: Bundle) {
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
super.onSaveInstanceState(outState)
|
super.onSaveInstanceState(outState)
|
||||||
navController.onSaveInstanceState(outState)
|
navController.onSaveInstanceState(outState)
|
||||||
|
|
|
@ -50,12 +50,15 @@ class MessagePreviewAdapter @Inject constructor() :
|
||||||
ViewType.MESSAGE.id -> MessageViewHolder(
|
ViewType.MESSAGE.id -> MessageViewHolder(
|
||||||
ItemMessagePreviewBinding.inflate(inflater, parent, false)
|
ItemMessagePreviewBinding.inflate(inflater, parent, false)
|
||||||
)
|
)
|
||||||
|
|
||||||
ViewType.DIVIDER.id -> DividerViewHolder(
|
ViewType.DIVIDER.id -> DividerViewHolder(
|
||||||
ItemMessageDividerBinding.inflate(inflater, parent, false)
|
ItemMessageDividerBinding.inflate(inflater, parent, false)
|
||||||
)
|
)
|
||||||
|
|
||||||
ViewType.ATTACHMENT.id -> AttachmentViewHolder(
|
ViewType.ATTACHMENT.id -> AttachmentViewHolder(
|
||||||
ItemMessageAttachmentBinding.inflate(inflater, parent, false)
|
ItemMessageAttachmentBinding.inflate(inflater, parent, false)
|
||||||
)
|
)
|
||||||
|
|
||||||
else -> throw IllegalStateException()
|
else -> throw IllegalStateException()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -66,6 +69,7 @@ class MessagePreviewAdapter @Inject constructor() :
|
||||||
holder,
|
holder,
|
||||||
requireNotNull(messageWithAttachment).message
|
requireNotNull(messageWithAttachment).message
|
||||||
)
|
)
|
||||||
|
|
||||||
is AttachmentViewHolder -> bindAttachment(
|
is AttachmentViewHolder -> bindAttachment(
|
||||||
holder,
|
holder,
|
||||||
requireNotNull(messageWithAttachment).attachments[position - 2]
|
requireNotNull(messageWithAttachment).attachments[position - 2]
|
||||||
|
@ -82,9 +86,11 @@ class MessagePreviewAdapter @Inject constructor() :
|
||||||
recipientCount > 1 -> {
|
recipientCount > 1 -> {
|
||||||
context.getString(R.string.message_read_by, message.readBy, recipientCount)
|
context.getString(R.string.message_read_by, message.readBy, recipientCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
message.readBy == 1 || (isReceived && !message.unread) -> {
|
message.readBy == 1 || (isReceived && !message.unread) -> {
|
||||||
context.getString(R.string.message_read, context.getString(R.string.all_yes))
|
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))
|
else -> context.getString(R.string.message_read, context.getString(R.string.all_no))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -44,18 +44,33 @@ class MessagePreviewFragment :
|
||||||
|
|
||||||
private var menuForwardButton: MenuItem? = null
|
private var menuForwardButton: MenuItem? = null
|
||||||
|
|
||||||
|
private var menuRestoreButton: MenuItem? = null
|
||||||
|
|
||||||
private var menuDeleteButton: MenuItem? = null
|
private var menuDeleteButton: MenuItem? = null
|
||||||
|
|
||||||
|
private var menuDeleteForeverButton: MenuItem? = null
|
||||||
|
|
||||||
private var menuShareButton: MenuItem? = null
|
private var menuShareButton: MenuItem? = null
|
||||||
|
|
||||||
private var menuPrintButton: MenuItem? = null
|
private var menuPrintButton: MenuItem? = null
|
||||||
|
|
||||||
|
private var menuMuteButton: MenuItem? = null
|
||||||
|
|
||||||
override val titleStringId: Int
|
override val titleStringId: Int
|
||||||
get() = R.string.message_title
|
get() = R.string.message_title
|
||||||
|
|
||||||
override val deleteMessageSuccessString: String
|
override val deleteMessageSuccessString: String
|
||||||
get() = getString(R.string.message_delete_success)
|
get() = getString(R.string.message_delete_success)
|
||||||
|
|
||||||
|
override val muteMessageSuccessString: String
|
||||||
|
get() = getString(R.string.message_mute_success)
|
||||||
|
|
||||||
|
override val unmuteMessageSuccessString: String
|
||||||
|
get() = getString(R.string.message_unmute_success)
|
||||||
|
|
||||||
|
override val restoreMessageSuccessString: String
|
||||||
|
get() = getString(R.string.message_restore_success)
|
||||||
|
|
||||||
override val messageNoSubjectString: String
|
override val messageNoSubjectString: String
|
||||||
get() = getString(R.string.message_no_subject)
|
get() = getString(R.string.message_no_subject)
|
||||||
|
|
||||||
|
@ -103,9 +118,12 @@ class MessagePreviewFragment :
|
||||||
inflater.inflate(R.menu.action_menu_message_preview, menu)
|
inflater.inflate(R.menu.action_menu_message_preview, menu)
|
||||||
menuReplyButton = menu.findItem(R.id.messagePreviewMenuReply)
|
menuReplyButton = menu.findItem(R.id.messagePreviewMenuReply)
|
||||||
menuForwardButton = menu.findItem(R.id.messagePreviewMenuForward)
|
menuForwardButton = menu.findItem(R.id.messagePreviewMenuForward)
|
||||||
|
menuRestoreButton = menu.findItem(R.id.messagePreviewMenuRestore)
|
||||||
menuDeleteButton = menu.findItem(R.id.messagePreviewMenuDelete)
|
menuDeleteButton = menu.findItem(R.id.messagePreviewMenuDelete)
|
||||||
|
menuDeleteForeverButton = menu.findItem(R.id.messagePreviewMenuDeleteForever)
|
||||||
menuShareButton = menu.findItem(R.id.messagePreviewMenuShare)
|
menuShareButton = menu.findItem(R.id.messagePreviewMenuShare)
|
||||||
menuPrintButton = menu.findItem(R.id.messagePreviewMenuPrint)
|
menuPrintButton = menu.findItem(R.id.messagePreviewMenuPrint)
|
||||||
|
menuMuteButton = menu.findItem(R.id.messagePreviewMenuMute)
|
||||||
presenter.onCreateOptionsMenu()
|
presenter.onCreateOptionsMenu()
|
||||||
|
|
||||||
menu.findItem(R.id.mainMenuAccount).isVisible = false
|
menu.findItem(R.id.mainMenuAccount).isVisible = false
|
||||||
|
@ -115,9 +133,12 @@ class MessagePreviewFragment :
|
||||||
return when (item.itemId) {
|
return when (item.itemId) {
|
||||||
R.id.messagePreviewMenuReply -> presenter.onReply()
|
R.id.messagePreviewMenuReply -> presenter.onReply()
|
||||||
R.id.messagePreviewMenuForward -> presenter.onForward()
|
R.id.messagePreviewMenuForward -> presenter.onForward()
|
||||||
|
R.id.messagePreviewMenuRestore -> presenter.onMessageRestore()
|
||||||
R.id.messagePreviewMenuDelete -> presenter.onMessageDelete()
|
R.id.messagePreviewMenuDelete -> presenter.onMessageDelete()
|
||||||
|
R.id.messagePreviewMenuDeleteForever -> presenter.onMessageDelete()
|
||||||
R.id.messagePreviewMenuShare -> presenter.onShare()
|
R.id.messagePreviewMenuShare -> presenter.onShare()
|
||||||
R.id.messagePreviewMenuPrint -> presenter.onPrint()
|
R.id.messagePreviewMenuPrint -> presenter.onPrint()
|
||||||
|
R.id.messagePreviewMenuMute -> presenter.onMute()
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -129,6 +150,11 @@ class MessagePreviewFragment :
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun updateMuteToggleButton(isMuted: Boolean) {
|
||||||
|
menuMuteButton?.setTitle(if (isMuted) R.string.message_unmute else R.string.message_mute)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
override fun showProgress(show: Boolean) {
|
override fun showProgress(show: Boolean) {
|
||||||
binding.messagePreviewProgress.visibility = if (show) VISIBLE else GONE
|
binding.messagePreviewProgress.visibility = if (show) VISIBLE else GONE
|
||||||
}
|
}
|
||||||
|
@ -137,20 +163,15 @@ class MessagePreviewFragment :
|
||||||
binding.messagePreviewRecycler.visibility = if (show) VISIBLE else GONE
|
binding.messagePreviewRecycler.visibility = if (show) VISIBLE else GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showOptions(show: Boolean, isReplayable: Boolean) {
|
override fun showOptions(show: Boolean, isReplayable: Boolean, isRestorable: Boolean) {
|
||||||
menuReplyButton?.isVisible = isReplayable
|
menuReplyButton?.isVisible = show && isReplayable
|
||||||
menuForwardButton?.isVisible = show
|
menuForwardButton?.isVisible = show
|
||||||
menuDeleteButton?.isVisible = show
|
menuRestoreButton?.isVisible = show && isRestorable
|
||||||
|
menuDeleteButton?.isVisible = show && !isRestorable
|
||||||
|
menuDeleteForeverButton?.isVisible = show && isRestorable
|
||||||
menuShareButton?.isVisible = show
|
menuShareButton?.isVisible = show
|
||||||
menuPrintButton?.isVisible = show
|
menuPrintButton?.isVisible = show
|
||||||
}
|
menuMuteButton?.isVisible = show && isReplayable
|
||||||
|
|
||||||
override fun setDeletedOptionsLabels() {
|
|
||||||
menuDeleteButton?.setTitle(R.string.message_delete_forever)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setNotDeletedOptionsLabels() {
|
|
||||||
menuDeleteButton?.setTitle(R.string.message_move_to_trash)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showErrorView(show: Boolean) {
|
override fun showErrorView(show: Boolean) {
|
||||||
|
@ -213,7 +234,7 @@ class MessagePreviewFragment :
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSaveInstanceState(outState: Bundle) {
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
outState.putSerializable(MESSAGE_ID_KEY, presenter.message)
|
outState.putSerializable(MESSAGE_ID_KEY, presenter.messageWithAttachments)
|
||||||
super.onSaveInstanceState(outState)
|
super.onSaveInstanceState(outState)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ import androidx.core.text.parseAsHtml
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
import io.github.wulkanowy.data.*
|
import io.github.wulkanowy.data.*
|
||||||
import io.github.wulkanowy.data.db.entities.Message
|
import io.github.wulkanowy.data.db.entities.Message
|
||||||
import io.github.wulkanowy.data.db.entities.MessageAttachment
|
import io.github.wulkanowy.data.db.entities.MessageWithAttachment
|
||||||
import io.github.wulkanowy.data.enums.MessageFolder
|
import io.github.wulkanowy.data.enums.MessageFolder
|
||||||
import io.github.wulkanowy.data.repositories.MessageRepository
|
import io.github.wulkanowy.data.repositories.MessageRepository
|
||||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||||
|
@ -14,9 +14,11 @@ import io.github.wulkanowy.ui.base.BasePresenter
|
||||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||||
import io.github.wulkanowy.utils.AnalyticsHelper
|
import io.github.wulkanowy.utils.AnalyticsHelper
|
||||||
import io.github.wulkanowy.utils.toFormattedString
|
import io.github.wulkanowy.utils.toFormattedString
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
class MessagePreviewPresenter @Inject constructor(
|
class MessagePreviewPresenter @Inject constructor(
|
||||||
errorHandler: ErrorHandler,
|
errorHandler: ErrorHandler,
|
||||||
|
@ -26,9 +28,7 @@ class MessagePreviewPresenter @Inject constructor(
|
||||||
private val analytics: AnalyticsHelper
|
private val analytics: AnalyticsHelper
|
||||||
) : BasePresenter<MessagePreviewView>(errorHandler, studentRepository) {
|
) : BasePresenter<MessagePreviewView>(errorHandler, studentRepository) {
|
||||||
|
|
||||||
var message: Message? = null
|
var messageWithAttachments: MessageWithAttachment? = null
|
||||||
|
|
||||||
var attachments: List<MessageAttachment>? = null
|
|
||||||
|
|
||||||
private lateinit var lastError: Throwable
|
private lateinit var lastError: Throwable
|
||||||
|
|
||||||
|
@ -38,7 +38,6 @@ class MessagePreviewPresenter @Inject constructor(
|
||||||
super.onAttachView(view)
|
super.onAttachView(view)
|
||||||
view.initView()
|
view.initView()
|
||||||
errorHandler.showErrorMessage = ::showErrorViewOnError
|
errorHandler.showErrorMessage = ::showErrorViewOnError
|
||||||
this.message = message
|
|
||||||
loadData(requireNotNull(message))
|
loadData(requireNotNull(message))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,25 +65,24 @@ class MessagePreviewPresenter @Inject constructor(
|
||||||
.logResourceStatus("message ${messageToLoad.messageId} preview")
|
.logResourceStatus("message ${messageToLoad.messageId} preview")
|
||||||
.onResourceData {
|
.onResourceData {
|
||||||
if (it != null) {
|
if (it != null) {
|
||||||
message = it.message
|
messageWithAttachments = it
|
||||||
attachments = it.attachments
|
|
||||||
view?.apply {
|
view?.apply {
|
||||||
setMessageWithAttachment(it)
|
setMessageWithAttachment(it)
|
||||||
showContent(true)
|
showContent(true)
|
||||||
initOptions()
|
initOptions()
|
||||||
|
updateMuteToggleButton(isMuted = it.mutedMessageSender != null)
|
||||||
if (preferencesRepository.isIncognitoMode && it.message.unread) {
|
if (preferencesRepository.isIncognitoMode && it.message.unread) {
|
||||||
showMessage(R.string.message_incognito_description)
|
showMessage(R.string.message_incognito_description)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
delay(1.seconds)
|
||||||
view?.run {
|
view?.run {
|
||||||
showMessage(messageNotExists)
|
showMessage(messageNotExists)
|
||||||
popView()
|
popView()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}.onResourceSuccess {
|
||||||
.onResourceSuccess {
|
|
||||||
if (it != null) {
|
if (it != null) {
|
||||||
analytics.logEvent(
|
analytics.logEvent(
|
||||||
"load_item",
|
"load_item",
|
||||||
|
@ -92,31 +90,28 @@ class MessagePreviewPresenter @Inject constructor(
|
||||||
"length" to it.message.content.length
|
"length" to it.message.content.length
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}.onResourceNotLoading { view?.showProgress(false) }.onResourceError {
|
||||||
.onResourceNotLoading { view?.showProgress(false) }
|
|
||||||
.onResourceError {
|
|
||||||
retryCallback = { onMessageLoadRetry(messageToLoad) }
|
retryCallback = { onMessageLoadRetry(messageToLoad) }
|
||||||
errorHandler.dispatch(it)
|
errorHandler.dispatch(it)
|
||||||
}
|
}.launch()
|
||||||
.launch()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onReply(): Boolean {
|
fun onReply(): Boolean {
|
||||||
return if (message != null) {
|
return if (messageWithAttachments?.message != null) {
|
||||||
view?.openMessageReply(message)
|
view?.openMessageReply(messageWithAttachments?.message)
|
||||||
true
|
true
|
||||||
} else false
|
} else false
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onForward(): Boolean {
|
fun onForward(): Boolean {
|
||||||
return if (message != null) {
|
return if (messageWithAttachments?.message != null) {
|
||||||
view?.openMessageForward(message)
|
view?.openMessageForward(messageWithAttachments?.message)
|
||||||
true
|
true
|
||||||
} else false
|
} else false
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onShare(): Boolean {
|
fun onShare(): Boolean {
|
||||||
val message = message ?: return false
|
val message = messageWithAttachments?.message ?: return false
|
||||||
val subject = message.subject.ifBlank { view?.messageNoSubjectString.orEmpty() }
|
val subject = message.subject.ifBlank { view?.messageNoSubjectString.orEmpty() }
|
||||||
|
|
||||||
val text = buildString {
|
val text = buildString {
|
||||||
|
@ -129,13 +124,15 @@ class MessagePreviewPresenter @Inject constructor(
|
||||||
|
|
||||||
appendLine(message.content.parseAsHtml())
|
appendLine(message.content.parseAsHtml())
|
||||||
|
|
||||||
if (!attachments.isNullOrEmpty()) {
|
if (!messageWithAttachments?.attachments.isNullOrEmpty()) {
|
||||||
appendLine()
|
appendLine()
|
||||||
appendLine("Załączniki:")
|
appendLine("Załączniki:")
|
||||||
|
|
||||||
append(attachments.orEmpty().joinToString(separator = "\n") { attachment ->
|
append(
|
||||||
"${attachment.filename}: ${attachment.url}"
|
messageWithAttachments?.attachments.orEmpty()
|
||||||
})
|
.joinToString(separator = "\n") { attachment ->
|
||||||
|
"${attachment.filename}: ${attachment.url}"
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,7 +145,7 @@ class MessagePreviewPresenter @Inject constructor(
|
||||||
|
|
||||||
@SuppressLint("NewApi")
|
@SuppressLint("NewApi")
|
||||||
fun onPrint(): Boolean {
|
fun onPrint(): Boolean {
|
||||||
val message = message ?: return false
|
val message = messageWithAttachments?.message ?: return false
|
||||||
val subject = message.subject.ifBlank { view?.messageNoSubjectString.orEmpty() }
|
val subject = message.subject.ifBlank { view?.messageNoSubjectString.orEmpty() }
|
||||||
|
|
||||||
val dateString = message.date.toFormattedString("yyyy-MM-dd HH:mm:ss")
|
val dateString = message.date.toFormattedString("yyyy-MM-dd HH:mm:ss")
|
||||||
|
@ -159,8 +156,7 @@ class MessagePreviewPresenter @Inject constructor(
|
||||||
append("<div><h4>Od</h4>${message.sender}</div>")
|
append("<div><h4>Od</h4>${message.sender}</div>")
|
||||||
append("<div><h4>DO</h4>${message.recipients}</div>")
|
append("<div><h4>DO</h4>${message.recipients}</div>")
|
||||||
}
|
}
|
||||||
val messageContent = "<p>${message.content}</p>"
|
val messageContent = "<p>${message.content}</p>".replace(Regex("[\\n\\r]{2,}"), "</p><p>")
|
||||||
.replace(Regex("[\\n\\r]{2,}"), "</p><p>")
|
|
||||||
.replace(Regex("[\\n\\r]"), "<br>")
|
.replace(Regex("[\\n\\r]"), "<br>")
|
||||||
|
|
||||||
val jobName = buildString {
|
val jobName = buildString {
|
||||||
|
@ -171,9 +167,7 @@ class MessagePreviewPresenter @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
view?.apply {
|
view?.apply {
|
||||||
val html = printHTML
|
val html = printHTML.replace("%SUBJECT%", subject).replace("%CONTENT%", messageContent)
|
||||||
.replace("%SUBJECT%", subject)
|
|
||||||
.replace("%CONTENT%", messageContent)
|
|
||||||
.replace("%INFO%", infoContent)
|
.replace("%INFO%", infoContent)
|
||||||
printDocument(html, jobName)
|
printDocument(html, jobName)
|
||||||
}
|
}
|
||||||
|
@ -181,34 +175,69 @@ class MessagePreviewPresenter @Inject constructor(
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun deleteMessage() {
|
private fun restoreMessage() {
|
||||||
message ?: return
|
val message = messageWithAttachments?.message ?: return
|
||||||
|
|
||||||
view?.run {
|
view?.run {
|
||||||
showContent(false)
|
showContent(false)
|
||||||
showProgress(true)
|
showProgress(true)
|
||||||
showOptions(show = false, isReplayable = false)
|
showOptions(
|
||||||
|
show = false,
|
||||||
|
isReplayable = false,
|
||||||
|
isRestorable = false,
|
||||||
|
)
|
||||||
showErrorView(false)
|
showErrorView(false)
|
||||||
}
|
}
|
||||||
|
Timber.i("Restore message ${message.messageGlobalKey}")
|
||||||
Timber.i("Delete message ${message?.messageGlobalKey}")
|
|
||||||
|
|
||||||
presenterScope.launch {
|
presenterScope.launch {
|
||||||
runCatching {
|
runCatching {
|
||||||
val student = studentRepository.getCurrentStudent(decryptPass = true)
|
val student = studentRepository.getCurrentStudent(decryptPass = true)
|
||||||
val mailbox = messageRepository.getMailboxByStudent(student)
|
val mailbox = messageRepository.getMailboxByStudent(student)
|
||||||
messageRepository.deleteMessage(student, mailbox, message!!)
|
messageRepository.restoreMessages(student, mailbox, listOfNotNull(message))
|
||||||
}
|
}
|
||||||
.onFailure {
|
.onFailure {
|
||||||
retryCallback = { onMessageDelete() }
|
retryCallback = { onMessageRestore() }
|
||||||
errorHandler.dispatch(it)
|
errorHandler.dispatch(it)
|
||||||
}
|
}
|
||||||
.onSuccess {
|
.onSuccess {
|
||||||
view?.run {
|
view?.run {
|
||||||
showMessage(deleteMessageSuccessString)
|
showMessage(restoreMessageSuccessString)
|
||||||
popView()
|
popView()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
view?.showProgress(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun deleteMessage() {
|
||||||
|
messageWithAttachments?.message ?: return
|
||||||
|
|
||||||
|
view?.run {
|
||||||
|
showContent(false)
|
||||||
|
showProgress(true)
|
||||||
|
showOptions(
|
||||||
|
show = false,
|
||||||
|
isReplayable = false,
|
||||||
|
isRestorable = false,
|
||||||
|
)
|
||||||
|
showErrorView(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
Timber.i("Delete message ${messageWithAttachments?.message?.messageGlobalKey}")
|
||||||
|
|
||||||
|
presenterScope.launch {
|
||||||
|
runCatching {
|
||||||
|
val student = studentRepository.getCurrentStudent(decryptPass = true)
|
||||||
|
messageRepository.deleteMessage(student, messageWithAttachments?.message!!)
|
||||||
|
}.onFailure {
|
||||||
|
retryCallback = { onMessageDelete() }
|
||||||
|
errorHandler.dispatch(it)
|
||||||
|
}.onSuccess {
|
||||||
|
view?.run {
|
||||||
|
showMessage(deleteMessageSuccessString)
|
||||||
|
popView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
view?.showProgress(false)
|
view?.showProgress(false)
|
||||||
}
|
}
|
||||||
|
@ -224,6 +253,11 @@ class MessagePreviewPresenter @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onMessageRestore(): Boolean {
|
||||||
|
restoreMessage()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
fun onMessageDelete(): Boolean {
|
fun onMessageDelete(): Boolean {
|
||||||
deleteMessage()
|
deleteMessage()
|
||||||
return true
|
return true
|
||||||
|
@ -232,20 +266,39 @@ class MessagePreviewPresenter @Inject constructor(
|
||||||
private fun initOptions() {
|
private fun initOptions() {
|
||||||
view?.apply {
|
view?.apply {
|
||||||
showOptions(
|
showOptions(
|
||||||
show = message != null,
|
show = messageWithAttachments?.message != null,
|
||||||
isReplayable = message?.folderId != MessageFolder.SENT.id,
|
isReplayable = messageWithAttachments?.message?.folderId == MessageFolder.RECEIVED.id,
|
||||||
|
isRestorable = messageWithAttachments?.message?.folderId == MessageFolder.TRASHED.id,
|
||||||
)
|
)
|
||||||
message?.let {
|
|
||||||
when (it.folderId == MessageFolder.TRASHED.id) {
|
|
||||||
true -> setDeletedOptionsLabels()
|
|
||||||
false -> setNotDeletedOptionsLabels()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onCreateOptionsMenu() {
|
fun onCreateOptionsMenu() {
|
||||||
initOptions()
|
initOptions()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onMute(): Boolean {
|
||||||
|
val message = messageWithAttachments?.message ?: return false
|
||||||
|
val isMuted = messageWithAttachments?.mutedMessageSender != null
|
||||||
|
|
||||||
|
presenterScope.launch {
|
||||||
|
runCatching {
|
||||||
|
when (isMuted) {
|
||||||
|
true -> {
|
||||||
|
messageRepository.unmuteMessage(message.correspondents)
|
||||||
|
view?.run { showMessage(unmuteMessageSuccessString) }
|
||||||
|
}
|
||||||
|
|
||||||
|
false -> {
|
||||||
|
messageRepository.muteMessage(message.correspondents)
|
||||||
|
view?.run { showMessage(muteMessageSuccessString) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.onFailure {
|
||||||
|
errorHandler.dispatch(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
view?.updateMuteToggleButton(isMuted)
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,12 @@ interface MessagePreviewView : BaseView {
|
||||||
|
|
||||||
val deleteMessageSuccessString: String
|
val deleteMessageSuccessString: String
|
||||||
|
|
||||||
|
val muteMessageSuccessString: String
|
||||||
|
|
||||||
|
val unmuteMessageSuccessString: String
|
||||||
|
|
||||||
|
val restoreMessageSuccessString: String
|
||||||
|
|
||||||
val messageNoSubjectString: String
|
val messageNoSubjectString: String
|
||||||
|
|
||||||
val printHTML: String
|
val printHTML: String
|
||||||
|
@ -19,6 +25,8 @@ interface MessagePreviewView : BaseView {
|
||||||
|
|
||||||
fun setMessageWithAttachment(item: MessageWithAttachment)
|
fun setMessageWithAttachment(item: MessageWithAttachment)
|
||||||
|
|
||||||
|
fun updateMuteToggleButton(isMuted: Boolean)
|
||||||
|
|
||||||
fun showProgress(show: Boolean)
|
fun showProgress(show: Boolean)
|
||||||
|
|
||||||
fun showContent(show: Boolean)
|
fun showContent(show: Boolean)
|
||||||
|
@ -29,11 +37,7 @@ interface MessagePreviewView : BaseView {
|
||||||
|
|
||||||
fun setErrorRetryCallback(callback: () -> Unit)
|
fun setErrorRetryCallback(callback: () -> Unit)
|
||||||
|
|
||||||
fun showOptions(show: Boolean, isReplayable: Boolean)
|
fun showOptions(show: Boolean, isReplayable: Boolean, isRestorable: Boolean)
|
||||||
|
|
||||||
fun setDeletedOptionsLabels()
|
|
||||||
|
|
||||||
fun setNotDeletedOptionsLabels()
|
|
||||||
|
|
||||||
fun openMessageReply(message: Message?)
|
fun openMessageReply(message: Message?)
|
||||||
|
|
||||||
|
|
|
@ -203,7 +203,7 @@ class SendMessagePresenter @Inject constructor(
|
||||||
subject = subject,
|
subject = subject,
|
||||||
content = content,
|
content = content,
|
||||||
recipients = recipients,
|
recipients = recipients,
|
||||||
mailboxId = mailbox.globalKey,
|
mailbox = mailbox,
|
||||||
)
|
)
|
||||||
}.logResourceStatus("sending message").onEach {
|
}.logResourceStatus("sending message").onEach {
|
||||||
when (it) {
|
when (it) {
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Reference in a new issue