1
0
Fork 1

Compare commits

..

No commits in common. "2.0.2" and "1.8.2" have entirely different histories.
2.0.2 ... 1.8.2

356 changed files with 3788 additions and 9541 deletions

1
.gitignore vendored
View file

@ -119,4 +119,3 @@ Thumbs.db
app/src/release/agconnect-services.json app/src/release/agconnect-services.json
app/src/release/agconnect-credentials.json app/src/release/agconnect-credentials.json
.idea/deploymentTargetDropDown.xml .idea/deploymentTargetDropDown.xml
.idea/kotlinc.xml

View file

@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier same "printed page" as the copyright notice for easier
identification within third-party archives. identification within third-party archives.
Copyright 2023 Wulkanowy Copyright 2022 Wulkanowy
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View file

@ -16,15 +16,15 @@ apply from: 'hooks.gradle'
android { android {
namespace 'io.github.wulkanowy' namespace 'io.github.wulkanowy'
compileSdkVersion 33 compileSdkVersion 32
defaultConfig { defaultConfig {
applicationId "io.github.wulkanowy" applicationId "io.github.wulkanowy"
testApplicationId "io.github.tests.wulkanowy" testApplicationId "io.github.tests.wulkanowy"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 33 targetSdkVersion 32
versionCode 124 versionCode 117
versionName "2.0.2" versionName "1.8.2"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
resValue "string", "app_name", "Wulkanowy" resValue "string", "app_name", "Wulkanowy"
@ -162,7 +162,7 @@ play {
track = 'production' track = 'production'
releaseStatus = com.github.triplet.gradle.androidpublisher.ReleaseStatus.IN_PROGRESS releaseStatus = com.github.triplet.gradle.androidpublisher.ReleaseStatus.IN_PROGRESS
userFraction = 0.10d userFraction = 0.10d
updatePriority = 2 updatePriority = 5
enabled.set(false) enabled.set(false)
} }
@ -177,36 +177,36 @@ huaweiPublish {
} }
ext { ext {
work_manager = "2.8.1" work_manager = "2.7.1"
android_hilt = "1.0.0" android_hilt = "1.0.0"
room = "2.5.1" room = "2.4.3"
chucker = "3.5.2" chucker = "3.5.2"
mockk = "1.13.5" mockk = "1.13.2"
coroutines = "1.6.4" coroutines = "1.6.4"
} }
dependencies { dependencies {
implementation 'io.github.wulkanowy:sdk:2.0.1' implementation "io.github.wulkanowy:sdk:1.8.2"
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.8'
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0" implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines"
implementation "androidx.core:core-ktx:1.10.0" implementation "androidx.core:core-ktx:1.8.0"
implementation 'androidx.core:core-splashscreen:1.0.1' implementation 'androidx.core:core-splashscreen:1.0.0'
implementation "androidx.activity:activity-ktx:1.7.1" implementation "androidx.activity:activity-ktx:1.5.1"
implementation "androidx.appcompat:appcompat:1.6.1" implementation "androidx.appcompat:appcompat:1.5.1"
implementation "androidx.fragment:fragment-ktx:1.5.7" implementation "androidx.fragment:fragment-ktx:1.5.4"
implementation "androidx.annotation:annotation:1.6.0" implementation "androidx.annotation:annotation:1.5.0"
implementation "androidx.preference:preference-ktx:1.2.0" implementation "androidx.preference:preference-ktx:1.2.0"
implementation "androidx.recyclerview:recyclerview:1.3.0" implementation "androidx.recyclerview:recyclerview:1.2.1"
implementation "androidx.viewpager2:viewpager2:1.1.0-beta01" implementation "androidx.viewpager2:viewpager2:1.1.0-beta01"
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0" implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
implementation "androidx.constraintlayout:constraintlayout:2.1.4" implementation "androidx.constraintlayout:constraintlayout:2.1.4"
implementation "androidx.coordinatorlayout:coordinatorlayout:1.2.0" implementation "androidx.coordinatorlayout:coordinatorlayout:1.2.0"
implementation "com.google.android.material:material:1.8.0" implementation "com.google.android.material:material:1.7.0"
implementation "com.github.wulkanowy:material-chips-input:2.3.1" implementation "com.github.wulkanowy:material-chips-input:2.3.1"
implementation "com.github.PhilJay:MPAndroidChart:v3.1.0" implementation "com.github.PhilJay:MPAndroidChart:v3.1.0"
implementation 'com.github.lopspower:CircularImageView:4.3.0' implementation 'com.github.lopspower:CircularImageView:4.3.0'
@ -214,7 +214,7 @@ dependencies {
implementation "androidx.work:work-runtime-ktx:$work_manager" implementation "androidx.work:work-runtime-ktx:$work_manager"
playImplementation "androidx.work:work-gcm:$work_manager" playImplementation "androidx.work:work-gcm:$work_manager"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.6.1" implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1"
implementation "androidx.room:room-runtime:$room" implementation "androidx.room:room-runtime:$room"
implementation "androidx.room:room-ktx:$room" implementation "androidx.room:room-ktx:$room"
@ -229,30 +229,28 @@ dependencies {
implementation "com.github.YarikSOffice:lingver:1.3.0" implementation "com.github.YarikSOffice:lingver:1.3.0"
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:0.8.0"
implementation "com.squareup.okhttp3:logging-interceptor:4.11.0" implementation "com.squareup.okhttp3:logging-interceptor:4.10.0"
implementation "com.jakewharton.timber:timber:5.0.1" implementation "com.jakewharton.timber:timber:5.0.1"
implementation "at.favre.lib:slf4j-timber:1.0.1" implementation "at.favre.lib:slf4j-timber:1.0.1"
implementation 'com.github.bastienpaulfr:Treessence:1.0.5' implementation 'com.github.bastienpaulfr:Treessence:1.0.5'
implementation "com.mikepenz:aboutlibraries-core:$about_libraries" implementation "com.mikepenz:aboutlibraries-core:$about_libraries"
implementation "io.coil-kt:coil:2.3.0" implementation "io.coil-kt:coil:2.2.2"
implementation "io.github.wulkanowy:AppKillerManager:3.0.1" implementation "io.github.wulkanowy:AppKillerManager:3.0.0"
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.8.0'
implementation 'org.apache.commons:commons-text:1.10.0'
playImplementation platform('com.google.firebase:firebase-bom:31.5.0') playImplementation platform('com.google.firebase:firebase-bom:31.0.3')
playImplementation 'com.google.firebase:firebase-analytics-ktx' playImplementation 'com.google.firebase:firebase-analytics-ktx'
playImplementation 'com.google.firebase:firebase-messaging:' playImplementation 'com.google.firebase:firebase-messaging:'
playImplementation 'com.google.firebase:firebase-crashlytics:' playImplementation 'com.google.firebase:firebase-crashlytics:'
playImplementation 'com.google.firebase:firebase-config-ktx'
playImplementation 'com.google.android.play:core:1.10.3' playImplementation 'com.google.android.play:core:1.10.3'
playImplementation 'com.google.android.play:core-ktx:1.8.1' playImplementation 'com.google.android.play:core-ktx:1.8.1'
playImplementation 'com.google.android.gms:play-services-ads:22.0.0' playImplementation 'com.google.android.gms:play-services-ads:21.3.0'
hmsImplementation 'com.huawei.hms:hianalytics:6.9.1.200' hmsImplementation 'com.huawei.hms:hianalytics:6.8.0.300'
hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.9.0.300' hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.7.3.300'
releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker" releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker"
@ -265,17 +263,17 @@ dependencies {
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines" testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines"
testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version" testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
testImplementation 'org.robolectric:robolectric:4.10' testImplementation 'org.robolectric:robolectric:4.9'
testImplementation "androidx.test:runner:1.5.2" testImplementation "androidx.test:runner:1.5.1"
testImplementation "androidx.test.ext:junit:1.1.5" testImplementation "androidx.test.ext:junit:1.1.4"
testImplementation "androidx.test:core:1.5.0" testImplementation "androidx.test:core:1.5.0"
testImplementation "androidx.room:room-testing:$room" testImplementation "androidx.room:room-testing:$room"
testImplementation "com.google.dagger:hilt-android-testing:$hilt_version" testImplementation "com.google.dagger:hilt-android-testing:$hilt_version"
kaptTest "com.google.dagger:hilt-android-compiler:$hilt_version" kaptTest "com.google.dagger:hilt-android-compiler:$hilt_version"
androidTestImplementation "androidx.test:core:1.5.0" androidTestImplementation "androidx.test:core:1.4.0"
androidTestImplementation "androidx.test:runner:1.5.2" androidTestImplementation "androidx.test:runner:1.4.0"
androidTestImplementation "androidx.test.ext:junit:1.1.5" androidTestImplementation "androidx.test.ext:junit:1.1.3"
androidTestImplementation "io.mockk:mockk-android:$mockk" androidTestImplementation "io.mockk:mockk-android:$mockk"
androidTestImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version" androidTestImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
} }

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/colorIcon" /> <background android:drawable="@color/colorPrimary" />
<foreground android:drawable="@drawable/ic_launcher_foreground_dev" /> <foreground android:drawable="@drawable/ic_launcher_foreground_dev" />
<monochrome android:drawable="@drawable/ic_launcher_foreground_dev_mono" /> </adaptive-icon>
</adaptive-icon>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/colorPrimary" />
<foreground android:drawable="@drawable/ic_launcher_foreground_dev" />
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View file

@ -1,7 +0,0 @@
package io.github.wulkanowy.utils
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class RemoteConfigHelper @Inject constructor() : BaseRemoteConfigHelper()

View file

@ -1,7 +0,0 @@
package io.github.wulkanowy.utils
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class RemoteConfigHelper @Inject constructor() : BaseRemoteConfigHelper()

View file

@ -8,8 +8,7 @@
<uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.VIBRATE" /> <uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" /> <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<queries> <queries>
<intent> <intent>
@ -37,14 +36,13 @@
<application <application
android:name=".WulkanowyApp" android:name=".WulkanowyApp"
android:allowBackup="false" android:allowBackup="false"
android:dataExtractionRules="@xml/data_extraction_rules"
android:enableOnBackInvokedCallback="true"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config" android:networkSecurityConfig="@xml/network_security_config"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="false" android:supportsRtl="false"
android:theme="@style/WulkanowyTheme" android:theme="@style/WulkanowyTheme"
tools:ignore="DataExtractionRules,UnusedAttribute"> tools:ignore="GoogleAppIndexingWarning,UnusedAttribute">
<activity <activity
android:name=".ui.modules.splash.SplashActivity" android:name=".ui.modules.splash.SplashActivity"
android:exported="true" android:exported="true"
@ -72,7 +70,7 @@
android:name=".ui.modules.message.send.SendMessageActivity" android:name=".ui.modules.message.send.SendMessageActivity"
android:configChanges="orientation|screenSize" android:configChanges="orientation|screenSize"
android:label="@string/send_message_title" android:label="@string/send_message_title"
android:theme="@style/WulkanowyTheme.NoActionBar" android:theme="@style/WulkanowyTheme.MessageSend"
android:windowSoftInputMode="adjustResize" /> android:windowSoftInputMode="adjustResize" />
<activity <activity
android:name=".ui.modules.timetablewidget.TimetableWidgetConfigureActivity" android:name=".ui.modules.timetablewidget.TimetableWidgetConfigureActivity"

View file

@ -34,15 +34,11 @@ class WulkanowyApp : Application(), Configuration.Provider {
@Inject @Inject
lateinit var adsHelper: AdsHelper lateinit var adsHelper: AdsHelper
@Inject
lateinit var remoteConfigHelper: RemoteConfigHelper
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
initializeAppLanguage() initializeAppLanguage()
themeManager.applyDefaultTheme() themeManager.applyDefaultTheme()
adsHelper.initialize() adsHelper.initialize()
remoteConfigHelper.initialize()
initLogging() initLogging()
} }

View file

@ -19,7 +19,7 @@ import io.github.wulkanowy.data.db.SharedPrefProvider
import io.github.wulkanowy.data.repositories.PreferencesRepository 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 kotlinx.serialization.ExperimentalSerializationApi
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
@ -36,11 +36,10 @@ internal class DataModule {
@Singleton @Singleton
@Provides @Provides
fun provideSdk(chuckerInterceptor: ChuckerInterceptor, remoteConfig: RemoteConfigHelper) = fun provideSdk(chuckerInterceptor: ChuckerInterceptor) =
Sdk().apply { Sdk().apply {
androidVersion = android.os.Build.VERSION.RELEASE androidVersion = android.os.Build.VERSION.RELEASE
buildTag = android.os.Build.MODEL buildTag = android.os.Build.MODEL
userAgentTemplate = remoteConfig.userAgentTemplate
setSimpleHttpLogger { Timber.d(it) } setSimpleHttpLogger { Timber.d(it) }
// for debug only // for debug only
@ -80,6 +79,7 @@ internal class DataModule {
.readTimeout(30, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS)
.build() .build()
@OptIn(ExperimentalSerializationApi::class)
@Singleton @Singleton
@Provides @Provides
fun provideRetrofit( fun provideRetrofit(

View file

@ -48,7 +48,6 @@ import javax.inject.Singleton
AutoMigration(from = 46, to = 47), AutoMigration(from = 46, to = 47),
AutoMigration(from = 47, to = 48), AutoMigration(from = 47, to = 48),
AutoMigration(from = 51, to = 52), AutoMigration(from = 51, to = 52),
AutoMigration(from = 54, to = 55, spec = Migration55::class),
], ],
version = AppDatabase.VERSION_SCHEMA, version = AppDatabase.VERSION_SCHEMA,
exportSchema = true exportSchema = true
@ -57,7 +56,7 @@ import javax.inject.Singleton
abstract class AppDatabase : RoomDatabase() { abstract class AppDatabase : RoomDatabase() {
companion object { companion object {
const val VERSION_SCHEMA = 55 const val VERSION_SCHEMA = 54
fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf( fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf(
Migration2(), Migration2(),

View file

@ -1,8 +1,8 @@
package io.github.wulkanowy.data.db.dao package io.github.wulkanowy.data.db.dao
import androidx.room.* import androidx.room.*
import androidx.room.OnConflictStrategy.ABORT
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.db.entities.StudentName
import io.github.wulkanowy.data.db.entities.StudentNickAndAvatar import io.github.wulkanowy.data.db.entities.StudentNickAndAvatar
import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.data.db.entities.StudentWithSemesters
import javax.inject.Singleton import javax.inject.Singleton
@ -11,7 +11,7 @@ import javax.inject.Singleton
@Dao @Dao
abstract class StudentDao { abstract class StudentDao {
@Insert(onConflict = OnConflictStrategy.ABORT) @Insert(onConflict = ABORT)
abstract suspend fun insertAll(student: List<Student>): List<Long> abstract suspend fun insertAll(student: List<Student>): List<Long>
@Delete @Delete
@ -20,9 +20,6 @@ abstract class StudentDao {
@Update(entity = Student::class) @Update(entity = Student::class)
abstract suspend fun update(studentNickAndAvatar: StudentNickAndAvatar) abstract suspend fun update(studentNickAndAvatar: StudentNickAndAvatar)
@Update(entity = Student::class)
abstract suspend fun update(studentName: StudentName)
@Query("SELECT * FROM Students WHERE is_current = 1") @Query("SELECT * FROM Students WHERE is_current = 1")
abstract suspend fun loadCurrent(): Student? abstract suspend fun loadCurrent(): Student?

View file

@ -22,7 +22,6 @@ data class Exam(
val subject: String, val subject: String,
@Deprecated("not available anymore")
val group: String, val group: String,
val type: String, val type: String,

View file

@ -2,14 +2,16 @@ package io.github.wulkanowy.data.db.entities
import androidx.room.ColumnInfo import androidx.room.ColumnInfo
import androidx.room.Entity import androidx.room.Entity
import androidx.room.PrimaryKey
import java.io.Serializable import java.io.Serializable
@Entity( @Entity(tableName = "MessageAttachments")
tableName = "MessageAttachments",
primaryKeys = ["message_global_key", "url", "filename"],
)
data class MessageAttachment( data class MessageAttachment(
@PrimaryKey
@ColumnInfo(name = "real_id")
val realId: Int,
@ColumnInfo(name = "message_global_key") @ColumnInfo(name = "message_global_key")
val messageGlobalKey: String, val messageGlobalKey: String,

View file

@ -1,18 +0,0 @@
package io.github.wulkanowy.data.db.entities
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import java.io.Serializable
@Entity
data class StudentName(
@ColumnInfo(name = "student_name")
val studentName: String
) : Serializable {
@PrimaryKey
var id: Long = 0
}

View file

@ -1,17 +0,0 @@
package io.github.wulkanowy.data.db.migrations
import androidx.room.DeleteColumn
import androidx.room.migration.AutoMigrationSpec
import androidx.sqlite.db.SupportSQLiteDatabase
@DeleteColumn(
tableName = "MessageAttachments",
columnName = "real_id",
)
class Migration55 : AutoMigrationSpec {
override fun onPostMigrate(db: SupportSQLiteDatabase) {
db.execSQL("DELETE FROM Messages")
db.execSQL("DELETE FROM MessageAttachments")
}
}

View file

@ -10,9 +10,9 @@ fun List<SdkConference>.mapToEntities(semester: Semester) = map {
diaryId = semester.diaryId, diaryId = semester.diaryId,
agenda = it.agenda, agenda = it.agenda,
conferenceId = it.id, conferenceId = it.id,
date = it.date.toInstant(), date = it.dateZoned.toInstant(),
presentOnConference = it.presentOnConference, presentOnConference = it.presentOnConference,
subject = it.topic, subject = it.subject,
title = it.place, title = it.title
) )
} }

View file

@ -11,7 +11,7 @@ fun List<SdkExam>.mapToEntities(semester: Semester) = map {
date = it.date, date = it.date,
entryDate = it.entryDate, entryDate = it.entryDate,
subject = it.subject, subject = it.subject,
group = "", group = it.group,
type = it.type, type = it.type,
description = it.description, description = it.description,
teacher = it.teacher, teacher = it.teacher,

View file

@ -2,7 +2,6 @@ package io.github.wulkanowy.data.mappers
import io.github.wulkanowy.data.db.entities.* import io.github.wulkanowy.data.db.entities.*
import io.github.wulkanowy.sdk.pojo.MailboxType import io.github.wulkanowy.sdk.pojo.MailboxType
import timber.log.Timber
import io.github.wulkanowy.sdk.pojo.Message as SdkMessage import io.github.wulkanowy.sdk.pojo.Message as SdkMessage
import io.github.wulkanowy.sdk.pojo.MessageAttachment as SdkMessageAttachment import io.github.wulkanowy.sdk.pojo.MessageAttachment as SdkMessageAttachment
import io.github.wulkanowy.sdk.pojo.Recipient as SdkRecipient import io.github.wulkanowy.sdk.pojo.Recipient as SdkRecipient
@ -17,16 +16,13 @@ fun List<SdkMessage>.mapToEntities(
mailboxKey = mailbox?.globalKey ?: allMailboxes.find { box -> mailboxKey = mailbox?.globalKey ?: allMailboxes.find { box ->
box.fullName == it.mailbox box.fullName == it.mailbox
}?.globalKey.let { mailboxKey -> }?.globalKey.let { mailboxKey ->
if (mailboxKey == null) { requireNotNull(mailboxKey) { "Can't find ${it.mailbox} in $allMailboxes" }
Timber.e("Can't find ${it.mailbox} in $allMailboxes")
"unknown"
} else mailboxKey
}, },
email = student.email, email = student.email,
messageId = it.id, messageId = it.id,
correspondents = it.correspondents, correspondents = it.correspondents,
subject = it.subject.trim(), subject = it.subject.trim(),
date = it.date.toInstant(), date = it.dateZoned.toInstant(),
folderId = it.folderId, folderId = it.folderId,
unread = it.unread, unread = it.unread,
unreadBy = it.unreadBy, unreadBy = it.unreadBy,
@ -40,6 +36,7 @@ fun List<SdkMessage>.mapToEntities(
fun List<SdkMessageAttachment>.mapToEntities(messageGlobalKey: String) = map { fun List<SdkMessageAttachment>.mapToEntities(messageGlobalKey: String) = map {
MessageAttachment( MessageAttachment(
messageGlobalKey = messageGlobalKey, messageGlobalKey = messageGlobalKey,
realId = it.url.hashCode(),
url = it.url, url = it.url,
filename = it.filename filename = it.filename
) )

View file

@ -9,7 +9,7 @@ import io.github.wulkanowy.sdk.pojo.Token as SdkToken
fun List<SdkDevice>.mapToEntities(student: Student) = map { fun List<SdkDevice>.mapToEntities(student: Student) = map {
MobileDevice( MobileDevice(
userLoginId = student.userLoginId, userLoginId = student.userLoginId,
date = it.createDate.toInstant(), date = it.createDateZoned.toInstant(),
deviceId = it.id, deviceId = it.id,
name = it.name name = it.name
) )

View file

@ -1,88 +0,0 @@
package io.github.wulkanowy.data.mappers
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
import io.github.wulkanowy.data.pojos.*
import java.time.Instant
import io.github.wulkanowy.sdk.pojo.RegisterStudent as SdkRegisterStudent
import io.github.wulkanowy.sdk.pojo.RegisterUser as SdkRegisterUser
fun SdkRegisterUser.mapToPojo(password: String?) = RegisterUser(
email = email,
login = login,
password = password,
scrapperBaseUrl = scrapperBaseUrl,
loginMode = loginMode,
loginType = loginType,
symbols = symbols.map { registerSymbol ->
RegisterSymbol(
symbol = registerSymbol.symbol,
error = registerSymbol.error,
hebeBaseUrl = registerSymbol.hebeBaseUrl,
keyId = registerSymbol.keyId,
privatePem = registerSymbol.privatePem,
userName = registerSymbol.userName,
schools = registerSymbol.schools.map {
RegisterUnit(
userLoginId = it.userLoginId,
schoolId = it.schoolId,
schoolName = it.schoolName,
schoolShortName = it.schoolShortName,
parentIds = it.parentIds,
studentIds = it.studentIds,
employeeIds = it.employeeIds,
error = it.error,
students = it.subjects
.filterIsInstance<SdkRegisterStudent>()
.map { registerSubject ->
RegisterStudent(
studentId = registerSubject.studentId,
studentName = registerSubject.studentName,
studentSecondName = registerSubject.studentSecondName,
studentSurname = registerSubject.studentSurname,
className = registerSubject.className,
classId = registerSubject.classId,
isParent = registerSubject.isParent,
semesters = registerSubject.semesters
.mapToEntities(registerSubject.studentId),
)
},
)
}
)
},
)
fun RegisterStudent.mapToStudentWithSemesters(
user: RegisterUser,
symbol: RegisterSymbol,
unit: RegisterUnit,
colors: List<Long>,
): StudentWithSemesters = StudentWithSemesters(
semesters = semesters,
student = Student(
email = user.login, // for compatibility
userName = symbol.userName,
userLoginId = unit.userLoginId,
isParent = isParent,
className = className,
classId = classId,
studentId = studentId,
symbol = symbol.symbol,
loginType = user.loginType?.name.orEmpty(),
schoolName = unit.schoolName,
schoolShortName = unit.schoolShortName,
schoolSymbol = unit.schoolId,
studentName = "$studentName $studentSurname",
loginMode = user.loginMode.name,
scrapperBaseUrl = user.scrapperBaseUrl.orEmpty(),
mobileBaseUrl = symbol.hebeBaseUrl.orEmpty(),
certificateKey = symbol.keyId.orEmpty(),
privateKey = symbol.privatePem.orEmpty(),
password = user.password.orEmpty(),
isCurrent = false,
registrationDate = Instant.now(),
).apply {
avatarColor = colors.random()
},
)

View file

@ -0,0 +1,37 @@
package io.github.wulkanowy.data.mappers
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
import java.time.Instant
import io.github.wulkanowy.sdk.pojo.Student as SdkStudent
fun List<SdkStudent>.mapToEntities(password: String = "", colors: List<Long>) = map {
StudentWithSemesters(
student = Student(
email = it.email,
password = password,
isParent = it.isParent,
symbol = it.symbol,
studentId = it.studentId,
userLoginId = it.userLoginId,
userName = it.userName,
studentName = it.studentName + " " + it.studentSurname,
schoolSymbol = it.schoolSymbol,
schoolShortName = it.schoolShortName,
schoolName = it.schoolName,
className = it.className,
classId = it.classId,
scrapperBaseUrl = it.scrapperBaseUrl,
loginType = it.loginType.name,
isCurrent = false,
registrationDate = Instant.now(),
mobileBaseUrl = it.mobileBaseUrl,
privateKey = it.privateKey,
certificateKey = it.certificateKey,
loginMode = it.loginMode.name,
).apply {
avatarColor = colors.random()
},
semesters = it.semesters.mapToEntities(it.studentId)
)
}

View file

@ -5,10 +5,10 @@ import io.github.wulkanowy.data.db.entities.Timetable
import io.github.wulkanowy.data.db.entities.TimetableAdditional import io.github.wulkanowy.data.db.entities.TimetableAdditional
import io.github.wulkanowy.data.db.entities.TimetableHeader import io.github.wulkanowy.data.db.entities.TimetableHeader
import io.github.wulkanowy.data.pojos.TimetableFull import io.github.wulkanowy.data.pojos.TimetableFull
import io.github.wulkanowy.sdk.pojo.Timetable as SdkTimetableFull import io.github.wulkanowy.sdk.pojo.TimetableFull as SdkTimetableFull
import io.github.wulkanowy.sdk.pojo.TimetableDayHeader as SdkTimetableHeader import io.github.wulkanowy.sdk.pojo.TimetableDayHeader as SdkTimetableHeader
import io.github.wulkanowy.sdk.pojo.Lesson as SdkLesson import io.github.wulkanowy.sdk.pojo.Timetable as SdkTimetable
import io.github.wulkanowy.sdk.pojo.LessonAdditional as SdkTimetableAdditional import io.github.wulkanowy.sdk.pojo.TimetableAdditional as SdkTimetableAdditional
fun SdkTimetableFull.mapToEntities(semester: Semester) = TimetableFull( fun SdkTimetableFull.mapToEntities(semester: Semester) = TimetableFull(
lessons = lessons.mapToEntities(semester), lessons = lessons.mapToEntities(semester),
@ -16,13 +16,13 @@ fun SdkTimetableFull.mapToEntities(semester: Semester) = TimetableFull(
headers = headers.mapToEntities(semester) headers = headers.mapToEntities(semester)
) )
fun List<SdkLesson>.mapToEntities(semester: Semester) = map { fun List<SdkTimetable>.mapToEntities(semester: Semester) = map {
Timetable( Timetable(
studentId = semester.studentId, studentId = semester.studentId,
diaryId = semester.diaryId, diaryId = semester.diaryId,
number = it.number, number = it.number,
start = it.start.toInstant(), start = it.startZoned.toInstant(),
end = it.end.toInstant(), end = it.endZoned.toInstant(),
date = it.date, date = it.date,
subject = it.subject, subject = it.subject,
subjectOld = it.subjectOld, subjectOld = it.subjectOld,
@ -45,8 +45,8 @@ fun List<SdkTimetableAdditional>.mapToEntities(semester: Semester) = map {
diaryId = semester.diaryId, diaryId = semester.diaryId,
subject = it.subject, subject = it.subject,
date = it.date, date = it.date,
start = it.start.toInstant(), start = it.startZoned.toInstant(),
end = it.end.toInstant(), end = it.endZoned.toInstant(),
) )
} }

View file

@ -1,48 +0,0 @@
package io.github.wulkanowy.data.pojos
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.sdk.scrapper.Scrapper
data class RegisterUser(
val email: String,
val password: String?,
val login: String, // may be the same as email
val scrapperBaseUrl: String?,
val loginType: Scrapper.LoginType?,
val loginMode: Sdk.Mode,
val symbols: List<RegisterSymbol>,
) : java.io.Serializable
data class RegisterSymbol(
val symbol: String,
val error: Throwable?,
val hebeBaseUrl: String?,
val keyId: String?,
val privatePem: String?,
val userName: String,
val schools: List<RegisterUnit>,
) : java.io.Serializable
data class RegisterUnit(
val userLoginId: Int,
val schoolId: String,
val schoolName: String,
val schoolShortName: String,
val parentIds: List<Int>,
val studentIds: List<Int>,
val employeeIds: List<Int>,
val error: Throwable?,
val students: List<RegisterStudent>,
) : java.io.Serializable
data class RegisterStudent(
val studentId: Int,
val studentName: String,
val studentSecondName: String,
val studentSurname: String,
val className: String,
val classId: Int,
val isParent: Boolean,
val semesters: List<Semester>,
) : java.io.Serializable

View file

@ -19,6 +19,7 @@ class AppCreatorRepository @Inject constructor(
) { ) {
@OptIn(ExperimentalSerializationApi::class) @OptIn(ExperimentalSerializationApi::class)
@Suppress("BlockingMethodInNonBlockingContext")
suspend fun getAppCreators() = withContext(dispatchers.io) { suspend fun getAppCreators() = withContext(dispatchers.io) {
val inputStream = context.assets.open("contributors.json").buffered() val inputStream = context.assets.open("contributors.json").buffered()
json.decodeFromStream<List<Contributor>>(inputStream) json.decodeFromStream<List<Contributor>>(inputStream)

View file

@ -59,7 +59,7 @@ class AttendanceRepository @Inject constructor(
} }
sdk.init(student) sdk.init(student)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear) .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
.getAttendance(start.monday, end.sunday) .getAttendance(start.monday, end.sunday, semester.semesterId)
.mapToEntities(semester, lessons) .mapToEntities(semester, lessons)
}, },
saveFetchResult = { old, new -> saveFetchResult = { old, new ->

View file

@ -52,7 +52,7 @@ class ExamRepository @Inject constructor(
fetch = { fetch = {
sdk.init(student) sdk.init(student)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear) .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
.getExams(start.startExamsDay, start.endExamsDay) .getExams(start.startExamsDay, start.endExamsDay, semester.semesterId)
.mapToEntities(semester) .mapToEntities(semester)
}, },
saveFetchResult = { old, new -> saveFetchResult = { old, new ->

View file

@ -103,10 +103,7 @@ class MessageRepository @Inject constructor(
messagesDb.loadMessageWithAttachment(message.messageGlobalKey) messagesDb.loadMessageWithAttachment(message.messageGlobalKey)
}, },
fetch = { fetch = {
sdk.init(student).getMessageDetails( sdk.init(student).getMessageDetails(it!!.message.messageGlobalKey, markAsRead)
messageKey = it!!.message.messageGlobalKey,
markAsRead = message.unread && markAsRead,
)
}, },
saveFetchResult = { old, new -> saveFetchResult = { old, new ->
checkNotNull(old) { "Fetched message no longer exist!" } checkNotNull(old) { "Fetched message no longer exist!" }
@ -181,7 +178,7 @@ class MessageRepository @Inject constructor(
).first() ).first()
} }
suspend fun deleteMessage(student: Student, mailbox: Mailbox?, message: Message) { suspend fun deleteMessage(student: Student, mailbox: Mailbox, message: Message) {
deleteMessages(student, mailbox, listOf(message)) deleteMessages(student, mailbox, listOf(message))
} }

View file

@ -42,7 +42,7 @@ class NoteRepository @Inject constructor(
fetch = { fetch = {
sdk.init(student) sdk.init(student)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear) .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
.getNotes() .getNotes(semester.semesterId)
.mapToEntities(semester) .mapToEntities(semester)
}, },
saveFetchResult = { old, new -> saveFetchResult = { old, new ->

View file

@ -2,17 +2,14 @@ package io.github.wulkanowy.data.repositories
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import androidx.annotation.StringRes
import androidx.core.content.edit import androidx.core.content.edit
import com.fredporciuncula.flow.preferences.FlowSharedPreferences import com.fredporciuncula.flow.preferences.FlowSharedPreferences
import com.fredporciuncula.flow.preferences.Preference import com.fredporciuncula.flow.preferences.Preference
import com.fredporciuncula.flow.preferences.Serializer
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.enums.* import io.github.wulkanowy.data.enums.*
import io.github.wulkanowy.ui.modules.dashboard.DashboardItem import io.github.wulkanowy.ui.modules.dashboard.DashboardItem
import io.github.wulkanowy.ui.modules.grade.GradeAverageMode import io.github.wulkanowy.ui.modules.grade.GradeAverageMode
import io.github.wulkanowy.ui.modules.settings.appearance.menuorder.AppMenuItem
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.serialization.decodeFromString import kotlinx.serialization.decodeFromString
@ -31,35 +28,29 @@ class PreferencesRepository @Inject constructor(
private val json: Json, private val json: Json,
) { ) {
val startMenuIndex: Int
get() = getString(R.string.pref_key_start_menu, R.string.pref_default_startup).toInt()
val isShowPresent: Boolean val isShowPresent: Boolean
get() = getBoolean( get() = getBoolean(
R.string.pref_key_attendance_present, R.string.pref_key_attendance_present,
R.bool.pref_default_attendance_present R.bool.pref_default_attendance_present
) )
private val gradeAverageModePref: Preference<GradeAverageMode> val gradeAverageMode: GradeAverageMode
get() = getObjectFlow( get() = GradeAverageMode.getByValue(
R.string.pref_key_grade_average_mode, getString(
R.string.pref_default_grade_average_mode, R.string.pref_key_grade_average_mode,
object : Serializer<GradeAverageMode> { R.string.pref_default_grade_average_mode
override fun serialize(value: GradeAverageMode) = value.value )
override fun deserialize(serialized: String) =
GradeAverageMode.getByValue(serialized)
},
) )
val gradeAverageModeFlow: Flow<GradeAverageMode> val gradeAverageForceCalc: Boolean
get() = gradeAverageModePref.asFlow() get() = getBoolean(
R.string.pref_key_grade_average_force_calc,
private val gradeAverageForceCalcPref: Preference<Boolean> R.bool.pref_default_grade_average_force_calc
get() = flowSharedPref.getBoolean(
context.getString(R.string.pref_key_grade_average_force_calc),
context.resources.getBoolean(R.bool.pref_default_grade_average_force_calc)
) )
val gradeAverageForceCalcFlow: Flow<Boolean>
get() = gradeAverageForceCalcPref.asFlow()
val gradeExpandMode: GradeExpandMode val gradeExpandMode: GradeExpandMode
get() = GradeExpandMode.getByValue( get() = GradeExpandMode.getByValue(
getString( getString(
@ -149,24 +140,12 @@ class PreferencesRepository @Inject constructor(
R.string.pref_default_grade_modifier_plus R.string.pref_default_grade_modifier_plus
).toDouble() ).toDouble()
val gradePlusModifierFlow: Flow<Double>
get() = getStringFlow(
R.string.pref_key_grade_modifier_plus,
R.string.pref_default_grade_modifier_plus
).asFlow().map { it.toDouble() }
val gradeMinusModifier: Double val gradeMinusModifier: Double
get() = getString( get() = getString(
R.string.pref_key_grade_modifier_minus, R.string.pref_key_grade_modifier_minus,
R.string.pref_default_grade_modifier_minus R.string.pref_default_grade_modifier_minus
).toDouble() ).toDouble()
val gradeMinusModifierFlow: Flow<Double>
get() = getStringFlow(
R.string.pref_key_grade_modifier_minus,
R.string.pref_default_grade_modifier_minus
).asFlow().map { it.toDouble() }
val fillMessageContent: Boolean val fillMessageContent: Boolean
get() = getBoolean( get() = getBoolean(
R.string.pref_key_fill_message_content, R.string.pref_key_fill_message_content,
@ -201,17 +180,24 @@ class PreferencesRepository @Inject constructor(
R.bool.pref_default_timetable_show_timers R.bool.pref_default_timetable_show_timers
) )
var isHomeworkFullscreen: Boolean
get() = getBoolean(
R.string.pref_key_homework_fullscreen,
R.bool.pref_default_homework_fullscreen
)
set(value) = sharedPref.edit().putBoolean("homework_fullscreen", value).apply()
val showSubjectsWithoutGrades: Boolean val showSubjectsWithoutGrades: Boolean
get() = getBoolean( get() = getBoolean(
R.string.pref_key_subjects_without_grades, R.string.pref_key_subjects_without_grades,
R.bool.pref_default_subjects_without_grades R.bool.pref_default_subjects_without_grades
) )
val isOptionalArithmeticAverageFlow: Flow<Boolean> val isOptionalArithmeticAverage: Boolean
get() = flowSharedPref.getBoolean( get() = getBoolean(
context.getString(R.string.pref_key_optional_arithmetic_average), R.string.pref_key_optional_arithmetic_average,
context.resources.getBoolean(R.bool.pref_default_optional_arithmetic_average) R.bool.pref_default_optional_arithmetic_average
).asFlow() )
var lasSyncDate: Instant? var lasSyncDate: Instant?
get() = getLong(R.string.pref_key_last_sync_date, R.string.pref_default_last_sync_date) get() = getLong(R.string.pref_key_last_sync_date, R.string.pref_default_last_sync_date)
@ -329,20 +315,6 @@ class PreferencesRepository @Inject constructor(
putBoolean(context.getString(R.string.pref_key_ads_enabled), value) putBoolean(context.getString(R.string.pref_key_ads_enabled), value)
} }
var appMenuItemOrder: List<AppMenuItem>
get() {
val value = sharedPref.getString(PREF_KEY_APP_MENU_ITEM_ORDER, null)
?: return AppMenuItem.defaultAppMenuItemList
return json.decodeFromString(value)
}
set(value) = sharedPref.edit {
putString(
PREF_KEY_APP_MENU_ITEM_ORDER,
json.encodeToString(value)
)
}
var installationId: String var installationId: String
get() = sharedPref.getString(PREF_KEY_INSTALLATION_ID, null).orEmpty() get() = sharedPref.getString(PREF_KEY_INSTALLATION_ID, null).orEmpty()
private set(value) = sharedPref.edit { putString(PREF_KEY_INSTALLATION_ID, value) } private set(value) = sharedPref.edit { putString(PREF_KEY_INSTALLATION_ID, value) }
@ -358,21 +330,6 @@ class PreferencesRepository @Inject constructor(
private fun getLong(id: String, default: Int) = private fun getLong(id: String, default: Int) =
sharedPref.getLong(id, context.resources.getString(default).toLong()) sharedPref.getLong(id, context.resources.getString(default).toLong())
private fun getStringFlow(id: Int, default: Int) =
flowSharedPref.getString(context.getString(id), context.getString(default))
private fun <T : Any> getObjectFlow(
@StringRes id: Int,
@StringRes default: Int,
serializer: Serializer<T>
): Preference<T> = flowSharedPref.getObject(
key = context.getString(id),
serializer = serializer,
defaultValue = serializer.deserialize(
flowSharedPref.getString(context.getString(default)).get()
)
)
private fun getString(id: Int, default: Int) = getString(context.getString(id), default) private fun getString(id: Int, default: Int) = getString(context.getString(id), default)
private fun getString(id: String, default: Int) = private fun getString(id: String, default: Int) =
@ -384,7 +341,6 @@ class PreferencesRepository @Inject constructor(
sharedPref.getBoolean(id, context.resources.getBoolean(default)) sharedPref.getBoolean(id, context.resources.getBoolean(default))
private companion object { private companion object {
private const val PREF_KEY_APP_MENU_ITEM_ORDER = "app_menu_item_order"
private const val PREF_KEY_INSTALLATION_ID = "installation_id" private const val PREF_KEY_INSTALLATION_ID = "installation_id"
private const val PREF_KEY_DASHBOARD_ITEMS_POSITION = "dashboard_items_position" private const val PREF_KEY_DASHBOARD_ITEMS_POSITION = "dashboard_items_position"
private const val PREF_KEY_IN_APP_REVIEW_COUNT = "in_app_review_count" private const val PREF_KEY_IN_APP_REVIEW_COUNT = "in_app_review_count"

View file

@ -40,7 +40,7 @@ class SemesterRepository @Inject constructor(
val isNoSemesters = semesters.isEmpty() val isNoSemesters = semesters.isEmpty()
val isRefreshOnModeChangeRequired = when { val isRefreshOnModeChangeRequired = when {
Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.HEBE -> { Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API -> {
semesters.firstOrNull { it.isCurrent }?.let { semesters.firstOrNull { it.isCurrent }?.let {
0 == it.diaryId && 0 == it.kindergartenDiaryId 0 == it.diaryId && 0 == it.kindergartenDiaryId
} == true } == true

View file

@ -6,17 +6,14 @@ import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.data.db.AppDatabase import io.github.wulkanowy.data.db.AppDatabase
import io.github.wulkanowy.data.db.dao.SemesterDao import io.github.wulkanowy.data.db.dao.SemesterDao
import io.github.wulkanowy.data.db.dao.StudentDao import io.github.wulkanowy.data.db.dao.StudentDao
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.db.entities.StudentName
import io.github.wulkanowy.data.db.entities.StudentNickAndAvatar import io.github.wulkanowy.data.db.entities.StudentNickAndAvatar
import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.data.db.entities.StudentWithSemesters
import io.github.wulkanowy.data.exceptions.NoCurrentStudentException import io.github.wulkanowy.data.exceptions.NoCurrentStudentException
import io.github.wulkanowy.data.mappers.mapToPojo import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.pojos.RegisterUser
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.DispatchersProvider import io.github.wulkanowy.utils.DispatchersProvider
import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.security.decrypt import io.github.wulkanowy.utils.security.decrypt
import io.github.wulkanowy.utils.security.encrypt import io.github.wulkanowy.utils.security.encrypt
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -30,51 +27,45 @@ class StudentRepository @Inject constructor(
private val studentDb: StudentDao, private val studentDb: StudentDao,
private val semesterDb: SemesterDao, private val semesterDb: SemesterDao,
private val sdk: Sdk, private val sdk: Sdk,
private val appInfo: AppInfo,
private val appDatabase: AppDatabase private val appDatabase: AppDatabase
) { ) {
suspend fun isStudentSaved() = getSavedStudents(false).isNotEmpty()
suspend fun isCurrentStudentSet() = studentDb.loadCurrent()?.isCurrent ?: false suspend fun isCurrentStudentSet() = studentDb.loadCurrent()?.isCurrent ?: false
suspend fun getStudentsApi( suspend fun getStudentsApi(
pin: String, pin: String,
symbol: String, symbol: String,
token: String token: String
): RegisterUser = sdk ): List<StudentWithSemesters> =
.getStudentsFromHebe(token, pin, symbol, "") sdk.getStudentsFromMobileApi(token, pin, symbol, "")
.mapToPojo(null) .mapToEntities(colors = appInfo.defaultColorsForAvatar)
suspend fun getStudentsScrapper( suspend fun getStudentsScrapper(
email: String, email: String,
password: String, password: String,
scrapperBaseUrl: String, scrapperBaseUrl: String,
symbol: String symbol: String
): RegisterUser = sdk ): List<StudentWithSemesters> =
.getUserSubjectsFromScrapper(email, password, scrapperBaseUrl, symbol) sdk.getStudentsFromScrapper(email, password, scrapperBaseUrl, symbol)
.mapToPojo(password) .mapToEntities(password, appInfo.defaultColorsForAvatar)
suspend fun getUserSubjectsFromScrapper(
email: String,
password: String,
scrapperBaseUrl: String,
symbol: String
): RegisterUser = sdk
.getUserSubjectsFromScrapper(email, password, scrapperBaseUrl, symbol)
.mapToPojo(password)
suspend fun getStudentsHybrid( suspend fun getStudentsHybrid(
email: String, email: String,
password: String, password: String,
scrapperBaseUrl: String, scrapperBaseUrl: String,
symbol: String symbol: String
): RegisterUser = sdk ): List<StudentWithSemesters> =
.getStudentsHybrid(email, password, scrapperBaseUrl, "", symbol) sdk.getStudentsHybrid(email, password, scrapperBaseUrl, "", symbol)
.mapToPojo(password) .mapToEntities(password, appInfo.defaultColorsForAvatar)
suspend fun getSavedStudents(decryptPass: Boolean = true) = suspend fun getSavedStudents(decryptPass: Boolean = true) =
studentDb.loadStudentsWithSemesters() studentDb.loadStudentsWithSemesters()
.map { .map {
it.apply { it.apply {
if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.HEBE) { if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) {
student.password = withContext(dispatchers.io) { student.password = withContext(dispatchers.io) {
decrypt(student.password) decrypt(student.password)
} }
@ -84,7 +75,7 @@ class StudentRepository @Inject constructor(
suspend fun getSavedStudentById(id: Long, decryptPass: Boolean = true) = suspend fun getSavedStudentById(id: Long, decryptPass: Boolean = true) =
studentDb.loadStudentWithSemestersById(id)?.apply { studentDb.loadStudentWithSemestersById(id)?.apply {
if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.HEBE) { if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) {
student.password = withContext(dispatchers.io) { student.password = withContext(dispatchers.io) {
decrypt(student.password) decrypt(student.password)
} }
@ -94,7 +85,7 @@ class StudentRepository @Inject constructor(
suspend fun getStudentById(id: Long, decryptPass: Boolean = true): Student { suspend fun getStudentById(id: Long, decryptPass: Boolean = true): Student {
val student = studentDb.loadById(id) ?: throw NoCurrentStudentException() val student = studentDb.loadById(id) ?: throw NoCurrentStudentException()
if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.HEBE) { if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) {
student.password = withContext(dispatchers.io) { student.password = withContext(dispatchers.io) {
decrypt(student.password) decrypt(student.password)
} }
@ -105,7 +96,7 @@ class StudentRepository @Inject constructor(
suspend fun getCurrentStudent(decryptPass: Boolean = true): Student { suspend fun getCurrentStudent(decryptPass: Boolean = true): Student {
val student = studentDb.loadCurrent() ?: throw NoCurrentStudentException() val student = studentDb.loadCurrent() ?: throw NoCurrentStudentException()
if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.HEBE) { if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) {
student.password = withContext(dispatchers.io) { student.password = withContext(dispatchers.io) {
decrypt(student.password) decrypt(student.password)
} }
@ -118,7 +109,7 @@ class StudentRepository @Inject constructor(
val students = studentsWithSemesters.map { it.student } val students = studentsWithSemesters.map { it.student }
.map { .map {
it.apply { it.apply {
if (Sdk.Mode.valueOf(it.loginMode) != Sdk.Mode.HEBE) { if (Sdk.Mode.valueOf(it.loginMode) != Sdk.Mode.API) {
password = withContext(dispatchers.io) { password = withContext(dispatchers.io) {
encrypt(password, context) encrypt(password, context)
} }
@ -149,21 +140,4 @@ class StudentRepository @Inject constructor(
suspend fun isOneUniqueStudent() = getSavedStudents(false) suspend fun isOneUniqueStudent() = getSavedStudents(false)
.distinctBy { it.student.studentName }.size == 1 .distinctBy { it.student.studentName }.size == 1
suspend fun authorizePermission(student: Student, semester: Semester, pesel: String) =
sdk.init(student)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
.authorizePermission(pesel)
suspend fun refreshStudentName(student: Student, semester: Semester) {
val newCurrentApiStudent = sdk.init(student)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
.getCurrentStudent() ?: return
val studentName = StudentName(
studentName = "${newCurrentApiStudent.studentName} ${newCurrentApiStudent.studentSurname}"
).apply { id = student.id }
studentDb.update(studentName)
}
} }

View file

@ -40,7 +40,7 @@ class TeacherRepository @Inject constructor(
fetch = { fetch = {
sdk.init(student) sdk.init(student)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear) .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
.getTeachers() .getTeachers(semester.semesterId)
.mapToEntities(semester) .mapToEntities(semester)
}, },
saveFetchResult = { old, new -> saveFetchResult = { old, new ->

View file

@ -13,7 +13,6 @@ import io.github.wulkanowy.utils.*
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
import java.time.Instant
import java.time.LocalDate import java.time.LocalDate
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -66,7 +65,7 @@ class TimetableRepository @Inject constructor(
fetch = { fetch = {
val timetableFull = sdk.init(student) val timetableFull = sdk.init(student)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear) .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
.getTimetable(start.monday, end.sunday) .getTimetableFull(start.monday, end.sunday)
timetableFull.mapToEntities(semester) timetableFull.mapToEntities(semester)
}, },
@ -165,11 +164,6 @@ class TimetableRepository @Inject constructor(
timetableHeaderDb.insertAll(new uniqueSubtract old) timetableHeaderDb.insertAll(new uniqueSubtract old)
} }
fun getLastRefreshTimestamp(semester: Semester, start: LocalDate, end: LocalDate): Instant {
val refreshKey = getRefreshKey(cacheKey, semester, start, end)
return refreshHelper.getLastRefreshTimestamp(refreshKey)
}
suspend fun saveAdditionalList(additionalList: List<TimetableAdditional>) = suspend fun saveAdditionalList(additionalList: List<TimetableAdditional>) =
timetableAdditionalDb.insertAll(additionalList) timetableAdditionalDb.insertAll(additionalList)

View file

@ -17,11 +17,8 @@ class GetMailboxByStudentUseCase @Inject constructor(
private fun List<Mailbox>.filterByStudent(student: Student): Mailbox? { private fun List<Mailbox>.filterByStudent(student: Student): Mailbox? {
val normalizedStudentName = student.studentName.normalizeStudentName() val normalizedStudentName = student.studentName.normalizeStudentName()
return singleOrNull { return find {
it.studentName.normalizeStudentName() == normalizedStudentName it.studentName.normalizeStudentName() == normalizedStudentName
} ?: singleOrNull {
it.studentName.normalizeStudentName() == normalizedStudentName
&& it.schoolNameShort == student.schoolShortName
} ?: singleOrNull { } ?: singleOrNull {
it.studentName.getFirstAndLastPart() == normalizedStudentName.getFirstAndLastPart() it.studentName.getFirstAndLastPart() == normalizedStudentName.getFirstAndLastPart()
} ?: singleOrNull { } ?: singleOrNull {

View file

@ -4,12 +4,18 @@ import android.os.Build.VERSION.SDK_INT
import android.os.Build.VERSION_CODES.O import android.os.Build.VERSION_CODES.O
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import androidx.lifecycle.asFlow import androidx.lifecycle.asFlow
import androidx.work.*
import androidx.work.BackoffPolicy.EXPONENTIAL import androidx.work.BackoffPolicy.EXPONENTIAL
import androidx.work.Constraints
import androidx.work.Data
import androidx.work.ExistingPeriodicWorkPolicy.KEEP import androidx.work.ExistingPeriodicWorkPolicy.KEEP
import androidx.work.ExistingPeriodicWorkPolicy.UPDATE import androidx.work.ExistingPeriodicWorkPolicy.REPLACE
import androidx.work.ExistingWorkPolicy
import androidx.work.NetworkType.CONNECTED import androidx.work.NetworkType.CONNECTED
import androidx.work.NetworkType.UNMETERED import androidx.work.NetworkType.UNMETERED
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkInfo
import androidx.work.WorkManager
import io.github.wulkanowy.data.db.SharedPrefProvider import io.github.wulkanowy.data.db.SharedPrefProvider
import io.github.wulkanowy.data.db.SharedPrefProvider.Companion.APP_VERSION_CODE_KEY import io.github.wulkanowy.data.db.SharedPrefProvider.Companion.APP_VERSION_CODE_KEY
import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.PreferencesRepository
@ -54,7 +60,7 @@ class SyncManager @Inject constructor(
val serviceInterval = preferencesRepository.servicesInterval val serviceInterval = preferencesRepository.servicesInterval
workManager.enqueueUniquePeriodicWork( workManager.enqueueUniquePeriodicWork(
SyncWorker::class.java.simpleName, if (restart) UPDATE else KEEP, SyncWorker::class.java.simpleName, if (restart) REPLACE else KEEP,
PeriodicWorkRequestBuilder<SyncWorker>(serviceInterval, MINUTES) PeriodicWorkRequestBuilder<SyncWorker>(serviceInterval, MINUTES)
.setInitialDelay(10, MINUTES) .setInitialDelay(10, MINUTES)
.setBackoffCriteria(EXPONENTIAL, 30, MINUTES) .setBackoffCriteria(EXPONENTIAL, 30, MINUTES)

View file

@ -4,6 +4,7 @@ import android.content.Intent
import android.widget.RemoteViewsService import android.widget.RemoteViewsService
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.data.db.SharedPrefProvider import io.github.wulkanowy.data.db.SharedPrefProvider
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.data.repositories.TimetableRepository
@ -23,13 +24,14 @@ class TimetableWidgetService : RemoteViewsService() {
@Inject @Inject
lateinit var semesterRepo: SemesterRepository lateinit var semesterRepo: SemesterRepository
@Inject
lateinit var prefRepository: PreferencesRepository
@Inject @Inject
lateinit var sharedPref: SharedPrefProvider lateinit var sharedPref: SharedPrefProvider
override fun onGetViewFactory(intent: Intent?): RemoteViewsFactory { override fun onGetViewFactory(intent: Intent?): RemoteViewsFactory {
Timber.d("TimetableWidgetFactory created") Timber.d("TimetableWidgetFactory created")
return TimetableWidgetFactory( return TimetableWidgetFactory(timetableRepo, studentRepo, semesterRepo, prefRepository, sharedPref, applicationContext, intent)
timetableRepo, studentRepo, semesterRepo, sharedPref, applicationContext, intent
)
} }
} }

View file

@ -4,13 +4,12 @@ import android.app.ActivityManager
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.viewbinding.ViewBinding import androidx.viewbinding.ViewBinding
import com.google.android.material.dialog.MaterialAlertDialogBuilder
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.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
@ -31,8 +30,6 @@ abstract class BaseActivity<T : BasePresenter<out BaseView>, VB : ViewBinding> :
protected var messageContainer: View? = null protected var messageContainer: View? = null
protected var messageAnchor: View? = null
abstract var presenter: T abstract var presenter: T
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -51,7 +48,6 @@ abstract class BaseActivity<T : BasePresenter<out BaseView>, VB : ViewBinding> :
if (messageContainer != null) { if (messageContainer != null) {
Snackbar.make(messageContainer!!, text, LENGTH_LONG) Snackbar.make(messageContainer!!, text, LENGTH_LONG)
.setAction(R.string.all_details) { showErrorDetailsDialog(error) } .setAction(R.string.all_details) { showErrorDetailsDialog(error) }
.apply { messageAnchor?.let { anchorView = it } }
.show() .show()
} else showMessage(text) } else showMessage(text)
} }
@ -61,15 +57,12 @@ abstract class BaseActivity<T : BasePresenter<out BaseView>, VB : ViewBinding> :
} }
override fun showMessage(text: String) { override fun showMessage(text: String) {
if (messageContainer != null) { if (messageContainer != null) Snackbar.make(messageContainer!!, text, LENGTH_LONG).show()
Snackbar.make(messageContainer!!, text, LENGTH_LONG) else Toast.makeText(this, text, Toast.LENGTH_LONG).show()
.apply { messageAnchor?.let { anchorView = it } }
.show()
} else Toast.makeText(this, text, Toast.LENGTH_LONG).show()
} }
override fun showExpiredDialog() { override fun showExpiredDialog() {
MaterialAlertDialogBuilder(this) AlertDialog.Builder(this)
.setTitle(R.string.main_session_expired) .setTitle(R.string.main_session_expired)
.setMessage(R.string.main_session_relogin) .setMessage(R.string.main_session_relogin)
.setPositiveButton(R.string.main_log_in) { _, _ -> presenter.onExpiredLoginSelected() } .setPositiveButton(R.string.main_log_in) { _, _ -> presenter.onExpiredLoginSelected() }
@ -77,15 +70,10 @@ abstract class BaseActivity<T : BasePresenter<out BaseView>, VB : ViewBinding> :
.show() .show()
} }
override fun showAuthDialog() {
AuthDialog.newInstance().show(supportFragmentManager, "auth_dialog")
}
override fun showChangePasswordSnackbar(redirectUrl: String) { override fun showChangePasswordSnackbar(redirectUrl: String) {
messageContainer?.let { messageContainer?.let {
Snackbar.make(it, R.string.error_password_change_required, LENGTH_LONG) Snackbar.make(it, R.string.error_password_change_required, LENGTH_LONG)
.setAction(R.string.all_change) { openInternetBrowser(redirectUrl) } .setAction(R.string.all_change) { openInternetBrowser(redirectUrl) }
.apply { messageAnchor?.let { anchorView = it } }
.show() .show()
} }
} }

View file

@ -1,14 +1,8 @@
package io.github.wulkanowy.ui.base package io.github.wulkanowy.ui.base
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast 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 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
@ -40,25 +34,10 @@ abstract class BaseDialogFragment<VB : ViewBinding> : DialogFragment(), BaseView
(activity as? BaseActivity<*, *>)?.showChangePasswordSnackbar(redirectUrl) (activity as? BaseActivity<*, *>)?.showChangePasswordSnackbar(redirectUrl)
} }
override fun showAuthDialog() {
AuthDialog.newInstance().show(childFragmentManager, "auth_dialog")
}
override fun showErrorDetailsDialog(error: Throwable) { override fun showErrorDetailsDialog(error: Throwable) {
ErrorDialog.newInstance(error).show(childFragmentManager, error.toString()) ErrorDialog.newInstance(error).show(childFragmentManager, error.toString())
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
view.setBackgroundColor(SurfaceColors.SURFACE_3.getColor(requireContext()))
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
) = binding.root
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
analyticsHelper.setCurrentScreen(requireActivity(), this::class.simpleName) analyticsHelper.setCurrentScreen(requireActivity(), this::class.simpleName)

View file

@ -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,10 +42,6 @@ abstract class BaseFragment<VB : ViewBinding>(@LayoutRes layoutId: Int) : Fragme
(activity as? BaseActivity<*, *>)?.showExpiredDialog() (activity as? BaseActivity<*, *>)?.showExpiredDialog()
} }
override fun showAuthDialog() {
AuthDialog.newInstance().show(childFragmentManager, "auth_dialog")
}
override fun openClearLoginView() { override fun openClearLoginView() {
(activity as? BaseActivity<*, *>)?.openClearLoginView() (activity as? BaseActivity<*, *>)?.openClearLoginView()
} }

View file

@ -1,15 +1,10 @@
package io.github.wulkanowy.ui.base package io.github.wulkanowy.ui.base
import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.repositories.StudentRepository
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancelChildren
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
open class BasePresenter<T : BaseView>( open class BasePresenter<T : BaseView>(
@ -31,7 +26,6 @@ open class BasePresenter<T : BaseView>(
onSessionExpired = view::showExpiredDialog onSessionExpired = view::showExpiredDialog
onNoCurrentStudent = view::openClearLoginView onNoCurrentStudent = view::openClearLoginView
onPasswordChangeRequired = view::showChangePasswordSnackbar onPasswordChangeRequired = view::showChangePasswordSnackbar
onAuthorizationRequired = view::showAuthDialog
} }
} }

View file

@ -8,8 +8,6 @@ interface BaseView {
fun showExpiredDialog() fun showExpiredDialog()
fun showAuthDialog()
fun openClearLoginView() fun openClearLoginView()
fun showErrorDetailsDialog(error: Throwable) fun showErrorDetailsDialog(error: Throwable)

View file

@ -4,13 +4,13 @@ import android.app.Dialog
import android.content.ClipData import android.content.ClipData
import android.content.ClipboardManager import android.content.ClipboardManager
import android.os.Bundle import android.os.Bundle
import android.view.View
import android.widget.Toast import android.widget.Toast
import android.widget.Toast.LENGTH_LONG import android.widget.Toast.LENGTH_LONG
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.content.getSystemService import androidx.core.content.getSystemService
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import androidx.core.view.isGone import androidx.core.view.isGone
import androidx.fragment.app.DialogFragment
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
@ -20,7 +20,7 @@ import io.github.wulkanowy.utils.*
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
class ErrorDialog : BaseDialogFragment<DialogErrorBinding>() { class ErrorDialog : DialogFragment() {
@Inject @Inject
lateinit var appInfo: AppInfo lateinit var appInfo: AppInfo
@ -28,8 +28,6 @@ class ErrorDialog : BaseDialogFragment<DialogErrorBinding>() {
@Inject @Inject
lateinit var preferencesRepository: PreferencesRepository lateinit var preferencesRepository: PreferencesRepository
private lateinit var error: Throwable
companion object { companion object {
private const val ARGUMENT_KEY = "error" private const val ARGUMENT_KEY = "error"
@ -38,31 +36,32 @@ class ErrorDialog : BaseDialogFragment<DialogErrorBinding>() {
} }
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
super.onCreate(savedInstanceState) val error = requireArguments().getSerializable(ARGUMENT_KEY) as Throwable
error = requireArguments().serializable(ARGUMENT_KEY)
val binding = DialogErrorBinding.inflate(layoutInflater)
binding.bindErrorDetails(error)
return getAlertDialog(binding, error).apply {
enableReportButtonIfErrorIsReportable(error)
}
} }
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { private fun getAlertDialog(binding: DialogErrorBinding, error: Throwable): AlertDialog {
return MaterialAlertDialogBuilder(requireContext()).apply { return MaterialAlertDialogBuilder(requireContext()).apply {
val errorStacktrace = error.stackTraceToString() val errorStacktrace = error.stackTraceToString()
setTitle(R.string.all_details) setTitle(R.string.all_details)
setView(DialogErrorBinding.inflate(layoutInflater).apply { binding = this }.root) setView(binding.root)
setNeutralButton(R.string.about_feedback) { _, _ -> setNeutralButton(R.string.about_feedback) { _, _ ->
openConfirmDialog { openEmailClient(errorStacktrace) } openConfirmDialog { openEmailClient(errorStacktrace) }
} }
setNegativeButton(android.R.string.cancel) { _, _ -> } setNegativeButton(android.R.string.cancel) { _, _ -> }
setPositiveButton(android.R.string.copy) { _, _ -> copyErrorToClipboard(errorStacktrace) } setPositiveButton(android.R.string.copy) { _, _ -> copyErrorToClipboard(errorStacktrace) }
}.create().apply { }.create()
setOnShowListener {
getButton(AlertDialog.BUTTON_NEUTRAL).isEnabled = error.isShouldBeReported()
}
}
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { private fun DialogErrorBinding.bindErrorDetails(error: Throwable) {
super.onViewCreated(view, savedInstanceState) return with(this) {
with(binding) {
errorDialogHumanizedMessage.text = resources.getErrorString(error) errorDialogHumanizedMessage.text = resources.getErrorString(error)
errorDialogErrorMessage.text = error.localizedMessage errorDialogErrorMessage.text = error.localizedMessage
errorDialogErrorMessage.isGone = error.localizedMessage.isNullOrBlank() errorDialogErrorMessage.isGone = error.localizedMessage.isNullOrBlank()
@ -71,6 +70,12 @@ class ErrorDialog : BaseDialogFragment<DialogErrorBinding>() {
} }
} }
private fun AlertDialog.enableReportButtonIfErrorIsReportable(error: Throwable) {
setOnShowListener {
getButton(AlertDialog.BUTTON_NEUTRAL).isEnabled = error.isShouldBeReported()
}
}
private fun copyErrorToClipboard(errorStacktrace: String) { private fun copyErrorToClipboard(errorStacktrace: String) {
val clip = ClipData.newPlainText("Error details", errorStacktrace) val clip = ClipData.newPlainText("Error details", errorStacktrace)
requireActivity().getSystemService<ClipboardManager>()?.setPrimaryClip(clip) requireActivity().getSystemService<ClipboardManager>()?.setPrimaryClip(clip)
@ -78,7 +83,7 @@ class ErrorDialog : BaseDialogFragment<DialogErrorBinding>() {
} }
private fun openConfirmDialog(callback: () -> Unit) { private fun openConfirmDialog(callback: () -> Unit) {
MaterialAlertDialogBuilder(requireContext()) AlertDialog.Builder(requireContext())
.setTitle(R.string.dialog_error_check_update) .setTitle(R.string.dialog_error_check_update)
.setMessage(R.string.dialog_error_check_update_message) .setMessage(R.string.dialog_error_check_update_message)
.setNeutralButton(R.string.about_feedback) { _, _ -> callback() } .setNeutralButton(R.string.about_feedback) { _, _ -> callback() }
@ -108,4 +113,8 @@ class ErrorDialog : BaseDialogFragment<DialogErrorBinding>() {
} }
) )
} }
private fun showMessage(text: String) {
Toast.makeText(requireContext(), text, LENGTH_LONG).show()
}
} }

View file

@ -3,7 +3,6 @@ package io.github.wulkanowy.ui.base
import android.content.Context 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.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
@ -21,8 +20,6 @@ open class ErrorHandler @Inject constructor(@ApplicationContext protected val co
var onPasswordChangeRequired: (String) -> Unit = {} var onPasswordChangeRequired: (String) -> Unit = {}
var onAuthorizationRequired: () -> 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)
@ -34,7 +31,6 @@ open class ErrorHandler @Inject constructor(@ApplicationContext protected val co
is PasswordChangeRequiredException -> onPasswordChangeRequired(error.redirectUrl) is PasswordChangeRequiredException -> onPasswordChangeRequired(error.redirectUrl)
is ScramblerException, is BadCredentialsException -> onSessionExpired() is ScramblerException, is BadCredentialsException -> onSessionExpired()
is NoCurrentStudentException -> onNoCurrentStudent() is NoCurrentStudentException -> onNoCurrentStudent()
is AuthorizationRequiredException -> onAuthorizationRequired()
} }
} }
@ -43,6 +39,5 @@ open class ErrorHandler @Inject constructor(@ApplicationContext protected val co
onSessionExpired = {} onSessionExpired = {}
onNoCurrentStudent = {} onNoCurrentStudent = {}
onPasswordChangeRequired = {} onPasswordChangeRequired = {}
onAuthorizationRequired = {}
} }
} }

View file

@ -1,19 +1,17 @@
package io.github.wulkanowy.ui.base package io.github.wulkanowy.ui.base
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.content.pm.PackageManager.GET_ACTIVITIES import android.content.pm.PackageManager.GET_ACTIVITIES
import android.os.Build
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import com.google.android.material.color.DynamicColors import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_NO
import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_YES
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.enums.AppTheme import io.github.wulkanowy.data.enums.AppTheme
import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.ui.modules.login.LoginActivity import io.github.wulkanowy.ui.modules.login.LoginActivity
import io.github.wulkanowy.ui.modules.luckynumberwidget.LuckyNumberWidgetConfigureActivity
import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetConfigureActivity import io.github.wulkanowy.ui.modules.message.send.SendMessageActivity
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -27,40 +25,31 @@ class ThemeManager @Inject constructor(private val preferencesRepository: Prefer
when (activity) { when (activity) {
is MainActivity -> activity.setTheme(R.style.WulkanowyTheme_Black) is MainActivity -> activity.setTheme(R.style.WulkanowyTheme_Black)
is LoginActivity -> activity.setTheme(R.style.WulkanowyTheme_Login_Black) is LoginActivity -> activity.setTheme(R.style.WulkanowyTheme_Login_Black)
is SendMessageActivity -> activity.setTheme(R.style.WulkanowyTheme_MessageSend_Black)
} }
} }
} else if (activity is TimetableWidgetConfigureActivity || activity is LuckyNumberWidgetConfigureActivity) {
DynamicColors.applyToActivityIfAvailable(activity)
} }
} }
fun applyDefaultTheme() { fun applyDefaultTheme() {
AppCompatDelegate.setDefaultNightMode( AppCompatDelegate.setDefaultNightMode(
when (preferencesRepository.appTheme) { when (preferencesRepository.appTheme) {
AppTheme.LIGHT -> AppCompatDelegate.MODE_NIGHT_NO AppTheme.LIGHT -> MODE_NIGHT_NO
AppTheme.DARK, AppTheme.BLACK -> AppCompatDelegate.MODE_NIGHT_YES AppTheme.DARK, AppTheme.BLACK -> MODE_NIGHT_YES
AppTheme.SYSTEM -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM AppTheme.SYSTEM -> MODE_NIGHT_FOLLOW_SYSTEM
} }
) )
} }
private fun isThemeApplicable(activity: AppCompatActivity): Boolean = private fun isThemeApplicable(activity: AppCompatActivity) =
getPackageInfo(activity) activity.packageManager
.getPackageInfo(activity.packageName, GET_ACTIVITIES)
.activities .activities
.singleOrNull { it.name == activity::class.java.canonicalName } .singleOrNull { it.name == activity::class.java.canonicalName }
?.theme ?.theme
.let { .let {
it == R.style.WulkanowyTheme_Black || it == R.style.WulkanowyTheme_NoActionBar it == R.style.WulkanowyTheme_Black || it == R.style.WulkanowyTheme_NoActionBar
|| it == R.style.WulkanowyTheme_Login || it == R.style.WulkanowyTheme_Login_Black || it == R.style.WulkanowyTheme_Login || it == R.style.WulkanowyTheme_Login_Black
|| it == R.style.WulkanowyTheme_MessageSend || it == R.style.WulkanowyTheme_MessageSend_Black
} }
@Suppress("DEPRECATION")
private fun getPackageInfo(activity: AppCompatActivity): PackageInfo {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
activity.packageManager.getPackageInfo(
activity.packageName,
PackageManager.PackageInfoFlags.of(GET_ACTIVITIES.toLong())
)
} else activity.packageManager.getPackageInfo(activity.packageName, GET_ACTIVITIES)
}
} }

View file

@ -9,14 +9,11 @@ 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
import io.github.wulkanowy.ui.modules.luckynumber.LuckyNumberFragment import io.github.wulkanowy.ui.modules.luckynumber.LuckyNumberFragment
import io.github.wulkanowy.ui.modules.luckynumber.history.LuckyNumberHistoryFragment
import io.github.wulkanowy.ui.modules.message.MessageFragment import io.github.wulkanowy.ui.modules.message.MessageFragment
import io.github.wulkanowy.ui.modules.mobiledevice.MobileDeviceFragment
import io.github.wulkanowy.ui.modules.more.MoreFragment import io.github.wulkanowy.ui.modules.more.MoreFragment
import io.github.wulkanowy.ui.modules.note.NoteFragment import io.github.wulkanowy.ui.modules.note.NoteFragment
import io.github.wulkanowy.ui.modules.schoolandteachers.SchoolAndTeachersFragment import io.github.wulkanowy.ui.modules.schoolandteachers.school.SchoolFragment
import io.github.wulkanowy.ui.modules.schoolannouncement.SchoolAnnouncementFragment import io.github.wulkanowy.ui.modules.schoolannouncement.SchoolAnnouncementFragment
import io.github.wulkanowy.ui.modules.settings.SettingsFragment
import io.github.wulkanowy.ui.modules.timetable.TimetableFragment import io.github.wulkanowy.ui.modules.timetable.TimetableFragment
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import java.time.LocalDate import java.time.LocalDate
@ -42,13 +39,10 @@ sealed class Destination {
NOTE(Note), NOTE(Note),
CONFERENCE(Conference), CONFERENCE(Conference),
SCHOOL_ANNOUNCEMENT(SchoolAnnouncement), SCHOOL_ANNOUNCEMENT(SchoolAnnouncement),
SCHOOL_AND_TEACHERS(SchoolAndTeachers), SCHOOL(School),
LUCKY_NUMBER(LuckyNumber), LUCKY_NUMBER(More),
LUCKY_NUMBER_HISTORY(LuckyNumberHistory),
MORE(More), MORE(More),
MESSAGE(Message), MESSAGE(Message);
MOBILE_DEVICE(MobileDevice),
SETTINGS(Settings);
} }
@Serializable @Serializable
@ -109,9 +103,9 @@ sealed class Destination {
} }
@Serializable @Serializable
object SchoolAndTeachers : Destination() { object School : Destination() {
override val destinationType get() = Type.SCHOOL_AND_TEACHERS override val destinationType get() = Type.SCHOOL
override val destinationFragment get() = SchoolAndTeachersFragment.newInstance() override val destinationFragment get() = SchoolFragment.newInstance()
} }
@Serializable @Serializable
@ -120,12 +114,6 @@ sealed class Destination {
override val destinationFragment get() = LuckyNumberFragment.newInstance() override val destinationFragment get() = LuckyNumberFragment.newInstance()
} }
@Serializable
object LuckyNumberHistory : Destination() {
override val destinationType get() = Type.LUCKY_NUMBER_HISTORY
override val destinationFragment get() = LuckyNumberHistoryFragment.newInstance()
}
@Serializable @Serializable
object More : Destination() { object More : Destination() {
override val destinationType get() = Type.MORE override val destinationType get() = Type.MORE
@ -137,16 +125,4 @@ sealed class Destination {
override val destinationType get() = Type.MESSAGE override val destinationType get() = Type.MESSAGE
override val destinationFragment get() = MessageFragment.newInstance() override val destinationFragment get() = MessageFragment.newInstance()
} }
@Serializable
object MobileDevice : Destination() {
override val destinationType get() = Type.MOBILE_DEVICE
override val destinationFragment get() = MobileDeviceFragment.newInstance()
}
@Serializable
object Settings : Destination() {
override val destinationType get() = Type.SETTINGS
override val destinationFragment get() = SettingsFragment.newInstance()
}
} }

View file

@ -34,7 +34,6 @@ class AccountFragment : BaseFragment<FragmentAccountBinding>(R.layout.fragment_a
override val titleStringId = R.string.account_title override val titleStringId = R.string.account_title
@Suppress("DEPRECATION")
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setHasOptionsMenu(true) setHasOptionsMenu(true)

View file

@ -6,10 +6,8 @@ import android.view.MenuInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.os.bundleOf
import androidx.core.view.get import androidx.core.view.get
import androidx.core.view.isVisible import androidx.core.view.isVisible
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.Student import io.github.wulkanowy.data.db.entities.Student
@ -23,7 +21,6 @@ import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoFragment
import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoView import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoView
import io.github.wulkanowy.utils.createNameInitialsDrawable import io.github.wulkanowy.utils.createNameInitialsDrawable
import io.github.wulkanowy.utils.nickOrName import io.github.wulkanowy.utils.nickOrName
import io.github.wulkanowy.utils.serializable
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
@ -40,12 +37,12 @@ class AccountDetailsFragment :
private const val ARGUMENT_KEY = "Data" private const val ARGUMENT_KEY = "Data"
fun newInstance(student: Student) = AccountDetailsFragment().apply { fun newInstance(student: Student) =
arguments = bundleOf(ARGUMENT_KEY to student) AccountDetailsFragment().apply {
} arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, student) }
}
} }
@Suppress("DEPRECATION")
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setHasOptionsMenu(true) setHasOptionsMenu(true)
@ -54,7 +51,7 @@ class AccountDetailsFragment :
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
binding = FragmentAccountDetailsBinding.bind(view) binding = FragmentAccountDetailsBinding.bind(view)
presenter.onAttachView(this, requireArguments().serializable(ARGUMENT_KEY)) presenter.onAttachView(this, requireArguments()[ARGUMENT_KEY] as Student)
} }
override fun initView() { override fun initView() {
@ -115,7 +112,7 @@ class AccountDetailsFragment :
override fun showLogoutConfirmDialog() { override fun showLogoutConfirmDialog() {
context?.let { context?.let {
MaterialAlertDialogBuilder(it) AlertDialog.Builder(it)
.setTitle(R.string.account_logout_student) .setTitle(R.string.account_logout_student)
.setMessage(R.string.account_confirm) .setMessage(R.string.account_confirm)
.setPositiveButton(R.string.account_logout) { _, _ -> presenter.onLogoutConfirm() } .setPositiveButton(R.string.account_logout) { _, _ -> presenter.onLogoutConfirm() }

View file

@ -1,16 +1,14 @@
package io.github.wulkanowy.ui.modules.account.accountedit package io.github.wulkanowy.ui.modules.account.accountedit
import android.app.Dialog
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater
import android.view.View import android.view.View
import androidx.core.os.bundleOf import android.view.ViewGroup
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.databinding.DialogAccountEditBinding import io.github.wulkanowy.databinding.DialogAccountEditBinding
import io.github.wulkanowy.ui.base.BaseDialogFragment import io.github.wulkanowy.ui.base.BaseDialogFragment
import io.github.wulkanowy.utils.serializable
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
@ -26,21 +24,28 @@ class AccountEditDialog : BaseDialogFragment<DialogAccountEditBinding>(), Accoun
private const val ARGUMENT_KEY = "student_with_semesters" private const val ARGUMENT_KEY = "student_with_semesters"
fun newInstance(student: Student) = AccountEditDialog().apply { fun newInstance(student: Student) =
arguments = bundleOf(ARGUMENT_KEY to student) AccountEditDialog().apply {
} arguments = Bundle().apply {
putSerializable(ARGUMENT_KEY, student)
}
}
} }
override fun onCreate(savedInstanceState: Bundle?) {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { super.onCreate(savedInstanceState)
return MaterialAlertDialogBuilder(requireContext(), theme) setStyle(STYLE_NO_TITLE, 0)
.setView(DialogAccountEditBinding.inflate(layoutInflater).apply { binding = this }.root)
.create()
} }
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View = DialogAccountEditBinding.inflate(inflater).apply { binding = this }.root
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
presenter.onAttachView(this, requireArguments().serializable(ARGUMENT_KEY)) presenter.onAttachView(this, requireArguments()[ARGUMENT_KEY] as Student)
} }
override fun initView() { override fun initView() {

View file

@ -1,11 +1,10 @@
package io.github.wulkanowy.ui.modules.account.accountquick package io.github.wulkanowy.ui.modules.account.accountquick
import android.app.Dialog
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater
import android.view.View import android.view.View
import androidx.core.os.bundleOf import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.data.db.entities.StudentWithSemesters
import io.github.wulkanowy.databinding.DialogAccountQuickBinding import io.github.wulkanowy.databinding.DialogAccountQuickBinding
@ -14,7 +13,6 @@ import io.github.wulkanowy.ui.modules.account.AccountAdapter
import io.github.wulkanowy.ui.modules.account.AccountFragment import io.github.wulkanowy.ui.modules.account.AccountFragment
import io.github.wulkanowy.ui.modules.account.AccountItem import io.github.wulkanowy.ui.modules.account.AccountItem
import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.utils.serializable
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
@ -32,23 +30,27 @@ class AccountQuickDialog : BaseDialogFragment<DialogAccountQuickBinding>(), Acco
fun newInstance(studentsWithSemesters: List<StudentWithSemesters>) = fun newInstance(studentsWithSemesters: List<StudentWithSemesters>) =
AccountQuickDialog().apply { AccountQuickDialog().apply {
arguments = bundleOf(STUDENTS_ARGUMENT_KEY to studentsWithSemesters.toTypedArray()) arguments = Bundle().apply {
putSerializable(STUDENTS_ARGUMENT_KEY, studentsWithSemesters.toTypedArray())
}
} }
} }
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreate(savedInstanceState: Bundle?) {
return MaterialAlertDialogBuilder(requireContext(), theme) super.onCreate(savedInstanceState)
.setView( setStyle(STYLE_NO_TITLE, 0)
DialogAccountQuickBinding.inflate(layoutInflater)
.apply { binding = this }.root
)
.create()
} }
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
) = DialogAccountQuickBinding.inflate(inflater).apply { binding = this }.root
@Suppress("UNCHECKED_CAST")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) val studentsWithSemesters =
val studentsWithSemesters = requireArguments() (requireArguments()[STUDENTS_ARGUMENT_KEY] as Array<StudentWithSemesters>).toList()
.serializable<Array<StudentWithSemesters>>(STUDENTS_ARGUMENT_KEY).toList()
presenter.onAttachView(this, studentsWithSemesters) presenter.onAttachView(this, studentsWithSemesters)
} }

View file

@ -1,20 +1,19 @@
package io.github.wulkanowy.ui.modules.attendance package io.github.wulkanowy.ui.modules.attendance
import android.app.Dialog
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater
import android.view.View import android.view.View
import androidx.core.os.bundleOf import android.view.ViewGroup
import com.google.android.material.dialog.MaterialAlertDialogBuilder import androidx.fragment.app.DialogFragment
import dagger.hilt.android.AndroidEntryPoint
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.utils.descriptionRes import io.github.wulkanowy.utils.descriptionRes
import io.github.wulkanowy.utils.serializable import io.github.wulkanowy.utils.lifecycleAwareVariable
import io.github.wulkanowy.utils.toFormattedString import io.github.wulkanowy.utils.toFormattedString
@AndroidEntryPoint class AttendanceDialog : DialogFragment() {
class AttendanceDialog : BaseDialogFragment<DialogAttendanceBinding>() {
private var binding: DialogAttendanceBinding by lifecycleAwareVariable()
private lateinit var attendance: Attendance private lateinit var attendance: Attendance
@ -23,20 +22,23 @@ class AttendanceDialog : BaseDialogFragment<DialogAttendanceBinding>() {
private const val ARGUMENT_KEY = "Item" private const val ARGUMENT_KEY = "Item"
fun newInstance(exam: Attendance) = AttendanceDialog().apply { fun newInstance(exam: Attendance) = AttendanceDialog().apply {
arguments = bundleOf(ARGUMENT_KEY to exam) arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, exam) }
} }
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
attendance = requireArguments().serializable(ARGUMENT_KEY) setStyle(STYLE_NO_TITLE, 0)
arguments?.run {
attendance = getSerializable(ARGUMENT_KEY) as Attendance
}
} }
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateView(
return MaterialAlertDialogBuilder(requireContext(), theme) inflater: LayoutInflater,
.setView(DialogAttendanceBinding.inflate(layoutInflater).apply { binding = this }.root) container: ViewGroup?,
.create() savedInstanceState: Bundle?
} ) = DialogAttendanceBinding.inflate(inflater).apply { binding = this }.root
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)

View file

@ -4,10 +4,10 @@ import android.content.DialogInterface.BUTTON_POSITIVE
import android.os.Bundle import android.os.Bundle
import android.view.* import android.view.*
import android.view.View.* import android.view.View.*
import androidx.appcompat.app.AlertDialog
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
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.Attendance import io.github.wulkanowy.data.db.entities.Attendance
@ -84,7 +84,6 @@ class AttendanceFragment : BaseFragment<FragmentAttendanceBinding>(R.layout.frag
} }
} }
@Suppress("DEPRECATION")
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setHasOptionsMenu(true) setHasOptionsMenu(true)
@ -124,7 +123,7 @@ class AttendanceFragment : BaseFragment<FragmentAttendanceBinding>(R.layout.frag
attendanceExcuseButton.setOnClickListener { presenter.onExcuseButtonClick() } attendanceExcuseButton.setOnClickListener { presenter.onExcuseButtonClick() }
attendanceNavContainer.elevation = requireContext().dpToPx(3f) attendanceNavContainer.elevation = requireContext().dpToPx(8f)
} }
} }
@ -228,7 +227,7 @@ class AttendanceFragment : BaseFragment<FragmentAttendanceBinding>(R.layout.frag
override fun showExcuseDialog() { override fun showExcuseDialog() {
val dialogBinding = DialogExcuseBinding.inflate(LayoutInflater.from(context)) val dialogBinding = DialogExcuseBinding.inflate(LayoutInflater.from(context))
MaterialAlertDialogBuilder(requireContext()) AlertDialog.Builder(requireContext())
.setTitle(R.string.attendance_excuse_title) .setTitle(R.string.attendance_excuse_title)
.setView(dialogBinding.root) .setView(dialogBinding.root)
.setNegativeButton(android.R.string.cancel) { _, _ -> } .setNegativeButton(android.R.string.cancel) { _, _ -> }

View file

@ -1,81 +0,0 @@
package io.github.wulkanowy.ui.modules.auth
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.text.parseAsHtml
import androidx.core.view.isVisible
import androidx.core.widget.doOnTextChanged
import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R
import io.github.wulkanowy.databinding.DialogAuthBinding
import io.github.wulkanowy.ui.base.BaseDialogFragment
import javax.inject.Inject
@AndroidEntryPoint
class AuthDialog : BaseDialogFragment<DialogAuthBinding>(), AuthView {
@Inject
lateinit var presenter: AuthPresenter
companion object {
fun newInstance() = AuthDialog()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NO_TITLE, R.style.FullScreenDialogStyle)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return DialogAuthBinding.inflate(inflater).apply { binding = this }.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
presenter.onAttachView(this)
binding.authInput.doOnTextChanged { text, _, _, _ ->
presenter.onPeselChange(text?.toString())
}
binding.authButton.setOnClickListener { presenter.authorize() }
binding.authSuccessButton.setOnClickListener {
activity?.recreate()
dismiss()
}
binding.authButtonSkip.setOnClickListener { dismiss() }
}
override fun enableAuthButton(isEnabled: Boolean) {
binding.authButton.isEnabled = isEnabled
}
override fun showProgress(show: Boolean) {
binding.authProgress.isVisible = show
}
override fun showPeselError(show: Boolean) {
binding.authInputLayout.error = getString(R.string.auth_api_error).takeIf { show }
}
override fun showInvalidPeselError(show: Boolean) {
binding.authInputLayout.error = getString(R.string.auth_invalid_error).takeIf { show }
}
override fun showSuccess(show: Boolean) {
binding.authSuccess.isVisible = show
}
override fun showContent(show: Boolean) {
binding.authForm.isVisible = show
}
override fun showDescriptionWithName(name: String) {
binding.authDescription.text = getString(R.string.auth_description, name).parseAsHtml()
}
}

View file

@ -1,100 +0,0 @@
package io.github.wulkanowy.ui.modules.auth
import io.github.wulkanowy.data.repositories.SemesterRepository
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
import kotlinx.coroutines.launch
import javax.inject.Inject
class AuthPresenter @Inject constructor(
private val semesterRepository: SemesterRepository,
errorHandler: ErrorHandler,
studentRepository: StudentRepository
) : BasePresenter<AuthView>(errorHandler, studentRepository) {
private var pesel: String = ""
override fun onAttachView(view: AuthView) {
super.onAttachView(view)
view.enableAuthButton(pesel.length == 11)
view.showSuccess(false)
view.showProgress(false)
loadName()
}
private fun loadName() {
presenterScope.launch {
runCatching { studentRepository.getCurrentStudent(false) }
.onSuccess { view?.showDescriptionWithName(it.studentName) }
.onFailure { errorHandler.dispatch(it) }
}
}
fun onPeselChange(newPesel: String?) {
pesel = newPesel.orEmpty()
view?.enableAuthButton(pesel.length == 11)
view?.showPeselError(false)
view?.showInvalidPeselError(false)
}
fun authorize() {
presenterScope.launch {
view?.showProgress(true)
view?.showContent(false)
if (!isValidPESEL(pesel)) {
view?.showInvalidPeselError(true)
view?.showProgress(false)
view?.showContent(true)
return@launch
}
runCatching {
val student = studentRepository.getCurrentStudent()
val semester = semesterRepository.getCurrentSemester(student)
val isSuccess = studentRepository.authorizePermission(student, semester, pesel)
if (isSuccess) {
studentRepository.refreshStudentName(student, semester)
}
isSuccess
}
.onFailure { errorHandler.dispatch(it) }
.onSuccess {
if (it) {
view?.showSuccess(true)
view?.showContent(false)
view?.showPeselError(false)
} else {
view?.showSuccess(false)
view?.showContent(true)
view?.showPeselError(true)
}
}
view?.showProgress(false)
}
}
private fun isValidPESEL(peselString: String): Boolean {
if (peselString.length != 11) {
return false
}
val weights = intArrayOf(1, 3, 7, 9, 1, 3, 7, 9, 1, 3)
var sum = 0
for (i in 0 until 10) {
sum += weights[i] * Character.getNumericValue(peselString[i])
}
sum %= 10
sum = 10 - sum
sum %= 10
return sum == Character.getNumericValue(peselString[10])
}
}

View file

@ -1,20 +0,0 @@
package io.github.wulkanowy.ui.modules.auth
import io.github.wulkanowy.ui.base.BaseView
interface AuthView : BaseView {
fun enableAuthButton(isEnabled: Boolean)
fun showProgress(show: Boolean)
fun showPeselError(show: Boolean)
fun showInvalidPeselError(show: Boolean)
fun showSuccess(show: Boolean)
fun showContent(show: Boolean)
fun showDescriptionWithName(name: String)
}

View file

@ -1,20 +1,19 @@
package io.github.wulkanowy.ui.modules.conference package io.github.wulkanowy.ui.modules.conference
import android.app.Dialog
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater
import android.view.View import android.view.View
import androidx.core.os.bundleOf import android.view.ViewGroup
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.google.android.material.dialog.MaterialAlertDialogBuilder import androidx.fragment.app.DialogFragment
import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.data.db.entities.Conference import io.github.wulkanowy.data.db.entities.Conference
import io.github.wulkanowy.databinding.DialogConferenceBinding import io.github.wulkanowy.databinding.DialogConferenceBinding
import io.github.wulkanowy.ui.base.BaseDialogFragment import io.github.wulkanowy.utils.lifecycleAwareVariable
import io.github.wulkanowy.utils.serializable
import io.github.wulkanowy.utils.toFormattedString import io.github.wulkanowy.utils.toFormattedString
@AndroidEntryPoint class ConferenceDialog : DialogFragment() {
class ConferenceDialog : BaseDialogFragment<DialogConferenceBinding>() {
private var binding: DialogConferenceBinding by lifecycleAwareVariable()
private lateinit var conference: Conference private lateinit var conference: Conference
@ -23,20 +22,23 @@ class ConferenceDialog : BaseDialogFragment<DialogConferenceBinding>() {
private const val ARGUMENT_KEY = "item" private const val ARGUMENT_KEY = "item"
fun newInstance(conference: Conference) = ConferenceDialog().apply { fun newInstance(conference: Conference) = ConferenceDialog().apply {
arguments = bundleOf(ARGUMENT_KEY to conference) arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, conference) }
} }
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
conference = requireArguments().serializable(ARGUMENT_KEY) setStyle(STYLE_NO_TITLE, 0)
arguments?.let {
conference = it.getSerializable(ARGUMENT_KEY) as Conference
}
} }
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateView(
return MaterialAlertDialogBuilder(requireContext(), theme) inflater: LayoutInflater,
.setView(DialogConferenceBinding.inflate(layoutInflater).apply { binding = this }.root) container: ViewGroup?,
.create() savedInstanceState: Bundle?
} ) = DialogConferenceBinding.inflate(inflater).also { binding = it }.root
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
@ -55,4 +57,4 @@ class ConferenceDialog : BaseDialogFragment<DialogConferenceBinding>() {
conferenceDialogAgendaTitle.isVisible = conference.agenda.isNotBlank() conferenceDialogAgendaTitle.isVisible = conference.agenda.isNotBlank()
} }
} }
} }

View file

@ -16,7 +16,7 @@ import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
class ConferenceFragment : BaseFragment<FragmentConferenceBinding>(R.layout.fragment_conference), class ConferenceFragment : BaseFragment<FragmentConferenceBinding>(R.layout.fragment_conference),
ConferenceView, MainView.TitledView, MainView.MainChildView { ConferenceView, MainView.TitledView {
@Inject @Inject
lateinit var presenter: ConferencePresenter lateinit var presenter: ConferencePresenter
@ -109,14 +109,6 @@ class ConferenceFragment : BaseFragment<FragmentConferenceBinding>(R.layout.frag
(activity as? MainActivity)?.showDialogFragment(ConferenceDialog.newInstance(conference)) (activity as? MainActivity)?.showDialogFragment(ConferenceDialog.newInstance(conference))
} }
override fun onFragmentReselected() {
if (::presenter.isInitialized) presenter.onFragmentReselected()
}
override fun resetView() {
binding.conferenceRecycler.smoothScrollToPosition(0)
}
override fun onDestroyView() { override fun onDestroyView() {
presenter.onDetachView() presenter.onDetachView()
super.onDestroyView() super.onDestroyView()

View file

@ -96,11 +96,4 @@ class ConferencePresenter @Inject constructor(
.onResourceError(errorHandler::dispatch) .onResourceError(errorHandler::dispatch)
.launch() .launch()
} }
fun onFragmentReselected() {
Timber.i("Conference is reselected")
if (view?.isViewEmpty == false) {
view?.resetView()
}
}
} }

View file

@ -28,6 +28,4 @@ interface ConferenceView : BaseView {
fun showContent(show: Boolean) fun showContent(show: Boolean)
fun openConferenceDialog(conference: Conference) fun openConferenceDialog(conference: Conference)
fun resetView()
} }

View file

@ -11,7 +11,6 @@ import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
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.databinding.FragmentDashboardBinding import io.github.wulkanowy.databinding.FragmentDashboardBinding
@ -62,7 +61,6 @@ class DashboardFragment : BaseFragment<FragmentDashboardBinding>(R.layout.fragme
fun newInstance() = DashboardFragment() fun newInstance() = DashboardFragment()
} }
@Suppress("DEPRECATION")
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setHasOptionsMenu(true) setHasOptionsMenu(true)
@ -149,7 +147,7 @@ class DashboardFragment : BaseFragment<FragmentDashboardBinding>(R.layout.fragme
val values = requireContext().resources.getStringArray(R.array.dashboard_tile_values) val values = requireContext().resources.getStringArray(R.array.dashboard_tile_values)
val selectedItemsState = values.map { value -> selectedItems.any { it.name == value } } val selectedItemsState = values.map { value -> selectedItems.any { it.name == value } }
MaterialAlertDialogBuilder(requireContext()) AlertDialog.Builder(requireContext())
.setTitle(R.string.pref_dashboard_appearance_tiles_title) .setTitle(R.string.pref_dashboard_appearance_tiles_title)
.setMultiChoiceItems(entries, selectedItemsState.toBooleanArray()) { _, _, _ -> } .setMultiChoiceItems(entries, selectedItemsState.toBooleanArray()) { _, _, _ -> }
.setPositiveButton(android.R.string.ok) { dialog, _ -> .setPositiveButton(android.R.string.ok) { dialog, _ ->

View file

@ -606,7 +606,7 @@ class DashboardPresenter @Inject constructor(
} }
is Resource.Error -> { is Resource.Error -> {
Timber.i("Loading dashboard admin message result: An exception occurred") Timber.i("Loading dashboard admin message result: An exception occurred")
Timber.e(it.error) errorHandler.dispatch(it.error)
updateData( updateData(
dashboardItem = DashboardItem.AdminMessages( dashboardItem = DashboardItem.AdminMessages(
adminMessage = null, adminMessage = null,
@ -748,7 +748,7 @@ class DashboardPresenter @Inject constructor(
itemsLoadedList.find { it.type == DashboardItem.Type.ACCOUNT }?.error != null itemsLoadedList.find { it.type == DashboardItem.Type.ACCOUNT }?.error != null
val isGeneralError = val isGeneralError =
filteredItems.none { it.error == null } && filteredItems.isNotEmpty() || isAccountItemError filteredItems.none { it.error == null } && filteredItems.isNotEmpty() || isAccountItemError
val firstError = itemsLoadedList.firstNotNullOfOrNull { it.error } val firstError = itemsLoadedList.mapNotNull { it.error }.firstOrNull()
val filteredOriginalLoadedList = val filteredOriginalLoadedList =
dashboardItemLoadedList.filterNot { it.type == DashboardItem.Type.ACCOUNT } dashboardItemLoadedList.filterNot { it.type == DashboardItem.Type.ACCOUNT }

View file

@ -1,6 +1,5 @@
package io.github.wulkanowy.ui.modules.dashboard.adapters package io.github.wulkanowy.ui.modules.dashboard.adapters
import android.content.res.ColorStateList
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
@ -9,7 +8,6 @@ import io.github.wulkanowy.data.enums.GradeColorTheme
import io.github.wulkanowy.databinding.SubitemDashboardGradesBinding import io.github.wulkanowy.databinding.SubitemDashboardGradesBinding
import io.github.wulkanowy.databinding.SubitemDashboardSmallGradeBinding import io.github.wulkanowy.databinding.SubitemDashboardSmallGradeBinding
import io.github.wulkanowy.utils.getBackgroundColor import io.github.wulkanowy.utils.getBackgroundColor
import io.github.wulkanowy.utils.getCompatColor
class DashboardGradesAdapter : RecyclerView.Adapter<DashboardGradesAdapter.ViewHolder>() { class DashboardGradesAdapter : RecyclerView.Adapter<DashboardGradesAdapter.ViewHolder>() {
@ -39,9 +37,7 @@ class DashboardGradesAdapter : RecyclerView.Adapter<DashboardGradesAdapter.ViewH
with(subitemBinding.dashboardSmallGradeSubitemValue) { with(subitemBinding.dashboardSmallGradeSubitemValue) {
text = it.entry text = it.entry
backgroundTintList = ColorStateList.valueOf( setBackgroundResource(it.getBackgroundColor(gradeColorTheme))
context.getCompatColor(it.getBackgroundColor(gradeColorTheme))
)
} }
dashboardGradesSubitemGradeContainer.addView(subitemBinding.root) dashboardGradesSubitemGradeContainer.addView(subitemBinding.root)

View file

@ -1,7 +1,9 @@
package io.github.wulkanowy.ui.modules.debug.logviewer package io.github.wulkanowy.ui.modules.debug.logviewer
import android.content.Intent import android.content.Intent
import android.content.Intent.* import android.content.Intent.EXTRA_EMAIL
import android.content.Intent.EXTRA_STREAM
import android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION
import android.os.Bundle import android.os.Bundle
import android.view.Menu import android.view.Menu
import android.view.MenuInflater import android.view.MenuInflater
@ -34,7 +36,6 @@ class LogViewerFragment : BaseFragment<FragmentLogviewerBinding>(R.layout.fragme
fun newInstance() = LogViewerFragment() fun newInstance() = LogViewerFragment()
} }
@Suppress("DEPRECATION")
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setHasOptionsMenu(true) setHasOptionsMenu(true)

View file

@ -1,22 +1,21 @@
package io.github.wulkanowy.ui.modules.exam package io.github.wulkanowy.ui.modules.exam
import android.app.Dialog
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater
import android.view.View import android.view.View
import androidx.core.os.bundleOf import android.view.ViewGroup
import com.google.android.material.dialog.MaterialAlertDialogBuilder import androidx.fragment.app.DialogFragment
import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Exam import io.github.wulkanowy.data.db.entities.Exam
import io.github.wulkanowy.databinding.DialogExamBinding import io.github.wulkanowy.databinding.DialogExamBinding
import io.github.wulkanowy.ui.base.BaseDialogFragment import io.github.wulkanowy.utils.lifecycleAwareVariable
import io.github.wulkanowy.utils.openCalendarEventAdd import io.github.wulkanowy.utils.openCalendarEventAdd
import io.github.wulkanowy.utils.serializable
import io.github.wulkanowy.utils.toFormattedString import io.github.wulkanowy.utils.toFormattedString
import java.time.LocalTime import java.time.LocalTime
@AndroidEntryPoint class ExamDialog : DialogFragment() {
class ExamDialog : BaseDialogFragment<DialogExamBinding>() {
private var binding: DialogExamBinding by lifecycleAwareVariable()
private lateinit var exam: Exam private lateinit var exam: Exam
@ -25,20 +24,23 @@ class ExamDialog : BaseDialogFragment<DialogExamBinding>() {
private const val ARGUMENT_KEY = "Item" private const val ARGUMENT_KEY = "Item"
fun newInstance(exam: Exam) = ExamDialog().apply { fun newInstance(exam: Exam) = ExamDialog().apply {
arguments = bundleOf(ARGUMENT_KEY to exam) arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, exam) }
} }
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
exam = requireArguments().serializable(ARGUMENT_KEY) setStyle(STYLE_NO_TITLE, 0)
arguments?.run {
exam = getSerializable(ARGUMENT_KEY) as Exam
}
} }
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateView(
return MaterialAlertDialogBuilder(requireContext(), theme) inflater: LayoutInflater,
.setView(DialogExamBinding.inflate(layoutInflater).apply { binding = this }.root) container: ViewGroup?,
.create() savedInstanceState: Bundle?
} ) = DialogExamBinding.inflate(inflater).apply { binding = this }.root
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)

View file

@ -2,7 +2,9 @@ package io.github.wulkanowy.ui.modules.exam
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.view.View.* import android.view.View.GONE
import android.view.View.INVISIBLE
import android.view.View.VISIBLE
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
@ -18,7 +20,7 @@ import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
class ExamFragment : BaseFragment<FragmentExamBinding>(R.layout.fragment_exam), ExamView, class ExamFragment : BaseFragment<FragmentExamBinding>(R.layout.fragment_exam), ExamView,
MainView.TitledView, MainView.MainChildView { MainView.TitledView {
@Inject @Inject
lateinit var presenter: ExamPresenter lateinit var presenter: ExamPresenter
@ -62,7 +64,7 @@ class ExamFragment : BaseFragment<FragmentExamBinding>(R.layout.fragment_exam),
examPreviousButton.setOnClickListener { presenter.onPreviousWeek() } examPreviousButton.setOnClickListener { presenter.onPreviousWeek() }
examNextButton.setOnClickListener { presenter.onNextWeek() } examNextButton.setOnClickListener { presenter.onNextWeek() }
examNavContainer.elevation = requireContext().dpToPx(3f) examNavContainer.elevation = requireContext().dpToPx(8f)
} }
} }
@ -124,14 +126,6 @@ class ExamFragment : BaseFragment<FragmentExamBinding>(R.layout.fragment_exam),
(activity as? MainActivity)?.showDialogFragment(ExamDialog.newInstance(exam)) (activity as? MainActivity)?.showDialogFragment(ExamDialog.newInstance(exam))
} }
override fun onFragmentReselected() {
if (::presenter.isInitialized) presenter.onViewReselected()
}
override fun resetView() {
binding.examRecycler.smoothScrollToPosition(0)
}
override fun onSaveInstanceState(outState: Bundle) { override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState) super.onSaveInstanceState(outState)
outState.putLong(SAVED_DATE_KEY, presenter.currentDate.toEpochDay()) outState.putLong(SAVED_DATE_KEY, presenter.currentDate.toEpochDay())

View file

@ -175,17 +175,4 @@ class ExamPresenter @Inject constructor(
) )
} }
} }
fun onViewReselected() {
Timber.i("Exam view is reselected")
baseDate = now().nextOrSameSchoolDay
if (currentDate != baseDate) {
reloadView(baseDate)
loadData()
} else if (view?.isViewEmpty == false) {
view?.resetView()
}
}
} }

View file

@ -34,6 +34,4 @@ interface ExamView : BaseView {
fun showPreButton(show: Boolean) fun showPreButton(show: Boolean)
fun showExamDialog(exam: Exam) fun showExamDialog(exam: Exam)
fun resetView()
} }

View file

@ -12,92 +12,70 @@ import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.ui.modules.grade.GradeAverageMode.* import io.github.wulkanowy.ui.modules.grade.GradeAverageMode.*
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.FlowPreview
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import javax.inject.Inject import javax.inject.Inject
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(FlowPreview::class)
class GradeAverageProvider @Inject constructor( class GradeAverageProvider @Inject constructor(
private val semesterRepository: SemesterRepository, private val semesterRepository: SemesterRepository,
private val gradeRepository: GradeRepository, private val gradeRepository: GradeRepository,
private val preferencesRepository: PreferencesRepository private val preferencesRepository: PreferencesRepository
) { ) {
private data class AverageCalcParams( private val plusModifier get() = preferencesRepository.gradePlusModifier
val gradeAverageMode: GradeAverageMode,
val forceAverageCalc: Boolean,
val isOptionalArithmeticAverage: Boolean,
val plusModifier: Double,
val minusModifier: Double,
)
fun getGradesDetailsWithAverage( private val minusModifier get() = preferencesRepository.gradeMinusModifier
student: Student,
semesterId: Int, private val isOptionalArithmeticAverage get() = preferencesRepository.isOptionalArithmeticAverage
forceRefresh: Boolean
): Flow<Resource<List<GradeSubject>>> = combine( fun getGradesDetailsWithAverage(student: Student, semesterId: Int, forceRefresh: Boolean) =
flow = preferencesRepository.gradeAverageModeFlow,
flow2 = preferencesRepository.gradeAverageForceCalcFlow,
flow3 = preferencesRepository.isOptionalArithmeticAverageFlow,
flow4 = preferencesRepository.gradePlusModifierFlow,
flow5 = preferencesRepository.gradeMinusModifierFlow,
) { gradeAverageMode, forceAverageCalc, isOptionalArithmeticAverage, plusModifier, minusModifier ->
AverageCalcParams(
gradeAverageMode = gradeAverageMode,
forceAverageCalc = forceAverageCalc,
isOptionalArithmeticAverage = isOptionalArithmeticAverage,
plusModifier = plusModifier,
minusModifier = minusModifier,
)
}.flatMapLatest { params ->
flatResourceFlow { flatResourceFlow {
val semesters = semesterRepository.getSemesters(student) val semesters = semesterRepository.getSemesters(student)
when (params.gradeAverageMode) {
when (preferencesRepository.gradeAverageMode) {
ONE_SEMESTER -> getGradeSubjects( ONE_SEMESTER -> getGradeSubjects(
student = student, student = student,
semester = semesters.single { it.semesterId == semesterId }, semester = semesters.single { it.semesterId == semesterId },
forceRefresh = forceRefresh, forceRefresh = forceRefresh
params = params,
) )
BOTH_SEMESTERS -> calculateCombinedAverage( BOTH_SEMESTERS -> calculateCombinedAverage(
student = student, student = student,
semesters = semesters, semesters = semesters,
semesterId = semesterId, semesterId = semesterId,
forceRefresh = forceRefresh, forceRefresh = forceRefresh,
config = params, averageMode = BOTH_SEMESTERS
) )
ALL_YEAR -> calculateCombinedAverage( ALL_YEAR -> calculateCombinedAverage(
student = student, student = student,
semesters = semesters, semesters = semesters,
semesterId = semesterId, semesterId = semesterId,
forceRefresh = forceRefresh, forceRefresh = forceRefresh,
config = params, averageMode = ALL_YEAR
) )
} }
} }.distinctUntilChanged()
}.distinctUntilChanged()
private fun calculateCombinedAverage( private fun calculateCombinedAverage(
student: Student, student: Student,
semesters: List<Semester>, semesters: List<Semester>,
semesterId: Int, semesterId: Int,
forceRefresh: Boolean, forceRefresh: Boolean,
config: AverageCalcParams, averageMode: GradeAverageMode
): Flow<Resource<List<GradeSubject>>> { ): Flow<Resource<List<GradeSubject>>> {
val isGradeAverageForceCalc = preferencesRepository.gradeAverageForceCalc
val selectedSemester = semesters.single { it.semesterId == semesterId } val selectedSemester = semesters.single { it.semesterId == semesterId }
val firstSemester = val firstSemester =
semesters.single { it.diaryId == selectedSemester.diaryId && it.semesterName == 1 } semesters.single { it.diaryId == selectedSemester.diaryId && it.semesterName == 1 }
val selectedSemesterGradeSubjects = val selectedSemesterGradeSubjects =
getGradeSubjects(student, selectedSemester, forceRefresh, config) getGradeSubjects(student, selectedSemester, forceRefresh)
if (selectedSemester == firstSemester) return selectedSemesterGradeSubjects if (selectedSemester == firstSemester) return selectedSemesterGradeSubjects
val firstSemesterGradeSubjects = val firstSemesterGradeSubjects = getGradeSubjects(student, firstSemester, forceRefresh)
getGradeSubjects(student, firstSemester, forceRefresh, config)
return selectedSemesterGradeSubjects.combine(firstSemesterGradeSubjects) { secondSemesterGradeSubject, firstSemesterGradeSubject -> return selectedSemesterGradeSubjects.combine(firstSemesterGradeSubjects) { secondSemesterGradeSubject, firstSemesterGradeSubject ->
if (firstSemesterGradeSubject.errorOrNull != null) { if (firstSemesterGradeSubject.errorOrNull != null) {
@ -113,21 +91,21 @@ class GradeAverageProvider @Inject constructor(
val firstSemesterSubject = firstSemesterGradeSubject.dataOrNull.orEmpty() val firstSemesterSubject = firstSemesterGradeSubject.dataOrNull.orEmpty()
.singleOrNull { it.subject == secondSemesterSubject.subject } .singleOrNull { it.subject == secondSemesterSubject.subject }
val updatedAverage = if (config.gradeAverageMode == ALL_YEAR) { val updatedAverage = if (averageMode == ALL_YEAR) {
calculateAllYearAverage( calculateAllYearAverage(
student = student, student = student,
isAnyVulcanAverage = isAnyVulcanAverageInFirstSemester || isAnyVulcanAverageInSecondSemester, isAnyVulcanAverage = isAnyVulcanAverageInFirstSemester || isAnyVulcanAverageInSecondSemester,
isGradeAverageForceCalc = isGradeAverageForceCalc,
secondSemesterSubject = secondSemesterSubject, secondSemesterSubject = secondSemesterSubject,
firstSemesterSubject = firstSemesterSubject, firstSemesterSubject = firstSemesterSubject
config = config,
) )
} else { } else {
calculateBothSemestersAverage( calculateBothSemestersAverage(
student = student, student = student,
isAnyVulcanAverage = isAnyVulcanAverageInFirstSemester || isAnyVulcanAverageInSecondSemester, isAnyVulcanAverage = isAnyVulcanAverageInFirstSemester || isAnyVulcanAverageInSecondSemester,
isGradeAverageForceCalc = isGradeAverageForceCalc,
secondSemesterSubject = secondSemesterSubject, secondSemesterSubject = secondSemesterSubject,
firstSemesterSubject = firstSemesterSubject, firstSemesterSubject = firstSemesterSubject
config = config
) )
} }
secondSemesterSubject.copy(average = updatedAverage) secondSemesterSubject.copy(average = updatedAverage)
@ -139,17 +117,17 @@ class GradeAverageProvider @Inject constructor(
private fun calculateAllYearAverage( private fun calculateAllYearAverage(
student: Student, student: Student,
isAnyVulcanAverage: Boolean, isAnyVulcanAverage: Boolean,
isGradeAverageForceCalc: Boolean,
secondSemesterSubject: GradeSubject, secondSemesterSubject: GradeSubject,
firstSemesterSubject: GradeSubject?, firstSemesterSubject: GradeSubject?
config: AverageCalcParams, ) = if (!isAnyVulcanAverage || isGradeAverageForceCalc) {
) = if (!isAnyVulcanAverage || config.forceAverageCalc) { val updatedSecondSemesterGrades =
val updatedSecondSemesterGrades = secondSemesterSubject.grades secondSemesterSubject.grades.updateModifiers(student)
.updateModifiers(student, config) val updatedFirstSemesterGrades =
val updatedFirstSemesterGrades = firstSemesterSubject?.grades firstSemesterSubject?.grades?.updateModifiers(student).orEmpty()
?.updateModifiers(student, config).orEmpty()
(updatedSecondSemesterGrades + updatedFirstSemesterGrades).calcAverage( (updatedSecondSemesterGrades + updatedFirstSemesterGrades).calcAverage(
config.isOptionalArithmeticAverage isOptionalArithmeticAverage
) )
} else { } else {
secondSemesterSubject.average secondSemesterSubject.average
@ -158,35 +136,32 @@ class GradeAverageProvider @Inject constructor(
private fun calculateBothSemestersAverage( private fun calculateBothSemestersAverage(
student: Student, student: Student,
isAnyVulcanAverage: Boolean, isAnyVulcanAverage: Boolean,
isGradeAverageForceCalc: Boolean,
secondSemesterSubject: GradeSubject, secondSemesterSubject: GradeSubject,
firstSemesterSubject: GradeSubject?, firstSemesterSubject: GradeSubject?
config: AverageCalcParams, ): Double = if (!isAnyVulcanAverage || isGradeAverageForceCalc) {
): Double { val divider = if (secondSemesterSubject.grades.any { it.weightValue > .0 }) 2 else 1
return if (!isAnyVulcanAverage || config.forceAverageCalc) {
val divider = if (secondSemesterSubject.grades.any { it.weightValue > .0 }) 2 else 1
val secondSemesterAverage = secondSemesterSubject.grades
.updateModifiers(student, config)
.calcAverage(config.isOptionalArithmeticAverage)
val firstSemesterAverage = firstSemesterSubject?.grades
?.updateModifiers(student, config)
?.calcAverage(config.isOptionalArithmeticAverage) ?: secondSemesterAverage
(secondSemesterAverage + firstSemesterAverage) / divider val secondSemesterAverage = secondSemesterSubject.grades.updateModifiers(student)
} else { .calcAverage(isOptionalArithmeticAverage)
val divider = if (secondSemesterSubject.average > 0) 2 else 1 val firstSemesterAverage = firstSemesterSubject?.grades?.updateModifiers(student)
?.calcAverage(isOptionalArithmeticAverage) ?: secondSemesterAverage
secondSemesterSubject.average.plus( (secondSemesterAverage + firstSemesterAverage) / divider
(firstSemesterSubject?.average ?: secondSemesterSubject.average) } else {
) / divider val divider = if (secondSemesterSubject.average > 0) 2 else 1
}
(secondSemesterSubject.average + (firstSemesterSubject?.average
?: secondSemesterSubject.average)) / divider
} }
private fun getGradeSubjects( private fun getGradeSubjects(
student: Student, student: Student,
semester: Semester, semester: Semester,
forceRefresh: Boolean, forceRefresh: Boolean
params: AverageCalcParams,
): Flow<Resource<List<GradeSubject>>> { ): Flow<Resource<List<GradeSubject>>> {
val isGradeAverageForceCalc = preferencesRepository.gradeAverageForceCalc
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) = res
@ -197,15 +172,13 @@ class GradeAverageProvider @Inject constructor(
student = student, student = student,
semester = semester, semester = semester,
grades = allGrades.toList(), grades = allGrades.toList(),
calcAverage = isAnyAverage, calcAverage = isAnyAverage
params = params,
).map { summary -> ).map { summary ->
val grades = allGrades[summary.subject].orEmpty() val grades = allGrades[summary.subject].orEmpty()
GradeSubject( GradeSubject(
subject = summary.subject, subject = summary.subject,
average = if (!isAnyAverage || params.forceAverageCalc) { average = if (!isAnyAverage || isGradeAverageForceCalc) {
grades.updateModifiers(student, params) grades.updateModifiers(student).calcAverage(isOptionalArithmeticAverage)
.calcAverage(params.isOptionalArithmeticAverage)
} else summary.average, } else summary.average,
points = summary.pointsSum, points = summary.pointsSum,
summary = summary, summary = summary,
@ -222,8 +195,7 @@ class GradeAverageProvider @Inject constructor(
student: Student, student: Student,
semester: Semester, semester: Semester,
grades: List<Pair<String, List<Grade>>>, grades: List<Pair<String, List<Grade>>>,
calcAverage: Boolean, calcAverage: Boolean
params: AverageCalcParams,
): List<GradeSummary> { ): List<GradeSummary> {
if (isNotEmpty() && size > grades.size) return this if (isNotEmpty() && size > grades.size) return this
@ -239,16 +211,15 @@ class GradeAverageProvider @Inject constructor(
proposedPoints = "", proposedPoints = "",
finalPoints = "", finalPoints = "",
pointsSum = "", pointsSum = "",
average = if (calcAverage) details.updateModifiers(student, params) average = if (calcAverage) details.updateModifiers(student)
.calcAverage(params.isOptionalArithmeticAverage) else .0 .calcAverage(isOptionalArithmeticAverage) else .0
) )
} }
} }
private fun List<Grade>.updateModifiers( private fun List<Grade>.updateModifiers(student: Student): List<Grade> {
student: Student, return if (student.loginMode == Sdk.Mode.SCRAPPER.name) {
params: AverageCalcParams, map { it.changeModifier(plusModifier, minusModifier) }
): List<Grade> = if (student.loginMode == Sdk.Mode.SCRAPPER.name) { } else this
map { it.changeModifier(params.plusModifier, params.minusModifier) } }
} else this
} }

View file

@ -8,7 +8,6 @@ import android.view.View
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.appcompat.app.AlertDialog
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.tabs.TabLayoutMediator import com.google.android.material.tabs.TabLayoutMediator
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R import io.github.wulkanowy.R
@ -52,7 +51,6 @@ class GradeFragment : BaseFragment<FragmentGradeBinding>(R.layout.fragment_grade
override val currentPageIndex get() = binding.gradeViewPager.currentItem override val currentPageIndex get() = binding.gradeViewPager.currentItem
@Suppress("DEPRECATION")
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setHasOptionsMenu(true) setHasOptionsMenu(true)
@ -142,7 +140,7 @@ class GradeFragment : BaseFragment<FragmentGradeBinding>(R.layout.fragment_grade
val choices = semesters.map { getString(R.string.grade_semester, it.semesterName) } val choices = semesters.map { getString(R.string.grade_semester, it.semesterName) }
.toTypedArray() .toTypedArray()
MaterialAlertDialogBuilder(requireContext()) AlertDialog.Builder(requireContext())
.setSingleChoiceItems(choices, selectedIndex) { dialog, which -> .setSingleChoiceItems(choices, selectedIndex) { dialog, which ->
presenter.onSemesterSelected(which) presenter.onSemesterSelected(which)
dialog.dismiss() dialog.dismiss()

View file

@ -1,7 +1,6 @@
package io.github.wulkanowy.ui.modules.grade.details package io.github.wulkanowy.ui.modules.grade.details
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.res.ColorStateList
import android.content.res.Resources import android.content.res.Resources
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
@ -18,10 +17,9 @@ import io.github.wulkanowy.databinding.HeaderGradeDetailsBinding
import io.github.wulkanowy.databinding.ItemGradeDetailsBinding import io.github.wulkanowy.databinding.ItemGradeDetailsBinding
import io.github.wulkanowy.ui.base.BaseExpandableAdapter import io.github.wulkanowy.ui.base.BaseExpandableAdapter
import io.github.wulkanowy.utils.getBackgroundColor import io.github.wulkanowy.utils.getBackgroundColor
import io.github.wulkanowy.utils.getCompatColor
import io.github.wulkanowy.utils.toFormattedString import io.github.wulkanowy.utils.toFormattedString
import timber.log.Timber import timber.log.Timber
import java.util.* import java.util.BitSet
import javax.inject.Inject import javax.inject.Inject
class GradeDetailsAdapter @Inject constructor() : BaseExpandableAdapter<RecyclerView.ViewHolder>() { class GradeDetailsAdapter @Inject constructor() : BaseExpandableAdapter<RecyclerView.ViewHolder>() {
@ -205,9 +203,7 @@ class GradeDetailsAdapter @Inject constructor() : BaseExpandableAdapter<Recycler
with(holder.binding) { with(holder.binding) {
gradeItemValue.run { gradeItemValue.run {
text = grade.entry text = grade.entry
backgroundTintList = ColorStateList.valueOf( setBackgroundResource(grade.getBackgroundColor(gradeColorTheme))
context.getCompatColor(grade.getBackgroundColor(gradeColorTheme))
)
} }
gradeItemDescription.text = when { gradeItemDescription.text = when {
grade.description.isNotBlank() -> grade.description grade.description.isNotBlank() -> grade.description

View file

@ -1,23 +1,21 @@
package io.github.wulkanowy.ui.modules.grade.details package io.github.wulkanowy.ui.modules.grade.details
import android.app.Dialog
import android.content.res.ColorStateList
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.View.GONE import android.view.View.GONE
import androidx.core.content.ContextCompat import android.view.ViewGroup
import androidx.core.os.bundleOf import androidx.fragment.app.DialogFragment
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
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.enums.GradeColorTheme import io.github.wulkanowy.data.enums.GradeColorTheme
import io.github.wulkanowy.databinding.DialogGradeBinding import io.github.wulkanowy.databinding.DialogGradeBinding
import io.github.wulkanowy.ui.base.BaseDialogFragment
import io.github.wulkanowy.utils.* import io.github.wulkanowy.utils.*
@AndroidEntryPoint
class GradeDetailsDialog : BaseDialogFragment<DialogGradeBinding>() { class GradeDetailsDialog : DialogFragment() {
private var binding: DialogGradeBinding by lifecycleAwareVariable()
private lateinit var grade: Grade private lateinit var grade: Grade
@ -29,25 +27,29 @@ class GradeDetailsDialog : BaseDialogFragment<DialogGradeBinding>() {
private const val COLOR_THEME_KEY = "Theme" private const val COLOR_THEME_KEY = "Theme"
fun newInstance(grade: Grade, colorTheme: GradeColorTheme) = GradeDetailsDialog().apply { fun newInstance(grade: Grade, colorTheme: GradeColorTheme) =
arguments = bundleOf( GradeDetailsDialog().apply {
ARGUMENT_KEY to grade, arguments = Bundle().apply {
COLOR_THEME_KEY to colorTheme putSerializable(ARGUMENT_KEY, grade)
) putSerializable(COLOR_THEME_KEY, colorTheme)
} }
}
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
grade = requireArguments().serializable(ARGUMENT_KEY) setStyle(STYLE_NO_TITLE, 0)
gradeColorTheme = requireArguments().serializable(COLOR_THEME_KEY) arguments?.run {
grade = getSerializable(ARGUMENT_KEY) as Grade
gradeColorTheme = getSerializable(COLOR_THEME_KEY) as GradeColorTheme
}
} }
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateView(
return MaterialAlertDialogBuilder(requireContext(), theme) inflater: LayoutInflater,
.setView(DialogGradeBinding.inflate(layoutInflater).apply { binding = this }.root) container: ViewGroup?,
.create() savedInstanceState: Bundle?
} ) = DialogGradeBinding.inflate(inflater).apply { binding = this }.root
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
@ -55,9 +57,10 @@ class GradeDetailsDialog : BaseDialogFragment<DialogGradeBinding>() {
with(binding) { with(binding) {
gradeDialogSubject.text = grade.subject gradeDialogSubject.text = grade.subject
gradeDialogWeightValue.text = grade.weight gradeDialogColorAndWeightValue.run {
gradeDialogWeightLayout.backgroundTintList = text = context.getString(R.string.grade_weight_value, grade.weight)
ColorStateList.valueOf(requireContext().getCompatColor(grade.getGradeColor())) setBackgroundResource(grade.getGradeColor())
}
gradeDialogDateValue.text = grade.date.toFormattedString() gradeDialogDateValue.text = grade.date.toFormattedString()
gradeDialogColorValue.text = getString(grade.colorStringId) gradeDialogColorValue.text = getString(grade.colorStringId)
@ -71,12 +74,7 @@ class GradeDetailsDialog : BaseDialogFragment<DialogGradeBinding>() {
gradeDialogValue.run { gradeDialogValue.run {
text = grade.entry text = grade.entry
backgroundTintList = ColorStateList.valueOf( setBackgroundResource(grade.getBackgroundColor(gradeColorTheme))
ContextCompat.getColor(
requireContext(),
grade.getBackgroundColor(gradeColorTheme)
)
)
} }
gradeDialogTeacherValue.text = grade.teacher.ifBlank { getString(R.string.all_no_data) } gradeDialogTeacherValue.text = grade.teacher.ifBlank { getString(R.string.all_no_data) }

View file

@ -5,7 +5,9 @@ import android.view.Menu
import android.view.MenuInflater import android.view.MenuInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.view.View.* import android.view.View.GONE
import android.view.View.INVISIBLE
import android.view.View.VISIBLE
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
@ -40,7 +42,6 @@ class GradeDetailsFragment :
override val isViewEmpty override val isViewEmpty
get() = gradeDetailsAdapter.itemCount == 0 get() = gradeDetailsAdapter.itemCount == 0
@Suppress("DEPRECATION")
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setHasOptionsMenu(true) setHasOptionsMenu(true)

View file

@ -116,9 +116,7 @@ class GradeStatisticsAdapter @Inject constructor() :
} }
) )
binding.gradeStatisticsTypeSwitch.addOnButtonCheckedListener { _, checkedId, isChecked -> binding.gradeStatisticsTypeSwitch.setOnCheckedChangeListener { _, checkedId ->
if (!isChecked) return@addOnButtonCheckedListener
currentDataType = when (checkedId) { currentDataType = when (checkedId) {
R.id.gradeStatisticsTypePartial -> GradeStatisticsItem.DataType.PARTIAL R.id.gradeStatisticsTypePartial -> GradeStatisticsItem.DataType.PARTIAL
R.id.gradeStatisticsTypeSemester -> GradeStatisticsItem.DataType.SEMESTER R.id.gradeStatisticsTypeSemester -> GradeStatisticsItem.DataType.SEMESTER

View file

@ -15,7 +15,6 @@ import io.github.wulkanowy.ui.modules.grade.GradeFragment
import io.github.wulkanowy.ui.modules.grade.GradeView import io.github.wulkanowy.ui.modules.grade.GradeView
import io.github.wulkanowy.utils.dpToPx import io.github.wulkanowy.utils.dpToPx
import io.github.wulkanowy.utils.getThemeAttrColor import io.github.wulkanowy.utils.getThemeAttrColor
import io.github.wulkanowy.utils.serializable
import io.github.wulkanowy.utils.setOnItemSelectedListener import io.github.wulkanowy.utils.setOnItemSelectedListener
import javax.inject.Inject import javax.inject.Inject
@ -49,8 +48,8 @@ class GradeStatisticsFragment :
messageContainer = binding.gradeStatisticsRecycler messageContainer = binding.gradeStatisticsRecycler
presenter.onAttachView( presenter.onAttachView(
view = this, view = this,
type = savedInstanceState?.serializable(SAVED_CHART_TYPE), type = savedInstanceState?.getSerializable(SAVED_CHART_TYPE) as? GradeStatisticsItem.DataType,
subjectName = savedInstanceState?.serializable(SAVED_SUBJECT_NAME), subjectName = savedInstanceState?.getSerializable(SAVED_SUBJECT_NAME) as? String,
) )
} }

View file

@ -7,7 +7,6 @@ import android.view.View.INVISIBLE
import android.view.View.VISIBLE import android.view.View.VISIBLE
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
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.data.db.entities.GradeSummary
@ -119,7 +118,7 @@ class GradeSummaryFragment :
} }
override fun showCalculatedAverageHelpDialog() { override fun showCalculatedAverageHelpDialog() {
MaterialAlertDialogBuilder(requireContext()) AlertDialog.Builder(requireContext())
.setTitle(R.string.grade_summary_calculated_average_help_dialog_title) .setTitle(R.string.grade_summary_calculated_average_help_dialog_title)
.setMessage(R.string.grade_summary_calculated_average_help_dialog_message) .setMessage(R.string.grade_summary_calculated_average_help_dialog_message)
.setPositiveButton(R.string.all_close) { _, _ -> } .setPositiveButton(R.string.all_close) { _, _ -> }
@ -127,7 +126,7 @@ class GradeSummaryFragment :
} }
override fun showFinalAverageHelpDialog() { override fun showFinalAverageHelpDialog() {
MaterialAlertDialogBuilder(requireContext()) AlertDialog.Builder(requireContext())
.setTitle(R.string.grade_summary_final_average_help_dialog_title) .setTitle(R.string.grade_summary_final_average_help_dialog_title)
.setMessage(R.string.grade_summary_final_average_help_dialog_message) .setMessage(R.string.grade_summary_final_average_help_dialog_message)
.setPositiveButton(R.string.all_close) { _, _ -> } .setPositiveButton(R.string.all_close) { _, _ -> }

View file

@ -21,7 +21,7 @@ import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
class HomeworkFragment : BaseFragment<FragmentHomeworkBinding>(R.layout.fragment_homework), class HomeworkFragment : BaseFragment<FragmentHomeworkBinding>(R.layout.fragment_homework),
HomeworkView, MainView.TitledView, MainView.MainChildView { HomeworkView, MainView.TitledView {
@Inject @Inject
lateinit var presenter: HomeworkPresenter lateinit var presenter: HomeworkPresenter
@ -67,7 +67,7 @@ class HomeworkFragment : BaseFragment<FragmentHomeworkBinding>(R.layout.fragment
openAddHomeworkButton.setOnClickListener { presenter.onHomeworkAddButtonClicked() } openAddHomeworkButton.setOnClickListener { presenter.onHomeworkAddButtonClicked() }
homeworkNavContainer.elevation = requireContext().dpToPx(3f) homeworkNavContainer.elevation = requireContext().dpToPx(8f)
} }
} }
@ -133,14 +133,6 @@ class HomeworkFragment : BaseFragment<FragmentHomeworkBinding>(R.layout.fragment
(activity as? MainActivity)?.showDialogFragment(HomeworkAddDialog()) (activity as? MainActivity)?.showDialogFragment(HomeworkAddDialog())
} }
override fun onFragmentReselected() {
if (::presenter.isInitialized) presenter.onViewReselected()
}
override fun resetView() {
binding.homeworkRecycler.smoothScrollToPosition(0)
}
override fun onSaveInstanceState(outState: Bundle) { override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState) super.onSaveInstanceState(outState)
outState.putLong(SAVED_DATE_KEY, presenter.currentDate.toEpochDay()) outState.putLong(SAVED_DATE_KEY, presenter.currentDate.toEpochDay())

View file

@ -177,21 +177,8 @@ class HomeworkPresenter @Inject constructor(
showNextButton(!currentDate.plusDays(7).isHolidays) showNextButton(!currentDate.plusDays(7).isHolidays)
updateNavigationWeek( updateNavigationWeek(
"${currentDate.monday.toFormattedString("dd.MM")} - " + "${currentDate.monday.toFormattedString("dd.MM")} - " +
currentDate.sunday.toFormattedString("dd.MM") currentDate.sunday.toFormattedString("dd.MM")
) )
} }
} }
fun onViewReselected() {
Timber.i("Homework view is reselected")
baseDate = LocalDate.now().nextOrSameSchoolDay
if (currentDate != baseDate) {
reloadView(baseDate)
loadData()
} else if (view?.isViewEmpty == false) {
view?.resetView()
}
}
} }

View file

@ -36,6 +36,4 @@ interface HomeworkView : BaseView {
fun showHomeworkDialog(homework: Homework) fun showHomeworkDialog(homework: Homework)
fun showAddHomeworkDialog() fun showAddHomeworkDialog()
fun resetView()
} }

View file

@ -1,10 +1,10 @@
package io.github.wulkanowy.ui.modules.homework.add package io.github.wulkanowy.ui.modules.homework.add
import android.app.Dialog
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup
import androidx.core.widget.doOnTextChanged import androidx.core.widget.doOnTextChanged
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.databinding.DialogHomeworkAddBinding import io.github.wulkanowy.databinding.DialogHomeworkAddBinding
@ -21,15 +21,20 @@ class HomeworkAddDialog : BaseDialogFragment<DialogHomeworkAddBinding>(), Homewo
@Inject @Inject
lateinit var presenter: HomeworkAddPresenter lateinit var presenter: HomeworkAddPresenter
//todo: move it to presenter // todo: move it to presenter
private var date: LocalDate? = null private var date: LocalDate? = null
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreate(savedInstanceState: Bundle?) {
return MaterialAlertDialogBuilder(requireContext(), theme) super.onCreate(savedInstanceState)
.setView(DialogHomeworkAddBinding.inflate(layoutInflater).apply { binding = this }.root) setStyle(STYLE_NO_TITLE, 0)
.create()
} }
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
) = DialogHomeworkAddBinding.inflate(inflater).apply { binding = this }.root
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
presenter.onAttachView(this) presenter.onAttachView(this)

View file

@ -31,8 +31,14 @@ class HomeworkDetailsAdapter @Inject constructor() :
attachments = value?.attachments.orEmpty() attachments = value?.attachments.orEmpty()
} }
var isHomeworkFullscreen = false
var onAttachmentClickListener: (url: String) -> Unit = {} var onAttachmentClickListener: (url: String) -> Unit = {}
var onFullScreenClickListener = {}
var onFullScreenExitClickListener = {}
var onDeleteClickListener: (homework: Homework) -> Unit = {} var onDeleteClickListener: (homework: Homework) -> Unit = {}
override fun getItemCount() = 1 + if (attachments.isNotEmpty()) attachments.size + 1 else 0 override fun getItemCount() = 1 + if (attachments.isNotEmpty()) attachments.size + 1 else 0
@ -76,6 +82,18 @@ class HomeworkDetailsAdapter @Inject constructor() :
homeworkDialogTeacher.text = homework?.teacher.ifNullOrBlank { noDataString } homeworkDialogTeacher.text = homework?.teacher.ifNullOrBlank { noDataString }
homeworkDialogContent.text = homework?.content.ifNullOrBlank { noDataString } homeworkDialogContent.text = homework?.content.ifNullOrBlank { noDataString }
homeworkDialogDelete.visibility = if (homework?.isAddedByUser == true) VISIBLE else GONE homeworkDialogDelete.visibility = if (homework?.isAddedByUser == true) VISIBLE else GONE
homeworkDialogFullScreen.visibility = if (isHomeworkFullscreen) GONE else VISIBLE
homeworkDialogFullScreenExit.visibility = if (isHomeworkFullscreen) VISIBLE else GONE
homeworkDialogFullScreen.setOnClickListener {
homeworkDialogFullScreen.visibility = GONE
homeworkDialogFullScreenExit.visibility = VISIBLE
onFullScreenClickListener()
}
homeworkDialogFullScreenExit.setOnClickListener {
homeworkDialogFullScreen.visibility = VISIBLE
homeworkDialogFullScreenExit.visibility = GONE
onFullScreenExitClickListener()
}
homeworkDialogDelete.setOnClickListener { homeworkDialogDelete.setOnClickListener {
onDeleteClickListener(homework!!) onDeleteClickListener(homework!!)
} }

View file

@ -1,19 +1,19 @@
package io.github.wulkanowy.ui.modules.homework.details package io.github.wulkanowy.ui.modules.homework.details
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Dialog
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater
import android.view.View import android.view.View
import androidx.core.os.bundleOf import android.view.ViewGroup
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
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.Homework import io.github.wulkanowy.data.db.entities.Homework
import io.github.wulkanowy.databinding.DialogHomeworkBinding import io.github.wulkanowy.databinding.DialogHomeworkBinding
import io.github.wulkanowy.ui.base.BaseDialogFragment import io.github.wulkanowy.ui.base.BaseDialogFragment
import io.github.wulkanowy.utils.openInternetBrowser import io.github.wulkanowy.utils.openInternetBrowser
import io.github.wulkanowy.utils.serializable
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
@ -35,20 +35,23 @@ class HomeworkDetailsDialog : BaseDialogFragment<DialogHomeworkBinding>(), Homew
private const val ARGUMENT_KEY = "Item" private const val ARGUMENT_KEY = "Item"
fun newInstance(homework: Homework) = HomeworkDetailsDialog().apply { fun newInstance(homework: Homework) = HomeworkDetailsDialog().apply {
arguments = bundleOf(ARGUMENT_KEY to homework) arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, homework) }
} }
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
homework = requireArguments().serializable(ARGUMENT_KEY) setStyle(STYLE_NO_TITLE, 0)
arguments?.run {
homework = getSerializable(ARGUMENT_KEY) as Homework
}
} }
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateView(
return MaterialAlertDialogBuilder(requireContext(), theme) inflater: LayoutInflater,
.setView(DialogHomeworkBinding.inflate(layoutInflater).apply { binding = this }.root) container: ViewGroup?,
.create() savedInstanceState: Bundle?
} ) = DialogHomeworkBinding.inflate(inflater).apply { binding = this }.root
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
@ -64,11 +67,26 @@ class HomeworkDetailsDialog : BaseDialogFragment<DialogHomeworkBinding>(), Homew
homeworkDialogClose.setOnClickListener { dismiss() } homeworkDialogClose.setOnClickListener { dismiss() }
} }
if (presenter.isHomeworkFullscreen) {
dialog?.window?.setLayout(MATCH_PARENT, MATCH_PARENT)
} else {
dialog?.window?.setLayout(WRAP_CONTENT, WRAP_CONTENT)
}
with(binding.homeworkDialogRecycler) { with(binding.homeworkDialogRecycler) {
layoutManager = LinearLayoutManager(context) layoutManager = LinearLayoutManager(context)
adapter = detailsAdapter.apply { adapter = detailsAdapter.apply {
onAttachmentClickListener = { context.openInternetBrowser(it, ::showMessage) } onAttachmentClickListener = { context.openInternetBrowser(it, ::showMessage) }
onFullScreenClickListener = {
dialog?.window?.setLayout(MATCH_PARENT, MATCH_PARENT)
presenter.isHomeworkFullscreen = true
}
onFullScreenExitClickListener = {
dialog?.window?.setLayout(WRAP_CONTENT, WRAP_CONTENT)
presenter.isHomeworkFullscreen = false
}
onDeleteClickListener = { homework -> presenter.deleteHomework(homework) } onDeleteClickListener = { homework -> presenter.deleteHomework(homework) }
isHomeworkFullscreen = presenter.isHomeworkFullscreen
homework = this@HomeworkDetailsDialog.homework homework = this@HomeworkDetailsDialog.homework
} }
} }

View file

@ -5,6 +5,7 @@ import io.github.wulkanowy.data.logResourceStatus
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.repositories.HomeworkRepository import io.github.wulkanowy.data.repositories.HomeworkRepository
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.ui.base.BasePresenter import io.github.wulkanowy.ui.base.BasePresenter
@ -18,8 +19,15 @@ class HomeworkDetailsPresenter @Inject constructor(
studentRepository: StudentRepository, studentRepository: StudentRepository,
private val homeworkRepository: HomeworkRepository, private val homeworkRepository: HomeworkRepository,
private val analytics: AnalyticsHelper, private val analytics: AnalyticsHelper,
private val preferencesRepository: PreferencesRepository
) : BasePresenter<HomeworkDetailsView>(errorHandler, studentRepository) { ) : BasePresenter<HomeworkDetailsView>(errorHandler, studentRepository) {
var isHomeworkFullscreen
get() = preferencesRepository.isHomeworkFullscreen
set(value) {
preferencesRepository.isHomeworkFullscreen = value
}
override fun onAttachView(view: HomeworkDetailsView) { override fun onAttachView(view: HomeworkDetailsView) {
super.onAttachView(view) super.onAttachView(view)
view.initView() view.initView()

View file

@ -2,17 +2,13 @@ package io.github.wulkanowy.ui.modules.login
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build.VERSION_CODES.TIRAMISU
import android.os.Bundle import android.os.Bundle
import android.view.MenuItem import android.view.MenuItem
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager.POP_BACK_STACK_INCLUSIVE
import androidx.fragment.app.commit import androidx.fragment.app.commit
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.pojos.RegisterUser import io.github.wulkanowy.data.db.entities.StudentWithSemesters
import io.github.wulkanowy.databinding.ActivityLoginBinding import io.github.wulkanowy.databinding.ActivityLoginBinding
import io.github.wulkanowy.ui.base.BaseActivity import io.github.wulkanowy.ui.base.BaseActivity
import io.github.wulkanowy.ui.modules.login.advanced.LoginAdvancedFragment import io.github.wulkanowy.ui.modules.login.advanced.LoginAdvancedFragment
@ -20,9 +16,6 @@ import io.github.wulkanowy.ui.modules.login.form.LoginFormFragment
import io.github.wulkanowy.ui.modules.login.recover.LoginRecoverFragment import io.github.wulkanowy.ui.modules.login.recover.LoginRecoverFragment
import io.github.wulkanowy.ui.modules.login.studentselect.LoginStudentSelectFragment import io.github.wulkanowy.ui.modules.login.studentselect.LoginStudentSelectFragment
import io.github.wulkanowy.ui.modules.login.symbol.LoginSymbolFragment import io.github.wulkanowy.ui.modules.login.symbol.LoginSymbolFragment
import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.notifications.NotificationsFragment
import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.UpdateHelper import io.github.wulkanowy.utils.UpdateHelper
import javax.inject.Inject import javax.inject.Inject
@ -35,9 +28,6 @@ class LoginActivity : BaseActivity<LoginPresenter, ActivityLoginBinding>(), Logi
@Inject @Inject
lateinit var updateHelper: UpdateHelper lateinit var updateHelper: UpdateHelper
@Inject
lateinit var appInfo: AppInfo
companion object { companion object {
fun getStartIntent(context: Context) = Intent(context, LoginActivity::class.java) fun getStartIntent(context: Context) = Intent(context, LoginActivity::class.java)
} }
@ -65,7 +55,7 @@ class LoginActivity : BaseActivity<LoginPresenter, ActivityLoginBinding>(), Logi
} }
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) onBackPressedDispatcher.onBackPressed() if (item.itemId == android.R.id.home) onBackPressed()
return true return true
} }
@ -77,24 +67,8 @@ class LoginActivity : BaseActivity<LoginPresenter, ActivityLoginBinding>(), Logi
openFragment(LoginSymbolFragment.newInstance(loginData)) openFragment(LoginSymbolFragment.newInstance(loginData))
} }
fun navigateToStudentSelect(loginData: LoginData, registerUser: RegisterUser) { fun navigateToStudentSelect(studentsWithSemesters: List<StudentWithSemesters>) {
openFragment(LoginStudentSelectFragment.newInstance(loginData, registerUser)) openFragment(LoginStudentSelectFragment.newInstance(studentsWithSemesters))
}
fun navigateToNotifications() {
val isNotificationsPermissionRequired = appInfo.systemVersion >= TIRAMISU
val isPermissionGranted = ContextCompat.checkSelfPermission(
this, "android.permission.POST_NOTIFICATIONS"
) == PackageManager.PERMISSION_GRANTED
if (isNotificationsPermissionRequired && !isPermissionGranted) {
openFragment(NotificationsFragment.newInstance(), clearBackStack = true)
} else navigateToFinish()
}
fun navigateToFinish() {
startActivity(MainActivity.getStartIntent(this))
finish()
} }
fun onAdvancedLoginClick() { fun onAdvancedLoginClick() {
@ -106,8 +80,6 @@ class LoginActivity : BaseActivity<LoginPresenter, ActivityLoginBinding>(), Logi
} }
private fun openFragment(fragment: Fragment, clearBackStack: Boolean = false) { private fun openFragment(fragment: Fragment, clearBackStack: Boolean = false) {
supportFragmentManager.popBackStack(fragment::class.java.name, POP_BACK_STACK_INCLUSIVE)
supportFragmentManager.commit { supportFragmentManager.commit {
replace(R.id.loginContainer, fragment) replace(R.id.loginContainer, fragment)
setReorderingAllowed(true) setReorderingAllowed(true)

View file

@ -6,5 +6,4 @@ data class LoginData(
val login: String, val login: String,
val password: String, val password: String,
val baseUrl: String, val baseUrl: String,
val symbol: String?,
) : Serializable ) : Serializable

View file

@ -4,15 +4,13 @@ import android.content.Context
import android.database.sqlite.SQLiteConstraintException import android.database.sqlite.SQLiteConstraintException
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.sdk.hebe.exception.InvalidPinException import io.github.wulkanowy.sdk.mobile.exception.InvalidPinException
import io.github.wulkanowy.sdk.hebe.exception.InvalidTokenException import io.github.wulkanowy.sdk.mobile.exception.InvalidSymbolException
import io.github.wulkanowy.sdk.hebe.exception.TokenDeadException import io.github.wulkanowy.sdk.mobile.exception.InvalidTokenException
import io.github.wulkanowy.sdk.hebe.exception.UnknownTokenException import io.github.wulkanowy.sdk.mobile.exception.TokenDeadException
import io.github.wulkanowy.sdk.scrapper.login.BadCredentialsException import io.github.wulkanowy.sdk.scrapper.login.BadCredentialsException
import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.base.ErrorHandler
import javax.inject.Inject import javax.inject.Inject
import io.github.wulkanowy.sdk.hebe.exception.InvalidSymbolException as InvalidHebeSymbolException
import io.github.wulkanowy.sdk.scrapper.login.InvalidSymbolException as InvalidScrapperSymbolException
class LoginErrorHandler @Inject constructor( class LoginErrorHandler @Inject constructor(
@ApplicationContext context: Context, @ApplicationContext context: Context,
@ -34,11 +32,9 @@ class LoginErrorHandler @Inject constructor(
is BadCredentialsException -> onBadCredentials(error.message) is BadCredentialsException -> onBadCredentials(error.message)
is SQLiteConstraintException -> onStudentDuplicate(resources.getString(R.string.login_duplicate_student)) is SQLiteConstraintException -> onStudentDuplicate(resources.getString(R.string.login_duplicate_student))
is TokenDeadException -> onInvalidToken(resources.getString(R.string.login_expired_token)) is TokenDeadException -> onInvalidToken(resources.getString(R.string.login_expired_token))
is UnknownTokenException,
is InvalidTokenException -> onInvalidToken(resources.getString(R.string.login_invalid_token)) is InvalidTokenException -> onInvalidToken(resources.getString(R.string.login_invalid_token))
is InvalidPinException -> onInvalidPin(resources.getString(R.string.login_invalid_pin)) is InvalidPinException -> onInvalidPin(resources.getString(R.string.login_invalid_pin))
is InvalidScrapperSymbolException, is InvalidSymbolException -> onInvalidSymbol(resources.getString(R.string.login_invalid_symbol))
is InvalidHebeSymbolException -> onInvalidSymbol(resources.getString(R.string.login_invalid_symbol))
else -> super.proceed(error) else -> super.proceed(error)
} }
} }

View file

@ -8,7 +8,7 @@ import android.widget.ArrayAdapter
import androidx.core.widget.doOnTextChanged import androidx.core.widget.doOnTextChanged
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.pojos.RegisterUser import io.github.wulkanowy.data.db.entities.StudentWithSemesters
import io.github.wulkanowy.databinding.FragmentLoginAdvancedBinding import io.github.wulkanowy.databinding.FragmentLoginAdvancedBinding
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.base.BaseFragment
@ -34,9 +34,9 @@ class LoginAdvancedFragment :
override val formLoginType: String override val formLoginType: String
get() = when (binding.loginTypeSwitch.checkedRadioButtonId) { get() = when (binding.loginTypeSwitch.checkedRadioButtonId) {
R.id.loginTypeApi -> Sdk.Mode.HEBE.name R.id.loginTypeApi -> "API"
R.id.loginTypeScrapper -> Sdk.Mode.SCRAPPER.name R.id.loginTypeScrapper -> "SCRAPPER"
else -> Sdk.Mode.HYBRID.name else -> "HYBRID"
} }
override val formUsernameValue: String override val formUsernameValue: String
@ -99,7 +99,7 @@ class LoginAdvancedFragment :
loginTypeSwitch.setOnCheckedChangeListener { _, checkedId -> loginTypeSwitch.setOnCheckedChangeListener { _, checkedId ->
presenter.onLoginModeSelected( presenter.onLoginModeSelected(
when (checkedId) { when (checkedId) {
R.id.loginTypeApi -> Sdk.Mode.HEBE R.id.loginTypeApi -> Sdk.Mode.API
R.id.loginTypeScrapper -> Sdk.Mode.SCRAPPER R.id.loginTypeScrapper -> Sdk.Mode.SCRAPPER
else -> Sdk.Mode.HYBRID else -> Sdk.Mode.HYBRID
} }
@ -327,8 +327,8 @@ class LoginAdvancedFragment :
(activity as? LoginActivity)?.navigateToSymbolFragment(loginData) (activity as? LoginActivity)?.navigateToSymbolFragment(loginData)
} }
override fun navigateToStudentSelect(loginData: LoginData, registerUser: RegisterUser) { override fun navigateToStudentSelect(studentsWithSemesters: List<StudentWithSemesters>) {
(activity as? LoginActivity)?.navigateToStudentSelect(loginData, registerUser) (activity as? LoginActivity)?.navigateToStudentSelect(studentsWithSemesters)
} }
override fun onResume() { override fun onResume() {

View file

@ -1,13 +1,12 @@
package io.github.wulkanowy.ui.modules.login.advanced package io.github.wulkanowy.ui.modules.login.advanced
import io.github.wulkanowy.data.Resource import io.github.wulkanowy.data.Resource
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
import io.github.wulkanowy.data.logResourceStatus import io.github.wulkanowy.data.logResourceStatus
import io.github.wulkanowy.data.onResourceNotLoading import io.github.wulkanowy.data.onResourceNotLoading
import io.github.wulkanowy.data.pojos.RegisterUser
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.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.sdk.scrapper.getNormalizedSymbol
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
@ -92,16 +91,14 @@ class LoginAdvancedPresenter @Inject constructor(
fun onLoginModeSelected(type: Sdk.Mode) { fun onLoginModeSelected(type: Sdk.Mode) {
view?.run { view?.run {
when (type) { when (type) {
Sdk.Mode.HEBE -> { Sdk.Mode.API -> {
showOnlyMobileApiModeInputs() showOnlyMobileApiModeInputs()
showMobileApiWarningMessage() showMobileApiWarningMessage()
} }
Sdk.Mode.SCRAPPER -> { Sdk.Mode.SCRAPPER -> {
showOnlyScrapperModeInputs() showOnlyScrapperModeInputs()
showScraperWarningMessage() showScraperWarningMessage()
} }
Sdk.Mode.HYBRID -> { Sdk.Mode.HYBRID -> {
showOnlyHybridModeInputs() showOnlyHybridModeInputs()
showHybridWarningMessage() showHybridWarningMessage()
@ -142,29 +139,23 @@ class LoginAdvancedPresenter @Inject constructor(
showProgress(true) showProgress(true)
showContent(false) showContent(false)
} }
is Resource.Success -> { is Resource.Success -> {
analytics.logEvent( analytics.logEvent(
"registration_form", "registration_form",
"success" to true, "success" to true,
"scrapperBaseUrl" to view?.formHostValue.orEmpty(), "students" to it.data.size,
"error" to "No error" "error" to "No error"
) )
val loginData = LoginData( val loginData = LoginData(
login = view?.formUsernameValue.orEmpty().trim(), login = view?.formUsernameValue.orEmpty().trim(),
password = view?.formPassValue.orEmpty().trim(), password = view?.formPassValue.orEmpty().trim(),
baseUrl = view?.formHostValue.orEmpty().trim(), baseUrl = view?.formHostValue.orEmpty().trim()
symbol = view?.formSymbolValue.orEmpty().trim().getNormalizedSymbol(), )
) when (it.data.size) {
when (it.data.symbols.size) { 0 -> view?.navigateToSymbol(loginData)
0 -> view?.navigateToSymbol(loginData) else -> view?.navigateToStudentSelect(it.data)
else -> view?.navigateToStudentSelect( }
loginData = loginData,
registerUser = it.data,
)
}
} }
is Resource.Error -> { is Resource.Error -> {
analytics.logEvent( analytics.logEvent(
"registration_form", "registration_form",
@ -182,7 +173,7 @@ class LoginAdvancedPresenter @Inject constructor(
}.launch("login") }.launch("login")
} }
private suspend fun getStudentsAppropriatesToLoginType(): RegisterUser { private suspend fun getStudentsAppropriatesToLoginType(): List<StudentWithSemesters> {
val email = view?.formUsernameValue.orEmpty() val email = view?.formUsernameValue.orEmpty()
val password = view?.formPassValue.orEmpty() val password = view?.formPassValue.orEmpty()
val endpoint = view?.formHostValue.orEmpty() val endpoint = view?.formHostValue.orEmpty()
@ -192,11 +183,10 @@ class LoginAdvancedPresenter @Inject constructor(
val token = view?.formTokenValue.orEmpty() val token = view?.formTokenValue.orEmpty()
return when (Sdk.Mode.valueOf(view?.formLoginType.orEmpty())) { return when (Sdk.Mode.valueOf(view?.formLoginType.orEmpty())) {
Sdk.Mode.HEBE -> studentRepository.getStudentsApi(pin, symbol, token) Sdk.Mode.API -> studentRepository.getStudentsApi(pin, symbol, token)
Sdk.Mode.SCRAPPER -> studentRepository.getStudentsScrapper( Sdk.Mode.SCRAPPER -> studentRepository.getStudentsScrapper(
email, password, endpoint, symbol email, password, endpoint, symbol
) )
Sdk.Mode.HYBRID -> studentRepository.getStudentsHybrid( Sdk.Mode.HYBRID -> studentRepository.getStudentsHybrid(
email, password, endpoint, symbol email, password, endpoint, symbol
) )
@ -215,8 +205,8 @@ class LoginAdvancedPresenter @Inject constructor(
var isCorrect = true var isCorrect = true
when (Sdk.Mode.valueOf(view?.formLoginType.orEmpty())) { when (Sdk.Mode.valueOf(view?.formLoginType ?: "")) {
Sdk.Mode.HEBE -> { Sdk.Mode.API -> {
if (pin.isEmpty()) { if (pin.isEmpty()) {
view?.setErrorPinRequired() view?.setErrorPinRequired()
isCorrect = false isCorrect = false
@ -232,17 +222,17 @@ class LoginAdvancedPresenter @Inject constructor(
isCorrect = false isCorrect = false
} }
} }
Sdk.Mode.HYBRID, Sdk.Mode.SCRAPPER -> { Sdk.Mode.HYBRID, Sdk.Mode.SCRAPPER -> {
if (login.isEmpty()) { if (login.isEmpty()) {
view?.setErrorUsernameRequired() view?.setErrorUsernameRequired()
isCorrect = false isCorrect = false
} else { } else {
if ("@" in login && "login" in host) { if ("@" in login && "standard" !in host) {
view?.setErrorLoginRequired() view?.setErrorLoginRequired()
isCorrect = false isCorrect = false
} }
if ("@" !in login && "email" in host) {
if ("@" !in login && "standard" in host) {
view?.setErrorEmailRequired() view?.setErrorEmailRequired()
isCorrect = false isCorrect = false
} }

View file

@ -1,6 +1,6 @@
package io.github.wulkanowy.ui.modules.login.advanced package io.github.wulkanowy.ui.modules.login.advanced
import io.github.wulkanowy.data.pojos.RegisterUser import io.github.wulkanowy.data.db.entities.StudentWithSemesters
import io.github.wulkanowy.ui.base.BaseView import io.github.wulkanowy.ui.base.BaseView
import io.github.wulkanowy.ui.modules.login.LoginData import io.github.wulkanowy.ui.modules.login.LoginData
@ -72,7 +72,7 @@ interface LoginAdvancedView : BaseView {
fun navigateToSymbol(loginData: LoginData) fun navigateToSymbol(loginData: LoginData)
fun navigateToStudentSelect(loginData: LoginData, registerUser: RegisterUser) fun navigateToStudentSelect(studentsWithSemesters: List<StudentWithSemesters>)
fun setErrorPinRequired() fun setErrorPinRequired()

View file

@ -9,13 +9,18 @@ import androidx.core.view.isVisible
import androidx.core.widget.doOnTextChanged import androidx.core.widget.doOnTextChanged
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.pojos.RegisterUser import io.github.wulkanowy.data.db.entities.StudentWithSemesters
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.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.utils.* import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.hideSoftInput
import io.github.wulkanowy.utils.openEmailClient
import io.github.wulkanowy.utils.openInternetBrowser
import io.github.wulkanowy.utils.setOnEditorDoneSignIn
import io.github.wulkanowy.utils.showSoftInput
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
@ -144,14 +149,12 @@ class LoginFormFragment : BaseFragment<FragmentLoginFormBinding>(R.layout.fragme
override fun setErrorPassRequired(focus: Boolean) { override fun setErrorPassRequired(focus: Boolean) {
with(binding.loginFormPassLayout) { with(binding.loginFormPassLayout) {
error = getString(R.string.error_field_required) error = getString(R.string.error_field_required)
setEndIconTintList(requireContext().getAttrColorStateList(R.attr.colorError))
} }
} }
override fun setErrorPassInvalid(focus: Boolean) { override fun setErrorPassInvalid(focus: Boolean) {
with(binding.loginFormPassLayout) { with(binding.loginFormPassLayout) {
error = getString(R.string.login_invalid_password) error = getString(R.string.login_invalid_password)
setEndIconTintList(requireContext().getAttrColorStateList(R.attr.colorError))
} }
} }
@ -159,7 +162,6 @@ class LoginFormFragment : BaseFragment<FragmentLoginFormBinding>(R.layout.fragme
with(binding) { with(binding) {
loginFormUsernameLayout.error = " " loginFormUsernameLayout.error = " "
loginFormPassLayout.error = " " loginFormPassLayout.error = " "
loginFormPassLayout.setEndIconTintList(requireContext().getAttrColorStateList(R.attr.colorError))
loginFormHostLayout.error = " " loginFormHostLayout.error = " "
loginFormErrorBox.text = message ?: getString(R.string.login_incorrect_password_default) loginFormErrorBox.text = message ?: getString(R.string.login_incorrect_password_default)
loginFormErrorBox.isVisible = true loginFormErrorBox.isVisible = true
@ -179,7 +181,6 @@ class LoginFormFragment : BaseFragment<FragmentLoginFormBinding>(R.layout.fragme
override fun clearPassError() { override fun clearPassError() {
binding.loginFormPassLayout.error = null binding.loginFormPassLayout.error = null
binding.loginFormPassLayout.setEndIconTintList(null)
binding.loginFormErrorBox.isVisible = false binding.loginFormErrorBox.isVisible = false
} }
@ -204,10 +205,6 @@ class LoginFormFragment : BaseFragment<FragmentLoginFormBinding>(R.layout.fragme
binding.loginFormContainer.visibility = if (show) VISIBLE else GONE binding.loginFormContainer.visibility = if (show) VISIBLE else GONE
} }
override fun showOtherOptionsButton(show: Boolean) {
binding.loginFormAdvancedButton.isVisible = show
}
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
override fun showVersion() { override fun showVersion() {
binding.loginFormVersion.text = "v${appInfo.versionName}" binding.loginFormVersion.text = "v${appInfo.versionName}"
@ -229,8 +226,8 @@ class LoginFormFragment : BaseFragment<FragmentLoginFormBinding>(R.layout.fragme
(activity as? LoginActivity)?.navigateToSymbolFragment(loginData) (activity as? LoginActivity)?.navigateToSymbolFragment(loginData)
} }
override fun navigateToStudentSelect(loginData: LoginData, registerUser: RegisterUser) { override fun navigateToStudentSelect(studentsWithSemesters: List<StudentWithSemesters>) {
(activity as? LoginActivity)?.navigateToStudentSelect(loginData, registerUser) (activity as? LoginActivity)?.navigateToStudentSelect(studentsWithSemesters)
} }
override fun openAdvancedLogin() { override fun openAdvancedLogin() {

View file

@ -7,7 +7,6 @@ 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
import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.AnalyticsHelper
import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.ifNullOrBlank import io.github.wulkanowy.utils.ifNullOrBlank
import timber.log.Timber import timber.log.Timber
import java.net.URL import java.net.URL
@ -16,7 +15,6 @@ import javax.inject.Inject
class LoginFormPresenter @Inject constructor( class LoginFormPresenter @Inject constructor(
studentRepository: StudentRepository, studentRepository: StudentRepository,
private val loginErrorHandler: LoginErrorHandler, private val loginErrorHandler: LoginErrorHandler,
private val appInfo: AppInfo,
private val analytics: AnalyticsHelper private val analytics: AnalyticsHelper
) : BasePresenter<LoginFormView>(loginErrorHandler, studentRepository) { ) : BasePresenter<LoginFormView>(loginErrorHandler, studentRepository) {
@ -27,7 +25,6 @@ class LoginFormPresenter @Inject constructor(
view.run { view.run {
initView() initView()
showContact(false) showContact(false)
showOtherOptionsButton(appInfo.isDebug)
showVersion() showVersion()
loginErrorHandler.onBadCredentials = { loginErrorHandler.onBadCredentials = {
@ -96,7 +93,7 @@ class LoginFormPresenter @Inject constructor(
if (!validateCredentials(email, password, host)) return if (!validateCredentials(email, password, host)) return
resourceFlow { resourceFlow {
studentRepository.getUserSubjectsFromScrapper( studentRepository.getStudentsScrapper(
email = email, email = email,
password = password, password = password,
scrapperBaseUrl = host, scrapperBaseUrl = host,
@ -112,14 +109,14 @@ class LoginFormPresenter @Inject constructor(
} }
} }
.onResourceSuccess { .onResourceSuccess {
val loginData = LoginData(email, password, host, symbol) when (it.size) {
when (it.symbols.size) { 0 -> view?.navigateToSymbol(LoginData(email, password, host))
0 -> view?.navigateToSymbol(loginData) else -> view?.navigateToStudentSelect(it)
else -> view?.navigateToStudentSelect(loginData, it)
} }
analytics.logEvent( analytics.logEvent(
"registration_form", "registration_form",
"success" to true, "success" to true,
"students" to it.size,
"scrapperBaseUrl" to host, "scrapperBaseUrl" to host,
"error" to "No error" "error" to "No error"
) )
@ -137,6 +134,7 @@ class LoginFormPresenter @Inject constructor(
analytics.logEvent( analytics.logEvent(
"registration_form", "registration_form",
"success" to false, "success" to false,
"students" to -1,
"scrapperBaseUrl" to host, "scrapperBaseUrl" to host,
"error" to it.message.ifNullOrBlank { "No message" } "error" to it.message.ifNullOrBlank { "No message" }
) )

View file

@ -1,6 +1,6 @@
package io.github.wulkanowy.ui.modules.login.form package io.github.wulkanowy.ui.modules.login.form
import io.github.wulkanowy.data.pojos.RegisterUser import io.github.wulkanowy.data.db.entities.StudentWithSemesters
import io.github.wulkanowy.ui.base.BaseView import io.github.wulkanowy.ui.base.BaseView
import io.github.wulkanowy.ui.modules.login.LoginData import io.github.wulkanowy.ui.modules.login.LoginData
@ -56,13 +56,11 @@ interface LoginFormView : BaseView {
fun showContent(show: Boolean) fun showContent(show: Boolean)
fun showOtherOptionsButton(show: Boolean)
fun showVersion() fun showVersion()
fun navigateToSymbol(loginData: LoginData) fun navigateToSymbol(loginData: LoginData)
fun navigateToStudentSelect(loginData: LoginData, registerUser: RegisterUser) fun navigateToStudentSelect(studentsWithSemesters: List<StudentWithSemesters>)
fun openPrivacyPolicyPage() fun openPrivacyPolicyPage()

View file

@ -98,7 +98,7 @@ class LoginRecoverFragment :
loginRecoverButton.setOnClickListener { presenter.onRecoverClick() } loginRecoverButton.setOnClickListener { presenter.onRecoverClick() }
loginRecoverErrorRetry.setOnClickListener { presenter.onRecoverClick() } loginRecoverErrorRetry.setOnClickListener { presenter.onRecoverClick() }
loginRecoverErrorDetails.setOnClickListener { presenter.onDetailsClick() } loginRecoverErrorDetails.setOnClickListener { presenter.onDetailsClick() }
loginRecoverLogin.setOnClickListener { (activity as LoginActivity).onBackPressedDispatcher.onBackPressed() } loginRecoverLogin.setOnClickListener { (activity as LoginActivity).onBackPressed() }
} }
with(bindingLocal.loginRecoverHost) { with(bindingLocal.loginRecoverHost) {

View file

@ -2,182 +2,65 @@ package io.github.wulkanowy.ui.modules.login.studentselect
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.DiffUtil.ItemCallback
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.StudentWithSemesters
import io.github.wulkanowy.databinding.* import io.github.wulkanowy.databinding.ItemLoginStudentSelectBinding
import javax.inject.Inject import javax.inject.Inject
@SuppressLint("SetTextI18n")
class LoginStudentSelectAdapter @Inject constructor() : class LoginStudentSelectAdapter @Inject constructor() :
ListAdapter<LoginStudentSelectItem, RecyclerView.ViewHolder>(Differ) { RecyclerView.Adapter<LoginStudentSelectAdapter.ItemViewHolder>() {
override fun getItemViewType(position: Int): Int = getItem(position).type.ordinal private val checkedList = mutableMapOf<Int, Boolean>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { var items = emptyList<Pair<StudentWithSemesters, Boolean>>()
val inflater = LayoutInflater.from(parent.context) set(value) {
return when (LoginStudentSelectItemType.values()[viewType]) { field = value
LoginStudentSelectItemType.EMPTY_SYMBOLS_HEADER -> EmptySymbolsHeaderViewHolder( checkedList.clear()
ItemLoginStudentSelectEmptySymbolHeaderBinding.inflate(inflater, parent, false),
)
LoginStudentSelectItemType.SYMBOL_HEADER -> SymbolsHeaderViewHolder(
ItemLoginStudentSelectHeaderSymbolBinding.inflate(inflater, parent, false)
)
LoginStudentSelectItemType.SCHOOL_HEADER -> SchoolHeaderViewHolder(
ItemLoginStudentSelectHeaderSchoolBinding.inflate(inflater, parent, false)
)
LoginStudentSelectItemType.STUDENT -> StudentViewHolder(
ItemLoginStudentSelectStudentBinding.inflate(inflater, parent, false)
)
LoginStudentSelectItemType.HELP -> HelpViewHolder(
ItemLoginStudentSelectHelpBinding.inflate(inflater, parent, false)
)
} }
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { var onClickListener: (StudentWithSemesters, alreadySaved: Boolean) -> Unit = { _, _ -> }
when (holder) {
is EmptySymbolsHeaderViewHolder -> holder.bind(getItem(position) as LoginStudentSelectItem.EmptySymbolsHeader)
is SymbolsHeaderViewHolder -> holder.bind(getItem(position) as LoginStudentSelectItem.SymbolHeader)
is SchoolHeaderViewHolder -> holder.bind(getItem(position) as LoginStudentSelectItem.SchoolHeader)
is StudentViewHolder -> holder.bind(getItem(position) as LoginStudentSelectItem.Student)
is HelpViewHolder -> holder.bind(getItem(position) as LoginStudentSelectItem.Help)
}
}
private class EmptySymbolsHeaderViewHolder( override fun getItemCount() = items.size
private val binding: ItemLoginStudentSelectEmptySymbolHeaderBinding,
) : RecyclerView.ViewHolder(binding.root) {
fun bind(item: LoginStudentSelectItem.EmptySymbolsHeader) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder(
with(binding) { ItemLoginStudentSelectBinding.inflate(LayoutInflater.from(parent.context), parent, false)
loginStudentSelectEmptySymbolChevron.rotation = if (item.isExpanded) 270f else 90f )
root.setOnClickListener { item.onClick() }
@SuppressLint("SetTextI18n")
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
val (studentAndSemesters, alreadySaved) = items[position]
val student = studentAndSemesters.student
val semesters = studentAndSemesters.semesters
val diary = semesters.maxByOrNull { it.semesterId }
with(holder.binding) {
loginItemName.text = "${student.studentName} ${diary?.diaryName.orEmpty()}"
loginItemSchool.text = student.schoolName
loginItemName.isEnabled = !alreadySaved
loginItemSchool.isEnabled = !alreadySaved
loginItemSignedIn.visibility = if (alreadySaved) View.VISIBLE else View.GONE
with(loginItemCheck) {
isEnabled = !alreadySaved
keyListener = null
isChecked = checkedList[position] ?: false
} }
}
}
private class SymbolsHeaderViewHolder( root.setOnClickListener {
private val binding: ItemLoginStudentSelectHeaderSymbolBinding, onClickListener(studentAndSemesters, alreadySaved)
) : RecyclerView.ViewHolder(binding.root) {
fun bind(item: LoginStudentSelectItem.SymbolHeader) {
with(binding) {
loginStudentSelectHeaderSymbolValue.text = buildString {
append(root.context.getString(R.string.mobile_device_symbol))
append(": ")
append(item.humanReadableName ?: item.symbol.symbol)
if (!item.humanReadableName.isNullOrBlank()) {
append(" (${item.symbol.symbol})")
}
}
loginStudentSelectHeaderSymbolUsername.text = item.symbol.userName
loginStudentSelectHeaderSymbolUsername.isVisible = item.symbol.userName.isNotBlank()
loginStudentSelectHeaderSymbolError.text = item.symbol.error?.message
loginStudentSelectHeaderSymbolError.isVisible = item.symbol.error != null
loginStudentSelectHeaderSymbolError.maxLines = when {
item.isErrorExpanded -> Int.MAX_VALUE
else -> 2
}
if (item.symbol.error != null) {
root.setOnClickListener { item.onClick(item.symbol) }
} else root.setOnClickListener(null)
}
}
}
private class SchoolHeaderViewHolder(
private val binding: ItemLoginStudentSelectHeaderSchoolBinding,
) : RecyclerView.ViewHolder(binding.root) {
fun bind(item: LoginStudentSelectItem.SchoolHeader) {
with(binding) {
loginStudentSelectHeaderSchoolName.text = buildString {
append(item.unit.schoolName.trim())
append(" (")
append(item.unit.schoolShortName)
append(")")
}
loginStudentSelectHeaderSchoolDetails.isVisible = item.unit.students.isEmpty()
loginStudentSelectHeaderSchoolError.text = item.unit.error?.message
loginStudentSelectHeaderSchoolError.isVisible = item.unit.error != null
loginStudentSelectHeaderSchoolError.maxLines = when {
item.isErrorExpanded -> Int.MAX_VALUE
else -> 2
}
if (item.unit.error != null) {
root.setOnClickListener { item.onClick(item.unit) }
} else root.setOnClickListener(null)
}
}
}
private class StudentViewHolder(
private val binding: ItemLoginStudentSelectStudentBinding,
) : RecyclerView.ViewHolder(binding.root) {
fun bind(item: LoginStudentSelectItem.Student) {
val student = item.student
val semesters = student.semesters
val diary = semesters.maxByOrNull { it.semesterId }
with(binding) {
loginItemName.text = "${student.studentName} ${student.studentSurname}"
loginItemName.isEnabled = item.isEnabled
loginItemSignedIn.text = if (!item.isEnabled) {
root.context.getString(R.string.login_signed_in)
} else diary?.diaryName
with(loginItemCheck) { with(loginItemCheck) {
keyListener = null if (isEnabled) {
isEnabled = item.isEnabled isChecked = !isChecked
isChecked = item.isSelected || !item.isEnabled checkedList[position] = isChecked
} }
root.isEnabled = item.isEnabled
root.setOnClickListener {
item.onClick(item)
} }
} }
} }
} }
private class HelpViewHolder( class ItemViewHolder(val binding: ItemLoginStudentSelectBinding) :
private val binding: ItemLoginStudentSelectHelpBinding, RecyclerView.ViewHolder(binding.root)
) : RecyclerView.ViewHolder(binding.root) {
fun bind(item: LoginStudentSelectItem.Help) {
with(binding) {
loginStudentSelectHelpSymbol.isVisible = item.isSymbolButtonVisible
loginStudentSelectHelpSymbol.setOnClickListener { item.onEnterSymbolClick() }
loginStudentSelectHelpMail.setOnClickListener { item.onContactUsClick() }
loginStudentSelectHelpDiscord.setOnClickListener { item.onDiscordClick() }
}
}
}
private object Differ : ItemCallback<LoginStudentSelectItem>() {
override fun areItemsTheSame(
oldItem: LoginStudentSelectItem, newItem: LoginStudentSelectItem
): Boolean = when {
oldItem is LoginStudentSelectItem.EmptySymbolsHeader && newItem is LoginStudentSelectItem.EmptySymbolsHeader -> true
oldItem is LoginStudentSelectItem.SymbolHeader && newItem is LoginStudentSelectItem.SymbolHeader -> {
oldItem.symbol == newItem.symbol
}
oldItem is LoginStudentSelectItem.Student && newItem is LoginStudentSelectItem.Student -> {
oldItem.student == newItem.student
}
else -> oldItem == newItem
}
override fun areContentsTheSame(
oldItem: LoginStudentSelectItem, newItem: LoginStudentSelectItem
): Boolean = oldItem == newItem
}
} }

View file

@ -2,20 +2,21 @@ package io.github.wulkanowy.ui.modules.login.studentselect
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.view.View.GONE
import android.view.View.VISIBLE
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import androidx.core.view.isVisible 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
import io.github.wulkanowy.data.pojos.RegisterUser import io.github.wulkanowy.data.db.entities.StudentWithSemesters
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.login.LoginActivity import io.github.wulkanowy.ui.modules.login.LoginActivity
import io.github.wulkanowy.ui.modules.login.LoginData import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.openEmailClient 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 javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
@ -35,73 +36,75 @@ class LoginStudentSelectFragment :
@Inject @Inject
lateinit var preferencesRepository: PreferencesRepository lateinit var preferencesRepository: PreferencesRepository
private lateinit var symbolsNames: Array<String>
private lateinit var symbolsValues: Array<String>
override val symbols: Map<String, String> by lazy {
symbolsValues.zip(symbolsNames).toMap()
}
companion object { companion object {
private const val ARG_LOGIN = "LOGIN" const val ARG_STUDENTS = "STUDENTS"
private const val ARG_STUDENTS = "STUDENTS"
fun newInstance(loginData: LoginData, registerUser: RegisterUser) = fun newInstance(studentsWithSemesters: List<StudentWithSemesters>) =
LoginStudentSelectFragment().apply { LoginStudentSelectFragment().apply {
arguments = bundleOf( arguments = bundleOf(ARG_STUDENTS to studentsWithSemesters)
ARG_LOGIN to loginData,
ARG_STUDENTS to registerUser,
)
} }
} }
@Suppress("UNCHECKED_CAST")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
binding = FragmentLoginStudentSelectBinding.bind(view) binding = FragmentLoginStudentSelectBinding.bind(view)
symbolsNames = resources.getStringArray(R.array.symbols)
symbolsValues = resources.getStringArray(R.array.symbols_values)
presenter.onAttachView( presenter.onAttachView(
view = this, view = this,
loginData = requireArguments().serializable(ARG_LOGIN), students = requireArguments().getSerializable(ARG_STUDENTS) as List<StudentWithSemesters>,
registerUser = requireArguments().serializable(ARG_STUDENTS),
) )
} }
override fun initView() { override fun initView() {
(requireActivity() as LoginActivity).showActionBar(true) (requireActivity() as LoginActivity).showActionBar(true)
loginAdapter.onClickListener = presenter::onItemSelected
with(binding) { with(binding) {
loginStudentSelectSignIn.setOnClickListener { presenter.onSignIn() } loginStudentSelectSignIn.setOnClickListener { presenter.onSignIn() }
loginStudentSelectRecycler.adapter = loginAdapter loginStudentSelectContactDiscord.setOnClickListener { presenter.onDiscordClick() }
loginStudentSelectContactEmail.setOnClickListener { presenter.onEmailClick() }
with(loginStudentSelectRecycler) {
layoutManager = LinearLayoutManager(context)
adapter = loginAdapter
}
} }
} }
override fun updateData(data: List<LoginStudentSelectItem>) { override fun updateData(data: List<Pair<StudentWithSemesters, Boolean>>) {
loginAdapter.submitList(data) with(loginAdapter) {
items = data
notifyDataSetChanged()
}
} }
override fun navigateToSymbol(loginData: LoginData) { override fun openMainView() {
(requireActivity() as LoginActivity).navigateToSymbolFragment(loginData) startActivity(MainActivity.getStartIntent(requireContext()))
} requireActivity().finish()
override fun navigateToNext() {
(requireActivity() as LoginActivity).navigateToNotifications()
} }
override fun showProgress(show: Boolean) { override fun showProgress(show: Boolean) {
binding.loginStudentSelectProgress.isVisible = show binding.loginStudentSelectProgress.visibility = if (show) VISIBLE else GONE
} }
override fun showContent(show: Boolean) { override fun showContent(show: Boolean) {
binding.loginStudentSelectContent.isVisible = show binding.loginStudentSelectContent.visibility = if (show) VISIBLE else GONE
} }
override fun enableSignIn(enable: Boolean) { override fun enableSignIn(enable: Boolean) {
binding.loginStudentSelectSignIn.isEnabled = enable binding.loginStudentSelectSignIn.isEnabled = enable
} }
override fun showContact(show: Boolean) {
binding.loginStudentSelectContact.visibility = if (show) VISIBLE else GONE
}
override fun onDestroyView() {
presenter.onDetachView()
super.onDestroyView()
}
override fun openDiscordInvite() { override fun openDiscordInvite() {
context?.openInternetBrowser("https://discord.gg/vccAQBr", ::showMessage) context?.openInternetBrowser("https://discord.gg/vccAQBr", ::showMessage)
} }
@ -122,9 +125,4 @@ class LoginStudentSelectFragment :
) )
) )
} }
override fun onDestroyView() {
presenter.onDetachView()
super.onDestroyView()
}
} }

Some files were not shown because too many files have changed in this diff Show more