forked from github/wulkanowy-mirror
Merge branch 'release/1.1.0'
This commit is contained in:
commit
e15eb03299
78
.github/workflows/test.yml
vendored
78
.github/workflows/test.yml
vendored
@ -10,8 +10,8 @@ on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Pre-build
|
||||
unit-tests:
|
||||
name: Unit tests
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
@ -27,36 +27,6 @@ jobs:
|
||||
~/.gradle/caches
|
||||
~/.gradle/wrapper
|
||||
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*') }}
|
||||
- name: Build
|
||||
run: ./gradlew --build-cache compileFdroidDebugUnitTestKotlin preFdroidDebugAndroidTestBuild dexBuilderFdroidDebugAndroidTest packageFdroidDebug packageFdroidDebugAndroidTest
|
||||
- name: Prepare build cache
|
||||
run: tar -cf prebuild.tar .build-cache .gradle app/build
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: prebuild.tar
|
||||
path: prebuild.tar
|
||||
|
||||
unit-tests:
|
||||
name: Unit tests
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
needs: [ build ]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 11
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
~/.gradle/wrapper
|
||||
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*') }}
|
||||
- uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: prebuild.tar
|
||||
- name: Extract build cache
|
||||
run: tar -xf prebuild.tar
|
||||
- name: Unit tests
|
||||
run: |
|
||||
./gradlew --build-cache -Pcoverage testFdroidDebugUnitTest --stacktrace
|
||||
@ -65,49 +35,12 @@ jobs:
|
||||
with:
|
||||
flags: unit
|
||||
|
||||
instrumentation-tests:
|
||||
name: Instrumentation tests
|
||||
runs-on: macOS-latest
|
||||
timeout-minutes: 15
|
||||
needs: [ build ]
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
api-level: [21, 29]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 11
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
~/.gradle/wrapper
|
||||
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*') }}
|
||||
- uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: prebuild.tar
|
||||
- name: Extract build cache
|
||||
run: tar -xf prebuild.tar
|
||||
- name: Instrumentation tests
|
||||
uses: reactivecircus/android-emulator-runner@v2
|
||||
with:
|
||||
api-level: ${{ matrix.api-level }}
|
||||
arch: x86
|
||||
script: |
|
||||
./gradlew --build-cache -Pcoverage connectedFdroidDebugAndroidTest --stacktrace
|
||||
./gradlew --build-cache -Pcoverage jacocoTestReport --stacktrace
|
||||
- uses: codecov/codecov-action@v1
|
||||
with:
|
||||
flags: instrumented,api-${{ matrix.api-level }}
|
||||
|
||||
deploy-google-play:
|
||||
name: Deploy to google play
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
environment: google-play
|
||||
needs: [ build, unit-tests, instrumentation-tests ]
|
||||
needs: [ unit-tests ]
|
||||
if: github.event_name == 'push' && contains(github.ref, 'refs/tags/')
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
@ -120,11 +53,6 @@ jobs:
|
||||
~/.gradle/caches
|
||||
~/.gradle/wrapper
|
||||
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*') }}
|
||||
- uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: prebuild.tar
|
||||
- name: Extract build cache
|
||||
run: tar -xf prebuild.tar
|
||||
- name: Decrypt keys
|
||||
env:
|
||||
ENCRYPT_KEY: ${{ secrets.ENCRYPT_KEY }}
|
||||
|
11
README.en.md
11
README.en.md
@ -12,7 +12,7 @@ Unofficial android VULCAN UONET+ register client for both students and their par
|
||||
|
||||
## Features
|
||||
|
||||
* logging in using the email and password OR using token and pin
|
||||
* logging in using the email and password
|
||||
* functions from the register website:
|
||||
* grades
|
||||
* grade statistics
|
||||
@ -25,15 +25,19 @@ Unofficial android VULCAN UONET+ register client for both students and their par
|
||||
* homework
|
||||
* notes
|
||||
* lucky number
|
||||
* additional lessons
|
||||
* school conferences
|
||||
* student and school information
|
||||
* calculation of the average independently of school's preferences
|
||||
* notifications, e.g. about a new grade
|
||||
* support for multiple accounts with the ability to rename students
|
||||
* dark and black (AMOLED) theme
|
||||
* offline mode
|
||||
* no ads
|
||||
|
||||
## Download
|
||||
|
||||
You can download the current beta version from the Google Play, F-Droid or Huawei AppGallery store
|
||||
You can download the current version from the Google Play, F-Droid or Huawei AppGallery store
|
||||
|
||||
[<img src="https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png"
|
||||
alt="Get it on Google Play"
|
||||
@ -60,6 +64,9 @@ You can also download a [development version](https://wulkanowy.github.io/#downl
|
||||
|
||||
Please contribute to the project either by creating a PR or submitting an issue on GitHub.
|
||||
|
||||
For people interested in translating the application into different languages, we provide Crowdin
|
||||
https://crowdin.com/project/wulkanowy2
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details
|
||||
|
11
README.md
11
README.md
@ -12,7 +12,7 @@ Nieoficjalny klient dziennika VULCAN UONET+ dla ucznia i rodzica
|
||||
|
||||
## Funkcje
|
||||
|
||||
* logowanie za pomocą e-maila i hasła LUB tokena i pinu
|
||||
* logowanie za pomocą e-maila i hasła
|
||||
* funkcje ze strony internetowej dziennika:
|
||||
* oceny
|
||||
* statystyki ocen
|
||||
@ -25,15 +25,19 @@ Nieoficjalny klient dziennika VULCAN UONET+ dla ucznia i rodzica
|
||||
* zadania domowe
|
||||
* uwagi
|
||||
* szczęśliwy numerek
|
||||
* dodatkowe lekcje
|
||||
* zebrania w szkole
|
||||
* informacje o uczniu i szkole
|
||||
* obliczanie średniej niezależnie od preferencji szkoły
|
||||
* powiadomienia np. o nowej ocenie
|
||||
* obsługa wielu kont wraz z możliwością zmiany nazwy ucznia
|
||||
* ciemny i czarny (AMOLED) motyw
|
||||
* tryb offilne
|
||||
* brak reklam
|
||||
|
||||
## Pobierz
|
||||
|
||||
Aktualną wersję beta możesz pobrać ze sklepu Google Play, F-Droid lub Huawei AppGallery
|
||||
Aktualną wersję możesz pobrać ze sklepu Google Play, F-Droid lub Huawei AppGallery
|
||||
|
||||
[<img src="https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png"
|
||||
alt="Pobierz z Google Play"
|
||||
@ -61,6 +65,9 @@ Możesz także pobrać [wersję rozwojową](https://wulkanowy.github.io/#downloa
|
||||
|
||||
Wnieś swój wkład w projekt, tworząc PR lub wysyłając issue na GitHub.
|
||||
|
||||
Dla osób zainteresowanych tłumaczeniem aplikacji na różne języki udostępniamy Crowdina
|
||||
https://crowdin.com/project/wulkanowy2
|
||||
|
||||
## Licencja
|
||||
|
||||
Ten projekt udostępniany jest na licencji Apache License 2.0 - szczegóły w pliku [LICENSE](LICENSE)
|
||||
|
@ -18,8 +18,8 @@ android {
|
||||
testApplicationId "io.github.tests.wulkanowy"
|
||||
minSdkVersion 17
|
||||
targetSdkVersion 30
|
||||
versionCode 84
|
||||
versionName "1.0.0"
|
||||
versionCode 86
|
||||
versionName "1.1.0"
|
||||
multiDexEnabled true
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
@ -41,7 +41,8 @@ android {
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
androidTest.assets.srcDirs += files("$projectDir/schemas".toString())
|
||||
// https://github.com/robolectric/robolectric/issues/3928#issuecomment-395309991
|
||||
debug.assets.srcDirs += files("$projectDir/schemas".toString())
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
@ -103,6 +104,10 @@ android {
|
||||
disable 'HardwareIds'
|
||||
}
|
||||
|
||||
testOptions.unitTests {
|
||||
includeAndroidResources = true
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
coreLibraryDesugaringEnabled true
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
@ -128,32 +133,32 @@ play {
|
||||
serviceAccountEmail = System.getenv("PLAY_SERVICE_ACCOUNT_EMAIL") ?: "jan@fakelog.cf"
|
||||
serviceAccountCredentials = file('key.p12')
|
||||
defaultToAppBundles = false
|
||||
track = 'alpha'
|
||||
track = 'production'
|
||||
updatePriority = 3
|
||||
}
|
||||
|
||||
ext {
|
||||
work_manager = "2.5.0"
|
||||
work_hilt = "1.0.0-alpha03"
|
||||
room = "2.3.0-beta01"
|
||||
room = "2.3.0-beta02"
|
||||
chucker = "3.4.0"
|
||||
mockk = "1.10.5"
|
||||
mockk = "1.10.6"
|
||||
moshi = "1.11.0"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "io.github.wulkanowy:sdk:1.0.0"
|
||||
implementation "io.github.wulkanowy:sdk:1.1.0"
|
||||
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.1'
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
|
||||
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.2'
|
||||
|
||||
implementation "androidx.core:core-ktx:1.3.2"
|
||||
implementation "androidx.activity:activity-ktx:1.1.0"
|
||||
implementation "androidx.activity:activity-ktx:1.2.0"
|
||||
implementation "androidx.appcompat:appcompat:1.2.0"
|
||||
implementation "androidx.appcompat:appcompat-resources:1.2.0"
|
||||
implementation "androidx.fragment:fragment-ktx:1.2.5"
|
||||
implementation "androidx.fragment:fragment-ktx:1.3.0"
|
||||
implementation "androidx.annotation:annotation:1.1.0"
|
||||
implementation "androidx.multidex:multidex:2.0.1"
|
||||
|
||||
@ -163,14 +168,15 @@ dependencies {
|
||||
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
|
||||
implementation "androidx.constraintlayout:constraintlayout:2.0.4"
|
||||
implementation "androidx.coordinatorlayout:coordinatorlayout:1.1.0"
|
||||
implementation "com.google.android.material:material:1.3.0-rc01"
|
||||
implementation "com.google.android.material:material:1.3.0"
|
||||
implementation "com.github.wulkanowy:material-chips-input:2.1.1"
|
||||
implementation "com.github.PhilJay:MPAndroidChart:v3.1.0"
|
||||
implementation 'com.mikhaellopez:circularimageview:4.2.0'
|
||||
|
||||
implementation "androidx.work:work-runtime-ktx:$work_manager"
|
||||
playImplementation "androidx.work:work-gcm:$work_manager"
|
||||
|
||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0"
|
||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.0"
|
||||
|
||||
implementation "androidx.room:room-runtime:$room"
|
||||
implementation "androidx.room:room-ktx:$room"
|
||||
@ -197,7 +203,7 @@ dependencies {
|
||||
implementation "io.github.wulkanowy:AppKillerManager:3.0.0"
|
||||
implementation 'me.xdrop:fuzzywuzzy:1.3.1'
|
||||
|
||||
playImplementation platform('com.google.firebase:firebase-bom:26.4.0')
|
||||
playImplementation platform('com.google.firebase:firebase-bom:26.6.0')
|
||||
playImplementation 'com.google.firebase:firebase-analytics-ktx'
|
||||
playImplementation 'com.google.firebase:firebase-inappmessaging-display-ktx'
|
||||
playImplementation "com.google.firebase:firebase-inappmessaging-ktx"
|
||||
@ -206,24 +212,31 @@ dependencies {
|
||||
playImplementation 'com.google.android.play:core-ktx:1.8.1'
|
||||
playImplementation 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava'
|
||||
|
||||
hmsImplementation 'com.huawei.hms:hianalytics:5.1.0.301'
|
||||
hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.5.0.200'
|
||||
hmsImplementation 'com.huawei.hms:hianalytics:5.2.0.300'
|
||||
hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.5.0.300'
|
||||
|
||||
releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker"
|
||||
|
||||
debugImplementation "com.github.ChuckerTeam.Chucker:library:$chucker"
|
||||
debugImplementation "com.amitshekhar.android:debug-db:1.0.6"
|
||||
|
||||
testImplementation "junit:junit:4.13.1"
|
||||
testImplementation "junit:junit:4.13.2"
|
||||
testImplementation "io.mockk:mockk:$mockk"
|
||||
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.4.2'
|
||||
testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
|
||||
|
||||
testImplementation 'org.robolectric:robolectric:4.5.1'
|
||||
testImplementation "androidx.test:runner:1.3.0"
|
||||
testImplementation "androidx.test.ext:junit:1.1.2"
|
||||
testImplementation "androidx.test:core:1.3.0"
|
||||
testImplementation "androidx.room:room-testing:$room"
|
||||
testImplementation "com.google.dagger:hilt-android-testing:$hilt_version"
|
||||
kaptTest "com.google.dagger:hilt-android-compiler:$hilt_version"
|
||||
|
||||
androidTestImplementation "androidx.test:core:1.3.0"
|
||||
androidTestImplementation "androidx.test:runner:1.3.0"
|
||||
androidTestImplementation "androidx.test.ext:junit:1.1.2"
|
||||
androidTestImplementation "io.mockk:mockk-android:$mockk"
|
||||
androidTestImplementation "androidx.room:room-testing:$room"
|
||||
androidTestImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
|
||||
}
|
||||
|
||||
|
2142
app/schemas/io.github.wulkanowy.data.db.AppDatabase/34.json
Normal file
2142
app/schemas/io.github.wulkanowy.data.db.AppDatabase/34.json
Normal file
File diff suppressed because it is too large
Load Diff
2148
app/schemas/io.github.wulkanowy.data.db.AppDatabase/35.json
Normal file
2148
app/schemas/io.github.wulkanowy.data.db.AppDatabase/35.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -56,7 +56,7 @@
|
||||
android:name=".ui.modules.login.LoginActivity"
|
||||
android:configChanges="orientation|screenSize"
|
||||
android:label="@string/login_title"
|
||||
android:theme="@style/WulkanowyTheme.NoActionBar"
|
||||
android:theme="@style/WulkanowyTheme.Login"
|
||||
android:windowSoftInputMode="adjustResize" />
|
||||
<activity
|
||||
android:name=".ui.modules.main.MainActivity"
|
||||
@ -68,7 +68,7 @@
|
||||
android:name=".ui.modules.message.send.SendMessageActivity"
|
||||
android:configChanges="orientation|screenSize"
|
||||
android:label="@string/send_message_title"
|
||||
android:theme="@style/WulkanowyTheme.NoActionBar"
|
||||
android:theme="@style/WulkanowyTheme.MessageSend"
|
||||
android:windowSoftInputMode="adjustResize" />
|
||||
<activity
|
||||
android:name=".ui.modules.timetablewidget.TimetableWidgetConfigureActivity"
|
||||
|
@ -38,5 +38,13 @@
|
||||
{
|
||||
"displayName": "MRmlik12",
|
||||
"githubUsername": "MRmlik12"
|
||||
},
|
||||
{
|
||||
"displayName": "Damian Czupryn",
|
||||
"githubUsername": "Daxxxis"
|
||||
},
|
||||
{
|
||||
"displayName": "Kamil Studziński",
|
||||
"githubUsername": "studzinskik"
|
||||
}
|
||||
]
|
||||
|
@ -1,11 +1,13 @@
|
||||
package io.github.wulkanowy
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.util.Log.DEBUG
|
||||
import android.util.Log.INFO
|
||||
import android.util.Log.VERBOSE
|
||||
import android.webkit.WebView
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.hilt.work.HiltWorkerFactory
|
||||
import androidx.multidex.MultiDex
|
||||
import androidx.work.Configuration
|
||||
@ -46,9 +48,10 @@ class WulkanowyApp : Application(), Configuration.Provider {
|
||||
MultiDex.install(this)
|
||||
}
|
||||
|
||||
@SuppressLint("UnsafeOptInUsageWarning")
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
FragmentManager.enableNewStateManager(false)
|
||||
initializeAppLanguage()
|
||||
themeManager.applyDefaultTheme()
|
||||
initLogging()
|
||||
|
@ -17,6 +17,7 @@ import io.github.wulkanowy.data.db.AppDatabase
|
||||
import io.github.wulkanowy.data.db.SharedPrefProvider
|
||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.utils.AppInfo
|
||||
import timber.log.Timber
|
||||
import javax.inject.Singleton
|
||||
|
||||
@ -60,7 +61,8 @@ internal class RepositoryModule {
|
||||
fun provideDatabase(
|
||||
@ApplicationContext context: Context,
|
||||
sharedPrefProvider: SharedPrefProvider,
|
||||
) = AppDatabase.newInstance(context, sharedPrefProvider)
|
||||
appInfo: AppInfo
|
||||
) = AppDatabase.newInstance(context, sharedPrefProvider, appInfo)
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
|
@ -6,7 +6,6 @@ import androidx.room.Room
|
||||
import androidx.room.RoomDatabase
|
||||
import androidx.room.RoomDatabase.JournalMode.TRUNCATE
|
||||
import androidx.room.TypeConverters
|
||||
import androidx.room.migration.Migration
|
||||
import io.github.wulkanowy.data.db.dao.AttendanceDao
|
||||
import io.github.wulkanowy.data.db.dao.AttendanceSummaryDao
|
||||
import io.github.wulkanowy.data.db.dao.CompletedLessonsDao
|
||||
@ -85,12 +84,15 @@ import io.github.wulkanowy.data.db.migrations.Migration30
|
||||
import io.github.wulkanowy.data.db.migrations.Migration31
|
||||
import io.github.wulkanowy.data.db.migrations.Migration32
|
||||
import io.github.wulkanowy.data.db.migrations.Migration33
|
||||
import io.github.wulkanowy.data.db.migrations.Migration34
|
||||
import io.github.wulkanowy.data.db.migrations.Migration35
|
||||
import io.github.wulkanowy.data.db.migrations.Migration4
|
||||
import io.github.wulkanowy.data.db.migrations.Migration5
|
||||
import io.github.wulkanowy.data.db.migrations.Migration6
|
||||
import io.github.wulkanowy.data.db.migrations.Migration7
|
||||
import io.github.wulkanowy.data.db.migrations.Migration8
|
||||
import io.github.wulkanowy.data.db.migrations.Migration9
|
||||
import io.github.wulkanowy.utils.AppInfo
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
@ -130,10 +132,9 @@ import javax.inject.Singleton
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
|
||||
companion object {
|
||||
const val VERSION_SCHEMA = 33
|
||||
const val VERSION_SCHEMA = 35
|
||||
|
||||
fun getMigrations(sharedPrefProvider: SharedPrefProvider): Array<Migration> {
|
||||
return arrayOf(
|
||||
fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf(
|
||||
Migration2(),
|
||||
Migration3(),
|
||||
Migration4(),
|
||||
@ -165,19 +166,22 @@ abstract class AppDatabase : RoomDatabase() {
|
||||
Migration30(),
|
||||
Migration31(),
|
||||
Migration32(),
|
||||
Migration33()
|
||||
Migration33(),
|
||||
Migration34(),
|
||||
Migration35(appInfo)
|
||||
)
|
||||
}
|
||||
|
||||
fun newInstance(context: Context, sharedPrefProvider: SharedPrefProvider): AppDatabase {
|
||||
return Room.databaseBuilder(context, AppDatabase::class.java, "wulkanowy_database")
|
||||
fun newInstance(
|
||||
context: Context,
|
||||
sharedPrefProvider: SharedPrefProvider,
|
||||
appInfo: AppInfo
|
||||
) = Room.databaseBuilder(context, AppDatabase::class.java, "wulkanowy_database")
|
||||
.setJournalMode(TRUNCATE)
|
||||
.fallbackToDestructiveMigrationFrom(VERSION_SCHEMA + 1)
|
||||
.fallbackToDestructiveMigrationOnDowngrade()
|
||||
.addMigrations(*getMigrations(sharedPrefProvider))
|
||||
.addMigrations(*getMigrations(sharedPrefProvider, appInfo))
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
abstract val studentDao: StudentDao
|
||||
|
||||
|
@ -13,4 +13,7 @@ interface LuckyNumberDao : BaseDao<LuckyNumber> {
|
||||
|
||||
@Query("SELECT * FROM LuckyNumbers WHERE student_id = :studentId AND date = :date")
|
||||
fun load(studentId: Int, date: LocalDate): Flow<LuckyNumber?>
|
||||
|
||||
@Query("SELECT * FROM LuckyNumbers WHERE student_id = :studentId AND date >= :start AND date <= :end")
|
||||
fun getAll(studentId: Int, start: LocalDate, end: LocalDate): Flow<List<LuckyNumber>>
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import androidx.room.Query
|
||||
import androidx.room.Transaction
|
||||
import androidx.room.Update
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.db.entities.StudentNick
|
||||
import io.github.wulkanowy.data.db.entities.StudentNickAndAvatar
|
||||
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||
import javax.inject.Singleton
|
||||
|
||||
@ -23,13 +23,13 @@ interface StudentDao {
|
||||
suspend fun delete(student: Student)
|
||||
|
||||
@Update(entity = Student::class)
|
||||
suspend fun update(studentNick: StudentNick)
|
||||
suspend fun update(studentNickAndAvatar: StudentNickAndAvatar)
|
||||
|
||||
@Query("SELECT * FROM Students WHERE is_current = 1")
|
||||
suspend fun loadCurrent(): Student?
|
||||
|
||||
@Query("SELECT * FROM Students WHERE id = :id")
|
||||
suspend fun loadById(id: Int): Student?
|
||||
suspend fun loadById(id: Long): Student?
|
||||
|
||||
@Query("SELECT * FROM Students")
|
||||
suspend fun loadAll(): List<Student>
|
||||
|
@ -10,7 +10,7 @@ import java.time.LocalDateTime
|
||||
data class Message(
|
||||
|
||||
@ColumnInfo(name = "student_id")
|
||||
val studentId: Int,
|
||||
val studentId: Long,
|
||||
|
||||
@ColumnInfo(name = "real_id")
|
||||
val realId: Int,
|
||||
|
@ -81,4 +81,7 @@ data class Student(
|
||||
var id: Long = 0
|
||||
|
||||
var nick = ""
|
||||
|
||||
@ColumnInfo(name = "avatar_color")
|
||||
var avatarColor = 0L
|
||||
}
|
||||
|
@ -1,13 +1,17 @@
|
||||
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 StudentNick(
|
||||
data class StudentNickAndAvatar(
|
||||
|
||||
val nick: String
|
||||
val nick: String,
|
||||
|
||||
@ColumnInfo(name = "avatar_color")
|
||||
var avatarColor: Long
|
||||
|
||||
) : Serializable {
|
||||
|
@ -0,0 +1,13 @@
|
||||
package io.github.wulkanowy.data.db.migrations
|
||||
|
||||
import androidx.room.migration.Migration
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
|
||||
class Migration34 : Migration(33, 34) {
|
||||
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL("DELETE FROM ReportingUnits")
|
||||
database.execSQL("DELETE FROM Recipients")
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,24 @@
|
||||
package io.github.wulkanowy.data.db.migrations
|
||||
|
||||
import androidx.core.database.getLongOrNull
|
||||
import androidx.room.migration.Migration
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
import io.github.wulkanowy.utils.AppInfo
|
||||
|
||||
class Migration35(private val appInfo: AppInfo) : Migration(34, 35) {
|
||||
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL("ALTER TABLE Students ADD COLUMN `avatar_color` INTEGER NOT NULL DEFAULT 0")
|
||||
|
||||
val studentsCursor = database.query("SELECT * FROM Students")
|
||||
|
||||
while (studentsCursor.moveToNext()) {
|
||||
val studentId = studentsCursor.getLongOrNull(0)
|
||||
database.execSQL(
|
||||
"""UPDATE Students
|
||||
SET avatar_color = ${appInfo.defaultColorsForAvatar.random()}
|
||||
WHERE id = $studentId"""
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -5,10 +5,9 @@ import io.github.wulkanowy.data.db.entities.GradePointsStatistics
|
||||
import io.github.wulkanowy.data.db.entities.GradeSemesterStatistics
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.pojos.GradeStatisticsItem
|
||||
import io.github.wulkanowy.ui.modules.grade.statistics.ViewType
|
||||
import io.github.wulkanowy.sdk.pojo.GradeStatisticsSubject as SdkGradeStatisticsSubject
|
||||
import io.github.wulkanowy.sdk.pojo.GradeStatisticsSemester as SdkGradeStatisticsSemester
|
||||
import io.github.wulkanowy.sdk.pojo.GradePointsStatistics as SdkGradePointsStatistics
|
||||
import io.github.wulkanowy.sdk.pojo.GradeStatisticsSemester as SdkGradeStatisticsSemester
|
||||
import io.github.wulkanowy.sdk.pojo.GradeStatisticsSubject as SdkGradeStatisticsSubject
|
||||
|
||||
@JvmName("mapToEntitiesSubject")
|
||||
fun List<SdkGradeStatisticsSubject>.mapToEntities(semester: Semester) = map {
|
||||
@ -51,7 +50,7 @@ fun List<SdkGradePointsStatistics>.mapToEntities(semester: Semester) = map {
|
||||
|
||||
fun List<GradePartialStatistics>.mapPartialToStatisticItems() = filterNot { it.classAmounts.isEmpty() }.map {
|
||||
GradeStatisticsItem(
|
||||
type = ViewType.PARTIAL,
|
||||
type = GradeStatisticsItem.DataType.PARTIAL,
|
||||
average = it.classAverage,
|
||||
partial = it,
|
||||
points = null,
|
||||
@ -61,7 +60,7 @@ fun List<GradePartialStatistics>.mapPartialToStatisticItems() = filterNot { it.c
|
||||
|
||||
fun List<GradeSemesterStatistics>.mapSemesterToStatisticItems() = filterNot { it.amounts.isEmpty() }.map {
|
||||
GradeStatisticsItem(
|
||||
type = ViewType.SEMESTER,
|
||||
type = GradeStatisticsItem.DataType.SEMESTER,
|
||||
partial = null,
|
||||
points = null,
|
||||
average = "",
|
||||
@ -71,7 +70,7 @@ fun List<GradeSemesterStatistics>.mapSemesterToStatisticItems() = filterNot { it
|
||||
|
||||
fun List<GradePointsStatistics>.mapPointsToStatisticsItems() = map {
|
||||
GradeStatisticsItem(
|
||||
type = ViewType.POINTS,
|
||||
type = GradeStatisticsItem.DataType.POINTS,
|
||||
partial = null,
|
||||
semester = null,
|
||||
average = "",
|
||||
|
@ -4,14 +4,14 @@ import io.github.wulkanowy.data.db.entities.Message
|
||||
import io.github.wulkanowy.data.db.entities.MessageAttachment
|
||||
import io.github.wulkanowy.data.db.entities.Recipient
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.sdk.pojo.Recipient as SdkRecipient
|
||||
import io.github.wulkanowy.sdk.pojo.MessageAttachment as SdkMessageAttachment
|
||||
import java.time.LocalDateTime
|
||||
import io.github.wulkanowy.sdk.pojo.Message as SdkMessage
|
||||
import io.github.wulkanowy.sdk.pojo.MessageAttachment as SdkMessageAttachment
|
||||
import io.github.wulkanowy.sdk.pojo.Recipient as SdkRecipient
|
||||
|
||||
fun List<SdkMessage>.mapToEntities(student: Student) = map {
|
||||
Message(
|
||||
studentId = student.id.toInt(),
|
||||
studentId = student.id,
|
||||
realId = it.id ?: 0,
|
||||
messageId = it.messageId ?: 0,
|
||||
sender = it.sender?.name.orEmpty(),
|
||||
|
@ -5,7 +5,7 @@ import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||
import java.time.LocalDateTime
|
||||
import io.github.wulkanowy.sdk.pojo.Student as SdkStudent
|
||||
|
||||
fun List<SdkStudent>.mapToEntities(password: String = "") = map {
|
||||
fun List<SdkStudent>.mapToEntities(password: String = "", colors: List<Long>) = map {
|
||||
StudentWithSemesters(
|
||||
student = Student(
|
||||
email = it.email,
|
||||
@ -28,8 +28,10 @@ fun List<SdkStudent>.mapToEntities(password: String = "") = map {
|
||||
mobileBaseUrl = it.mobileBaseUrl,
|
||||
privateKey = it.privateKey,
|
||||
certificateKey = it.certificateKey,
|
||||
loginMode = it.loginMode.name
|
||||
),
|
||||
loginMode = it.loginMode.name,
|
||||
).apply {
|
||||
avatarColor = colors.random()
|
||||
},
|
||||
semesters = it.semesters.mapToEntities(it.studentId)
|
||||
)
|
||||
}
|
||||
|
@ -3,11 +3,10 @@ package io.github.wulkanowy.data.pojos
|
||||
import io.github.wulkanowy.data.db.entities.GradePartialStatistics
|
||||
import io.github.wulkanowy.data.db.entities.GradePointsStatistics
|
||||
import io.github.wulkanowy.data.db.entities.GradeSemesterStatistics
|
||||
import io.github.wulkanowy.ui.modules.grade.statistics.ViewType
|
||||
|
||||
data class GradeStatisticsItem(
|
||||
|
||||
val type: ViewType,
|
||||
val type: DataType,
|
||||
|
||||
val average: String,
|
||||
|
||||
@ -16,4 +15,11 @@ data class GradeStatisticsItem(
|
||||
val semester: GradeSemesterStatistics?,
|
||||
|
||||
val points: GradePointsStatistics?
|
||||
)
|
||||
|
||||
) {
|
||||
enum class DataType {
|
||||
SEMESTER,
|
||||
PARTIAL,
|
||||
POINTS,
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ import io.github.wulkanowy.utils.monday
|
||||
import io.github.wulkanowy.utils.networkBoundResource
|
||||
import io.github.wulkanowy.utils.sunday
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDateTime
|
||||
import java.time.LocalTime
|
||||
@ -27,9 +28,12 @@ class AttendanceRepository @Inject constructor(
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
) {
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
|
||||
private val cacheKey = "attendance"
|
||||
|
||||
fun getAttendance(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester, start, end)) },
|
||||
query = { attendanceDb.loadAll(semester.diaryId, semester.studentId, start.monday, end.sunday) },
|
||||
fetch = {
|
||||
|
@ -10,6 +10,7 @@ import io.github.wulkanowy.utils.getRefreshKey
|
||||
import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.networkBoundResource
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@ -20,9 +21,12 @@ class AttendanceSummaryRepository @Inject constructor(
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
) {
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
|
||||
private val cacheKey = "attendance_summary"
|
||||
|
||||
fun getAttendanceSummary(student: Student, semester: Semester, subjectId: Int, forceRefresh: Boolean) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester)) },
|
||||
query = { attendanceDb.loadAll(semester.diaryId, semester.studentId, subjectId) },
|
||||
fetch = {
|
||||
|
@ -12,6 +12,7 @@ import io.github.wulkanowy.utils.monday
|
||||
import io.github.wulkanowy.utils.networkBoundResource
|
||||
import io.github.wulkanowy.utils.sunday
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import java.time.LocalDate
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
@ -23,9 +24,12 @@ class CompletedLessonsRepository @Inject constructor(
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
) {
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
|
||||
private val cacheKey = "completed"
|
||||
|
||||
fun getCompletedLessons(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester, start, end)) },
|
||||
query = { completedLessonsDb.loadAll(semester.studentId, semester.diaryId, start.monday, end.sunday) },
|
||||
fetch = {
|
||||
|
@ -10,6 +10,7 @@ import io.github.wulkanowy.utils.getRefreshKey
|
||||
import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.networkBoundResource
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@ -20,9 +21,12 @@ class ConferenceRepository @Inject constructor(
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
) {
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
|
||||
private val cacheKey = "conference"
|
||||
|
||||
fun getConferences(student: Student, semester: Semester, forceRefresh: Boolean) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester)) },
|
||||
query = { conferenceDb.loadAll(semester.diaryId, student.studentId) },
|
||||
fetch = {
|
||||
|
@ -12,6 +12,7 @@ import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.networkBoundResource
|
||||
import io.github.wulkanowy.utils.startExamsDay
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import java.time.LocalDate
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
@ -23,9 +24,12 @@ class ExamRepository @Inject constructor(
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
) {
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
|
||||
private val cacheKey = "exam"
|
||||
|
||||
fun getExams(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester, start, end)) },
|
||||
query = { examDb.loadAll(semester.diaryId, semester.studentId, start.startExamsDay, start.endExamsDay) },
|
||||
fetch = {
|
||||
|
@ -16,6 +16,7 @@ import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import java.time.LocalDateTime
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
@ -28,14 +29,20 @@ class GradeRepository @Inject constructor(
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
) {
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
|
||||
private val cacheKey = "grade"
|
||||
|
||||
fun getGrades(student: Student, semester: Semester, forceRefresh: Boolean, notify: Boolean = false) = networkBoundResource(
|
||||
shouldFetch = { (details, summaries) -> details.isEmpty() || summaries.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester)) },
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { (details, summaries) ->
|
||||
val isShouldBeRefreshed = refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
||||
details.isEmpty() || summaries.isEmpty() || forceRefresh || isShouldBeRefreshed
|
||||
},
|
||||
query = {
|
||||
gradeDb.loadAll(semester.semesterId, semester.studentId).combine(gradeSummaryDb.loadAll(semester.semesterId, semester.studentId)) { details, summaries ->
|
||||
details to summaries
|
||||
}
|
||||
val detailsFlow = gradeDb.loadAll(semester.semesterId, semester.studentId)
|
||||
val summaryFlow = gradeSummaryDb.loadAll(semester.semesterId, semester.studentId)
|
||||
detailsFlow.combine(summaryFlow) { details, summaries -> details to summaries }
|
||||
},
|
||||
fetch = {
|
||||
val (details, summary) = sdk.init(student)
|
||||
@ -92,19 +99,27 @@ class GradeRepository @Inject constructor(
|
||||
}
|
||||
|
||||
fun getUnreadGrades(semester: Semester): Flow<List<Grade>> {
|
||||
return gradeDb.loadAll(semester.semesterId, semester.studentId).map { it.filter { grade -> !grade.isRead } }
|
||||
return gradeDb.loadAll(semester.semesterId, semester.studentId).map {
|
||||
it.filter { grade -> !grade.isRead }
|
||||
}
|
||||
}
|
||||
|
||||
fun getNotNotifiedGrades(semester: Semester): Flow<List<Grade>> {
|
||||
return gradeDb.loadAll(semester.semesterId, semester.studentId).map { it.filter { grade -> !grade.isNotified } }
|
||||
return gradeDb.loadAll(semester.semesterId, semester.studentId).map {
|
||||
it.filter { grade -> !grade.isNotified }
|
||||
}
|
||||
}
|
||||
|
||||
fun getNotNotifiedPredictedGrades(semester: Semester): Flow<List<GradeSummary>> {
|
||||
return gradeSummaryDb.loadAll(semester.semesterId, semester.studentId).map { it.filter { gradeSummary -> !gradeSummary.isPredictedGradeNotified } }
|
||||
return gradeSummaryDb.loadAll(semester.semesterId, semester.studentId).map {
|
||||
it.filter { gradeSummary -> !gradeSummary.isPredictedGradeNotified }
|
||||
}
|
||||
}
|
||||
|
||||
fun getNotNotifiedFinalGrades(semester: Semester): Flow<List<GradeSummary>> {
|
||||
return gradeSummaryDb.loadAll(semester.semesterId, semester.studentId).map { it.filter { gradeSummary -> !gradeSummary.isFinalGradeNotified } }
|
||||
return gradeSummaryDb.loadAll(semester.semesterId, semester.studentId).map {
|
||||
it.filter { gradeSummary -> !gradeSummary.isFinalGradeNotified }
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun updateGrade(grade: Grade) {
|
||||
|
@ -17,6 +17,7 @@ import io.github.wulkanowy.utils.getRefreshKey
|
||||
import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.networkBoundResource
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import java.util.Locale
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
@ -30,11 +31,16 @@ class GradeStatisticsRepository @Inject constructor(
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
) {
|
||||
|
||||
private val partialMutex = Mutex()
|
||||
private val semesterMutex = Mutex()
|
||||
private val pointsMutex = Mutex()
|
||||
|
||||
private val partialCacheKey = "grade_stats_partial"
|
||||
private val semesterCacheKey = "grade_stats_semester"
|
||||
private val pointsCacheKey = "grade_stats_points"
|
||||
|
||||
fun getGradesPartialStatistics(student: Student, semester: Semester, subjectName: String, forceRefresh: Boolean) = networkBoundResource(
|
||||
mutex = partialMutex,
|
||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(partialCacheKey, semester)) },
|
||||
query = { gradePartialStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
|
||||
fetch = {
|
||||
@ -71,6 +77,7 @@ class GradeStatisticsRepository @Inject constructor(
|
||||
)
|
||||
|
||||
fun getGradesSemesterStatistics(student: Student, semester: Semester, subjectName: String, forceRefresh: Boolean) = networkBoundResource(
|
||||
mutex = semesterMutex,
|
||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(semesterCacheKey, semester)) },
|
||||
query = { gradeSemesterStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
|
||||
fetch = {
|
||||
@ -112,6 +119,7 @@ class GradeStatisticsRepository @Inject constructor(
|
||||
)
|
||||
|
||||
fun getGradesPointsStatistics(student: Student, semester: Semester, subjectName: String, forceRefresh: Boolean) = networkBoundResource(
|
||||
mutex = pointsMutex,
|
||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(pointsCacheKey, semester)) },
|
||||
query = { gradePointsStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
|
||||
fetch = {
|
||||
|
@ -13,6 +13,7 @@ import io.github.wulkanowy.utils.monday
|
||||
import io.github.wulkanowy.utils.networkBoundResource
|
||||
import io.github.wulkanowy.utils.sunday
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import java.time.LocalDate
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
@ -24,9 +25,12 @@ class HomeworkRepository @Inject constructor(
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
) {
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
|
||||
private val cacheKey = "homework"
|
||||
|
||||
fun getHomework(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester, start, end)) },
|
||||
query = { homeworkDb.loadAll(semester.semesterId, semester.studentId, start.monday, end.sunday) },
|
||||
fetch = {
|
||||
|
@ -9,6 +9,8 @@ import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.networkBoundResource
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDate.now
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
@ -19,7 +21,10 @@ class LuckyNumberRepository @Inject constructor(
|
||||
private val sdk: Sdk
|
||||
) {
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
|
||||
fun getLuckyNumber(student: Student, forceRefresh: Boolean, notify: Boolean = false) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { it == null || forceRefresh },
|
||||
query = { luckyNumberDb.load(student.studentId, now()) },
|
||||
fetch = { sdk.init(student).getLuckyNumber(student.schoolShortName)?.mapToEntity(student) },
|
||||
@ -33,6 +38,9 @@ class LuckyNumberRepository @Inject constructor(
|
||||
}
|
||||
)
|
||||
|
||||
fun getLuckyNumberHistory(student: Student, start: LocalDate, end: LocalDate) =
|
||||
luckyNumberDb.getAll(student.studentId, start, end)
|
||||
|
||||
suspend fun getNotNotifiedLuckyNumber(student: Student) = luckyNumberDb.load(student.studentId, now()).map {
|
||||
if (it?.isNotified == false) it else null
|
||||
}.first()
|
||||
|
@ -20,6 +20,7 @@ import io.github.wulkanowy.utils.networkBoundResource
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import timber.log.Timber
|
||||
import java.time.LocalDateTime.now
|
||||
import javax.inject.Inject
|
||||
@ -33,10 +34,13 @@ class MessageRepository @Inject constructor(
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
) {
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
|
||||
private val cacheKey = "message"
|
||||
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
fun getMessages(student: Student, semester: Semester, folder: MessageFolder, forceRefresh: Boolean, notify: Boolean = false) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, student, folder)) },
|
||||
query = { messagesDb.loadAll(student.id.toInt(), folder.id) },
|
||||
fetch = { sdk.init(student).getMessages(Folder.valueOf(folder.name), now().minusMonths(3), now()).mapToEntities(student) },
|
||||
|
@ -13,6 +13,7 @@ import io.github.wulkanowy.utils.getRefreshKey
|
||||
import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.networkBoundResource
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@ -23,9 +24,12 @@ class MobileDeviceRepository @Inject constructor(
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
) {
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
|
||||
private val cacheKey = "devices"
|
||||
|
||||
fun getDevices(student: Student, semester: Semester, forceRefresh: Boolean) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, student)) },
|
||||
query = { mobileDb.loadAll(student.userLoginId.takeIf { it != 0 } ?: student.studentId) },
|
||||
fetch = {
|
||||
|
@ -13,6 +13,7 @@ import io.github.wulkanowy.utils.networkBoundResource
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@ -23,9 +24,12 @@ class NoteRepository @Inject constructor(
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
) {
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
|
||||
private val cacheKey = "note"
|
||||
|
||||
fun getNotes(student: Student, semester: Semester, forceRefresh: Boolean, notify: Boolean = false) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester)) },
|
||||
query = { noteDb.loadAll(student.studentId) },
|
||||
fetch = {
|
||||
|
@ -18,26 +18,43 @@ class PreferencesRepository @Inject constructor(
|
||||
get() = getString(R.string.pref_key_start_menu, R.string.pref_default_startup).toInt()
|
||||
|
||||
val isShowPresent: Boolean
|
||||
get() = getBoolean(R.string.pref_key_attendance_present, R.bool.pref_default_attendance_present)
|
||||
get() = getBoolean(
|
||||
R.string.pref_key_attendance_present,
|
||||
R.bool.pref_default_attendance_present
|
||||
)
|
||||
|
||||
val gradeAverageMode: GradeAverageMode
|
||||
get() = GradeAverageMode.getByValue(getString(R.string.pref_key_grade_average_mode, R.string.pref_default_grade_average_mode))
|
||||
get() = GradeAverageMode.getByValue(
|
||||
getString(
|
||||
R.string.pref_key_grade_average_mode,
|
||||
R.string.pref_default_grade_average_mode
|
||||
)
|
||||
)
|
||||
|
||||
val gradeAverageForceCalc: Boolean
|
||||
get() = getBoolean(R.string.pref_key_grade_average_force_calc, R.bool.pref_default_grade_average_force_calc)
|
||||
get() = getBoolean(
|
||||
R.string.pref_key_grade_average_force_calc,
|
||||
R.bool.pref_default_grade_average_force_calc
|
||||
)
|
||||
|
||||
val isGradeExpandable: Boolean
|
||||
get() = !getBoolean(R.string.pref_key_expand_grade, R.bool.pref_default_expand_grade)
|
||||
|
||||
val showAllSubjectsOnStatisticsList: Boolean
|
||||
get() = getBoolean(R.string.pref_key_grade_statistics_list, R.bool.pref_default_grade_statistics_list)
|
||||
get() = getBoolean(
|
||||
R.string.pref_key_grade_statistics_list,
|
||||
R.bool.pref_default_grade_statistics_list
|
||||
)
|
||||
|
||||
val appThemeKey = context.getString(R.string.pref_key_app_theme)
|
||||
val appTheme: String
|
||||
get() = getString(appThemeKey, R.string.pref_default_app_theme)
|
||||
|
||||
val gradeColorTheme: String
|
||||
get() = getString(R.string.pref_key_grade_color_scheme, R.string.pref_default_grade_color_scheme)
|
||||
get() = getString(
|
||||
R.string.pref_key_grade_color_scheme,
|
||||
R.string.pref_default_grade_color_scheme
|
||||
)
|
||||
|
||||
val appLanguageKey = context.getString(R.string.pref_key_app_language)
|
||||
val appLanguage
|
||||
@ -55,50 +72,86 @@ class PreferencesRepository @Inject constructor(
|
||||
val isServicesOnlyWifi: Boolean
|
||||
get() = getBoolean(servicesOnlyWifiKey, R.bool.pref_default_services_wifi_only)
|
||||
|
||||
val notificationsEnableKey = context.getString(R.string.pref_key_notifications_enable)
|
||||
val isNotificationsEnable: Boolean
|
||||
get() = getBoolean(R.string.pref_key_notifications_enable, R.bool.pref_default_notifications_enable)
|
||||
get() = getBoolean(notificationsEnableKey, R.bool.pref_default_notifications_enable)
|
||||
|
||||
val isUpcomingLessonsNotificationsEnableKey = context.getString(R.string.pref_key_notifications_upcoming_lessons_enable)
|
||||
val isUpcomingLessonsNotificationsEnableKey =
|
||||
context.getString(R.string.pref_key_notifications_upcoming_lessons_enable)
|
||||
val isUpcomingLessonsNotificationsEnable: Boolean
|
||||
get() = getBoolean(isUpcomingLessonsNotificationsEnableKey, R.bool.pref_default_notification_upcoming_lessons_enable)
|
||||
get() = getBoolean(
|
||||
isUpcomingLessonsNotificationsEnableKey,
|
||||
R.bool.pref_default_notification_upcoming_lessons_enable
|
||||
)
|
||||
|
||||
val isDebugNotificationEnableKey = context.getString(R.string.pref_key_notification_debug)
|
||||
val isDebugNotificationEnable: Boolean
|
||||
get() = getBoolean(isDebugNotificationEnableKey, R.bool.pref_default_notification_debug)
|
||||
|
||||
val gradePlusModifier: Double
|
||||
get() = getString(R.string.pref_key_grade_modifier_plus, R.string.pref_default_grade_modifier_plus).toDouble()
|
||||
get() = getString(
|
||||
R.string.pref_key_grade_modifier_plus,
|
||||
R.string.pref_default_grade_modifier_plus
|
||||
).toDouble()
|
||||
|
||||
val gradeMinusModifier: Double
|
||||
get() = getString(R.string.pref_key_grade_modifier_minus, R.string.pref_default_grade_modifier_minus).toDouble()
|
||||
get() = getString(
|
||||
R.string.pref_key_grade_modifier_minus,
|
||||
R.string.pref_default_grade_modifier_minus
|
||||
).toDouble()
|
||||
|
||||
val fillMessageContent: Boolean
|
||||
get() = getBoolean(R.string.pref_key_fill_message_content, R.bool.pref_default_fill_message_content)
|
||||
get() = getBoolean(
|
||||
R.string.pref_key_fill_message_content,
|
||||
R.bool.pref_default_fill_message_content
|
||||
)
|
||||
|
||||
val showGroupsInPlan: Boolean
|
||||
get() = getBoolean(R.string.pref_key_timetable_show_groups, R.bool.pref_default_timetable_show_groups)
|
||||
get() = getBoolean(
|
||||
R.string.pref_key_timetable_show_groups,
|
||||
R.bool.pref_default_timetable_show_groups
|
||||
)
|
||||
|
||||
val showWholeClassPlan: String
|
||||
get() = getString(R.string.pref_key_timetable_show_whole_class, R.string.pref_default_timetable_show_whole_class)
|
||||
get() = getString(
|
||||
R.string.pref_key_timetable_show_whole_class,
|
||||
R.string.pref_default_timetable_show_whole_class
|
||||
)
|
||||
|
||||
val gradeSortingMode: GradeSortingMode
|
||||
get() = GradeSortingMode.getByValue(getString(R.string.pref_key_grade_sorting_mode, R.string.pref_default_grade_sorting_mode))
|
||||
get() = GradeSortingMode.getByValue(
|
||||
getString(
|
||||
R.string.pref_key_grade_sorting_mode,
|
||||
R.string.pref_default_grade_sorting_mode
|
||||
)
|
||||
)
|
||||
|
||||
val showTimetableTimers: Boolean
|
||||
get() = getBoolean(R.string.pref_key_timetable_show_timers, R.bool.pref_default_timetable_show_timers)
|
||||
get() = getBoolean(
|
||||
R.string.pref_key_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)
|
||||
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
|
||||
get() = getBoolean(R.string.pref_key_subjects_without_grades, R.bool.pref_default_subjects_without_grades)
|
||||
get() = getBoolean(
|
||||
R.string.pref_key_subjects_without_grades,
|
||||
R.bool.pref_default_subjects_without_grades
|
||||
)
|
||||
|
||||
private fun getString(id: Int, default: Int) = getString(context.getString(id), default)
|
||||
|
||||
private fun getString(id: String, default: Int) = sharedPref.getString(id, context.getString(default)) ?: context.getString(default)
|
||||
private fun getString(id: String, default: Int) =
|
||||
sharedPref.getString(id, context.getString(default)) ?: context.getString(default)
|
||||
|
||||
private fun getBoolean(id: Int, default: Int) = getBoolean(context.getString(id), default)
|
||||
|
||||
private fun getBoolean(id: String, default: Int) = sharedPref.getBoolean(id, context.resources.getBoolean(default))
|
||||
private fun getBoolean(id: String, default: Int) =
|
||||
sharedPref.getBoolean(id, context.resources.getBoolean(default))
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import io.github.wulkanowy.data.mappers.mapToEntity
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.networkBoundResource
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@ -16,8 +17,11 @@ class SchoolRepository @Inject constructor(
|
||||
private val sdk: Sdk
|
||||
) {
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
|
||||
fun getSchoolInfo(student: Student, semester: Semester, forceRefresh: Boolean) =
|
||||
networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { it == null || forceRefresh },
|
||||
query = { schoolDb.load(semester.studentId, semester.classId) },
|
||||
fetch = {
|
||||
|
@ -7,6 +7,7 @@ import io.github.wulkanowy.data.mappers.mapToEntity
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.networkBoundResource
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@ -16,8 +17,11 @@ class StudentInfoRepository @Inject constructor(
|
||||
private val sdk: Sdk
|
||||
) {
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
|
||||
fun getStudentInfo(student: Student, semester: Semester, forceRefresh: Boolean) =
|
||||
networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { it == null || forceRefresh },
|
||||
query = { studentInfoDao.loadStudentInfo(student.studentId) },
|
||||
fetch = {
|
||||
|
@ -5,11 +5,12 @@ import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.github.wulkanowy.data.db.dao.SemesterDao
|
||||
import io.github.wulkanowy.data.db.dao.StudentDao
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.db.entities.StudentNick
|
||||
import io.github.wulkanowy.data.db.entities.StudentNickAndAvatar
|
||||
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||
import io.github.wulkanowy.data.exceptions.NoCurrentStudentException
|
||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.utils.AppInfo
|
||||
import io.github.wulkanowy.utils.DispatchersProvider
|
||||
import io.github.wulkanowy.utils.security.decrypt
|
||||
import io.github.wulkanowy.utils.security.encrypt
|
||||
@ -23,7 +24,8 @@ class StudentRepository @Inject constructor(
|
||||
private val dispatchers: DispatchersProvider,
|
||||
private val studentDb: StudentDao,
|
||||
private val semesterDb: SemesterDao,
|
||||
private val sdk: Sdk
|
||||
private val sdk: Sdk,
|
||||
private val appInfo: AppInfo
|
||||
) {
|
||||
|
||||
suspend fun isStudentSaved() = getSavedStudents(false).isNotEmpty()
|
||||
@ -35,7 +37,8 @@ class StudentRepository @Inject constructor(
|
||||
symbol: String,
|
||||
token: String
|
||||
): List<StudentWithSemesters> =
|
||||
sdk.getStudentsFromMobileApi(token, pin, symbol, "").mapToEntities()
|
||||
sdk.getStudentsFromMobileApi(token, pin, symbol, "")
|
||||
.mapToEntities(colors = appInfo.defaultColorsForAvatar)
|
||||
|
||||
suspend fun getStudentsScrapper(
|
||||
email: String,
|
||||
@ -44,7 +47,7 @@ class StudentRepository @Inject constructor(
|
||||
symbol: String
|
||||
): List<StudentWithSemesters> =
|
||||
sdk.getStudentsFromScrapper(email, password, scrapperBaseUrl, symbol)
|
||||
.mapToEntities(password)
|
||||
.mapToEntities(password, appInfo.defaultColorsForAvatar)
|
||||
|
||||
suspend fun getStudentsHybrid(
|
||||
email: String,
|
||||
@ -52,47 +55,59 @@ class StudentRepository @Inject constructor(
|
||||
scrapperBaseUrl: String,
|
||||
symbol: String
|
||||
): List<StudentWithSemesters> =
|
||||
sdk.getStudentsHybrid(email, password, scrapperBaseUrl, "", symbol).mapToEntities(password)
|
||||
sdk.getStudentsHybrid(email, password, scrapperBaseUrl, "", symbol)
|
||||
.mapToEntities(password, appInfo.defaultColorsForAvatar)
|
||||
|
||||
suspend fun getSavedStudents(decryptPass: Boolean = true) =
|
||||
withContext(dispatchers.backgroundThread) {
|
||||
studentDb.loadStudentsWithSemesters().map {
|
||||
studentDb.loadStudentsWithSemesters()
|
||||
.map {
|
||||
it.apply {
|
||||
if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) {
|
||||
student.password = decrypt(student.password)
|
||||
student.password = withContext(dispatchers.backgroundThread) {
|
||||
decrypt(student.password)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getStudentById(id: Int) = withContext(dispatchers.backgroundThread) {
|
||||
studentDb.loadById(id)?.apply {
|
||||
if (Sdk.Mode.valueOf(loginMode) != Sdk.Mode.API) {
|
||||
password = decrypt(password)
|
||||
}
|
||||
}
|
||||
} ?: throw NoCurrentStudentException()
|
||||
suspend fun getStudentById(id: Long, decryptPass: Boolean = true): Student {
|
||||
val student = studentDb.loadById(id) ?: throw NoCurrentStudentException()
|
||||
|
||||
suspend fun getCurrentStudent(decryptPass: Boolean = true) =
|
||||
withContext(dispatchers.backgroundThread) {
|
||||
studentDb.loadCurrent()?.apply {
|
||||
if (decryptPass && Sdk.Mode.valueOf(loginMode) != Sdk.Mode.API) {
|
||||
password = decrypt(password)
|
||||
if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) {
|
||||
student.password = withContext(dispatchers.backgroundThread) {
|
||||
decrypt(student.password)
|
||||
}
|
||||
}
|
||||
} ?: throw NoCurrentStudentException()
|
||||
return student
|
||||
}
|
||||
|
||||
suspend fun getCurrentStudent(decryptPass: Boolean = true): Student {
|
||||
val student = studentDb.loadCurrent() ?: throw NoCurrentStudentException()
|
||||
|
||||
if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) {
|
||||
student.password = withContext(dispatchers.backgroundThread) {
|
||||
decrypt(student.password)
|
||||
}
|
||||
}
|
||||
return student
|
||||
}
|
||||
|
||||
suspend fun saveStudents(studentsWithSemesters: List<StudentWithSemesters>): List<Long> {
|
||||
semesterDb.insertSemesters(studentsWithSemesters.flatMap { it.semesters })
|
||||
|
||||
return withContext(dispatchers.backgroundThread) {
|
||||
studentDb.insertAll(studentsWithSemesters.map { it.student }.map {
|
||||
val semesters = studentsWithSemesters.flatMap { it.semesters }
|
||||
val students = studentsWithSemesters.map { it.student }
|
||||
.map {
|
||||
it.apply {
|
||||
if (Sdk.Mode.valueOf(it.loginMode) != Sdk.Mode.API) {
|
||||
it.copy(password = encrypt(it.password, context))
|
||||
} else it
|
||||
})
|
||||
password = withContext(dispatchers.backgroundThread) {
|
||||
encrypt(password, context)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
semesterDb.insertSemesters(semesters)
|
||||
return studentDb.insertAll(students)
|
||||
}
|
||||
|
||||
suspend fun switchStudent(studentWithSemesters: StudentWithSemesters) {
|
||||
with(studentDb) {
|
||||
@ -103,5 +118,6 @@ class StudentRepository @Inject constructor(
|
||||
|
||||
suspend fun logoutStudent(student: Student) = studentDb.delete(student)
|
||||
|
||||
suspend fun updateStudentNick(studentNick: StudentNick) = studentDb.update(studentNick)
|
||||
suspend fun updateStudentNickAndAvatar(studentNickAndAvatar: StudentNickAndAvatar) =
|
||||
studentDb.update(studentNickAndAvatar)
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.networkBoundResource
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@ -17,7 +18,10 @@ class SubjectRepository @Inject constructor(
|
||||
private val sdk: Sdk
|
||||
) {
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
|
||||
fun getSubjects(student: Student, semester: Semester, forceRefresh: Boolean = false) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { it.isEmpty() || forceRefresh },
|
||||
query = { subjectDao.loadAll(semester.diaryId, semester.studentId) },
|
||||
fetch = {
|
||||
|
@ -8,6 +8,7 @@ import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.networkBoundResource
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@ -17,7 +18,10 @@ class TeacherRepository @Inject constructor(
|
||||
private val sdk: Sdk
|
||||
) {
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
|
||||
fun getTeachers(student: Student, semester: Semester, forceRefresh: Boolean) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { it.isEmpty() || forceRefresh },
|
||||
query = { teacherDb.loadAll(semester.studentId, semester.classId) },
|
||||
fetch = {
|
||||
|
@ -18,6 +18,7 @@ import io.github.wulkanowy.utils.sunday
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import java.time.LocalDate
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
@ -31,9 +32,12 @@ class TimetableRepository @Inject constructor(
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
) {
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
|
||||
private val cacheKey = "timetable"
|
||||
|
||||
fun getTimetable(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean, refreshAdditional: Boolean = false) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { (timetable, additional) -> timetable.isEmpty() || (additional.isEmpty() && refreshAdditional) || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester, start, end)) },
|
||||
query = {
|
||||
timetableDb.loadAll(semester.diaryId, semester.studentId, start.monday, end.sunday)
|
||||
|
@ -37,6 +37,7 @@ abstract class BaseActivity<T : BasePresenter<out BaseView>, VB : ViewBinding> :
|
||||
abstract var presenter: T
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
inject()
|
||||
themeManager.applyActivityTheme(this)
|
||||
super.onCreate(savedInstanceState)
|
||||
supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleLogger, true)
|
||||
@ -44,7 +45,9 @@ abstract class BaseActivity<T : BasePresenter<out BaseView>, VB : ViewBinding> :
|
||||
|
||||
if (SDK_INT >= LOLLIPOP) {
|
||||
@Suppress("DEPRECATION")
|
||||
setTaskDescription(ActivityManager.TaskDescription(null, null, getThemeAttrColor(R.attr.colorSurface)))
|
||||
setTaskDescription(
|
||||
ActivityManager.TaskDescription(null, null, getThemeAttrColor(R.attr.colorSurface))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -84,4 +87,9 @@ abstract class BaseActivity<T : BasePresenter<out BaseView>, VB : ViewBinding> :
|
||||
invalidateOptionsMenu()
|
||||
presenter.onDetachView()
|
||||
}
|
||||
|
||||
//https://github.com/google/dagger/releases/tag/dagger-2.33
|
||||
protected open fun inject() {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.fragment.app.FragmentPagerAdapter
|
||||
|
||||
//TODO Use ViewPager2
|
||||
class BaseFragmentPagerAdapter(private val fragmentManager: FragmentManager) :
|
||||
FragmentPagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
|
||||
|
||||
|
@ -42,12 +42,10 @@ class ErrorDialog : BaseDialogFragment<DialogErrorBinding>() {
|
||||
companion object {
|
||||
private const val ARGUMENT_KEY = "Data"
|
||||
|
||||
fun newInstance(error: Throwable): ErrorDialog {
|
||||
return ErrorDialog().apply {
|
||||
fun newInstance(error: Throwable) = ErrorDialog().apply {
|
||||
arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, error) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@ -57,12 +55,14 @@ class ErrorDialog : BaseDialogFragment<DialogErrorBinding>() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
return DialogErrorBinding.inflate(inflater).apply { binding = this }.root
|
||||
}
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
) = DialogErrorBinding.inflate(inflater).apply { binding = this }.root
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
val stringWriter = StringWriter().apply {
|
||||
error.printStackTrace(PrintWriter(this))
|
||||
|
@ -8,6 +8,9 @@ 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.data.repositories.PreferencesRepository
|
||||
import io.github.wulkanowy.ui.modules.login.LoginActivity
|
||||
import io.github.wulkanowy.ui.modules.main.MainActivity
|
||||
import io.github.wulkanowy.ui.modules.message.send.SendMessageActivity
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@ -17,7 +20,13 @@ class ThemeManager @Inject constructor(private val preferencesRepository: Prefer
|
||||
fun applyActivityTheme(activity: AppCompatActivity) {
|
||||
if (isThemeApplicable(activity)) {
|
||||
applyDefaultTheme()
|
||||
if (preferencesRepository.appTheme == "black") activity.setTheme(R.style.WulkanowyTheme_Black)
|
||||
if (preferencesRepository.appTheme == "black") {
|
||||
when (activity) {
|
||||
is MainActivity -> activity.setTheme(R.style.WulkanowyTheme_Black)
|
||||
is LoginActivity -> activity.setTheme(R.style.WulkanowyTheme_Login_Black)
|
||||
is SendMessageActivity -> activity.setTheme(R.style.WulkanowyTheme_MessageSend_Black)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -33,8 +42,13 @@ class ThemeManager @Inject constructor(private val preferencesRepository: Prefer
|
||||
}
|
||||
|
||||
private fun isThemeApplicable(activity: AppCompatActivity): Boolean {
|
||||
return activity.packageManager.getPackageInfo(activity.packageName, GET_ACTIVITIES)
|
||||
.activities.singleOrNull { it.name == activity::class.java.canonicalName }?.theme
|
||||
.let { it == R.style.WulkanowyTheme_Black || it == R.style.WulkanowyTheme_NoActionBar }
|
||||
return activity.packageManager
|
||||
.getPackageInfo(activity.packageName, GET_ACTIVITIES)
|
||||
.activities.singleOrNull { it.name == activity::class.java.canonicalName }
|
||||
?.theme.let {
|
||||
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_MessageSend || it == R.style.WulkanowyTheme_MessageSend_Black
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,9 +4,9 @@ import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.View.GONE
|
||||
import android.view.View.VISIBLE
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.text.parseAsHtml
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.mikepenz.aboutlibraries.Libs
|
||||
import com.mikepenz.aboutlibraries.entity.Library
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
@ -26,14 +26,9 @@ class LicenseFragment : BaseFragment<FragmentLicenseBinding>(R.layout.fragment_l
|
||||
@Inject
|
||||
lateinit var licenseAdapter: LicenseAdapter
|
||||
|
||||
private val libs by lazy { Libs(requireContext()) }
|
||||
|
||||
override val titleStringId get() = R.string.license_title
|
||||
|
||||
override val appLibraries: ArrayList<Library>?
|
||||
get() = context?.let {
|
||||
libs.prepareLibraries(it, emptyArray(), emptyArray(), autoDetect = true, checkCachedDetection = true, sort = true)
|
||||
}
|
||||
override val appLibraries by lazy { Libs(requireContext()).libraries }
|
||||
|
||||
companion object {
|
||||
fun newInstance() = LicenseFragment()
|
||||
@ -63,7 +58,7 @@ class LicenseFragment : BaseFragment<FragmentLicenseBinding>(R.layout.fragment_l
|
||||
|
||||
override fun openLicense(licenseHtml: String) {
|
||||
context?.let {
|
||||
AlertDialog.Builder(it).apply {
|
||||
MaterialAlertDialogBuilder(it).apply {
|
||||
setTitle(R.string.license_dialog_title)
|
||||
setMessage(licenseHtml.parseAsHtml())
|
||||
setPositiveButton(android.R.string.ok) { _, _ -> }
|
||||
|
@ -5,7 +5,7 @@ import io.github.wulkanowy.ui.base.BaseView
|
||||
|
||||
interface LicenseView : BaseView {
|
||||
|
||||
val appLibraries: ArrayList<Library>?
|
||||
val appLibraries: List<Library>
|
||||
|
||||
fun initView()
|
||||
|
||||
|
@ -1,16 +1,17 @@
|
||||
package io.github.wulkanowy.ui.modules.account
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.graphics.PorterDuff
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View.GONE
|
||||
import android.view.View.VISIBLE
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||
import io.github.wulkanowy.databinding.HeaderAccountBinding
|
||||
import io.github.wulkanowy.databinding.ItemAccountBinding
|
||||
import io.github.wulkanowy.utils.createNameInitialsDrawable
|
||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||
import io.github.wulkanowy.utils.nickOrName
|
||||
import javax.inject.Inject
|
||||
@ -72,9 +73,13 @@ class AccountAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView.V
|
||||
binding: ItemAccountBinding,
|
||||
studentWithSemesters: StudentWithSemesters
|
||||
) {
|
||||
val context = binding.root.context
|
||||
val student = studentWithSemesters.student
|
||||
val semesters = studentWithSemesters.semesters
|
||||
val diary = semesters.maxByOrNull { it.semesterId }
|
||||
val avatar = context.createNameInitialsDrawable(student.nickOrName, student.avatarColor)
|
||||
val checkBackgroundColor =
|
||||
context.getThemeAttrColor(if (isAccountQuickDialogMode) R.attr.colorBackgroundFloating else R.attr.colorSurface)
|
||||
val isDuplicatedStudent = items.filter {
|
||||
if (it.value !is StudentWithSemesters) return@filter false
|
||||
val studentToCompare = it.value.student
|
||||
@ -87,15 +92,17 @@ class AccountAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView.V
|
||||
with(binding) {
|
||||
accountItemName.text = "${student.nickOrName} ${diary?.diaryName.orEmpty()}"
|
||||
accountItemSchool.text = studentWithSemesters.student.schoolName
|
||||
accountItemAccountType.setText(if (student.isParent) R.string.account_type_parent else R.string.account_type_student)
|
||||
accountItemAccountType.visibility = if (isDuplicatedStudent) VISIBLE else GONE
|
||||
accountItemImage.setImageDrawable(avatar)
|
||||
|
||||
with(accountItemImage) {
|
||||
val colorImage =
|
||||
if (student.isCurrent) context.getThemeAttrColor(R.attr.colorPrimary)
|
||||
else context.getThemeAttrColor(R.attr.colorOnSurface, 153)
|
||||
with(accountItemAccountType) {
|
||||
setText(if (student.isParent) R.string.account_type_parent else R.string.account_type_student)
|
||||
isVisible = isDuplicatedStudent
|
||||
}
|
||||
|
||||
setColorFilter(colorImage, PorterDuff.Mode.SRC_IN)
|
||||
with(accountItemCheck) {
|
||||
isVisible = student.isCurrent
|
||||
borderColor = checkBackgroundColor
|
||||
circleColor = checkBackgroundColor
|
||||
}
|
||||
|
||||
root.setOnClickListener { onClickListener(studentWithSemesters) }
|
||||
|
@ -34,25 +34,20 @@ class AccountFragment : BaseFragment<FragmentAccountBinding>(R.layout.fragment_a
|
||||
|
||||
override val titleStringId = R.string.account_title
|
||||
|
||||
override var subtitleString = ""
|
||||
|
||||
override val isViewEmpty get() = accountAdapter.items.isEmpty()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setHasOptionsMenu(true)
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
binding = FragmentAccountBinding.bind(view)
|
||||
presenter.onAttachView(this)
|
||||
}
|
||||
|
||||
override fun initView() {
|
||||
binding.accountErrorRetry.setOnClickListener { presenter.onRetry() }
|
||||
binding.accountErrorDetails.setOnClickListener { presenter.onDetailsClick() }
|
||||
|
||||
binding.accountRecycler.apply {
|
||||
layoutManager = LinearLayoutManager(context)
|
||||
adapter = accountAdapter
|
||||
@ -60,9 +55,7 @@ class AccountFragment : BaseFragment<FragmentAccountBinding>(R.layout.fragment_a
|
||||
|
||||
accountAdapter.onClickListener = presenter::onItemSelected
|
||||
|
||||
with(binding) {
|
||||
accountAdd.setOnClickListener { presenter.onAddSelected() }
|
||||
}
|
||||
binding.accountAdd.setOnClickListener { presenter.onAddSelected() }
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
@ -84,28 +77,7 @@ class AccountFragment : BaseFragment<FragmentAccountBinding>(R.layout.fragment_a
|
||||
|
||||
override fun openAccountDetailsView(studentWithSemesters: StudentWithSemesters) {
|
||||
(activity as? MainActivity)?.pushView(
|
||||
AccountDetailsFragment.newInstance(
|
||||
studentWithSemesters
|
||||
)
|
||||
AccountDetailsFragment.newInstance(studentWithSemesters)
|
||||
)
|
||||
}
|
||||
|
||||
override fun showErrorView(show: Boolean) {
|
||||
binding.accountError.visibility = if (show) View.VISIBLE else View.GONE
|
||||
}
|
||||
|
||||
override fun setErrorDetails(message: String) {
|
||||
binding.accountErrorMessage.text = message
|
||||
}
|
||||
|
||||
override fun showProgress(show: Boolean) {
|
||||
binding.accountProgress.visibility = if (show) View.VISIBLE else View.GONE
|
||||
}
|
||||
|
||||
override fun showContent(show: Boolean) {
|
||||
with(binding) {
|
||||
accountRecycler.visibility = if (show) View.VISIBLE else View.GONE
|
||||
accountAdd.visibility = if (show) View.VISIBLE else View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||
import io.github.wulkanowy.ui.base.BasePresenter
|
||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||
import io.github.wulkanowy.utils.afterLoading
|
||||
import io.github.wulkanowy.utils.flowWithResource
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import timber.log.Timber
|
||||
@ -16,28 +15,13 @@ class AccountPresenter @Inject constructor(
|
||||
studentRepository: StudentRepository,
|
||||
) : BasePresenter<AccountView>(errorHandler, studentRepository) {
|
||||
|
||||
private lateinit var lastError: Throwable
|
||||
|
||||
override fun onAttachView(view: AccountView) {
|
||||
super.onAttachView(view)
|
||||
view.initView()
|
||||
Timber.i("Account view was initialized")
|
||||
errorHandler.showErrorMessage = ::showErrorViewOnError
|
||||
loadData()
|
||||
}
|
||||
|
||||
fun onRetry() {
|
||||
view?.run {
|
||||
showErrorView(false)
|
||||
showProgress(true)
|
||||
}
|
||||
loadData()
|
||||
}
|
||||
|
||||
fun onDetailsClick() {
|
||||
view?.showErrorDetailsDialog(lastError)
|
||||
}
|
||||
|
||||
fun onAddSelected() {
|
||||
Timber.i("Select add account")
|
||||
view?.openLoginView()
|
||||
@ -47,6 +31,24 @@ class AccountPresenter @Inject constructor(
|
||||
view?.openAccountDetailsView(studentWithSemesters)
|
||||
}
|
||||
|
||||
private fun loadData() {
|
||||
flowWithResource { studentRepository.getSavedStudents(false) }
|
||||
.onEach {
|
||||
when (it.status) {
|
||||
Status.LOADING -> Timber.i("Loading account data started")
|
||||
Status.SUCCESS -> {
|
||||
Timber.i("Loading account result: Success")
|
||||
view?.updateData(createAccountItems(it.data!!))
|
||||
}
|
||||
Status.ERROR -> {
|
||||
Timber.i("Loading account result: An exception occurred")
|
||||
errorHandler.dispatch(it.error!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
.launch("load")
|
||||
}
|
||||
|
||||
private fun createAccountItems(items: List<StudentWithSemesters>): List<AccountItem<*>> {
|
||||
return items.groupBy {
|
||||
Account("${it.student.userName} (${it.student.email})", it.student.isParent)
|
||||
@ -60,45 +62,4 @@ class AccountPresenter @Inject constructor(
|
||||
}
|
||||
.flatten()
|
||||
}
|
||||
|
||||
private fun loadData() {
|
||||
flowWithResource { studentRepository.getSavedStudents(false) }
|
||||
.onEach {
|
||||
when (it.status) {
|
||||
Status.LOADING -> {
|
||||
Timber.i("Loading account data started")
|
||||
view?.run {
|
||||
showProgress(true)
|
||||
showContent(false)
|
||||
}
|
||||
}
|
||||
Status.SUCCESS -> {
|
||||
Timber.i("Loading account result: Success")
|
||||
view?.updateData(createAccountItems(it.data!!))
|
||||
view?.run {
|
||||
showContent(true)
|
||||
showErrorView(false)
|
||||
}
|
||||
}
|
||||
Status.ERROR -> {
|
||||
Timber.i("Loading account result: An exception occurred")
|
||||
errorHandler.dispatch(it.error!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
.afterLoading { view?.showProgress(false) }
|
||||
.launch()
|
||||
}
|
||||
|
||||
private fun showErrorViewOnError(message: String, error: Throwable) {
|
||||
view?.run {
|
||||
if (isViewEmpty) {
|
||||
lastError = error
|
||||
setErrorDetails(message)
|
||||
showErrorView(true)
|
||||
showContent(false)
|
||||
showProgress(false)
|
||||
} else showError(message, error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,8 +5,6 @@ import io.github.wulkanowy.ui.base.BaseView
|
||||
|
||||
interface AccountView : BaseView {
|
||||
|
||||
val isViewEmpty: Boolean
|
||||
|
||||
fun initView()
|
||||
|
||||
fun updateData(data: List<AccountItem<*>>)
|
||||
@ -14,13 +12,4 @@ interface AccountView : BaseView {
|
||||
fun openLoginView()
|
||||
|
||||
fun openAccountDetailsView(studentWithSemesters: StudentWithSemesters)
|
||||
|
||||
fun showErrorView(show: Boolean)
|
||||
|
||||
fun setErrorDetails(message: String)
|
||||
|
||||
fun showProgress(show: Boolean)
|
||||
|
||||
fun showContent(show: Boolean)
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,7 @@ import android.view.MenuItem
|
||||
import android.view.View
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.view.get
|
||||
import androidx.core.view.isVisible
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
@ -18,6 +19,7 @@ import io.github.wulkanowy.ui.modules.main.MainActivity
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoFragment
|
||||
import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoView
|
||||
import io.github.wulkanowy.utils.createNameInitialsDrawable
|
||||
import io.github.wulkanowy.utils.nickOrName
|
||||
import javax.inject.Inject
|
||||
|
||||
@ -31,8 +33,6 @@ class AccountDetailsFragment :
|
||||
|
||||
override val titleStringId = R.string.account_details_title
|
||||
|
||||
override var subtitleString = ""
|
||||
|
||||
companion object {
|
||||
|
||||
private const val ARGUMENT_KEY = "Data"
|
||||
@ -88,8 +88,15 @@ class AccountDetailsFragment :
|
||||
|
||||
override fun showAccountData(student: Student) {
|
||||
with(binding) {
|
||||
accountDetailsCheck.isVisible = student.isCurrent
|
||||
accountDetailsName.text = student.nickOrName
|
||||
accountDetailsSchool.text = student.schoolName
|
||||
accountDetailsAvatar.setImageDrawable(
|
||||
requireContext().createNameInitialsDrawable(
|
||||
student.nickOrName,
|
||||
student.avatarColor
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -21,7 +21,7 @@ class AccountDetailsPresenter @Inject constructor(
|
||||
private val syncManager: SyncManager
|
||||
) : BasePresenter<AccountDetailsView>(errorHandler, studentRepository) {
|
||||
|
||||
private lateinit var studentWithSemesters: StudentWithSemesters
|
||||
private var studentWithSemesters: StudentWithSemesters? = null
|
||||
|
||||
private lateinit var lastError: Throwable
|
||||
|
||||
@ -69,10 +69,10 @@ class AccountDetailsPresenter @Inject constructor(
|
||||
}
|
||||
Status.SUCCESS -> {
|
||||
Timber.i("Loading account details view result: Success")
|
||||
studentWithSemesters = it.data!!
|
||||
studentWithSemesters = it.data
|
||||
view?.run {
|
||||
showAccountData(studentWithSemesters.student)
|
||||
enableSelectStudentButton(!studentWithSemesters.student.isCurrent)
|
||||
showAccountData(studentWithSemesters!!.student)
|
||||
enableSelectStudentButton(!studentWithSemesters!!.student.isCurrent)
|
||||
showContent(true)
|
||||
showErrorView(false)
|
||||
}
|
||||
@ -88,17 +88,23 @@ class AccountDetailsPresenter @Inject constructor(
|
||||
}
|
||||
|
||||
fun onAccountEditSelected() {
|
||||
view?.showAccountEditDetailsDialog(studentWithSemesters.student)
|
||||
studentWithSemesters?.let {
|
||||
view?.showAccountEditDetailsDialog(it.student)
|
||||
}
|
||||
}
|
||||
|
||||
fun onStudentInfoSelected(infoType: StudentInfoView.Type) {
|
||||
view?.openStudentInfoView(infoType, studentWithSemesters)
|
||||
studentWithSemesters?.let {
|
||||
view?.openStudentInfoView(infoType, it)
|
||||
}
|
||||
}
|
||||
|
||||
fun onStudentSelect() {
|
||||
Timber.i("Select student ${studentWithSemesters.student.id}")
|
||||
if (studentWithSemesters == null) return
|
||||
|
||||
flowWithResource { studentRepository.switchStudent(studentWithSemesters) }
|
||||
Timber.i("Select student ${studentWithSemesters!!.student.id}")
|
||||
|
||||
flowWithResource { studentRepository.switchStudent(studentWithSemesters!!) }
|
||||
.onEach {
|
||||
when (it.status) {
|
||||
Status.LOADING -> Timber.i("Attempt to change a student")
|
||||
@ -122,8 +128,10 @@ class AccountDetailsPresenter @Inject constructor(
|
||||
}
|
||||
|
||||
fun onLogoutConfirm() {
|
||||
if (studentWithSemesters == null) return
|
||||
|
||||
flowWithResource {
|
||||
val studentToLogout = studentWithSemesters.student
|
||||
val studentToLogout = studentWithSemesters!!.student
|
||||
|
||||
studentRepository.logoutStudent(studentToLogout)
|
||||
val students = studentRepository.getSavedStudents(false)
|
||||
@ -143,7 +151,7 @@ class AccountDetailsPresenter @Inject constructor(
|
||||
syncManager.stopSyncWorker()
|
||||
openClearLoginView()
|
||||
}
|
||||
studentWithSemesters.student.isCurrent -> {
|
||||
studentWithSemesters!!.student.isCurrent -> {
|
||||
Timber.i("Logout result: Logout student and switch to another")
|
||||
recreateMainView()
|
||||
}
|
||||
|
@ -0,0 +1,90 @@
|
||||
package io.github.wulkanowy.ui.modules.account.accountedit
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.graphics.drawable.GradientDrawable
|
||||
import android.graphics.drawable.RippleDrawable
|
||||
import android.graphics.drawable.StateListDrawable
|
||||
import android.os.Build
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import io.github.wulkanowy.databinding.ItemAccountEditColorBinding
|
||||
import javax.inject.Inject
|
||||
|
||||
class AccountEditColorAdapter @Inject constructor() :
|
||||
RecyclerView.Adapter<AccountEditColorAdapter.ViewHolder>() {
|
||||
|
||||
var items = listOf<Int>()
|
||||
|
||||
var selectedColor = 0
|
||||
|
||||
override fun getItemCount() = items.size
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(
|
||||
ItemAccountEditColorBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
)
|
||||
|
||||
@SuppressLint("RestrictedApi", "NewApi")
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
val item = items[position]
|
||||
|
||||
with(holder.binding) {
|
||||
accountEditItemColor.setImageDrawable(GradientDrawable().apply {
|
||||
shape = GradientDrawable.OVAL
|
||||
setColor(item)
|
||||
})
|
||||
|
||||
accountEditItemColorContainer.foreground = item.createForegroundDrawable()
|
||||
accountEditCheck.isVisible = selectedColor == item
|
||||
|
||||
root.setOnClickListener {
|
||||
val oldSelectedPosition = items.indexOf(selectedColor)
|
||||
selectedColor = item
|
||||
|
||||
notifyItemChanged(oldSelectedPosition)
|
||||
notifyItemChanged(position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Int.createForegroundDrawable(): Drawable =
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
val mask = GradientDrawable().apply {
|
||||
shape = GradientDrawable.OVAL
|
||||
setColor(Color.BLACK)
|
||||
}
|
||||
RippleDrawable(ColorStateList.valueOf(this.rippleColor), null, mask)
|
||||
} else {
|
||||
val foreground = StateListDrawable().apply {
|
||||
alpha = 80
|
||||
setEnterFadeDuration(250)
|
||||
setExitFadeDuration(250)
|
||||
}
|
||||
|
||||
val mask = GradientDrawable().apply {
|
||||
shape = GradientDrawable.OVAL
|
||||
setColor(this@createForegroundDrawable.rippleColor)
|
||||
}
|
||||
|
||||
foreground.apply {
|
||||
addState(intArrayOf(android.R.attr.state_pressed), mask)
|
||||
addState(intArrayOf(), ColorDrawable(Color.TRANSPARENT))
|
||||
}
|
||||
}
|
||||
|
||||
private inline val Int.rippleColor: Int
|
||||
get() {
|
||||
val hsv = FloatArray(3)
|
||||
Color.colorToHSV(this, hsv)
|
||||
hsv[2] = hsv[2] * 0.5f
|
||||
return Color.HSVToColor(hsv)
|
||||
}
|
||||
|
||||
class ViewHolder(val binding: ItemAccountEditColorBinding) :
|
||||
RecyclerView.ViewHolder(binding.root)
|
||||
}
|
@ -4,6 +4,7 @@ import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.databinding.DialogAccountEditBinding
|
||||
@ -16,6 +17,9 @@ class AccountEditDialog : BaseDialogFragment<DialogAccountEditBinding>(), Accoun
|
||||
@Inject
|
||||
lateinit var presenter: AccountEditPresenter
|
||||
|
||||
@Inject
|
||||
lateinit var accountEditColorAdapter: AccountEditColorAdapter
|
||||
|
||||
companion object {
|
||||
|
||||
private const val ARGUMENT_KEY = "student_with_semesters"
|
||||
@ -48,8 +52,30 @@ class AccountEditDialog : BaseDialogFragment<DialogAccountEditBinding>(), Accoun
|
||||
with(binding) {
|
||||
accountEditDetailsCancel.setOnClickListener { dismiss() }
|
||||
accountEditDetailsSave.setOnClickListener {
|
||||
presenter.changeStudentNick(binding.accountEditDetailsNickText.text.toString())
|
||||
presenter.changeStudentNickAndAvatar(
|
||||
binding.accountEditDetailsNickText.text.toString(),
|
||||
accountEditColorAdapter.selectedColor
|
||||
)
|
||||
}
|
||||
|
||||
with(binding.accountEditColors) {
|
||||
layoutManager = GridLayoutManager(context, 4)
|
||||
adapter = accountEditColorAdapter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateSelectedColorData(color: Int) {
|
||||
with(accountEditColorAdapter) {
|
||||
selectedColor = color
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateColorsData(colors: List<Int>) {
|
||||
with(accountEditColorAdapter) {
|
||||
items = colors
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,10 +2,11 @@ package io.github.wulkanowy.ui.modules.account.accountedit
|
||||
|
||||
import io.github.wulkanowy.data.Status
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.db.entities.StudentNick
|
||||
import io.github.wulkanowy.data.db.entities.StudentNickAndAvatar
|
||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||
import io.github.wulkanowy.ui.base.BasePresenter
|
||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||
import io.github.wulkanowy.utils.AppInfo
|
||||
import io.github.wulkanowy.utils.afterLoading
|
||||
import io.github.wulkanowy.utils.flowWithResource
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
@ -13,12 +14,15 @@ import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
class AccountEditPresenter @Inject constructor(
|
||||
private val appInfo: AppInfo,
|
||||
errorHandler: ErrorHandler,
|
||||
studentRepository: StudentRepository
|
||||
) : BasePresenter<AccountEditView>(errorHandler, studentRepository) {
|
||||
|
||||
lateinit var student: Student
|
||||
|
||||
private val colors = appInfo.defaultColorsForAvatar.map { it.toInt() }
|
||||
|
||||
fun onAttachView(view: AccountEditView, student: Student) {
|
||||
super.onAttachView(view)
|
||||
this.student = student
|
||||
@ -28,27 +32,49 @@ class AccountEditPresenter @Inject constructor(
|
||||
showCurrentNick(student.nick.trim())
|
||||
}
|
||||
Timber.i("Account edit dialog view was initialized")
|
||||
loadData()
|
||||
|
||||
view.updateColorsData(colors)
|
||||
}
|
||||
|
||||
fun changeStudentNick(nick: String) {
|
||||
private fun loadData() {
|
||||
flowWithResource {
|
||||
studentRepository.getStudentById(student.id, false).avatarColor
|
||||
}.onEach { resource ->
|
||||
when (resource.status) {
|
||||
Status.LOADING -> Timber.i("Attempt to load student")
|
||||
Status.SUCCESS -> {
|
||||
view?.updateSelectedColorData(resource.data?.toInt()!!)
|
||||
Timber.i("Attempt to load student: Success")
|
||||
}
|
||||
Status.ERROR -> {
|
||||
Timber.i("Attempt to load student: An exception occurred")
|
||||
errorHandler.dispatch(resource.error!!)
|
||||
}
|
||||
}
|
||||
}.launch("load_data")
|
||||
}
|
||||
|
||||
fun changeStudentNickAndAvatar(nick: String, avatarColor: Int) {
|
||||
flowWithResource {
|
||||
val studentNick =
|
||||
StudentNick(nick = nick.trim()).apply { id = student.id }
|
||||
studentRepository.updateStudentNick(studentNick)
|
||||
StudentNickAndAvatar(nick = nick.trim(), avatarColor = avatarColor.toLong())
|
||||
.apply { id = student.id }
|
||||
studentRepository.updateStudentNickAndAvatar(studentNick)
|
||||
}.onEach {
|
||||
when (it.status) {
|
||||
Status.LOADING -> Timber.i("Attempt to change a student nick")
|
||||
Status.LOADING -> Timber.i("Attempt to change a student nick and avatar")
|
||||
Status.SUCCESS -> {
|
||||
Timber.i("Change a student nick result: Success")
|
||||
Timber.i("Change a student nick and avatar result: Success")
|
||||
view?.recreateMainView()
|
||||
}
|
||||
Status.ERROR -> {
|
||||
Timber.i("Change a student result: An exception occurred")
|
||||
Timber.i("Change a student nick and avatar result: An exception occurred")
|
||||
errorHandler.dispatch(it.error!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
.afterLoading { view?.popView() }
|
||||
.launch()
|
||||
.launch("update_student")
|
||||
}
|
||||
}
|
||||
|
@ -11,4 +11,8 @@ interface AccountEditView : BaseView {
|
||||
fun recreateMainView()
|
||||
|
||||
fun showCurrentNick(nick: String)
|
||||
|
||||
fun updateSelectedColorData(color: Int)
|
||||
|
||||
fun updateColorsData(colors: List<Int>)
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||
import io.github.wulkanowy.databinding.DialogAccountQuickBinding
|
||||
import io.github.wulkanowy.ui.base.BaseDialogFragment
|
||||
import io.github.wulkanowy.ui.modules.account.AccountAdapter
|
||||
@ -24,7 +25,15 @@ class AccountQuickDialog : BaseDialogFragment<DialogAccountQuickBinding>(), Acco
|
||||
lateinit var presenter: AccountQuickPresenter
|
||||
|
||||
companion object {
|
||||
fun newInstance() = AccountQuickDialog()
|
||||
|
||||
private const val STUDENTS_ARGUMENT_KEY = "students"
|
||||
|
||||
fun newInstance(studentsWithSemesters: List<StudentWithSemesters>) =
|
||||
AccountQuickDialog().apply {
|
||||
arguments = Bundle().apply {
|
||||
putSerializable(STUDENTS_ARGUMENT_KEY, studentsWithSemesters.toTypedArray())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
@ -38,8 +47,12 @@ class AccountQuickDialog : BaseDialogFragment<DialogAccountQuickBinding>(), Acco
|
||||
savedInstanceState: Bundle?
|
||||
) = DialogAccountQuickBinding.inflate(inflater).apply { binding = this }.root
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
presenter.onAttachView(this)
|
||||
val studentsWithSemesters =
|
||||
(requireArguments()[STUDENTS_ARGUMENT_KEY] as Array<StudentWithSemesters>).toList()
|
||||
|
||||
presenter.onAttachView(this, studentsWithSemesters)
|
||||
}
|
||||
|
||||
override fun initView() {
|
||||
|
@ -17,11 +17,15 @@ class AccountQuickPresenter @Inject constructor(
|
||||
studentRepository: StudentRepository
|
||||
) : BasePresenter<AccountQuickView>(errorHandler, studentRepository) {
|
||||
|
||||
override fun onAttachView(view: AccountQuickView) {
|
||||
private lateinit var studentsWithSemesters: List<StudentWithSemesters>
|
||||
|
||||
fun onAttachView(view: AccountQuickView, studentsWithSemesters: List<StudentWithSemesters>) {
|
||||
super.onAttachView(view)
|
||||
this.studentsWithSemesters = studentsWithSemesters
|
||||
|
||||
view.initView()
|
||||
Timber.i("Account quick dialog view was initialized")
|
||||
loadData()
|
||||
view.updateData(createAccountItems(studentsWithSemesters))
|
||||
}
|
||||
|
||||
fun onManagerSelected() {
|
||||
@ -57,22 +61,6 @@ class AccountQuickPresenter @Inject constructor(
|
||||
.launch("switch")
|
||||
}
|
||||
|
||||
private fun loadData() {
|
||||
flowWithResource { studentRepository.getSavedStudents(false) }.onEach {
|
||||
when (it.status) {
|
||||
Status.LOADING -> Timber.i("Loading account data started")
|
||||
Status.SUCCESS -> {
|
||||
Timber.i("Loading account result: Success")
|
||||
view?.updateData(createAccountItems(it.data!!))
|
||||
}
|
||||
Status.ERROR -> {
|
||||
Timber.i("Loading account result: An exception occurred")
|
||||
errorHandler.dispatch(it.error!!)
|
||||
}
|
||||
}
|
||||
}.launch()
|
||||
}
|
||||
|
||||
private fun createAccountItems(items: List<StudentWithSemesters>) = items.map {
|
||||
AccountItem(it, AccountItem.ViewType.ITEM)
|
||||
}
|
||||
|
@ -18,14 +18,13 @@ class AttendanceDialog : DialogFragment() {
|
||||
private lateinit var attendance: Attendance
|
||||
|
||||
companion object {
|
||||
|
||||
private const val ARGUMENT_KEY = "Item"
|
||||
|
||||
fun newInstance(exam: Attendance): AttendanceDialog {
|
||||
return AttendanceDialog().apply {
|
||||
fun newInstance(exam: Attendance) = AttendanceDialog().apply {
|
||||
arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, exam) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@ -35,12 +34,14 @@ class AttendanceDialog : DialogFragment() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
return DialogAttendanceBinding.inflate(inflater).apply { binding = this }.root
|
||||
}
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
) = DialogAttendanceBinding.inflate(inflater).apply { binding = this }.root
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
with(binding) {
|
||||
attendanceDialogSubject.text = attendance.subject
|
||||
|
@ -17,14 +17,13 @@ class ExamDialog : DialogFragment() {
|
||||
private lateinit var exam: Exam
|
||||
|
||||
companion object {
|
||||
|
||||
private const val ARGUMENT_KEY = "Item"
|
||||
|
||||
fun newInstance(exam: Exam): ExamDialog {
|
||||
return ExamDialog().apply {
|
||||
fun newInstance(exam: Exam) = ExamDialog().apply {
|
||||
arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, exam) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@ -34,12 +33,14 @@ class ExamDialog : DialogFragment() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
return DialogExamBinding.inflate(inflater).apply { binding = this }.root
|
||||
}
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
) = DialogExamBinding.inflate(inflater).apply { binding = this }.root
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
with(binding) {
|
||||
examDialogSubjectValue.text = exam.subject
|
||||
|
@ -86,7 +86,11 @@ class GradeAverageProvider @Inject constructor(
|
||||
return@combine firstSemesterGradeSubject
|
||||
}
|
||||
|
||||
val isAnyAverage = secondSemesterGradeSubject.data.orEmpty().any { it.average != .0 }
|
||||
val isAnyVulcanAverageInFirstSemester =
|
||||
firstSemesterGradeSubject.data.orEmpty().any { it.average != .0 }
|
||||
val isAnyVulcanAverageInSecondSemester =
|
||||
secondSemesterGradeSubject.data.orEmpty().any { it.average != .0 }
|
||||
|
||||
val updatedData = secondSemesterGradeSubject.data?.map { secondSemesterSubject ->
|
||||
val firstSemesterSubject = firstSemesterGradeSubject.data.orEmpty()
|
||||
.singleOrNull { it.subject == secondSemesterSubject.subject }
|
||||
@ -94,7 +98,7 @@ class GradeAverageProvider @Inject constructor(
|
||||
val updatedAverage = if (averageMode == ALL_YEAR) {
|
||||
calculateAllYearAverage(
|
||||
student = student,
|
||||
isAnyAverage = isAnyAverage,
|
||||
isAnyVulcanAverage = isAnyVulcanAverageInFirstSemester || isAnyVulcanAverageInSecondSemester,
|
||||
gradeAverageForceCalc = gradeAverageForceCalc,
|
||||
secondSemesterSubject = secondSemesterSubject,
|
||||
firstSemesterSubject = firstSemesterSubject
|
||||
@ -102,7 +106,7 @@ class GradeAverageProvider @Inject constructor(
|
||||
} else {
|
||||
calculateBothSemestersAverage(
|
||||
student = student,
|
||||
isAnyAverage = isAnyAverage,
|
||||
isAnyVulcanAverage = isAnyVulcanAverageInFirstSemester || isAnyVulcanAverageInSecondSemester,
|
||||
gradeAverageForceCalc = gradeAverageForceCalc,
|
||||
secondSemesterSubject = secondSemesterSubject,
|
||||
firstSemesterSubject = firstSemesterSubject
|
||||
@ -116,11 +120,11 @@ class GradeAverageProvider @Inject constructor(
|
||||
|
||||
private fun calculateAllYearAverage(
|
||||
student: Student,
|
||||
isAnyAverage: Boolean,
|
||||
isAnyVulcanAverage: Boolean,
|
||||
gradeAverageForceCalc: Boolean,
|
||||
secondSemesterSubject: GradeSubject,
|
||||
firstSemesterSubject: GradeSubject?
|
||||
) = if (!isAnyAverage || gradeAverageForceCalc) {
|
||||
) = if (!isAnyVulcanAverage || gradeAverageForceCalc) {
|
||||
val updatedSecondSemesterGrades =
|
||||
secondSemesterSubject.grades.updateModifiers(student)
|
||||
val updatedFirstSemesterGrades =
|
||||
@ -133,20 +137,23 @@ class GradeAverageProvider @Inject constructor(
|
||||
|
||||
private fun calculateBothSemestersAverage(
|
||||
student: Student,
|
||||
isAnyAverage: Boolean,
|
||||
isAnyVulcanAverage: Boolean,
|
||||
gradeAverageForceCalc: Boolean,
|
||||
secondSemesterSubject: GradeSubject,
|
||||
firstSemesterSubject: GradeSubject?
|
||||
) = if (!isAnyAverage || gradeAverageForceCalc) {
|
||||
): Double {
|
||||
val divider = if (secondSemesterSubject.grades.any { it.weightValue > .0 }) 2 else 1
|
||||
|
||||
return if (!isAnyVulcanAverage || gradeAverageForceCalc) {
|
||||
val secondSemesterAverage =
|
||||
secondSemesterSubject.grades.updateModifiers(student).calcAverage()
|
||||
val firstSemesterAverage = firstSemesterSubject?.grades?.updateModifiers(student)
|
||||
?.calcAverage() ?: secondSemesterAverage
|
||||
val divider = if (secondSemesterSubject.grades.any { it.weightValue > .0 }) 2 else 1
|
||||
|
||||
(secondSemesterAverage + firstSemesterAverage) / divider
|
||||
} else {
|
||||
(secondSemesterSubject.average + (firstSemesterSubject?.average ?: secondSemesterSubject.average)) / 2
|
||||
(secondSemesterSubject.average + (firstSemesterSubject?.average ?: secondSemesterSubject.average)) / divider
|
||||
}
|
||||
}
|
||||
|
||||
private fun getGradeSubjects(
|
||||
|
@ -63,11 +63,13 @@ class GradeFragment : BaseFragment<FragmentGradeBinding>(R.layout.fragment_grade
|
||||
override fun initView() {
|
||||
with(pagerAdapter) {
|
||||
containerId = binding.gradeViewPager.id
|
||||
addFragmentsWithTitle(mapOf(
|
||||
addFragmentsWithTitle(
|
||||
mapOf(
|
||||
GradeDetailsFragment.newInstance() to getString(R.string.all_details),
|
||||
GradeSummaryFragment.newInstance() to getString(R.string.grade_menu_summary),
|
||||
GradeStatisticsFragment.newInstance() to getString(R.string.grade_menu_statistics)
|
||||
))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
with(binding.gradeViewPager) {
|
||||
@ -137,7 +139,10 @@ class GradeFragment : BaseFragment<FragmentGradeBinding>(R.layout.fragment_grade
|
||||
|
||||
override fun setCurrentSemesterName(semester: Int, schoolYear: Int) {
|
||||
subtitleString = getString(R.string.grade_subtitle, semester, schoolYear, schoolYear + 1)
|
||||
(activity as MainView).setViewSubTitle(subtitleString)
|
||||
|
||||
if (isVisible) {
|
||||
(activity as MainView?)?.setViewSubTitle(subtitleString)
|
||||
}
|
||||
}
|
||||
|
||||
fun onChildRefresh() {
|
||||
@ -149,7 +154,8 @@ class GradeFragment : BaseFragment<FragmentGradeBinding>(R.layout.fragment_grade
|
||||
}
|
||||
|
||||
override fun notifyChildLoadData(index: Int, semesterId: Int, forceRefresh: Boolean) {
|
||||
(pagerAdapter.getFragmentInstance(index) as? GradeView.GradeChildView)?.onParentLoadData(semesterId, forceRefresh)
|
||||
(pagerAdapter.getFragmentInstance(index) as? GradeView.GradeChildView)
|
||||
?.onParentLoadData(semesterId, forceRefresh)
|
||||
}
|
||||
|
||||
override fun notifyChildParentReselected(index: Int) {
|
||||
|
@ -24,18 +24,19 @@ class GradeDetailsDialog : DialogFragment() {
|
||||
private lateinit var colorScheme: String
|
||||
|
||||
companion object {
|
||||
|
||||
private const val ARGUMENT_KEY = "Item"
|
||||
|
||||
private const val COLOR_SCHEME_KEY = "Scheme"
|
||||
|
||||
fun newInstance(grade: Grade, colorScheme: String): GradeDetailsDialog {
|
||||
return GradeDetailsDialog().apply {
|
||||
fun newInstance(grade: Grade, colorScheme: String) =
|
||||
GradeDetailsDialog().apply {
|
||||
arguments = Bundle().apply {
|
||||
putSerializable(ARGUMENT_KEY, grade)
|
||||
putString(COLOR_SCHEME_KEY, colorScheme)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@ -46,12 +47,14 @@ class GradeDetailsDialog : DialogFragment() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
return DialogGradeBinding.inflate(inflater).apply { binding = this }.root
|
||||
}
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
) = DialogGradeBinding.inflate(inflater).apply { binding = this }.root
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
with(binding) {
|
||||
gradeDialogSubject.text = grade.subject
|
||||
|
@ -22,6 +22,7 @@ import io.github.wulkanowy.data.db.entities.GradePointsStatistics
|
||||
import io.github.wulkanowy.data.db.entities.GradeSemesterStatistics
|
||||
import io.github.wulkanowy.data.pojos.GradeStatisticsItem
|
||||
import io.github.wulkanowy.databinding.ItemGradeStatisticsBarBinding
|
||||
import io.github.wulkanowy.databinding.ItemGradeStatisticsHeaderBinding
|
||||
import io.github.wulkanowy.databinding.ItemGradeStatisticsPieBinding
|
||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||
import javax.inject.Inject
|
||||
@ -29,12 +30,16 @@ import javax.inject.Inject
|
||||
class GradeStatisticsAdapter @Inject constructor() :
|
||||
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||
|
||||
var currentDataType = GradeStatisticsItem.DataType.PARTIAL
|
||||
|
||||
var items = emptyList<GradeStatisticsItem>()
|
||||
|
||||
var theme: String = "vulcan"
|
||||
|
||||
var showAllSubjectsOnList: Boolean = false
|
||||
|
||||
var onDataTypeChangeListener: () -> Unit = {}
|
||||
|
||||
private val vulcanGradeColors = listOf(
|
||||
6 to R.color.grade_vulcan_six,
|
||||
5 to R.color.grade_vulcan_five,
|
||||
@ -62,37 +67,90 @@ class GradeStatisticsAdapter @Inject constructor() :
|
||||
"6, 6-", "5, 5-, 5+", "4, 4-, 4+", "3, 3-, 3+", "2, 2-, 2+", "1, 1+"
|
||||
)
|
||||
|
||||
override fun getItemCount() = if (showAllSubjectsOnList) items.size else (if (items.isEmpty()) 0 else 1)
|
||||
override fun getItemCount() =
|
||||
(if (showAllSubjectsOnList) items.size else (if (items.isEmpty()) 0 else 1)) + 1
|
||||
|
||||
override fun getItemViewType(position: Int) = items[position].type.id
|
||||
override fun getItemViewType(position: Int) =
|
||||
if (position == 0) {
|
||||
ViewType.HEADER.id
|
||||
} else {
|
||||
when (items[position - 1].type) {
|
||||
GradeStatisticsItem.DataType.PARTIAL -> ViewType.PARTIAL.id
|
||||
GradeStatisticsItem.DataType.POINTS -> ViewType.POINTS.id
|
||||
GradeStatisticsItem.DataType.SEMESTER -> ViewType.SEMESTER.id
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||
val inflater = LayoutInflater.from(parent.context)
|
||||
return when (viewType) {
|
||||
ViewType.PARTIAL.id -> PartialViewHolder(ItemGradeStatisticsPieBinding.inflate(inflater, parent, false))
|
||||
ViewType.SEMESTER.id -> SemesterViewHolder(ItemGradeStatisticsPieBinding.inflate(inflater, parent, false))
|
||||
ViewType.POINTS.id -> PointsViewHolder(ItemGradeStatisticsBarBinding.inflate(inflater, parent, false))
|
||||
ViewType.PARTIAL.id -> PartialViewHolder(
|
||||
ItemGradeStatisticsPieBinding.inflate(inflater, parent, false)
|
||||
)
|
||||
ViewType.SEMESTER.id -> SemesterViewHolder(
|
||||
ItemGradeStatisticsPieBinding.inflate(inflater, parent, false)
|
||||
)
|
||||
ViewType.POINTS.id -> PointsViewHolder(
|
||||
ItemGradeStatisticsBarBinding.inflate(inflater, parent, false)
|
||||
)
|
||||
ViewType.HEADER.id -> HeaderViewHolder(
|
||||
ItemGradeStatisticsHeaderBinding.inflate(inflater, parent, false)
|
||||
)
|
||||
else -> throw IllegalStateException()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||
val index = position - 1
|
||||
|
||||
when (holder) {
|
||||
is PartialViewHolder -> bindPartialChart(holder.binding, items[position].partial!!)
|
||||
is SemesterViewHolder -> bindSemesterChart(holder.binding, items[position].semester!!)
|
||||
is PointsViewHolder -> bindBarChart(holder.binding, items[position].points!!)
|
||||
is PartialViewHolder -> bindPartialChart(holder.binding, items[index].partial!!)
|
||||
is SemesterViewHolder -> bindSemesterChart(holder.binding, items[index].semester!!)
|
||||
is PointsViewHolder -> bindBarChart(holder.binding, items[index].points!!)
|
||||
is HeaderViewHolder -> bindHeader(holder.binding)
|
||||
}
|
||||
}
|
||||
|
||||
private fun bindPartialChart(binding: ItemGradeStatisticsPieBinding, partials: GradePartialStatistics) {
|
||||
private fun bindHeader(binding: ItemGradeStatisticsHeaderBinding) {
|
||||
binding.gradeStatisticsTypeSwitch.check(
|
||||
when (currentDataType) {
|
||||
GradeStatisticsItem.DataType.PARTIAL -> R.id.gradeStatisticsTypePartial
|
||||
GradeStatisticsItem.DataType.SEMESTER -> R.id.gradeStatisticsTypeSemester
|
||||
GradeStatisticsItem.DataType.POINTS -> R.id.gradeStatisticsTypePoints
|
||||
}
|
||||
)
|
||||
|
||||
binding.gradeStatisticsTypeSwitch.setOnCheckedChangeListener { _, checkedId ->
|
||||
currentDataType = when (checkedId) {
|
||||
R.id.gradeStatisticsTypePartial -> GradeStatisticsItem.DataType.PARTIAL
|
||||
R.id.gradeStatisticsTypeSemester -> GradeStatisticsItem.DataType.SEMESTER
|
||||
R.id.gradeStatisticsTypePoints -> GradeStatisticsItem.DataType.POINTS
|
||||
else -> GradeStatisticsItem.DataType.PARTIAL
|
||||
}
|
||||
onDataTypeChangeListener()
|
||||
}
|
||||
}
|
||||
|
||||
private fun bindPartialChart(
|
||||
binding: ItemGradeStatisticsPieBinding,
|
||||
partials: GradePartialStatistics
|
||||
) {
|
||||
bindPieChart(binding, partials.subject, partials.classAverage, partials.classAmounts)
|
||||
}
|
||||
|
||||
private fun bindSemesterChart(binding: ItemGradeStatisticsPieBinding, semester: GradeSemesterStatistics) {
|
||||
private fun bindSemesterChart(
|
||||
binding: ItemGradeStatisticsPieBinding,
|
||||
semester: GradeSemesterStatistics
|
||||
) {
|
||||
bindPieChart(binding, semester.subject, semester.average, semester.amounts)
|
||||
}
|
||||
|
||||
private fun bindPieChart(binding: ItemGradeStatisticsPieBinding, subject: String, average: String, amounts: List<Int>) {
|
||||
private fun bindPieChart(
|
||||
binding: ItemGradeStatisticsPieBinding,
|
||||
subject: String,
|
||||
average: String,
|
||||
amounts: List<Int>
|
||||
) {
|
||||
with(binding.gradeStatisticsPieTitle) {
|
||||
text = subject
|
||||
visibility = if (items.size == 1 || !showAllSubjectsOnList) GONE else VISIBLE
|
||||
@ -114,7 +172,8 @@ class GradeStatisticsAdapter @Inject constructor() :
|
||||
valueTextSize = 12f
|
||||
sliceSpace = 1f
|
||||
valueTextColor = Color.WHITE
|
||||
val grades = amounts.mapIndexed { grade, amount -> (grade + 1) to amount }.filterNot { it.second == 0 }
|
||||
val grades = amounts.mapIndexed { grade, amount -> (grade + 1) to amount }
|
||||
.filterNot { it.second == 0 }
|
||||
setColors(grades.reversed().map { (grade, _) ->
|
||||
gradeColors.single { color -> color.first == grade }.second
|
||||
}.toIntArray(), binding.root.context)
|
||||
@ -126,7 +185,11 @@ class GradeStatisticsAdapter @Inject constructor() :
|
||||
data = PieData(dataset).apply {
|
||||
setValueFormatter(object : ValueFormatter() {
|
||||
override fun getPieLabel(value: Float, pieEntry: PieEntry): String {
|
||||
return resources.getQuantityString(R.plurals.grade_number_item, value.toInt(), value.toInt())
|
||||
return resources.getQuantityString(
|
||||
R.plurals.grade_number_item,
|
||||
value.toInt(),
|
||||
value.toInt()
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -143,11 +206,14 @@ class GradeStatisticsAdapter @Inject constructor() :
|
||||
|
||||
val numberOfGradesString = amounts.fold(0) { acc, it -> acc + it }
|
||||
.let { resources.getQuantityString(R.plurals.grade_number_item, it, it) }
|
||||
val averageString = binding.root.context.getString(R.string.grade_statistics_average, average)
|
||||
val averageString =
|
||||
binding.root.context.getString(R.string.grade_statistics_average, average)
|
||||
|
||||
minAngleForSlices = 25f
|
||||
description.isEnabled = false
|
||||
centerText = numberOfGradesString + ("\n\n" + averageString).takeIf { average.isNotBlank() }.orEmpty()
|
||||
centerText =
|
||||
numberOfGradesString + ("\n\n" + averageString).takeIf { average.isNotBlank() }
|
||||
.orEmpty()
|
||||
|
||||
setHoleColor(context.getThemeAttrColor(android.R.attr.windowBackground))
|
||||
setCenterTextColor(context.getThemeAttrColor(android.R.attr.textColorPrimary))
|
||||
@ -155,16 +221,21 @@ class GradeStatisticsAdapter @Inject constructor() :
|
||||
}
|
||||
}
|
||||
|
||||
private fun bindBarChart(binding: ItemGradeStatisticsBarBinding, points: GradePointsStatistics) {
|
||||
private fun bindBarChart(
|
||||
binding: ItemGradeStatisticsBarBinding,
|
||||
points: GradePointsStatistics
|
||||
) {
|
||||
with(binding.gradeStatisticsBarTitle) {
|
||||
text = points.subject
|
||||
visibility = if (items.size == 1) GONE else VISIBLE
|
||||
}
|
||||
|
||||
val dataset = BarDataSet(listOf(
|
||||
val dataset = BarDataSet(
|
||||
listOf(
|
||||
BarEntry(1f, points.others.toFloat()),
|
||||
BarEntry(2f, points.student.toFloat())
|
||||
), binding.root.context.getString(R.string.grade_statistics_legend))
|
||||
), binding.root.context.getString(R.string.grade_statistics_legend)
|
||||
)
|
||||
|
||||
with(dataset) {
|
||||
valueTextSize = 12f
|
||||
@ -189,7 +260,8 @@ class GradeStatisticsAdapter @Inject constructor() :
|
||||
form = Legend.LegendForm.SQUARE
|
||||
},
|
||||
LegendEntry().apply {
|
||||
label = binding.root.context.getString(R.string.grade_statistics_average_student)
|
||||
label =
|
||||
binding.root.context.getString(R.string.grade_statistics_average_student)
|
||||
formColor = gradePointsColors[1]
|
||||
form = Legend.LegendForm.SQUARE
|
||||
}
|
||||
@ -226,4 +298,7 @@ class GradeStatisticsAdapter @Inject constructor() :
|
||||
|
||||
private class PointsViewHolder(val binding: ItemGradeStatisticsBarBinding) :
|
||||
RecyclerView.ViewHolder(binding.root)
|
||||
|
||||
private class HeaderViewHolder(val binding: ItemGradeStatisticsHeaderBinding) :
|
||||
RecyclerView.ViewHolder(binding.root)
|
||||
}
|
||||
|
@ -38,27 +38,28 @@ class GradeStatisticsFragment :
|
||||
|
||||
override val isViewEmpty get() = statisticsAdapter.items.isEmpty()
|
||||
|
||||
override val currentType
|
||||
get() = when (binding.gradeStatisticsTypeSwitch.checkedRadioButtonId) {
|
||||
R.id.gradeStatisticsTypeSemester -> ViewType.SEMESTER
|
||||
R.id.gradeStatisticsTypePartial -> ViewType.PARTIAL
|
||||
else -> ViewType.POINTS
|
||||
}
|
||||
override val currentType get() = statisticsAdapter.currentDataType
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
binding = FragmentGradeStatisticsBinding.bind(view)
|
||||
messageContainer = binding.gradeStatisticsSwipe
|
||||
presenter.onAttachView(this, savedInstanceState?.getSerializable(SAVED_CHART_TYPE) as? ViewType)
|
||||
presenter.onAttachView(
|
||||
this,
|
||||
savedInstanceState?.getSerializable(SAVED_CHART_TYPE) as? GradeStatisticsItem.DataType
|
||||
)
|
||||
}
|
||||
|
||||
override fun initView() {
|
||||
statisticsAdapter.onDataTypeChangeListener = presenter::onTypeChange
|
||||
|
||||
with(binding.gradeStatisticsRecycler) {
|
||||
layoutManager = LinearLayoutManager(requireContext())
|
||||
adapter = statisticsAdapter
|
||||
}
|
||||
|
||||
subjectsAdapter = ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item, mutableListOf())
|
||||
subjectsAdapter =
|
||||
ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item, mutableListOf())
|
||||
subjectsAdapter.setDropDownViewResource(R.layout.item_attendance_summary_subject)
|
||||
|
||||
with(binding.gradeStatisticsSubjects) {
|
||||
@ -71,7 +72,9 @@ class GradeStatisticsFragment :
|
||||
|
||||
gradeStatisticsSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
|
||||
gradeStatisticsSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary))
|
||||
gradeStatisticsSwipe.setProgressBackgroundColorSchemeColor(requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh))
|
||||
gradeStatisticsSwipe.setProgressBackgroundColorSchemeColor(
|
||||
requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh)
|
||||
)
|
||||
gradeStatisticsErrorRetry.setOnClickListener { presenter.onRetry() }
|
||||
gradeStatisticsErrorDetails.setOnClickListener { presenter.onDetailsClick() }
|
||||
}
|
||||
@ -85,11 +88,15 @@ class GradeStatisticsFragment :
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateData(items: List<GradeStatisticsItem>, theme: String, showAllSubjectsOnStatisticsList: Boolean) {
|
||||
override fun updateData(
|
||||
newItems: List<GradeStatisticsItem>,
|
||||
newTheme: String,
|
||||
showAllSubjectsOnStatisticsList: Boolean
|
||||
) {
|
||||
with(statisticsAdapter) {
|
||||
this.showAllSubjectsOnList = showAllSubjectsOnStatisticsList
|
||||
this.theme = theme
|
||||
this.items = items
|
||||
showAllSubjectsOnList = showAllSubjectsOnStatisticsList
|
||||
theme = newTheme
|
||||
items = newItems
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
@ -103,11 +110,7 @@ class GradeStatisticsFragment :
|
||||
}
|
||||
|
||||
override fun resetView() {
|
||||
binding.gradeStatisticsScroll.scrollTo(0, 0)
|
||||
}
|
||||
|
||||
override fun showContent(show: Boolean) {
|
||||
binding.gradeStatisticsRecycler.visibility = if (show) View.VISIBLE else View.GONE
|
||||
binding.gradeStatisticsRecycler.scrollToPosition(0)
|
||||
}
|
||||
|
||||
override fun showEmpty(show: Boolean) {
|
||||
@ -154,11 +157,6 @@ class GradeStatisticsFragment :
|
||||
(parentFragment as? GradeFragment)?.onChildRefresh()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
binding.gradeStatisticsTypeSwitch.setOnCheckedChangeListener { _, _ -> presenter.onTypeChange() }
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
outState.putSerializable(SAVED_CHART_TYPE, presenter.currentType)
|
||||
|
@ -35,12 +35,12 @@ class GradeStatisticsPresenter @Inject constructor(
|
||||
|
||||
private lateinit var lastError: Throwable
|
||||
|
||||
var currentType: ViewType = ViewType.PARTIAL
|
||||
var currentType: GradeStatisticsItem.DataType = GradeStatisticsItem.DataType.PARTIAL
|
||||
private set
|
||||
|
||||
fun onAttachView(view: GradeStatisticsView, type: ViewType?) {
|
||||
fun onAttachView(view: GradeStatisticsView, type: GradeStatisticsItem.DataType?) {
|
||||
super.onAttachView(view)
|
||||
currentType = type ?: ViewType.PARTIAL
|
||||
currentType = type ?: GradeStatisticsItem.DataType.PARTIAL
|
||||
view.initView()
|
||||
errorHandler.showErrorMessage = ::showErrorViewOnError
|
||||
}
|
||||
@ -59,11 +59,11 @@ class GradeStatisticsPresenter @Inject constructor(
|
||||
}
|
||||
|
||||
fun onParentViewChangeSemester() {
|
||||
clearDataInView()
|
||||
view?.run {
|
||||
showProgress(true)
|
||||
enableSwipe(false)
|
||||
showRefresh(false)
|
||||
showContent(false)
|
||||
showErrorView(false)
|
||||
showEmpty(false)
|
||||
clearView()
|
||||
@ -90,8 +90,8 @@ class GradeStatisticsPresenter @Inject constructor(
|
||||
|
||||
fun onSubjectSelected(name: String?) {
|
||||
Timber.i("Select grade stats subject $name")
|
||||
clearDataInView()
|
||||
view?.run {
|
||||
showContent(false)
|
||||
showProgress(true)
|
||||
enableSwipe(false)
|
||||
showEmpty(false)
|
||||
@ -104,11 +104,11 @@ class GradeStatisticsPresenter @Inject constructor(
|
||||
}
|
||||
|
||||
fun onTypeChange() {
|
||||
val type = view?.currentType ?: ViewType.POINTS
|
||||
val type = view?.currentType ?: GradeStatisticsItem.DataType.POINTS
|
||||
Timber.i("Select grade stats semester: $type")
|
||||
cancelJobs("load")
|
||||
clearDataInView()
|
||||
view?.run {
|
||||
showContent(false)
|
||||
showProgress(true)
|
||||
enableSwipe(false)
|
||||
showEmpty(false)
|
||||
@ -143,10 +143,16 @@ class GradeStatisticsPresenter @Inject constructor(
|
||||
}.launch("subjects")
|
||||
}
|
||||
|
||||
private fun loadDataByType(semesterId: Int, subjectName: String, type: ViewType, forceRefresh: Boolean = false) {
|
||||
private fun loadDataByType(
|
||||
semesterId: Int,
|
||||
subjectName: String,
|
||||
type: GradeStatisticsItem.DataType,
|
||||
forceRefresh: Boolean = false
|
||||
) {
|
||||
Timber.i("Loading grade stats data started")
|
||||
|
||||
currentSubjectName = if (preferencesRepository.showAllSubjectsOnStatisticsList) "Wszystkie" else subjectName
|
||||
currentSubjectName =
|
||||
if (preferencesRepository.showAllSubjectsOnStatisticsList) "Wszystkie" else subjectName
|
||||
currentType = type
|
||||
|
||||
flowWithResourceIn {
|
||||
@ -156,9 +162,30 @@ class GradeStatisticsPresenter @Inject constructor(
|
||||
|
||||
with(gradeStatisticsRepository) {
|
||||
when (type) {
|
||||
ViewType.PARTIAL -> getGradesPartialStatistics(student, semester, currentSubjectName, forceRefresh)
|
||||
ViewType.SEMESTER -> getGradesSemesterStatistics(student, semester, currentSubjectName, forceRefresh)
|
||||
ViewType.POINTS -> getGradesPointsStatistics(student, semester, currentSubjectName, forceRefresh)
|
||||
GradeStatisticsItem.DataType.PARTIAL -> {
|
||||
getGradesPartialStatistics(
|
||||
student = student,
|
||||
semester = semester,
|
||||
subjectName = currentSubjectName,
|
||||
forceRefresh = forceRefresh
|
||||
)
|
||||
}
|
||||
GradeStatisticsItem.DataType.SEMESTER -> {
|
||||
getGradesSemesterStatistics(
|
||||
student = student,
|
||||
semester = semester,
|
||||
subjectName = currentSubjectName,
|
||||
forceRefresh = forceRefresh
|
||||
)
|
||||
}
|
||||
GradeStatisticsItem.DataType.POINTS -> {
|
||||
getGradesPointsStatistics(
|
||||
student = student,
|
||||
semester = semester,
|
||||
subjectName = currentSubjectName,
|
||||
forceRefresh = forceRefresh
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}.onEach {
|
||||
@ -168,12 +195,15 @@ class GradeStatisticsPresenter @Inject constructor(
|
||||
if (!isNoContent) {
|
||||
view?.run {
|
||||
showEmpty(isNoContent)
|
||||
showContent(!isNoContent)
|
||||
showErrorView(false)
|
||||
enableSwipe(true)
|
||||
showRefresh(true)
|
||||
showProgress(false)
|
||||
updateData(it.data!!, preferencesRepository.gradeColorTheme, preferencesRepository.showAllSubjectsOnStatisticsList)
|
||||
updateData(
|
||||
if (isNoContent) emptyList() else it.data!!,
|
||||
preferencesRepository.gradeColorTheme,
|
||||
preferencesRepository.showAllSubjectsOnStatisticsList
|
||||
)
|
||||
showSubjects(!preferencesRepository.showAllSubjectsOnStatisticsList)
|
||||
}
|
||||
}
|
||||
@ -183,9 +213,12 @@ class GradeStatisticsPresenter @Inject constructor(
|
||||
view?.run {
|
||||
val isNoContent = checkIsNoContent(it.data!!, type)
|
||||
showEmpty(isNoContent)
|
||||
showContent(!isNoContent)
|
||||
showErrorView(false)
|
||||
updateData(it.data, preferencesRepository.gradeColorTheme, preferencesRepository.showAllSubjectsOnStatisticsList)
|
||||
updateData(
|
||||
if (isNoContent) emptyList() else it.data,
|
||||
preferencesRepository.gradeColorTheme,
|
||||
preferencesRepository.showAllSubjectsOnStatisticsList
|
||||
)
|
||||
showSubjects(!preferencesRepository.showAllSubjectsOnStatisticsList)
|
||||
}
|
||||
analytics.logEvent(
|
||||
@ -209,14 +242,29 @@ class GradeStatisticsPresenter @Inject constructor(
|
||||
}.launch("load")
|
||||
}
|
||||
|
||||
private fun checkIsNoContent(items: List<GradeStatisticsItem>, type: ViewType): Boolean {
|
||||
private fun checkIsNoContent(
|
||||
items: List<GradeStatisticsItem>,
|
||||
type: GradeStatisticsItem.DataType
|
||||
): Boolean {
|
||||
return items.isEmpty() || when (type) {
|
||||
ViewType.SEMESTER -> items.firstOrNull()?.semester?.amounts.orEmpty().sum() == 0
|
||||
ViewType.PARTIAL -> items.firstOrNull()?.partial?.classAmounts.orEmpty().sum() == 0
|
||||
ViewType.POINTS -> items.firstOrNull()?.points?.let { points ->
|
||||
points.student == .0 && points.others == .0
|
||||
} ?: false
|
||||
GradeStatisticsItem.DataType.SEMESTER -> {
|
||||
items.firstOrNull()?.semester?.amounts.orEmpty().sum() == 0
|
||||
}
|
||||
GradeStatisticsItem.DataType.PARTIAL -> {
|
||||
items.firstOrNull()?.partial?.classAmounts.orEmpty().sum() == 0
|
||||
}
|
||||
GradeStatisticsItem.DataType.POINTS -> {
|
||||
items.firstOrNull()?.points?.let { points -> points.student == .0 && points.others == .0 } ?: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun clearDataInView() {
|
||||
view?.updateData(
|
||||
emptyList(),
|
||||
preferencesRepository.gradeColorTheme,
|
||||
preferencesRepository.showAllSubjectsOnStatisticsList
|
||||
)
|
||||
}
|
||||
|
||||
private fun showErrorViewOnError(message: String, error: Throwable) {
|
||||
|
@ -7,13 +7,17 @@ interface GradeStatisticsView : BaseView {
|
||||
|
||||
val isViewEmpty: Boolean
|
||||
|
||||
val currentType: ViewType
|
||||
val currentType: GradeStatisticsItem.DataType
|
||||
|
||||
fun initView()
|
||||
|
||||
fun updateSubjects(data: ArrayList<String>)
|
||||
|
||||
fun updateData(items: List<GradeStatisticsItem>, theme: String, showAllSubjectsOnStatisticsList: Boolean)
|
||||
fun updateData(
|
||||
newItems: List<GradeStatisticsItem>,
|
||||
newTheme: String,
|
||||
showAllSubjectsOnStatisticsList: Boolean
|
||||
)
|
||||
|
||||
fun showSubjects(show: Boolean)
|
||||
|
||||
@ -25,8 +29,6 @@ interface GradeStatisticsView : BaseView {
|
||||
|
||||
fun resetView()
|
||||
|
||||
fun showContent(show: Boolean)
|
||||
|
||||
fun showEmpty(show: Boolean)
|
||||
|
||||
fun showErrorView(show: Boolean)
|
||||
|
@ -3,5 +3,6 @@ package io.github.wulkanowy.ui.modules.grade.statistics
|
||||
enum class ViewType(val id: Int) {
|
||||
SEMESTER(1),
|
||||
PARTIAL(2),
|
||||
POINTS(3)
|
||||
POINTS(3),
|
||||
HEADER(4)
|
||||
}
|
||||
|
@ -28,14 +28,13 @@ class HomeworkDetailsDialog : BaseDialogFragment<DialogHomeworkBinding>(), Homew
|
||||
private lateinit var homework: Homework
|
||||
|
||||
companion object {
|
||||
|
||||
private const val ARGUMENT_KEY = "Item"
|
||||
|
||||
fun newInstance(homework: Homework): HomeworkDetailsDialog {
|
||||
return HomeworkDetailsDialog().apply {
|
||||
fun newInstance(homework: Homework) = HomeworkDetailsDialog().apply {
|
||||
arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, homework) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@ -45,19 +44,22 @@ class HomeworkDetailsDialog : BaseDialogFragment<DialogHomeworkBinding>(), Homew
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
return DialogHomeworkBinding.inflate(inflater).apply { binding = this }.root
|
||||
}
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
) = DialogHomeworkBinding.inflate(inflater).apply { binding = this }.root
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
presenter.onAttachView(this)
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun initView() {
|
||||
with(binding) {
|
||||
homeworkDialogRead.text = view?.context?.getString(if (homework.isDone) R.string.homework_mark_as_undone else R.string.homework_mark_as_done)
|
||||
homeworkDialogRead.text =
|
||||
view?.context?.getString(if (homework.isDone) R.string.homework_mark_as_undone else R.string.homework_mark_as_done)
|
||||
homeworkDialogRead.setOnClickListener { presenter.toggleDone(homework) }
|
||||
homeworkDialogClose.setOnClickListener { dismiss() }
|
||||
}
|
||||
@ -87,7 +89,8 @@ class HomeworkDetailsDialog : BaseDialogFragment<DialogHomeworkBinding>(), Homew
|
||||
}
|
||||
|
||||
override fun updateMarkAsDoneLabel(isDone: Boolean) {
|
||||
binding.homeworkDialogRead.text = view?.context?.getString(if (isDone) R.string.homework_mark_as_undone else R.string.homework_mark_as_done)
|
||||
binding.homeworkDialogRead.text =
|
||||
view?.context?.getString(if (isDone) R.string.homework_mark_as_undone else R.string.homework_mark_as_done)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
|
@ -52,6 +52,8 @@ class LoginActivity : BaseActivity<LoginPresenter, ActivityLoginBinding>(), Logi
|
||||
updateHelper.onResume(this)
|
||||
}
|
||||
|
||||
//https://developer.android.com/guide/playcore/in-app-updates#status_callback
|
||||
@Suppress("DEPRECATION")
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
updateHelper.onActivityResult(requestCode, resultCode)
|
||||
@ -65,13 +67,15 @@ class LoginActivity : BaseActivity<LoginPresenter, ActivityLoginBinding>(), Logi
|
||||
|
||||
with(loginAdapter) {
|
||||
containerId = binding.loginViewpager.id
|
||||
addFragments(listOf(
|
||||
addFragments(
|
||||
listOf(
|
||||
LoginFormFragment.newInstance(),
|
||||
LoginSymbolFragment.newInstance(),
|
||||
LoginStudentSelectFragment.newInstance(),
|
||||
LoginAdvancedFragment.newInstance(),
|
||||
LoginRecoverFragment.newInstance()
|
||||
))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
with(binding.loginViewpager) {
|
||||
@ -99,14 +103,20 @@ class LoginActivity : BaseActivity<LoginPresenter, ActivityLoginBinding>(), Logi
|
||||
}
|
||||
|
||||
override fun notifyInitSymbolFragment(loginData: Triple<String, String, String>) {
|
||||
(loginAdapter.getFragmentInstance(1) as? LoginSymbolFragment)?.onParentInitSymbolFragment(loginData)
|
||||
(loginAdapter.getFragmentInstance(1) as? LoginSymbolFragment)?.onParentInitSymbolFragment(
|
||||
loginData
|
||||
)
|
||||
}
|
||||
|
||||
override fun notifyInitStudentSelectFragment(studentsWithSemesters: List<StudentWithSemesters>) {
|
||||
(loginAdapter.getFragmentInstance(2) as? LoginStudentSelectFragment)?.onParentInitStudentSelectFragment(studentsWithSemesters)
|
||||
(loginAdapter.getFragmentInstance(2) as? LoginStudentSelectFragment)
|
||||
?.onParentInitStudentSelectFragment(studentsWithSemesters)
|
||||
}
|
||||
|
||||
fun onFormFragmentAccountLogged(studentsWithSemesters: List<StudentWithSemesters>, loginData: Triple<String, String, String>) {
|
||||
fun onFormFragmentAccountLogged(
|
||||
studentsWithSemesters: List<StudentWithSemesters>,
|
||||
loginData: Triple<String, String, String>
|
||||
) {
|
||||
presenter.onFormViewAccountLogged(studentsWithSemesters, loginData)
|
||||
}
|
||||
|
||||
|
@ -150,6 +150,13 @@ class LoginFormFragment : BaseFragment<FragmentLoginFormBinding>(R.layout.fragme
|
||||
}
|
||||
}
|
||||
|
||||
override fun setErrorEmailInvalid(domain: String) {
|
||||
with(binding.loginFormUsernameLayout) {
|
||||
requestFocus()
|
||||
error = getString(R.string.login_invalid_custom_email,domain)
|
||||
}
|
||||
}
|
||||
|
||||
override fun clearUsernameError() {
|
||||
binding.loginFormUsernameLayout.error = null
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import io.github.wulkanowy.utils.flowWithResource
|
||||
import io.github.wulkanowy.utils.ifNullOrBlank
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import timber.log.Timber
|
||||
import java.net.URL
|
||||
import javax.inject.Inject
|
||||
|
||||
class LoginFormPresenter @Inject constructor(
|
||||
@ -87,7 +88,14 @@ class LoginFormPresenter @Inject constructor(
|
||||
|
||||
if (!validateCredentials(email, password, host)) return
|
||||
|
||||
flowWithResource { studentRepository.getStudentsScrapper(email, password, host, symbol) }.onEach {
|
||||
flowWithResource {
|
||||
studentRepository.getStudentsScrapper(
|
||||
email,
|
||||
password,
|
||||
host,
|
||||
symbol
|
||||
)
|
||||
}.onEach {
|
||||
when (it.status) {
|
||||
Status.LOADING -> view?.run {
|
||||
Timber.i("Login started")
|
||||
@ -150,11 +158,18 @@ class LoginFormPresenter @Inject constructor(
|
||||
view?.setErrorLoginRequired()
|
||||
isCorrect = false
|
||||
}
|
||||
|
||||
if ("@" !in login && "email" in host) {
|
||||
view?.setErrorEmailRequired()
|
||||
isCorrect = false
|
||||
}
|
||||
if ("@" in login && "login" !in host && "email" !in host) {
|
||||
val emailHost = login.substringAfter("@")
|
||||
val emailDomain = URL(host).host
|
||||
if (emailHost != emailDomain) {
|
||||
view?.setErrorEmailInvalid(domain = emailDomain)
|
||||
isCorrect = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (password.isEmpty()) {
|
||||
|
@ -39,6 +39,8 @@ interface LoginFormView : BaseView {
|
||||
|
||||
fun setErrorPassIncorrect()
|
||||
|
||||
fun setErrorEmailInvalid(domain: String)
|
||||
|
||||
fun clearUsernameError()
|
||||
|
||||
fun clearPassError()
|
||||
|
@ -9,6 +9,8 @@ import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.LuckyNumber
|
||||
import io.github.wulkanowy.databinding.FragmentLuckyNumberBinding
|
||||
import io.github.wulkanowy.ui.base.BaseFragment
|
||||
import io.github.wulkanowy.ui.modules.luckynumber.history.LuckyNumberHistoryFragment
|
||||
import io.github.wulkanowy.ui.modules.main.MainActivity
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||
import javax.inject.Inject
|
||||
@ -42,6 +44,7 @@ class LuckyNumberFragment :
|
||||
luckyNumberSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
|
||||
luckyNumberSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary))
|
||||
luckyNumberSwipe.setProgressBackgroundColorSchemeColor(requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh))
|
||||
luckyNumberHistoryButton.setOnClickListener { openLuckyNumberHistory() }
|
||||
luckyNumberErrorRetry.setOnClickListener { presenter.onRetry() }
|
||||
luckyNumberErrorDetails.setOnClickListener { presenter.onDetailsClick() }
|
||||
}
|
||||
@ -79,6 +82,10 @@ class LuckyNumberFragment :
|
||||
binding.luckyNumberContent.visibility = if (show) VISIBLE else GONE
|
||||
}
|
||||
|
||||
override fun openLuckyNumberHistory() {
|
||||
(activity as? MainActivity)?.pushView(LuckyNumberHistoryFragment.newInstance())
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
presenter.onDetachView()
|
||||
super.onDestroyView()
|
||||
|
@ -24,4 +24,6 @@ interface LuckyNumberView : BaseView {
|
||||
fun enableSwipe(enable: Boolean)
|
||||
|
||||
fun showContent(show: Boolean)
|
||||
|
||||
fun openLuckyNumberHistory()
|
||||
}
|
||||
|
@ -0,0 +1,36 @@
|
||||
package io.github.wulkanowy.ui.modules.luckynumber.history
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import io.github.wulkanowy.data.db.entities.LuckyNumber
|
||||
import io.github.wulkanowy.databinding.ItemLuckyNumberHistoryBinding
|
||||
import io.github.wulkanowy.utils.toFormattedString
|
||||
import io.github.wulkanowy.utils.weekDayName
|
||||
import java.util.Locale
|
||||
import javax.inject.Inject
|
||||
|
||||
class LuckyNumberHistoryAdapter @Inject constructor() :
|
||||
RecyclerView.Adapter<LuckyNumberHistoryAdapter.ItemViewHolder>() {
|
||||
|
||||
var items = emptyList<LuckyNumber>()
|
||||
|
||||
override fun getItemCount() = items.size
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder(
|
||||
ItemLuckyNumberHistoryBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
)
|
||||
|
||||
@SuppressLint("DefaultLocale")
|
||||
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
|
||||
val item = items[position]
|
||||
with(holder.binding) {
|
||||
luckyNumberHistoryWeekName.text = item.date.weekDayName.capitalize()
|
||||
luckyNumberHistoryDate.text = item.date.toFormattedString()
|
||||
luckyNumberHistory.text = item.luckyNumber.toString()
|
||||
}
|
||||
}
|
||||
|
||||
class ItemViewHolder(val binding: ItemLuckyNumberHistoryBinding) : RecyclerView.ViewHolder(binding.root)
|
||||
}
|
@ -0,0 +1,134 @@
|
||||
package io.github.wulkanowy.ui.modules.luckynumber.history
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.View.GONE
|
||||
import android.view.View.VISIBLE
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.wdullaer.materialdatetimepicker.date.DatePickerDialog
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.LuckyNumber
|
||||
import io.github.wulkanowy.databinding.FragmentLuckyNumberHistoryBinding
|
||||
import io.github.wulkanowy.ui.base.BaseFragment
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import io.github.wulkanowy.ui.widgets.DividerItemDecoration
|
||||
import io.github.wulkanowy.utils.SchooldaysRangeLimiter
|
||||
import io.github.wulkanowy.utils.dpToPx
|
||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||
import java.time.LocalDate
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class LuckyNumberHistoryFragment :
|
||||
BaseFragment<FragmentLuckyNumberHistoryBinding>(R.layout.fragment_lucky_number_history), LuckyNumberHistoryView,
|
||||
MainView.TitledView {
|
||||
|
||||
@Inject
|
||||
lateinit var presenter: LuckyNumberHistoryPresenter
|
||||
|
||||
@Inject
|
||||
lateinit var luckyNumberHistoryAdapter: LuckyNumberHistoryAdapter
|
||||
|
||||
companion object {
|
||||
fun newInstance() = LuckyNumberHistoryFragment()
|
||||
}
|
||||
|
||||
override val titleStringId: Int
|
||||
get() = R.string.lucky_number_history_title
|
||||
|
||||
override val isViewEmpty get() = luckyNumberHistoryAdapter.items.isEmpty()
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
binding = FragmentLuckyNumberHistoryBinding.bind(view)
|
||||
messageContainer = binding.luckyNumberHistoryRecycler
|
||||
presenter.onAttachView(this)
|
||||
}
|
||||
|
||||
override fun initView() {
|
||||
with(binding.luckyNumberHistoryRecycler) {
|
||||
layoutManager = LinearLayoutManager(context)
|
||||
adapter = luckyNumberHistoryAdapter
|
||||
addItemDecoration(DividerItemDecoration(context))
|
||||
}
|
||||
|
||||
with(binding) {
|
||||
luckyNumberHistoryNavDate.setOnClickListener { presenter.onPickDate() }
|
||||
luckyNumberHistoryErrorRetry.setOnClickListener { presenter.onRetry() }
|
||||
luckyNumberHistoryErrorDetails.setOnClickListener { presenter.onDetailsClick() }
|
||||
|
||||
luckyNumberHistoryPreviousButton.setOnClickListener { presenter.onPreviousWeek() }
|
||||
luckyNumberHistoryNextButton.setOnClickListener { presenter.onNextWeek() }
|
||||
|
||||
luckyNumberHistoryNavContainer.setElevationCompat(requireContext().dpToPx(8f))
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateData(data: List<LuckyNumber>) {
|
||||
with(luckyNumberHistoryAdapter) {
|
||||
items = data
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
override fun clearData() {
|
||||
with(luckyNumberHistoryAdapter) {
|
||||
items = emptyList()
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
override fun showEmpty(show: Boolean) {
|
||||
binding.luckyNumberHistoryEmpty.visibility = if (show) VISIBLE else GONE
|
||||
}
|
||||
|
||||
override fun showErrorView(show: Boolean) {
|
||||
binding.luckyNumberHistoryError.visibility = if (show) VISIBLE else GONE
|
||||
}
|
||||
|
||||
override fun setErrorDetails(message: String) {
|
||||
binding.luckyNumberHistoryErrorMessage.text = message
|
||||
}
|
||||
|
||||
override fun updateNavigationWeek(date: String) {
|
||||
binding.luckyNumberHistoryNavDate.text = date
|
||||
}
|
||||
|
||||
override fun showProgress(show: Boolean) {
|
||||
binding.luckyNumberHistoryProgress.visibility = if (show) VISIBLE else GONE
|
||||
}
|
||||
|
||||
override fun showPreButton(show: Boolean) {
|
||||
binding.luckyNumberHistoryPreviousButton.visibility = if (show) VISIBLE else View.INVISIBLE
|
||||
}
|
||||
|
||||
override fun showNextButton(show: Boolean) {
|
||||
binding.luckyNumberHistoryNextButton.visibility = if (show) VISIBLE else View.INVISIBLE
|
||||
}
|
||||
|
||||
override fun showDatePickerDialog(currentDate: LocalDate) {
|
||||
val dateSetListener = DatePickerDialog.OnDateSetListener { _, year, month, dayOfMonth ->
|
||||
presenter.onDateSet(year, month + 1, dayOfMonth)
|
||||
}
|
||||
val datePickerDialog = DatePickerDialog.newInstance(dateSetListener,
|
||||
currentDate.year, currentDate.monthValue - 1, currentDate.dayOfMonth)
|
||||
|
||||
with(datePickerDialog) {
|
||||
setDateRangeLimiter(SchooldaysRangeLimiter())
|
||||
version = DatePickerDialog.Version.VERSION_2
|
||||
scrollOrientation = DatePickerDialog.ScrollOrientation.VERTICAL
|
||||
vibrate(false)
|
||||
show(this@LuckyNumberHistoryFragment.parentFragmentManager, null)
|
||||
}
|
||||
}
|
||||
|
||||
override fun showContent(show: Boolean) {
|
||||
binding.luckyNumberHistoryRecycler.visibility = if (show) VISIBLE else GONE
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
presenter.onDetachView()
|
||||
super.onDestroyView()
|
||||
}
|
||||
}
|
@ -0,0 +1,151 @@
|
||||
package io.github.wulkanowy.ui.modules.luckynumber.history
|
||||
|
||||
import io.github.wulkanowy.data.Status
|
||||
import io.github.wulkanowy.data.repositories.LuckyNumberRepository
|
||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||
import io.github.wulkanowy.ui.base.BasePresenter
|
||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||
import io.github.wulkanowy.utils.AnalyticsHelper
|
||||
import io.github.wulkanowy.utils.afterLoading
|
||||
import io.github.wulkanowy.utils.flowWithResource
|
||||
import io.github.wulkanowy.utils.isHolidays
|
||||
import io.github.wulkanowy.utils.monday
|
||||
import io.github.wulkanowy.utils.previousOrSameSchoolDay
|
||||
import io.github.wulkanowy.utils.sunday
|
||||
import io.github.wulkanowy.utils.toFormattedString
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import timber.log.Timber
|
||||
import java.time.LocalDate
|
||||
import javax.inject.Inject
|
||||
|
||||
class LuckyNumberHistoryPresenter @Inject constructor(
|
||||
errorHandler: ErrorHandler,
|
||||
studentRepository: StudentRepository,
|
||||
private val luckyNumberRepository: LuckyNumberRepository,
|
||||
private val analytics: AnalyticsHelper
|
||||
) : BasePresenter<LuckyNumberHistoryView>(errorHandler, studentRepository) {
|
||||
|
||||
private lateinit var lastError: Throwable
|
||||
|
||||
var currentDate: LocalDate = LocalDate.now().previousOrSameSchoolDay
|
||||
|
||||
override fun onAttachView(view: LuckyNumberHistoryView) {
|
||||
super.onAttachView(view)
|
||||
view.run {
|
||||
initView()
|
||||
reloadNavigation()
|
||||
showContent(false)
|
||||
}
|
||||
Timber.i("Lucky number history view was initialized")
|
||||
errorHandler.showErrorMessage = ::showErrorViewOnError
|
||||
loadData()
|
||||
}
|
||||
|
||||
private fun loadData() {
|
||||
flowWithResource {
|
||||
val student = studentRepository.getCurrentStudent()
|
||||
luckyNumberRepository.getLuckyNumberHistory(student, currentDate.monday, currentDate.sunday)
|
||||
}.onEach {
|
||||
when (it.status) {
|
||||
Status.LOADING -> Timber.i("Loading lucky number history started")
|
||||
Status.SUCCESS -> {
|
||||
if (!it.data?.first().isNullOrEmpty()) {
|
||||
Timber.i("Loading lucky number result: Success")
|
||||
view?.apply {
|
||||
updateData(it.data!!.first())
|
||||
showContent(true)
|
||||
showEmpty(false)
|
||||
showErrorView(false)
|
||||
showProgress(false)
|
||||
}
|
||||
analytics.logEvent(
|
||||
"load_items",
|
||||
"type" to "lucky_number_history",
|
||||
"numbers" to it.data
|
||||
)
|
||||
} else {
|
||||
Timber.i("Loading lucky number history result: No lucky numbers found")
|
||||
view?.run {
|
||||
showContent(false)
|
||||
showEmpty(true)
|
||||
showErrorView(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
Status.ERROR -> {
|
||||
Timber.i("Loading lucky number history result: An exception occurred")
|
||||
errorHandler.dispatch(it.error!!)
|
||||
}
|
||||
}
|
||||
}.afterLoading {
|
||||
view?.run {
|
||||
showProgress(false)
|
||||
}
|
||||
}.launch()
|
||||
}
|
||||
|
||||
private fun showErrorViewOnError(message: String, error: Throwable) {
|
||||
view?.run {
|
||||
if (isViewEmpty) {
|
||||
lastError = error
|
||||
setErrorDetails(message)
|
||||
showErrorView(true)
|
||||
showEmpty(false)
|
||||
} else showError(message, error)
|
||||
}
|
||||
}
|
||||
|
||||
private fun reloadView(date: LocalDate) {
|
||||
currentDate = date
|
||||
Timber.i("Reload lucky number history view with the date ${currentDate.toFormattedString()}")
|
||||
view?.apply {
|
||||
showProgress(true)
|
||||
showContent(false)
|
||||
showEmpty(false)
|
||||
showErrorView(false)
|
||||
clearData()
|
||||
reloadNavigation()
|
||||
}
|
||||
}
|
||||
|
||||
fun onRetry() {
|
||||
view?.run {
|
||||
showErrorView(false)
|
||||
showProgress(true)
|
||||
}
|
||||
loadData()
|
||||
}
|
||||
|
||||
fun onDetailsClick() {
|
||||
view?.showErrorDetailsDialog(lastError)
|
||||
}
|
||||
|
||||
private fun reloadNavigation() {
|
||||
view?.apply {
|
||||
showPreButton(!currentDate.minusDays(7).isHolidays)
|
||||
showNextButton(!currentDate.plusDays(7).isHolidays)
|
||||
updateNavigationWeek("${currentDate.monday.toFormattedString("dd.MM")} - " +
|
||||
currentDate.sunday.toFormattedString("dd.MM"))
|
||||
}
|
||||
}
|
||||
|
||||
fun onDateSet(year: Int, month: Int, day: Int) {
|
||||
reloadView(LocalDate.of(year, month, day))
|
||||
loadData()
|
||||
}
|
||||
|
||||
fun onPickDate() {
|
||||
view?.showDatePickerDialog(currentDate)
|
||||
}
|
||||
|
||||
fun onPreviousWeek() {
|
||||
reloadView(currentDate.minusDays(7))
|
||||
loadData()
|
||||
}
|
||||
|
||||
fun onNextWeek() {
|
||||
reloadView(currentDate.plusDays(7))
|
||||
loadData()
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package io.github.wulkanowy.ui.modules.luckynumber.history
|
||||
|
||||
import io.github.wulkanowy.data.db.entities.LuckyNumber
|
||||
import io.github.wulkanowy.ui.base.BaseView
|
||||
import java.time.LocalDate
|
||||
|
||||
interface LuckyNumberHistoryView : BaseView {
|
||||
|
||||
val isViewEmpty: Boolean
|
||||
|
||||
fun initView()
|
||||
|
||||
fun updateData(data: List<LuckyNumber>)
|
||||
|
||||
fun clearData()
|
||||
|
||||
fun showEmpty(show: Boolean)
|
||||
|
||||
fun showErrorView(show: Boolean)
|
||||
|
||||
fun setErrorDetails(message: String)
|
||||
|
||||
fun updateNavigationWeek(date: String)
|
||||
|
||||
fun showProgress(show: Boolean)
|
||||
|
||||
fun showPreButton(show: Boolean)
|
||||
|
||||
fun showNextButton(show: Boolean)
|
||||
|
||||
fun showDatePickerDialog(currentDate: LocalDate)
|
||||
|
||||
fun showContent(show: Boolean)
|
||||
|
||||
fun onDestroyView()
|
||||
}
|
@ -11,15 +11,21 @@ import android.graphics.drawable.Icon
|
||||
import android.os.Build
|
||||
import android.os.Build.VERSION.SDK_INT
|
||||
import android.os.Build.VERSION_CODES.LOLLIPOP
|
||||
import android.os.Build.VERSION_CODES.P
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.core.view.updateMargins
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import com.aurelhubert.ahbottomnavigation.AHBottomNavigation.TitleState.ALWAYS_SHOW
|
||||
import com.aurelhubert.ahbottomnavigation.AHBottomNavigationItem
|
||||
import com.google.android.material.elevation.ElevationOverlayProvider
|
||||
@ -27,6 +33,8 @@ import com.ncapdevi.fragnav.FragNavController
|
||||
import com.ncapdevi.fragnav.FragNavController.Companion.HIDE
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||
import io.github.wulkanowy.databinding.ActivityMainBinding
|
||||
import io.github.wulkanowy.ui.base.BaseActivity
|
||||
import io.github.wulkanowy.ui.modules.account.accountquick.AccountQuickDialog
|
||||
@ -42,15 +50,18 @@ import io.github.wulkanowy.ui.modules.timetable.TimetableFragment
|
||||
import io.github.wulkanowy.utils.AnalyticsHelper
|
||||
import io.github.wulkanowy.utils.AppInfo
|
||||
import io.github.wulkanowy.utils.UpdateHelper
|
||||
import io.github.wulkanowy.utils.createNameInitialsDrawable
|
||||
import io.github.wulkanowy.utils.dpToPx
|
||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||
import io.github.wulkanowy.utils.nickOrName
|
||||
import io.github.wulkanowy.utils.safelyPopFragments
|
||||
import io.github.wulkanowy.utils.setOnViewChangeListener
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainView {
|
||||
class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainView,
|
||||
PreferenceFragmentCompat.OnPreferenceStartFragmentCallback {
|
||||
|
||||
@Inject
|
||||
override lateinit var presenter: MainPresenter
|
||||
@ -64,6 +75,8 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
|
||||
@Inject
|
||||
lateinit var appInfo: AppInfo
|
||||
|
||||
private var accountMenu: MenuItem? = null
|
||||
|
||||
private val overlayProvider by lazy { ElevationOverlayProvider(this) }
|
||||
|
||||
private val navController =
|
||||
@ -121,6 +134,11 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
|
||||
initialize(startMenuIndex, savedInstanceState)
|
||||
pushFragment(moreMenuFragments[startMenuMoreIndex])
|
||||
}
|
||||
|
||||
if (appInfo.systemVersion >= Build.VERSION_CODES.N_MR1) {
|
||||
initShortcuts()
|
||||
}
|
||||
|
||||
updateHelper.checkAndInstallUpdates(this)
|
||||
}
|
||||
|
||||
@ -129,11 +147,11 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
|
||||
updateHelper.onResume(this)
|
||||
}
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
//https://developer.android.com/guide/playcore/in-app-updates#status_callback
|
||||
@Suppress("DEPRECATION")
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
updateHelper.onActivityResult(requestCode, resultCode)
|
||||
if (appInfo.systemVersion >= Build.VERSION_CODES.N_MR1) initShortcuts()
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.N_MR1)
|
||||
@ -160,11 +178,6 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
|
||||
getString(R.string.timetable_title),
|
||||
R.drawable.ic_shortcut_timetable,
|
||||
MainView.Section.TIMETABLE
|
||||
),
|
||||
Triple(
|
||||
getString(R.string.message_title),
|
||||
R.drawable.ic_shortcut_message,
|
||||
MainView.Section.MESSAGE
|
||||
)
|
||||
).forEach { (title, icon, enum) ->
|
||||
shortcutsList.add(
|
||||
@ -191,9 +204,13 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
||||
menuInflater.inflate(R.menu.action_menu_main, menu)
|
||||
accountMenu = menu?.findItem(R.id.mainMenuAccount)
|
||||
|
||||
presenter.onActionMenuCreated()
|
||||
return true
|
||||
}
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
override fun initView() {
|
||||
with(binding.mainToolbar) {
|
||||
if (SDK_INT >= LOLLIPOP) stateListAnimator = null
|
||||
@ -233,11 +250,25 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
|
||||
|
||||
with(navController) {
|
||||
setOnViewChangeListener { section, name ->
|
||||
binding.mainBottomNav.visibility =
|
||||
if (section == MainView.Section.ACCOUNT || section == MainView.Section.STUDENT_INFO) {
|
||||
View.GONE
|
||||
binding.mainBottomNav.isVisible = false
|
||||
binding.mainFragmentContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||
updateMargins(bottom = 0)
|
||||
}
|
||||
|
||||
if (appInfo.systemVersion >= P) {
|
||||
window.navigationBarColor = getThemeAttrColor(R.attr.colorSurface)
|
||||
}
|
||||
} else {
|
||||
View.VISIBLE
|
||||
binding.mainBottomNav.isVisible = true
|
||||
binding.mainFragmentContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||
updateMargins(bottom = dpToPx(56f).toInt())
|
||||
}
|
||||
|
||||
if (appInfo.systemVersion >= P) {
|
||||
window.navigationBarColor =
|
||||
getThemeAttrColor(android.R.attr.navigationBarColor)
|
||||
}
|
||||
}
|
||||
|
||||
analytics.setCurrentScreen(this@MainActivity, name)
|
||||
@ -254,6 +285,16 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPreferenceStartFragment(
|
||||
caller: PreferenceFragmentCompat,
|
||||
pref: Preference
|
||||
): Boolean {
|
||||
val fragment =
|
||||
supportFragmentManager.fragmentFactory.instantiate(classLoader, pref.fragment)
|
||||
navController.pushFragment(fragment)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return if (item.itemId == R.id.mainMenuAccount) presenter.onAccountManagerSelected()
|
||||
else false
|
||||
@ -280,8 +321,8 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(show)
|
||||
}
|
||||
|
||||
override fun showAccountPicker() {
|
||||
navController.showDialogFragment(AccountQuickDialog.newInstance())
|
||||
override fun showAccountPicker(studentWithSemesters: List<StudentWithSemesters>) {
|
||||
navController.showDialogFragment(AccountQuickDialog.newInstance(studentWithSemesters))
|
||||
}
|
||||
|
||||
override fun showActionBarElevation(show: Boolean) {
|
||||
@ -315,6 +356,13 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
|
||||
presenter.onBackPressed { super.onBackPressed() }
|
||||
}
|
||||
|
||||
override fun showStudentAvatar(student: Student) {
|
||||
accountMenu?.run {
|
||||
icon = createNameInitialsDrawable(student.nickOrName, student.avatarColor, 0.44f)
|
||||
title = getString(R.string.main_account_picker)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
navController.onSaveInstanceState(outState)
|
||||
|
@ -1,5 +1,7 @@
|
||||
package io.github.wulkanowy.ui.modules.main
|
||||
|
||||
import io.github.wulkanowy.data.Status
|
||||
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||
import io.github.wulkanowy.services.sync.SyncManager
|
||||
@ -9,6 +11,8 @@ import io.github.wulkanowy.ui.modules.main.MainView.Section.GRADE
|
||||
import io.github.wulkanowy.ui.modules.main.MainView.Section.MESSAGE
|
||||
import io.github.wulkanowy.ui.modules.main.MainView.Section.SCHOOL
|
||||
import io.github.wulkanowy.utils.AnalyticsHelper
|
||||
import io.github.wulkanowy.utils.flowWithResource
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@ -17,9 +21,11 @@ class MainPresenter @Inject constructor(
|
||||
studentRepository: StudentRepository,
|
||||
private val prefRepository: PreferencesRepository,
|
||||
private val syncManager: SyncManager,
|
||||
private val analytics: AnalyticsHelper
|
||||
private val analytics: AnalyticsHelper,
|
||||
) : BasePresenter<MainView>(errorHandler, studentRepository) {
|
||||
|
||||
var studentsWitSemesters: List<StudentWithSemesters>? = null
|
||||
|
||||
fun onAttachView(view: MainView, initMenu: MainView.Section?) {
|
||||
super.onAttachView(view)
|
||||
view.apply {
|
||||
@ -35,6 +41,28 @@ class MainPresenter @Inject constructor(
|
||||
analytics.logEvent("app_open", "destination" to initMenu?.name)
|
||||
}
|
||||
|
||||
fun onActionMenuCreated() {
|
||||
if (!studentsWitSemesters.isNullOrEmpty()) {
|
||||
showCurrentStudentAvatar()
|
||||
return
|
||||
}
|
||||
|
||||
flowWithResource { studentRepository.getSavedStudents(false) }
|
||||
.onEach { resource ->
|
||||
when (resource.status) {
|
||||
Status.LOADING -> Timber.i("Loading student avatar data started")
|
||||
Status.SUCCESS -> {
|
||||
studentsWitSemesters = resource.data
|
||||
showCurrentStudentAvatar()
|
||||
}
|
||||
Status.ERROR -> {
|
||||
Timber.i("Loading student avatar result: An exception occurred")
|
||||
errorHandler.dispatch(resource.error!!)
|
||||
}
|
||||
}
|
||||
}.launch("avatar")
|
||||
}
|
||||
|
||||
fun onViewChange(section: MainView.Section?) {
|
||||
view?.apply {
|
||||
showActionBarElevation(section != GRADE && section != MESSAGE && section != SCHOOL)
|
||||
@ -48,8 +76,10 @@ class MainPresenter @Inject constructor(
|
||||
}
|
||||
|
||||
fun onAccountManagerSelected(): Boolean {
|
||||
if (studentsWitSemesters.isNullOrEmpty()) return true
|
||||
|
||||
Timber.i("Select account manager")
|
||||
view?.showAccountPicker()
|
||||
view?.showAccountPicker(studentsWitSemesters!!)
|
||||
return true
|
||||
}
|
||||
|
||||
@ -81,6 +111,13 @@ class MainPresenter @Inject constructor(
|
||||
} == true
|
||||
}
|
||||
|
||||
private fun showCurrentStudentAvatar() {
|
||||
val currentStudent =
|
||||
studentsWitSemesters!!.single { it.student.isCurrent }.student
|
||||
|
||||
view?.showStudentAvatar(currentStudent)
|
||||
}
|
||||
|
||||
private fun getProperViewIndexes(initMenu: MainView.Section?): Pair<Int, Int> {
|
||||
return when (initMenu?.id) {
|
||||
in 0..3 -> initMenu!!.id to -1
|
||||
|
@ -1,5 +1,7 @@
|
||||
package io.github.wulkanowy.ui.modules.main
|
||||
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||
import io.github.wulkanowy.ui.base.BaseView
|
||||
|
||||
interface MainView : BaseView {
|
||||
@ -22,7 +24,7 @@ interface MainView : BaseView {
|
||||
|
||||
fun showHomeArrow(show: Boolean)
|
||||
|
||||
fun showAccountPicker()
|
||||
fun showAccountPicker(studentWithSemesters: List<StudentWithSemesters>)
|
||||
|
||||
fun showActionBarElevation(show: Boolean)
|
||||
|
||||
@ -36,6 +38,8 @@ interface MainView : BaseView {
|
||||
|
||||
fun popView(depth: Int = 1)
|
||||
|
||||
fun showStudentAvatar(student: Student)
|
||||
|
||||
interface MainChildView {
|
||||
|
||||
fun onFragmentReselected()
|
||||
|
@ -20,13 +20,15 @@ import io.github.wulkanowy.ui.base.BaseDialogFragment
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class MobileDeviceTokenDialog : BaseDialogFragment<DialogMobileDeviceBinding>(), MobileDeviceTokenVIew {
|
||||
class MobileDeviceTokenDialog : BaseDialogFragment<DialogMobileDeviceBinding>(),
|
||||
MobileDeviceTokenVIew {
|
||||
|
||||
@Inject
|
||||
lateinit var presenter: MobileDeviceTokenPresenter
|
||||
|
||||
companion object {
|
||||
fun newInstance(): MobileDeviceTokenDialog = MobileDeviceTokenDialog()
|
||||
|
||||
fun newInstance() = MobileDeviceTokenDialog()
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
@ -34,12 +36,14 @@ class MobileDeviceTokenDialog : BaseDialogFragment<DialogMobileDeviceBinding>(),
|
||||
setStyle(STYLE_NO_TITLE, 0)
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
return DialogMobileDeviceBinding.inflate(inflater).apply { binding = this }.root
|
||||
}
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
) = DialogMobileDeviceBinding.inflate(inflater).apply { binding = this }.root
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
presenter.onAttachView(this)
|
||||
}
|
||||
|
||||
|
@ -63,9 +63,6 @@ class MoreFragment : BaseFragment<FragmentMoreBinding>(R.layout.fragment_more),
|
||||
override val settingsRes: Pair<String, Drawable?>?
|
||||
get() = context?.run { getString(R.string.settings_title) to getCompatDrawable(R.drawable.ic_more_settings) }
|
||||
|
||||
override val aboutRes: Pair<String, Drawable?>?
|
||||
get() = context?.run { getString(R.string.about_title) to getCompatDrawable(R.drawable.ic_all_about) }
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
binding = FragmentMoreBinding.bind(view)
|
||||
@ -124,10 +121,6 @@ class MoreFragment : BaseFragment<FragmentMoreBinding>(R.layout.fragment_more),
|
||||
(activity as? MainActivity)?.pushView(SettingsFragment.newInstance())
|
||||
}
|
||||
|
||||
override fun openAboutView() {
|
||||
(activity as? MainActivity)?.pushView(AboutFragment.newInstance())
|
||||
}
|
||||
|
||||
override fun popView(depth: Int) {
|
||||
(activity as? MainActivity)?.popView(depth)
|
||||
}
|
||||
|
@ -30,7 +30,6 @@ class MorePresenter @Inject constructor(
|
||||
conferencesRes?.first -> openConferencesView()
|
||||
schoolAndTeachersRes?.first -> openSchoolAndTeachersView()
|
||||
settingsRes?.first -> openSettingsView()
|
||||
aboutRes?.first -> openAboutView()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -51,8 +50,7 @@ class MorePresenter @Inject constructor(
|
||||
mobileDevicesRes,
|
||||
conferencesRes,
|
||||
schoolAndTeachersRes,
|
||||
settingsRes,
|
||||
aboutRes
|
||||
settingsRes
|
||||
))
|
||||
}
|
||||
}
|
||||
|
@ -21,16 +21,12 @@ interface MoreView : BaseView {
|
||||
|
||||
val settingsRes: Pair<String, Drawable?>?
|
||||
|
||||
val aboutRes: Pair<String, Drawable?>?
|
||||
|
||||
fun initView()
|
||||
|
||||
fun updateData(data: List<Pair<String, Drawable?>>)
|
||||
|
||||
fun openSettingsView()
|
||||
|
||||
fun openAboutView()
|
||||
|
||||
fun popView(depth: Int)
|
||||
|
||||
fun openMessagesView()
|
||||
|
@ -22,14 +22,13 @@ class NoteDialog : DialogFragment() {
|
||||
private lateinit var note: Note
|
||||
|
||||
companion object {
|
||||
|
||||
private const val ARGUMENT_KEY = "Item"
|
||||
|
||||
fun newInstance(exam: Note): NoteDialog {
|
||||
return NoteDialog().apply {
|
||||
fun newInstance(exam: Note) = NoteDialog().apply {
|
||||
arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, exam) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@ -39,13 +38,15 @@ class NoteDialog : DialogFragment() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
return DialogNoteBinding.inflate(inflater).apply { binding = this }.root
|
||||
}
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
) = DialogNoteBinding.inflate(inflater).apply { binding = this }.root
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
with(binding) {
|
||||
noteDialogDate.text = note.date.toFormattedString()
|
||||
@ -57,11 +58,19 @@ class NoteDialog : DialogFragment() {
|
||||
if (note.isPointsShow) {
|
||||
with(binding.noteDialogPoints) {
|
||||
text = "${if (note.points > 0) "+" else ""}${note.points}"
|
||||
setTextColor(when (NoteCategory.getByValue(note.categoryType)) {
|
||||
NoteCategory.POSITIVE -> ContextCompat.getColor(requireContext(), R.color.note_positive)
|
||||
NoteCategory.NEGATIVE -> ContextCompat.getColor(requireContext(), R.color.note_negative)
|
||||
setTextColor(
|
||||
when (NoteCategory.getByValue(note.categoryType)) {
|
||||
NoteCategory.POSITIVE -> ContextCompat.getColor(
|
||||
requireContext(),
|
||||
R.color.note_positive
|
||||
)
|
||||
NoteCategory.NEGATIVE -> ContextCompat.getColor(
|
||||
requireContext(),
|
||||
R.color.note_negative
|
||||
)
|
||||
else -> requireContext().getThemeAttrColor(android.R.attr.textColorPrimary)
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,148 +1,22 @@
|
||||
package io.github.wulkanowy.ui.modules.settings
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import com.thelittlefireman.appkillermanager.AppKillerManager
|
||||
import com.thelittlefireman.appkillermanager.exceptions.NoActionFoundException
|
||||
import com.yariksoffice.lingver.Lingver
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.ui.base.BaseActivity
|
||||
import io.github.wulkanowy.ui.base.ErrorDialog
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import io.github.wulkanowy.utils.AppInfo
|
||||
import io.github.wulkanowy.utils.openInternetBrowser
|
||||
import javax.inject.Inject
|
||||
import timber.log.Timber
|
||||
|
||||
@AndroidEntryPoint
|
||||
class SettingsFragment : PreferenceFragmentCompat(),
|
||||
SharedPreferences.OnSharedPreferenceChangeListener,
|
||||
MainView.TitledView, SettingsView {
|
||||
|
||||
@Inject
|
||||
lateinit var presenter: SettingsPresenter
|
||||
|
||||
@Inject
|
||||
lateinit var appInfo: AppInfo
|
||||
|
||||
@Inject
|
||||
lateinit var lingver: Lingver
|
||||
class SettingsFragment : PreferenceFragmentCompat(), MainView.TitledView {
|
||||
|
||||
companion object {
|
||||
|
||||
fun newInstance() = SettingsFragment()
|
||||
}
|
||||
|
||||
override val titleStringId get() = R.string.settings_title
|
||||
|
||||
override val syncSuccessString get() = getString(R.string.pref_services_message_sync_success)
|
||||
|
||||
override val syncFailedString get() = getString(R.string.pref_services_message_sync_failed)
|
||||
|
||||
override fun initView() {
|
||||
findPreference<Preference>(getString(R.string.pref_key_services_force_sync))?.run {
|
||||
onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
||||
presenter.onSyncNowClicked()
|
||||
true
|
||||
}
|
||||
}
|
||||
findPreference<Preference>(getString(R.string.pref_key_notifications_fix_issues))?.run {
|
||||
isVisible = AppKillerManager.isDeviceSupported() && AppKillerManager.isAnyActionAvailable(requireContext())
|
||||
setOnPreferenceClickListener {
|
||||
presenter.onFixSyncIssuesClicked()
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
presenter.onAttachView(this)
|
||||
}
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.scheme_preferences, rootKey)
|
||||
findPreference<Preference>(getString(R.string.pref_key_notification_debug))?.isVisible = appInfo.isDebug
|
||||
}
|
||||
|
||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
|
||||
presenter.onSharedPreferenceChanged(key)
|
||||
}
|
||||
|
||||
override fun recreateView() {
|
||||
activity?.recreate()
|
||||
}
|
||||
|
||||
override fun updateLanguage(langCode: String) {
|
||||
lingver.setLocale(requireContext(), langCode)
|
||||
}
|
||||
|
||||
override fun updateLanguageToFollowSystem() {
|
||||
lingver.setFollowSystemLocale(requireContext())
|
||||
}
|
||||
|
||||
override fun setServicesSuspended(serviceEnablesKey: String, isHolidays: Boolean) {
|
||||
findPreference<Preference>(serviceEnablesKey)?.run {
|
||||
summary = if (isHolidays) getString(R.string.pref_services_suspended) else ""
|
||||
isEnabled = !isHolidays
|
||||
}
|
||||
}
|
||||
|
||||
override fun setSyncInProgress(inProgress: Boolean) {
|
||||
if (activity == null || !isAdded) return
|
||||
|
||||
findPreference<Preference>(getString(R.string.pref_key_services_force_sync))?.run {
|
||||
isEnabled = !inProgress
|
||||
summary = if (inProgress) getString(R.string.pref_services_sync_in_progress) else ""
|
||||
}
|
||||
}
|
||||
|
||||
override fun showError(text: String, error: Throwable) {
|
||||
(activity as? BaseActivity<*, *>)?.showError(text, error)
|
||||
}
|
||||
|
||||
override fun showMessage(text: String) {
|
||||
(activity as? BaseActivity<*, *>)?.showMessage(text)
|
||||
}
|
||||
|
||||
override fun showExpiredDialog() {
|
||||
(activity as? BaseActivity<*, *>)?.showExpiredDialog()
|
||||
}
|
||||
|
||||
override fun openClearLoginView() {
|
||||
(activity as? BaseActivity<*, *>)?.openClearLoginView()
|
||||
}
|
||||
|
||||
override fun showErrorDetailsDialog(error: Throwable) {
|
||||
ErrorDialog.newInstance(error).show(childFragmentManager, error.toString())
|
||||
}
|
||||
|
||||
override fun showFixSyncDialog() {
|
||||
AlertDialog.Builder(requireContext())
|
||||
.setTitle(R.string.pref_notify_fix_sync_issues)
|
||||
.setMessage(R.string.pref_notify_fix_sync_issues_message)
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||
.setPositiveButton(R.string.pref_notify_fix_sync_issues_settings_button) { _, _ ->
|
||||
try {
|
||||
AppKillerManager.doActionPowerSaving(requireContext())
|
||||
AppKillerManager.doActionAutoStart(requireContext())
|
||||
AppKillerManager.doActionNotification(requireContext())
|
||||
} catch (e: NoActionFoundException) {
|
||||
requireContext().openInternetBrowser("https://dontkillmyapp.com/${AppKillerManager.getDevice()?.manufacturer}", ::showMessage)
|
||||
}
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
preferenceScreen.sharedPreferences.registerOnSharedPreferenceChangeListener(this)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
preferenceScreen.sharedPreferences.unregisterOnSharedPreferenceChangeListener(this)
|
||||
Timber.i("Settings view was initialized")
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,78 @@
|
||||
package io.github.wulkanowy.ui.modules.settings.advanced
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import com.yariksoffice.lingver.Lingver
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.ui.base.BaseActivity
|
||||
import io.github.wulkanowy.ui.base.ErrorDialog
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import io.github.wulkanowy.utils.AppInfo
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class AdvancedFragment : PreferenceFragmentCompat(),
|
||||
SharedPreferences.OnSharedPreferenceChangeListener,
|
||||
MainView.TitledView, AdvancedView {
|
||||
|
||||
@Inject
|
||||
lateinit var presenter: AdvancedPresenter
|
||||
|
||||
@Inject
|
||||
lateinit var appInfo: AppInfo
|
||||
|
||||
@Inject
|
||||
lateinit var lingver: Lingver
|
||||
|
||||
companion object {
|
||||
fun newInstance() = AdvancedFragment()
|
||||
}
|
||||
|
||||
override val titleStringId get() = R.string.pref_settings_advanced_title
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
presenter.onAttachView(this)
|
||||
}
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.scheme_preferences_advanced, rootKey)
|
||||
}
|
||||
|
||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
|
||||
presenter.onSharedPreferenceChanged(key)
|
||||
}
|
||||
|
||||
override fun showError(text: String, error: Throwable) {
|
||||
(activity as? BaseActivity<*, *>)?.showError(text, error)
|
||||
}
|
||||
|
||||
override fun showMessage(text: String) {
|
||||
(activity as? BaseActivity<*, *>)?.showMessage(text)
|
||||
}
|
||||
|
||||
override fun showExpiredDialog() {
|
||||
(activity as? BaseActivity<*, *>)?.showExpiredDialog()
|
||||
}
|
||||
|
||||
override fun openClearLoginView() {
|
||||
(activity as? BaseActivity<*, *>)?.openClearLoginView()
|
||||
}
|
||||
|
||||
override fun showErrorDetailsDialog(error: Throwable) {
|
||||
ErrorDialog.newInstance(error).show(childFragmentManager, error.toString())
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
preferenceScreen.sharedPreferences.registerOnSharedPreferenceChangeListener(this)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
preferenceScreen.sharedPreferences.unregisterOnSharedPreferenceChangeListener(this)
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package io.github.wulkanowy.ui.modules.settings.advanced
|
||||
|
||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||
import io.github.wulkanowy.ui.base.BasePresenter
|
||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||
import io.github.wulkanowy.utils.AnalyticsHelper
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
class AdvancedPresenter @Inject constructor(
|
||||
errorHandler: ErrorHandler,
|
||||
studentRepository: StudentRepository,
|
||||
private val analytics: AnalyticsHelper,
|
||||
) : BasePresenter<AdvancedView>(errorHandler, studentRepository) {
|
||||
|
||||
override fun onAttachView(view: AdvancedView) {
|
||||
super.onAttachView(view)
|
||||
Timber.i("Settings advanced view was initialized")
|
||||
}
|
||||
|
||||
fun onSharedPreferenceChanged(key: String) {
|
||||
Timber.i("Change settings $key")
|
||||
analytics.logEvent("setting_changed", "name" to key)
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package io.github.wulkanowy.ui.modules.settings.advanced
|
||||
|
||||
import io.github.wulkanowy.ui.base.BaseView
|
||||
|
||||
interface AdvancedView : BaseView {}
|
@ -0,0 +1,90 @@
|
||||
package io.github.wulkanowy.ui.modules.settings.appearance
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import com.yariksoffice.lingver.Lingver
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.ui.base.BaseActivity
|
||||
import io.github.wulkanowy.ui.base.ErrorDialog
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import io.github.wulkanowy.utils.AppInfo
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class AppearanceFragment : PreferenceFragmentCompat(),
|
||||
SharedPreferences.OnSharedPreferenceChangeListener,
|
||||
MainView.TitledView, AppearanceView {
|
||||
|
||||
@Inject
|
||||
lateinit var presenter: AppearancePresenter
|
||||
|
||||
@Inject
|
||||
lateinit var appInfo: AppInfo
|
||||
|
||||
@Inject
|
||||
lateinit var lingver: Lingver
|
||||
|
||||
companion object {
|
||||
fun newInstance() = AppearanceFragment()
|
||||
}
|
||||
|
||||
override val titleStringId get() = R.string.pref_settings_appearance_title
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
presenter.onAttachView(this)
|
||||
}
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.scheme_preferences_appearance, rootKey)
|
||||
}
|
||||
|
||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
|
||||
presenter.onSharedPreferenceChanged(key)
|
||||
}
|
||||
|
||||
override fun recreateView() {
|
||||
activity?.recreate()
|
||||
}
|
||||
|
||||
override fun updateLanguage(langCode: String) {
|
||||
lingver.setLocale(requireContext(), langCode)
|
||||
}
|
||||
|
||||
override fun updateLanguageToFollowSystem() {
|
||||
lingver.setFollowSystemLocale(requireContext())
|
||||
}
|
||||
|
||||
override fun showError(text: String, error: Throwable) {
|
||||
(activity as? BaseActivity<*, *>)?.showError(text, error)
|
||||
}
|
||||
|
||||
override fun showMessage(text: String) {
|
||||
(activity as? BaseActivity<*, *>)?.showMessage(text)
|
||||
}
|
||||
|
||||
override fun showExpiredDialog() {
|
||||
(activity as? BaseActivity<*, *>)?.showExpiredDialog()
|
||||
}
|
||||
|
||||
override fun openClearLoginView() {
|
||||
(activity as? BaseActivity<*, *>)?.openClearLoginView()
|
||||
}
|
||||
|
||||
override fun showErrorDetailsDialog(error: Throwable) {
|
||||
ErrorDialog.newInstance(error).show(childFragmentManager, error.toString())
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
preferenceScreen.sharedPreferences.registerOnSharedPreferenceChangeListener(this)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
preferenceScreen.sharedPreferences.unregisterOnSharedPreferenceChangeListener(this)
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
package io.github.wulkanowy.ui.modules.settings.appearance
|
||||
|
||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||
import io.github.wulkanowy.ui.base.BasePresenter
|
||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||
import io.github.wulkanowy.utils.AnalyticsHelper
|
||||
import io.github.wulkanowy.utils.AppInfo
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
class AppearancePresenter @Inject constructor(
|
||||
errorHandler: ErrorHandler,
|
||||
studentRepository: StudentRepository,
|
||||
private val preferencesRepository: PreferencesRepository,
|
||||
private val analytics: AnalyticsHelper,
|
||||
private val appInfo: AppInfo
|
||||
) : BasePresenter<AppearanceView>(errorHandler, studentRepository) {
|
||||
|
||||
override fun onAttachView(view: AppearanceView) {
|
||||
super.onAttachView(view)
|
||||
Timber.i("Settings appearance view was initialized")
|
||||
}
|
||||
|
||||
fun onSharedPreferenceChanged(key: String) {
|
||||
Timber.i("Change settings $key")
|
||||
|
||||
preferencesRepository.apply {
|
||||
when (key) {
|
||||
appThemeKey -> view?.recreateView()
|
||||
appLanguageKey -> view?.run {
|
||||
if (appLanguage == "system") {
|
||||
updateLanguageToFollowSystem()
|
||||
analytics.logEvent("language", "setting_changed" to appInfo.systemLanguage)
|
||||
} else {
|
||||
updateLanguage(appLanguage)
|
||||
analytics.logEvent("language", "setting_changed" to appLanguage)
|
||||
}
|
||||
recreateView()
|
||||
}
|
||||
}
|
||||
}
|
||||
analytics.logEvent("setting_changed", "name" to key)
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package io.github.wulkanowy.ui.modules.settings.appearance
|
||||
|
||||
import io.github.wulkanowy.ui.base.BaseView
|
||||
|
||||
interface AppearanceView : BaseView {
|
||||
|
||||
fun recreateView()
|
||||
|
||||
fun updateLanguage(langCode: String)
|
||||
|
||||
fun updateLanguageToFollowSystem()
|
||||
}
|
@ -0,0 +1,130 @@
|
||||
package io.github.wulkanowy.ui.modules.settings.notifications
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.thelittlefireman.appkillermanager.AppKillerManager
|
||||
import com.thelittlefireman.appkillermanager.exceptions.NoActionFoundException
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.ui.base.BaseActivity
|
||||
import io.github.wulkanowy.ui.base.ErrorDialog
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import io.github.wulkanowy.utils.openInternetBrowser
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class NotificationsFragment : PreferenceFragmentCompat(),
|
||||
SharedPreferences.OnSharedPreferenceChangeListener,
|
||||
MainView.TitledView, NotificationsView {
|
||||
|
||||
@Inject
|
||||
lateinit var presenter: NotificationsPresenter
|
||||
|
||||
companion object {
|
||||
fun newInstance() = NotificationsFragment()
|
||||
}
|
||||
|
||||
override val titleStringId get() = R.string.pref_settings_notifications_title
|
||||
|
||||
override fun initView(showDebugNotificationSwitch: Boolean) {
|
||||
findPreference<Preference>(getString(R.string.pref_key_notification_debug))?.isVisible =
|
||||
showDebugNotificationSwitch
|
||||
|
||||
findPreference<Preference>(getString(R.string.pref_key_notifications_fix_issues))?.run {
|
||||
isVisible = AppKillerManager.isDeviceSupported()
|
||||
&& AppKillerManager.isAnyActionAvailable(requireContext())
|
||||
|
||||
setOnPreferenceClickListener {
|
||||
presenter.onFixSyncIssuesClicked()
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateRecyclerView(
|
||||
inflater: LayoutInflater?,
|
||||
parent: ViewGroup?,
|
||||
state: Bundle?
|
||||
): RecyclerView? = super.onCreateRecyclerView(inflater, parent, state)
|
||||
.also {
|
||||
it.itemAnimator = null
|
||||
it.layoutAnimation = null
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
presenter.onAttachView(this)
|
||||
}
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.scheme_preferences_notifications, rootKey)
|
||||
}
|
||||
|
||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
|
||||
presenter.onSharedPreferenceChanged(key)
|
||||
}
|
||||
|
||||
override fun enableNotification(notificationKey: String, enable: Boolean) {
|
||||
findPreference<Preference>(notificationKey)?.run {
|
||||
isEnabled = enable
|
||||
summary = if (enable) null else getString(R.string.pref_notify_disabled_summary)
|
||||
}
|
||||
}
|
||||
|
||||
override fun showError(text: String, error: Throwable) {
|
||||
(activity as? BaseActivity<*, *>)?.showError(text, error)
|
||||
}
|
||||
|
||||
override fun showMessage(text: String) {
|
||||
(activity as? BaseActivity<*, *>)?.showMessage(text)
|
||||
}
|
||||
|
||||
override fun showExpiredDialog() {
|
||||
(activity as? BaseActivity<*, *>)?.showExpiredDialog()
|
||||
}
|
||||
|
||||
override fun openClearLoginView() {
|
||||
(activity as? BaseActivity<*, *>)?.openClearLoginView()
|
||||
}
|
||||
|
||||
override fun showErrorDetailsDialog(error: Throwable) {
|
||||
ErrorDialog.newInstance(error).show(childFragmentManager, error.toString())
|
||||
}
|
||||
|
||||
override fun showFixSyncDialog() {
|
||||
AlertDialog.Builder(requireContext())
|
||||
.setTitle(R.string.pref_notify_fix_sync_issues)
|
||||
.setMessage(R.string.pref_notify_fix_sync_issues_message)
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||
.setPositiveButton(R.string.pref_notify_fix_sync_issues_settings_button) { _, _ ->
|
||||
try {
|
||||
AppKillerManager.doActionPowerSaving(requireContext())
|
||||
AppKillerManager.doActionAutoStart(requireContext())
|
||||
AppKillerManager.doActionNotification(requireContext())
|
||||
} catch (e: NoActionFoundException) {
|
||||
requireContext().openInternetBrowser(
|
||||
"https://dontkillmyapp.com/${AppKillerManager.getDevice()?.manufacturer}",
|
||||
::showMessage
|
||||
)
|
||||
}
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
preferenceScreen.sharedPreferences.registerOnSharedPreferenceChangeListener(this)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
preferenceScreen.sharedPreferences.unregisterOnSharedPreferenceChangeListener(this)
|
||||
}
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
package io.github.wulkanowy.ui.modules.settings.notifications
|
||||
|
||||
import com.chuckerteam.chucker.api.ChuckerCollector
|
||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||
import io.github.wulkanowy.services.alarm.TimetableNotificationSchedulerHelper
|
||||
import io.github.wulkanowy.ui.base.BasePresenter
|
||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||
import io.github.wulkanowy.utils.AnalyticsHelper
|
||||
import io.github.wulkanowy.utils.AppInfo
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
class NotificationsPresenter @Inject constructor(
|
||||
errorHandler: ErrorHandler,
|
||||
studentRepository: StudentRepository,
|
||||
private val preferencesRepository: PreferencesRepository,
|
||||
private val timetableNotificationHelper: TimetableNotificationSchedulerHelper,
|
||||
private val appInfo: AppInfo,
|
||||
private val analytics: AnalyticsHelper,
|
||||
private val chuckerCollector: ChuckerCollector
|
||||
) : BasePresenter<NotificationsView>(errorHandler, studentRepository) {
|
||||
|
||||
override fun onAttachView(view: NotificationsView) {
|
||||
super.onAttachView(view)
|
||||
|
||||
with(view) {
|
||||
enableNotification(
|
||||
preferencesRepository.notificationsEnableKey,
|
||||
preferencesRepository.isServiceEnabled
|
||||
)
|
||||
initView(appInfo.isDebug)
|
||||
}
|
||||
Timber.i("Settings notifications view was initialized")
|
||||
}
|
||||
|
||||
fun onSharedPreferenceChanged(key: String) {
|
||||
Timber.i("Change settings $key")
|
||||
|
||||
preferencesRepository.apply {
|
||||
when (key) {
|
||||
isUpcomingLessonsNotificationsEnableKey -> {
|
||||
if (!isUpcomingLessonsNotificationsEnable) {
|
||||
timetableNotificationHelper.cancelNotification()
|
||||
}
|
||||
}
|
||||
isDebugNotificationEnableKey -> {
|
||||
chuckerCollector.showNotification = isDebugNotificationEnable
|
||||
}
|
||||
}
|
||||
}
|
||||
analytics.logEvent("setting_changed", "name" to key)
|
||||
}
|
||||
|
||||
fun onFixSyncIssuesClicked() {
|
||||
view?.showFixSyncDialog()
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package io.github.wulkanowy.ui.modules.settings.notifications
|
||||
|
||||
import io.github.wulkanowy.ui.base.BaseView
|
||||
|
||||
interface NotificationsView : BaseView {
|
||||
|
||||
fun initView(showDebugNotificationSwitch: Boolean)
|
||||
|
||||
fun showFixSyncDialog()
|
||||
|
||||
fun enableNotification(notificationKey: String, enable: Boolean)
|
||||
}
|
@ -0,0 +1,100 @@
|
||||
package io.github.wulkanowy.ui.modules.settings.sync
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.ui.base.BaseActivity
|
||||
import io.github.wulkanowy.ui.base.ErrorDialog
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class SyncFragment : PreferenceFragmentCompat(),
|
||||
SharedPreferences.OnSharedPreferenceChangeListener,
|
||||
MainView.TitledView, SyncView {
|
||||
|
||||
@Inject
|
||||
lateinit var presenter: SyncPresenter
|
||||
|
||||
companion object {
|
||||
fun newInstance() = SyncFragment()
|
||||
}
|
||||
|
||||
override val titleStringId get() = R.string.pref_settings_sync_title
|
||||
|
||||
override val syncSuccessString get() = getString(R.string.pref_services_message_sync_success)
|
||||
|
||||
override val syncFailedString get() = getString(R.string.pref_services_message_sync_failed)
|
||||
|
||||
override fun initView() {
|
||||
findPreference<Preference>(getString(R.string.pref_key_services_force_sync))?.run {
|
||||
onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
||||
presenter.onSyncNowClicked()
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
presenter.onAttachView(this)
|
||||
}
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.scheme_preferences_sync, rootKey)
|
||||
}
|
||||
|
||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
|
||||
presenter.onSharedPreferenceChanged(key)
|
||||
}
|
||||
|
||||
override fun setServicesSuspended(serviceEnablesKey: String, isHolidays: Boolean) {
|
||||
findPreference<Preference>(serviceEnablesKey)?.run {
|
||||
summary = if (isHolidays) getString(R.string.pref_services_suspended) else ""
|
||||
isEnabled = !isHolidays
|
||||
}
|
||||
}
|
||||
|
||||
override fun setSyncInProgress(inProgress: Boolean) {
|
||||
if (activity == null || !isAdded) return
|
||||
|
||||
findPreference<Preference>(getString(R.string.pref_key_services_force_sync))?.run {
|
||||
isEnabled = !inProgress
|
||||
summary = if (inProgress) getString(R.string.pref_services_sync_in_progress) else ""
|
||||
}
|
||||
}
|
||||
|
||||
override fun showError(text: String, error: Throwable) {
|
||||
(activity as? BaseActivity<*, *>)?.showError(text, error)
|
||||
}
|
||||
|
||||
override fun showMessage(text: String) {
|
||||
(activity as? BaseActivity<*, *>)?.showMessage(text)
|
||||
}
|
||||
|
||||
override fun showExpiredDialog() {
|
||||
(activity as? BaseActivity<*, *>)?.showExpiredDialog()
|
||||
}
|
||||
|
||||
override fun openClearLoginView() {
|
||||
(activity as? BaseActivity<*, *>)?.openClearLoginView()
|
||||
}
|
||||
|
||||
override fun showErrorDetailsDialog(error: Throwable) {
|
||||
ErrorDialog.newInstance(error).show(childFragmentManager, error.toString())
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
preferenceScreen.sharedPreferences.registerOnSharedPreferenceChangeListener(this)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
preferenceScreen.sharedPreferences.unregisterOnSharedPreferenceChangeListener(this)
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user