Merge branch 'release/0.25.0'
15
.idea/codeStyles/Project.xml
generated
@ -18,18 +18,9 @@
|
||||
</option>
|
||||
<option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="2147483647" />
|
||||
<option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="2147483647" />
|
||||
<option name="CONTINUATION_INDENT_IN_PARAMETER_LISTS" value="false" />
|
||||
<option name="CONTINUATION_INDENT_IN_ARGUMENT_LISTS" value="false" />
|
||||
<option name="CONTINUATION_INDENT_FOR_EXPRESSION_BODIES" value="false" />
|
||||
<option name="CONTINUATION_INDENT_FOR_CHAINED_CALLS" value="false" />
|
||||
<option name="CONTINUATION_INDENT_IN_SUPERTYPE_LISTS" value="false" />
|
||||
<option name="CONTINUATION_INDENT_IN_IF_CONDITIONS" value="false" />
|
||||
<option name="CONTINUATION_INDENT_IN_ELVIS" value="false" />
|
||||
<option name="WRAP_ELVIS_EXPRESSIONS" value="0" />
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
</JetCodeStyleSettings>
|
||||
<MarkdownNavigatorCodeStyleSettings>
|
||||
<option name="RIGHT_MARGIN" value="72" />
|
||||
</MarkdownNavigatorCodeStyleSettings>
|
||||
<codeStyleSettings language="XML">
|
||||
<indentOptions>
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
@ -143,13 +134,11 @@
|
||||
</arrangement>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="kotlin">
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
<option name="KEEP_BLANK_LINES_IN_DECLARATIONS" value="1" />
|
||||
<option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
|
||||
<option name="KEEP_BLANK_LINES_BEFORE_RBRACE" value="0" />
|
||||
<option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
|
||||
<option name="METHOD_PARAMETERS_LPAREN_ON_NEXT_LINE" value="true" />
|
||||
<option name="METHOD_PARAMETERS_RPAREN_ON_NEXT_LINE" value="true" />
|
||||
<option name="EXTENDS_LIST_WRAP" value="1" />
|
||||
<indentOptions>
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
</indentOptions>
|
||||
|
@ -11,19 +11,22 @@ apply from: 'hooks.gradle'
|
||||
|
||||
android {
|
||||
compileSdkVersion 30
|
||||
buildToolsVersion '30.0.2'
|
||||
buildToolsVersion '30.0.3'
|
||||
|
||||
defaultConfig {
|
||||
applicationId "io.github.wulkanowy"
|
||||
testApplicationId "io.github.tests.wulkanowy"
|
||||
minSdkVersion 17
|
||||
targetSdkVersion 30
|
||||
versionCode 81
|
||||
versionName "0.24.3"
|
||||
versionCode 82
|
||||
versionName "0.25.0"
|
||||
multiDexEnabled true
|
||||
resValue "string", "app_name", "Wulkanowy"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
|
||||
resValue "string", "app_name", "Wulkanowy"
|
||||
buildConfigField "long", "BUILD_TIMESTAMP", String.valueOf(System.currentTimeMillis())
|
||||
|
||||
manifestPlaceholders = [
|
||||
firebase_enabled: project.hasProperty("enableFirebase")
|
||||
]
|
||||
@ -126,19 +129,20 @@ play {
|
||||
serviceAccountCredentials = file('key.p12')
|
||||
defaultToAppBundles = false
|
||||
track = 'alpha'
|
||||
updatePriority = 5
|
||||
updatePriority = 3
|
||||
}
|
||||
|
||||
ext {
|
||||
work_manager = "2.4.0"
|
||||
room = "2.2.6"
|
||||
work_manager = "2.5.0"
|
||||
work_hilt = "1.0.0-alpha03"
|
||||
room = "2.3.0-beta01"
|
||||
chucker = "3.4.0"
|
||||
mockk = "1.10.5"
|
||||
moshi = "1.11.0"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "io.github.wulkanowy:sdk:0.24.1"
|
||||
implementation "io.github.wulkanowy:sdk:0.25.0"
|
||||
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.1'
|
||||
|
||||
@ -159,10 +163,9 @@ 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.2.1"
|
||||
implementation "com.google.android.material:material:1.3.0-rc01"
|
||||
implementation "com.github.wulkanowy:material-chips-input:2.1.1"
|
||||
implementation "com.github.PhilJay:MPAndroidChart:v3.1.0"
|
||||
implementation "me.zhanghai.android.materialprogressbar:library:1.6.1"
|
||||
|
||||
implementation "androidx.work:work-runtime-ktx:$work_manager"
|
||||
playImplementation "androidx.work:work-gcm:$work_manager"
|
||||
@ -175,12 +178,12 @@ dependencies {
|
||||
|
||||
implementation "com.google.dagger:hilt-android:$hilt_version"
|
||||
kapt "com.google.dagger:hilt-android-compiler:$hilt_version"
|
||||
implementation 'androidx.hilt:hilt-work:1.0.0-alpha02'
|
||||
kapt 'androidx.hilt:hilt-compiler:1.0.0-alpha02'
|
||||
implementation "androidx.hilt:hilt-work:$work_hilt"
|
||||
kapt "androidx.hilt:hilt-compiler:$work_hilt"
|
||||
|
||||
implementation "com.aurelhubert:ahbottomnavigation:2.3.4"
|
||||
implementation "com.ncapdevi:frag-nav:3.3.0"
|
||||
implementation "com.github.YarikSOffice:lingver:1.2.2"
|
||||
implementation "com.github.YarikSOffice:lingver:1.3.0"
|
||||
|
||||
implementation "com.squareup.moshi:moshi:$moshi"
|
||||
implementation "com.squareup.moshi:moshi-adapters:$moshi"
|
||||
@ -194,7 +197,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.3.0')
|
||||
playImplementation platform('com.google.firebase:firebase-bom:26.4.0')
|
||||
playImplementation 'com.google.firebase:firebase-analytics-ktx'
|
||||
playImplementation 'com.google.firebase:firebase-inappmessaging-display-ktx'
|
||||
playImplementation "com.google.firebase:firebase-inappmessaging-ktx"
|
||||
@ -204,7 +207,7 @@ dependencies {
|
||||
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.4.2.301'
|
||||
hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.5.0.200'
|
||||
|
||||
releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker"
|
||||
|
||||
@ -214,6 +217,7 @@ dependencies {
|
||||
testImplementation "junit:junit:4.13.1"
|
||||
testImplementation "io.mockk:mockk:$mockk"
|
||||
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.4.2'
|
||||
testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
|
||||
|
||||
androidTestImplementation "androidx.test:core:1.3.0"
|
||||
androidTestImplementation "androidx.test:runner:1.3.0"
|
||||
|
2136
app/schemas/io.github.wulkanowy.data.db.AppDatabase/31.json
Normal file
2142
app/schemas/io.github.wulkanowy.data.db.AppDatabase/32.json
Normal file
@ -1,11 +0,0 @@
|
||||
package io.github.wulkanowy.data
|
||||
|
||||
import io.github.wulkanowy.utils.DispatchersProvider
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
||||
class TestDispatchersProvider : DispatchersProvider() {
|
||||
|
||||
override val backgroundThread: CoroutineDispatcher
|
||||
get() = Dispatchers.Unconfined
|
||||
}
|
19
app/src/debug/res/drawable-anydpi-v24/ic_stat_timetable.xml
Normal file
@ -0,0 +1,19 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="#FFFFFF"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<group
|
||||
android:scaleX="0.92"
|
||||
android:scaleY="0.92"
|
||||
android:translateX="0.96"
|
||||
android:translateY="0.96">
|
||||
<path
|
||||
android:fillColor="#FFF"
|
||||
android:pathData="M3.9512,2A2,2 0,0 0,2 4L2,18A2,2 0,0 0,4 20L10.0996,20C11.3596,21.24 13.09,22 15,22A7,7 0,0 0,15.7988 21.9551L15.7988,19.7832A4.85,4.85 0,0 1,15 19.8496C12.32,19.8496 10.1504,17.68 10.1504,15A4.85,4.85 0,0 1,15 10.1504C17.4677,10.1504 19.4978,11.9912 19.8047,14.375C20.566,14.3758 21.3108,14.5325 21.9922,14.834C21.9491,12.9905 21.2036,11.3226 20,10.0996L20,4A2,2 0,0 0,18 2L4,2A2,2 0,0 0,3.9512 2zM4,5L10,5L10,8L4,8L4,5zM12,5L18,5L18,8L12,8L12,5zM4,10L10.0996,10C9.2596,10.82 8.6291,11.85 8.2891,13L4,13L4,10zM14,12L14,15.6895L15.7988,16.7266L15.7988,14.9922L15.5,14.8203L15.5,12L14,12zM4,15L8,15C8,16.07 8.2399,17.09 8.6699,18L4,18L4,15z" />
|
||||
<path
|
||||
android:fillColor="#FFF"
|
||||
android:pathData="m17.298,24v-8.1249h2.5c0.7143,0 1.3523,0.1618 1.9141,0.4855 0.5655,0.3199 1.0063,0.7775 1.3225,1.3728 0.3162,0.5915 0.4743,1.2649 0.4743,2.0201v0.3739c0,0.7552 -0.1562,1.4267 -0.4687,2.0145 -0.3088,0.5878 -0.7459,1.0435 -1.3114,1.3672C21.1633,23.8326 20.5253,23.9963 19.8148,24ZM18.9721,17.2311v5.4241h0.8091c0.6548,0 1.1551,-0.2139 1.5011,-0.6417 0.346,-0.4278 0.5227,-1.0398 0.5301,-1.8359v-0.4297c0,-0.8259 -0.1711,-1.4509 -0.5134,-1.875 -0.3423,-0.4278 -0.8426,-0.6417 -1.5011,-0.6417z" />
|
||||
</group>
|
||||
</vector>
|
BIN
app/src/debug/res/drawable-hdpi/ic_stat_timetable.png
Normal file
After Width: | Height: | Size: 426 B |
BIN
app/src/debug/res/drawable-mdpi/ic_stat_timetable.png
Normal file
After Width: | Height: | Size: 335 B |
BIN
app/src/debug/res/drawable-xhdpi/ic_stat_timetable.png
Normal file
After Width: | Height: | Size: 519 B |
BIN
app/src/debug/res/drawable-xxhdpi/ic_stat_timetable.png
Normal file
After Width: | Height: | Size: 700 B |
@ -18,6 +18,18 @@
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<data android:scheme="https" />
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<data android:scheme="mailto" />
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<data android:scheme="tel" />
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<data android:scheme="geo" />
|
||||
</intent>
|
||||
</queries>
|
||||
|
||||
<application
|
||||
|
@ -5,6 +5,7 @@ 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.hilt.work.HiltWorkerFactory
|
||||
import androidx.multidex.MultiDex
|
||||
import androidx.work.Configuration
|
||||
@ -47,17 +48,18 @@ class WulkanowyApp : Application(), Configuration.Provider {
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
Lingver.init(this)
|
||||
themeManager.applyDefaultTheme()
|
||||
|
||||
initializeAppLanguage()
|
||||
themeManager.applyDefaultTheme()
|
||||
initLogging()
|
||||
logCurrentLanguage()
|
||||
fixWebViewLocale()
|
||||
}
|
||||
|
||||
private fun initLogging() {
|
||||
if (appInfo.isDebug) {
|
||||
Timber.plant(DebugLogTree())
|
||||
Timber.plant(FileLoggerTree.Builder()
|
||||
Timber.plant(
|
||||
FileLoggerTree.Builder()
|
||||
.withFileName("wulkanowy.%g.log")
|
||||
.withDirName(applicationContext.filesDir.absolutePath)
|
||||
.withFileLimit(10)
|
||||
@ -71,14 +73,20 @@ class WulkanowyApp : Application(), Configuration.Provider {
|
||||
registerActivityLifecycleCallbacks(ActivityLifecycleLogger())
|
||||
}
|
||||
|
||||
private fun logCurrentLanguage() {
|
||||
val newLang = if (preferencesRepository.appLanguage == "system") {
|
||||
appInfo.systemLanguage
|
||||
private fun initializeAppLanguage() {
|
||||
Lingver.init(this)
|
||||
|
||||
if (preferencesRepository.appLanguage == "system") {
|
||||
Lingver.getInstance().setFollowSystemLocale(this)
|
||||
analyticsHelper.logEvent("language", "startup" to appInfo.systemLanguage)
|
||||
} else {
|
||||
preferencesRepository.appLanguage
|
||||
analyticsHelper.logEvent("language", "startup" to preferencesRepository.appLanguage)
|
||||
}
|
||||
}
|
||||
|
||||
analyticsHelper.logEvent("language", "startup" to newLang)
|
||||
private fun fixWebViewLocale() {
|
||||
//https://stackoverflow.com/questions/40398528/android-webview-language-changes-abruptly-on-android-7-0-and-above
|
||||
WebView(this).destroy()
|
||||
}
|
||||
|
||||
override fun getWorkManagerConfiguration() = Configuration.Builder()
|
||||
|
@ -33,7 +33,8 @@ internal class RepositoryModule {
|
||||
setSimpleHttpLogger { Timber.d(it) }
|
||||
|
||||
// for debug only
|
||||
addInterceptor(ChuckerInterceptor.Builder(context)
|
||||
addInterceptor(
|
||||
ChuckerInterceptor.Builder(context)
|
||||
.collector(chuckerCollector)
|
||||
.alwaysReadResponseBody(true)
|
||||
.build(), network = true
|
||||
@ -43,7 +44,10 @@ internal class RepositoryModule {
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideChuckerCollector(@ApplicationContext context: Context, prefRepository: PreferencesRepository): ChuckerCollector {
|
||||
fun provideChuckerCollector(
|
||||
@ApplicationContext context: Context,
|
||||
prefRepository: PreferencesRepository
|
||||
): ChuckerCollector {
|
||||
return ChuckerCollector(
|
||||
context = context,
|
||||
showNotification = prefRepository.isDebugNotificationEnable,
|
||||
@ -53,7 +57,10 @@ internal class RepositoryModule {
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideDatabase(@ApplicationContext context: Context, sharedPrefProvider: SharedPrefProvider) = AppDatabase.newInstance(context, sharedPrefProvider)
|
||||
fun provideDatabase(
|
||||
@ApplicationContext context: Context,
|
||||
sharedPrefProvider: SharedPrefProvider,
|
||||
) = AppDatabase.newInstance(context, sharedPrefProvider)
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
@ -65,7 +72,8 @@ internal class RepositoryModule {
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideSharedPref(@ApplicationContext context: Context): SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
fun provideSharedPref(@ApplicationContext context: Context): SharedPreferences =
|
||||
PreferenceManager.getDefaultSharedPreferences(context)
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
@ -89,7 +97,8 @@ internal class RepositoryModule {
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideGradeSemesterStatisticsDao(database: AppDatabase) = database.gradeSemesterStatisticsDao
|
||||
fun provideGradeSemesterStatisticsDao(database: AppDatabase) =
|
||||
database.gradeSemesterStatisticsDao
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
@ -166,4 +175,8 @@ internal class RepositoryModule {
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideTimetableAdditionalDao(database: AppDatabase) = database.timetableAdditionalDao
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideStudentInfoDao(database: AppDatabase) = database.studentInfoDao
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ import io.github.wulkanowy.data.db.dao.ReportingUnitDao
|
||||
import io.github.wulkanowy.data.db.dao.SchoolDao
|
||||
import io.github.wulkanowy.data.db.dao.SemesterDao
|
||||
import io.github.wulkanowy.data.db.dao.StudentDao
|
||||
import io.github.wulkanowy.data.db.dao.StudentInfoDao
|
||||
import io.github.wulkanowy.data.db.dao.SubjectDao
|
||||
import io.github.wulkanowy.data.db.dao.TeacherDao
|
||||
import io.github.wulkanowy.data.db.dao.TimetableAdditionalDao
|
||||
@ -53,6 +54,7 @@ import io.github.wulkanowy.data.db.entities.ReportingUnit
|
||||
import io.github.wulkanowy.data.db.entities.School
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.db.entities.StudentInfo
|
||||
import io.github.wulkanowy.data.db.entities.Subject
|
||||
import io.github.wulkanowy.data.db.entities.Teacher
|
||||
import io.github.wulkanowy.data.db.entities.Timetable
|
||||
@ -80,6 +82,8 @@ import io.github.wulkanowy.data.db.migrations.Migration28
|
||||
import io.github.wulkanowy.data.db.migrations.Migration29
|
||||
import io.github.wulkanowy.data.db.migrations.Migration3
|
||||
import io.github.wulkanowy.data.db.migrations.Migration30
|
||||
import io.github.wulkanowy.data.db.migrations.Migration31
|
||||
import io.github.wulkanowy.data.db.migrations.Migration32
|
||||
import io.github.wulkanowy.data.db.migrations.Migration4
|
||||
import io.github.wulkanowy.data.db.migrations.Migration5
|
||||
import io.github.wulkanowy.data.db.migrations.Migration6
|
||||
@ -116,6 +120,7 @@ import javax.inject.Singleton
|
||||
School::class,
|
||||
Conference::class,
|
||||
TimetableAdditional::class,
|
||||
StudentInfo::class,
|
||||
],
|
||||
version = AppDatabase.VERSION_SCHEMA,
|
||||
exportSchema = true
|
||||
@ -124,7 +129,7 @@ import javax.inject.Singleton
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
|
||||
companion object {
|
||||
const val VERSION_SCHEMA = 30
|
||||
const val VERSION_SCHEMA = 32
|
||||
|
||||
fun getMigrations(sharedPrefProvider: SharedPrefProvider): Array<Migration> {
|
||||
return arrayOf(
|
||||
@ -157,6 +162,8 @@ abstract class AppDatabase : RoomDatabase() {
|
||||
Migration28(),
|
||||
Migration29(),
|
||||
Migration30(),
|
||||
Migration31(),
|
||||
Migration32()
|
||||
)
|
||||
}
|
||||
|
||||
@ -219,4 +226,6 @@ abstract class AppDatabase : RoomDatabase() {
|
||||
abstract val conferenceDao: ConferenceDao
|
||||
|
||||
abstract val timetableAdditionalDao: TimetableAdditionalDao
|
||||
|
||||
abstract val studentInfoDao: StudentInfoDao
|
||||
}
|
||||
|
@ -6,7 +6,9 @@ import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy.ABORT
|
||||
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.StudentWithSemesters
|
||||
import javax.inject.Singleton
|
||||
|
||||
@ -20,6 +22,9 @@ interface StudentDao {
|
||||
@Delete
|
||||
suspend fun delete(student: Student)
|
||||
|
||||
@Update(entity = Student::class)
|
||||
suspend fun update(studentNick: StudentNick)
|
||||
|
||||
@Query("SELECT * FROM Students WHERE is_current = 1")
|
||||
suspend fun loadCurrent(): Student?
|
||||
|
||||
|
@ -0,0 +1,15 @@
|
||||
package io.github.wulkanowy.data.db.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Query
|
||||
import io.github.wulkanowy.data.db.entities.StudentInfo
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
@Dao
|
||||
interface StudentInfoDao : BaseDao<StudentInfo> {
|
||||
|
||||
@Query("SELECT * FROM StudentInfo WHERE student_id = :studentId")
|
||||
fun loadStudentInfo(studentId: Int): Flow<StudentInfo?>
|
||||
}
|
@ -7,7 +7,13 @@ import androidx.room.PrimaryKey
|
||||
import java.io.Serializable
|
||||
import java.time.LocalDateTime
|
||||
|
||||
@Entity(tableName = "Students", indices = [Index(value = ["email", "symbol", "student_id", "school_id", "class_id"], unique = true)])
|
||||
@Entity(
|
||||
tableName = "Students",
|
||||
indices = [Index(
|
||||
value = ["email", "symbol", "student_id", "school_id", "class_id"],
|
||||
unique = true
|
||||
)]
|
||||
)
|
||||
data class Student(
|
||||
|
||||
@ColumnInfo(name = "scrapper_base_url")
|
||||
@ -52,7 +58,7 @@ data class Student(
|
||||
@ColumnInfo(name = "school_id")
|
||||
val schoolSymbol: String,
|
||||
|
||||
@ColumnInfo(name ="school_short")
|
||||
@ColumnInfo(name = "school_short")
|
||||
val schoolShortName: String,
|
||||
|
||||
@ColumnInfo(name = "school_name")
|
||||
@ -73,4 +79,6 @@ data class Student(
|
||||
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
var id: Long = 0
|
||||
|
||||
var nick = ""
|
||||
}
|
||||
|
@ -0,0 +1,85 @@
|
||||
package io.github.wulkanowy.data.db.entities
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Embedded
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import io.github.wulkanowy.data.enums.Gender
|
||||
import java.io.Serializable
|
||||
import java.time.LocalDate
|
||||
|
||||
@Entity(tableName = "StudentInfo")
|
||||
data class StudentInfo(
|
||||
|
||||
@ColumnInfo(name = "student_id")
|
||||
val studentId: Int,
|
||||
|
||||
@ColumnInfo(name = "full_name")
|
||||
val fullName: String,
|
||||
|
||||
@ColumnInfo(name = "first_name")
|
||||
val firstName: String,
|
||||
|
||||
@ColumnInfo(name = "second_name")
|
||||
val secondName: String,
|
||||
|
||||
val surname: String,
|
||||
|
||||
@ColumnInfo(name = "birth_date")
|
||||
val birthDate: LocalDate,
|
||||
|
||||
@ColumnInfo(name = "birth_place")
|
||||
val birthPlace: String,
|
||||
|
||||
val gender: Gender,
|
||||
|
||||
@ColumnInfo(name = "has_polish_citizenship")
|
||||
val hasPolishCitizenship: Boolean,
|
||||
|
||||
@ColumnInfo(name = "family_name")
|
||||
val familyName: String,
|
||||
|
||||
@ColumnInfo(name = "parents_names")
|
||||
val parentsNames: String,
|
||||
|
||||
val address: String,
|
||||
|
||||
@ColumnInfo(name = "registered_address")
|
||||
val registeredAddress: String,
|
||||
|
||||
@ColumnInfo(name = "correspondence_address")
|
||||
val correspondenceAddress: String,
|
||||
|
||||
@ColumnInfo(name = "phone_number")
|
||||
val phoneNumber: String,
|
||||
|
||||
@ColumnInfo(name = "cell_phone_number")
|
||||
val cellPhoneNumber: String,
|
||||
|
||||
val email: String,
|
||||
|
||||
@Embedded(prefix = "first_guardian_")
|
||||
val firstGuardian: StudentGuardian,
|
||||
|
||||
@Embedded(prefix = "second_guardian_")
|
||||
val secondGuardian: StudentGuardian
|
||||
|
||||
) : Serializable {
|
||||
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
var id: Long = 0
|
||||
}
|
||||
|
||||
data class StudentGuardian(
|
||||
|
||||
@ColumnInfo(name = "full_name")
|
||||
val fullName: String,
|
||||
|
||||
val kinship: String,
|
||||
|
||||
val address: String,
|
||||
|
||||
val phones: String,
|
||||
|
||||
val email: String
|
||||
) : Serializable
|
@ -0,0 +1,16 @@
|
||||
package io.github.wulkanowy.data.db.entities
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import java.io.Serializable
|
||||
|
||||
@Entity
|
||||
data class StudentNick(
|
||||
|
||||
val nick: String
|
||||
|
||||
) : Serializable {
|
||||
|
||||
@PrimaryKey
|
||||
var id: Long = 0
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
package io.github.wulkanowy.data.db.migrations
|
||||
|
||||
import androidx.room.migration.Migration
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
|
||||
class Migration31 : Migration(30, 31) {
|
||||
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL(
|
||||
"""CREATE TABLE IF NOT EXISTS StudentInfo (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
student_id INTEGER NOT NULL,
|
||||
full_name TEXT NOT NULL,
|
||||
first_name TEXT NOT NULL,
|
||||
second_name TEXT NOT NULL,
|
||||
surname TEXT NOT NULL,
|
||||
birth_date INTEGER NOT NULL,
|
||||
birth_place TEXT NOT NULL,
|
||||
gender TEXT NOT NULL,
|
||||
has_polish_citizenship INTEGER NOT NULL,
|
||||
family_name TEXT NOT NULL,
|
||||
parents_names TEXT NOT NULL,
|
||||
address TEXT NOT NULL,
|
||||
registered_address TEXT NOT NULL,
|
||||
correspondence_address TEXT NOT NULL,
|
||||
phone_number TEXT NOT NULL,
|
||||
cell_phone_number TEXT NOT NULL,
|
||||
email TEXT NOT NULL,
|
||||
first_guardian_full_name TEXT NOT NULL,
|
||||
first_guardian_kinship TEXT NOT NULL,
|
||||
first_guardian_address TEXT NOT NULL,
|
||||
first_guardian_phones TEXT NOT NULL,
|
||||
first_guardian_email TEXT NOT NULL,
|
||||
second_guardian_full_name TEXT NOT NULL,
|
||||
second_guardian_kinship TEXT NOT NULL,
|
||||
second_guardian_address TEXT NOT NULL,
|
||||
second_guardian_phones TEXT NOT NULL,
|
||||
second_guardian_email TEXT NOT NULL)
|
||||
"""
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package io.github.wulkanowy.data.db.migrations
|
||||
|
||||
import androidx.room.migration.Migration
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
|
||||
class Migration32 : Migration(31, 32) {
|
||||
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL("ALTER TABLE Students ADD COLUMN nick TEXT NOT NULL DEFAULT \"\"")
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,3 @@
|
||||
package io.github.wulkanowy.data.enums
|
||||
|
||||
enum class Gender { MALE, FEMALE }
|
@ -0,0 +1,38 @@
|
||||
package io.github.wulkanowy.data.mappers
|
||||
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.StudentGuardian
|
||||
import io.github.wulkanowy.data.db.entities.StudentInfo
|
||||
import io.github.wulkanowy.data.enums.Gender
|
||||
import io.github.wulkanowy.sdk.pojo.StudentGuardian as SdkStudentGuardian
|
||||
import io.github.wulkanowy.sdk.pojo.StudentInfo as SdkStudentInfo
|
||||
|
||||
fun SdkStudentInfo.mapToEntity(semester: Semester) = StudentInfo(
|
||||
studentId = semester.studentId,
|
||||
fullName = fullName,
|
||||
firstName = firstName,
|
||||
secondName = secondName,
|
||||
surname = surname,
|
||||
birthDate = birthDate,
|
||||
birthPlace = birthPlace,
|
||||
gender = Gender.valueOf(gender.name),
|
||||
hasPolishCitizenship = hasPolishCitizenship,
|
||||
familyName = familyName,
|
||||
parentsNames = parentsNames,
|
||||
address = address,
|
||||
registeredAddress = registeredAddress,
|
||||
correspondenceAddress = correspondenceAddress,
|
||||
phoneNumber = phoneNumber,
|
||||
cellPhoneNumber = phoneNumber,
|
||||
email = email,
|
||||
firstGuardian = guardians[0].mapToEntity(),
|
||||
secondGuardian = guardians[1].mapToEntity()
|
||||
)
|
||||
|
||||
fun SdkStudentGuardian.mapToEntity() = StudentGuardian(
|
||||
fullName = fullName,
|
||||
kinship = kinship,
|
||||
address = address,
|
||||
phones = phones,
|
||||
email = email
|
||||
)
|
@ -16,16 +16,23 @@ class SchoolRepository @Inject constructor(
|
||||
private val sdk: Sdk
|
||||
) {
|
||||
|
||||
fun getSchoolInfo(student: Student, semester: Semester, forceRefresh: Boolean) = networkBoundResource(
|
||||
fun getSchoolInfo(student: Student, semester: Semester, forceRefresh: Boolean) =
|
||||
networkBoundResource(
|
||||
shouldFetch = { it == null || forceRefresh },
|
||||
query = { schoolDb.load(semester.studentId, semester.classId) },
|
||||
fetch = { sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear).getSchool().mapToEntity(semester) },
|
||||
fetch = {
|
||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear).getSchool()
|
||||
.mapToEntity(semester)
|
||||
},
|
||||
saveFetchResult = { old, new ->
|
||||
if (new != old && old != null) {
|
||||
schoolDb.deleteAll(listOf(old))
|
||||
if (old != null && new != old) {
|
||||
with(schoolDb) {
|
||||
deleteAll(listOf(old))
|
||||
insertAll(listOf(new))
|
||||
}
|
||||
} else if (old == null) {
|
||||
schoolDb.insertAll(listOf(new))
|
||||
}
|
||||
schoolDb.insertAll(listOf(new))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -0,0 +1,38 @@
|
||||
package io.github.wulkanowy.data.repositories
|
||||
|
||||
import io.github.wulkanowy.data.db.dao.StudentInfoDao
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
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 javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class StudentInfoRepository @Inject constructor(
|
||||
private val studentInfoDao: StudentInfoDao,
|
||||
private val sdk: Sdk
|
||||
) {
|
||||
|
||||
fun getStudentInfo(student: Student, semester: Semester, forceRefresh: Boolean) =
|
||||
networkBoundResource(
|
||||
shouldFetch = { it == null || forceRefresh },
|
||||
query = { studentInfoDao.loadStudentInfo(student.studentId) },
|
||||
fetch = {
|
||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||
.getStudentInfo().mapToEntity(semester)
|
||||
},
|
||||
saveFetchResult = { old, new ->
|
||||
if (old != null && new != old) {
|
||||
with(studentInfoDao) {
|
||||
deleteAll(listOf(old))
|
||||
insertAll(listOf(new))
|
||||
}
|
||||
} else if (old == null) {
|
||||
studentInfoDao.insertAll(listOf(new))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
@ -5,6 +5,7 @@ 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.StudentWithSemesters
|
||||
import io.github.wulkanowy.data.exceptions.NoCurrentStudentException
|
||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||
@ -25,39 +26,59 @@ class StudentRepository @Inject constructor(
|
||||
private val sdk: Sdk
|
||||
) {
|
||||
|
||||
suspend fun isStudentSaved(): Boolean = getSavedStudents(false).isNotEmpty()
|
||||
suspend fun isStudentSaved() = getSavedStudents(false).isNotEmpty()
|
||||
|
||||
suspend fun isCurrentStudentSet(): Boolean = studentDb.loadCurrent()?.isCurrent ?: false
|
||||
suspend fun isCurrentStudentSet() = studentDb.loadCurrent()?.isCurrent ?: false
|
||||
|
||||
suspend fun getStudentsApi(pin: String, symbol: String, token: String): List<StudentWithSemesters> {
|
||||
return sdk.getStudentsFromMobileApi(token, pin, symbol, "").mapToEntities()
|
||||
}
|
||||
suspend fun getStudentsApi(
|
||||
pin: String,
|
||||
symbol: String,
|
||||
token: String
|
||||
): List<StudentWithSemesters> =
|
||||
sdk.getStudentsFromMobileApi(token, pin, symbol, "").mapToEntities()
|
||||
|
||||
suspend fun getStudentsScrapper(email: String, password: String, scrapperBaseUrl: String, symbol: String): List<StudentWithSemesters> {
|
||||
return sdk.getStudentsFromScrapper(email, password, scrapperBaseUrl, symbol).mapToEntities(password)
|
||||
}
|
||||
suspend fun getStudentsScrapper(
|
||||
email: String,
|
||||
password: String,
|
||||
scrapperBaseUrl: String,
|
||||
symbol: String
|
||||
): List<StudentWithSemesters> =
|
||||
sdk.getStudentsFromScrapper(email, password, scrapperBaseUrl, symbol)
|
||||
.mapToEntities(password)
|
||||
|
||||
suspend fun getStudentsHybrid(email: String, password: String, scrapperBaseUrl: String, symbol: String): List<StudentWithSemesters> {
|
||||
return sdk.getStudentsHybrid(email, password, scrapperBaseUrl, "", symbol).mapToEntities(password)
|
||||
}
|
||||
suspend fun getStudentsHybrid(
|
||||
email: String,
|
||||
password: String,
|
||||
scrapperBaseUrl: String,
|
||||
symbol: String
|
||||
): List<StudentWithSemesters> =
|
||||
sdk.getStudentsHybrid(email, password, scrapperBaseUrl, "", symbol).mapToEntities(password)
|
||||
|
||||
suspend fun getSavedStudents(decryptPass: Boolean = true) = withContext(dispatchers.backgroundThread) {
|
||||
suspend fun getSavedStudents(decryptPass: Boolean = true) =
|
||||
withContext(dispatchers.backgroundThread) {
|
||||
studentDb.loadStudentsWithSemesters().map {
|
||||
it.apply {
|
||||
if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) student.password = decrypt(student.password)
|
||||
if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) {
|
||||
student.password = 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)
|
||||
if (Sdk.Mode.valueOf(loginMode) != Sdk.Mode.API) {
|
||||
password = decrypt(password)
|
||||
}
|
||||
}
|
||||
} ?: throw NoCurrentStudentException()
|
||||
|
||||
suspend fun getCurrentStudent(decryptPass: Boolean = true) = withContext(dispatchers.backgroundThread) {
|
||||
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(loginMode) != Sdk.Mode.API) {
|
||||
password = decrypt(password)
|
||||
}
|
||||
}
|
||||
} ?: throw NoCurrentStudentException()
|
||||
|
||||
@ -66,8 +87,9 @@ class StudentRepository @Inject constructor(
|
||||
|
||||
return withContext(dispatchers.backgroundThread) {
|
||||
studentDb.insertAll(studentsWithSemesters.map { it.student }.map {
|
||||
if (Sdk.Mode.valueOf(it.loginMode) != Sdk.Mode.API) it.copy(password = encrypt(it.password, context))
|
||||
else it
|
||||
if (Sdk.Mode.valueOf(it.loginMode) != Sdk.Mode.API) {
|
||||
it.copy(password = encrypt(it.password, context))
|
||||
} else it
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -79,7 +101,7 @@ class StudentRepository @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun logoutStudent(student: Student) {
|
||||
studentDb.delete(student)
|
||||
}
|
||||
suspend fun logoutStudent(student: Student) = studentDb.delete(student)
|
||||
|
||||
suspend fun updateStudentNick(studentNick: StudentNick) = studentDb.update(studentNick)
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companio
|
||||
import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.STUDENT_NAME
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import io.github.wulkanowy.utils.DispatchersProvider
|
||||
import io.github.wulkanowy.utils.nickOrName
|
||||
import io.github.wulkanowy.utils.toTimestamp
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
@ -41,17 +42,23 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
|
||||
private val dispatchersProvider: DispatchersProvider,
|
||||
) {
|
||||
|
||||
private fun getRequestCode(time: LocalDateTime, studentId: Int) = (time.toTimestamp() * studentId).toInt()
|
||||
private fun getRequestCode(time: LocalDateTime, studentId: Int) =
|
||||
(time.toTimestamp() * studentId).toInt()
|
||||
|
||||
private fun getUpcomingLessonTime(index: Int, day: List<Timetable>, lesson: Timetable): LocalDateTime {
|
||||
return day.getOrNull(index - 1)?.end ?: lesson.start.minusMinutes(30)
|
||||
}
|
||||
private fun getUpcomingLessonTime(
|
||||
index: Int,
|
||||
day: List<Timetable>,
|
||||
lesson: Timetable
|
||||
) = day.getOrNull(index - 1)?.end ?: lesson.start.minusMinutes(30)
|
||||
|
||||
suspend fun cancelScheduled(lessons: List<Timetable>, studentId: Int = 1) {
|
||||
withContext(dispatchersProvider.backgroundThread) {
|
||||
lessons.sortedBy { it.start }.forEachIndexed { index, lesson ->
|
||||
val upcomingTime = getUpcomingLessonTime(index, lessons, lesson)
|
||||
cancelScheduledTo(upcomingTime..lesson.start, getRequestCode(upcomingTime, studentId))
|
||||
cancelScheduledTo(
|
||||
upcomingTime..lesson.start,
|
||||
getRequestCode(upcomingTime, studentId)
|
||||
)
|
||||
cancelScheduledTo(lesson.start..lesson.end, getRequestCode(lesson.start, studentId))
|
||||
|
||||
Timber.d("TimetableNotification canceled: type 1 & 2, subject: ${lesson.subject}, start: ${lesson.start}, student: $studentId")
|
||||
@ -61,13 +68,18 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
|
||||
|
||||
private fun cancelScheduledTo(range: ClosedRange<LocalDateTime>, requestCode: Int) {
|
||||
if (now() in range) cancelNotification()
|
||||
alarmManager.cancel(PendingIntent.getBroadcast(context, requestCode, Intent(), FLAG_UPDATE_CURRENT))
|
||||
alarmManager.cancel(
|
||||
PendingIntent.getBroadcast(context, requestCode, Intent(), FLAG_UPDATE_CURRENT)
|
||||
)
|
||||
}
|
||||
|
||||
fun cancelNotification() = NotificationManagerCompat.from(context).cancel(MainView.Section.TIMETABLE.id)
|
||||
fun cancelNotification() =
|
||||
NotificationManagerCompat.from(context).cancel(MainView.Section.TIMETABLE.id)
|
||||
|
||||
suspend fun scheduleNotifications(lessons: List<Timetable>, student: Student) {
|
||||
if (!preferencesRepository.isUpcomingLessonsNotificationsEnable) return cancelScheduled(lessons, student.studentId)
|
||||
if (!preferencesRepository.isUpcomingLessonsNotificationsEnable) {
|
||||
return cancelScheduled(lessons, student.studentId)
|
||||
}
|
||||
|
||||
withContext(dispatchersProvider.backgroundThread) {
|
||||
lessons.groupBy { it.date }
|
||||
@ -82,13 +94,28 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
|
||||
val intent = createIntent(student, lesson, active.getOrNull(index + 1))
|
||||
|
||||
if (lesson.start > now()) {
|
||||
scheduleBroadcast(intent, student.studentId, NOTIFICATION_TYPE_UPCOMING, getUpcomingLessonTime(index, active, lesson))
|
||||
scheduleBroadcast(
|
||||
intent,
|
||||
student.studentId,
|
||||
NOTIFICATION_TYPE_UPCOMING,
|
||||
getUpcomingLessonTime(index, active, lesson)
|
||||
)
|
||||
}
|
||||
|
||||
if (lesson.end > now()) {
|
||||
scheduleBroadcast(intent, student.studentId, NOTIFICATION_TYPE_CURRENT, lesson.start)
|
||||
scheduleBroadcast(
|
||||
intent,
|
||||
student.studentId,
|
||||
NOTIFICATION_TYPE_CURRENT,
|
||||
lesson.start
|
||||
)
|
||||
if (active.lastIndex == index) {
|
||||
scheduleBroadcast(intent, student.studentId, NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION, lesson.end)
|
||||
scheduleBroadcast(
|
||||
intent,
|
||||
student.studentId,
|
||||
NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION,
|
||||
lesson.end
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -99,7 +126,7 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
|
||||
private fun createIntent(student: Student, lesson: Timetable, nextLesson: Timetable?): Intent {
|
||||
return Intent(context, TimetableNotificationReceiver::class.java).apply {
|
||||
putExtra(STUDENT_ID, student.studentId)
|
||||
putExtra(STUDENT_NAME, student.studentName)
|
||||
putExtra(STUDENT_NAME, student.nickOrName)
|
||||
putExtra(LESSON_ROOM, lesson.room)
|
||||
putExtra(LESSON_START, lesson.start.toTimestamp())
|
||||
putExtra(LESSON_END, lesson.end.toTimestamp())
|
||||
@ -109,13 +136,23 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun scheduleBroadcast(intent: Intent, studentId: Int, notificationType: Int, time: LocalDateTime) {
|
||||
AlarmManagerCompat.setExactAndAllowWhileIdle(alarmManager, RTC_WAKEUP, time.toTimestamp(),
|
||||
private fun scheduleBroadcast(
|
||||
intent: Intent,
|
||||
studentId: Int,
|
||||
notificationType: Int,
|
||||
time: LocalDateTime
|
||||
) {
|
||||
AlarmManagerCompat.setExactAndAllowWhileIdle(
|
||||
alarmManager, RTC_WAKEUP, time.toTimestamp(),
|
||||
PendingIntent.getBroadcast(context, getRequestCode(time, studentId), intent.also {
|
||||
it.putExtra(NOTIFICATION_ID, MainView.Section.TIMETABLE.id)
|
||||
it.putExtra(LESSON_TYPE, notificationType)
|
||||
}, FLAG_UPDATE_CURRENT)
|
||||
)
|
||||
Timber.d("TimetableNotification scheduled: type: $notificationType, subject: ${intent.getStringExtra(LESSON_TITLE)}, start: $time, student: $studentId")
|
||||
Timber.d(
|
||||
"TimetableNotification scheduled: type: $notificationType, subject: ${
|
||||
intent.getStringExtra(LESSON_TITLE)
|
||||
}, start: $time, student: $studentId"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -5,11 +5,12 @@ import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationCompat.BigTextStyle
|
||||
import androidx.core.app.NotificationCompat.PRIORITY_DEFAULT
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.hilt.Assisted
|
||||
import androidx.hilt.work.WorkerInject
|
||||
import androidx.hilt.work.HiltWorker
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.Data
|
||||
import androidx.work.WorkerParameters
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||
import io.github.wulkanowy.data.repositories.SemesterRepository
|
||||
@ -23,7 +24,8 @@ import kotlinx.coroutines.coroutineScope
|
||||
import timber.log.Timber
|
||||
import kotlin.random.Random
|
||||
|
||||
class SyncWorker @WorkerInject constructor(
|
||||
@HiltWorker
|
||||
class SyncWorker @AssistedInject constructor(
|
||||
@Assisted appContext: Context,
|
||||
@Assisted workerParameters: WorkerParameters,
|
||||
private val studentRepository: StudentRepository,
|
||||
@ -58,7 +60,8 @@ class SyncWorker @WorkerInject constructor(
|
||||
}
|
||||
val result = when {
|
||||
exceptions.isNotEmpty() && inputData.getBoolean("one_time", false) -> {
|
||||
Result.failure(Data.Builder()
|
||||
Result.failure(
|
||||
Data.Builder()
|
||||
.putString("error", exceptions.map { it.stackTraceToString() }.toString())
|
||||
.build()
|
||||
)
|
||||
@ -74,13 +77,16 @@ class SyncWorker @WorkerInject constructor(
|
||||
}
|
||||
|
||||
private fun notify(result: Result) {
|
||||
notificationManager.notify(Random.nextInt(Int.MAX_VALUE), NotificationCompat.Builder(applicationContext, DebugChannel.CHANNEL_ID)
|
||||
notificationManager.notify(
|
||||
Random.nextInt(Int.MAX_VALUE),
|
||||
NotificationCompat.Builder(applicationContext, DebugChannel.CHANNEL_ID)
|
||||
.setContentTitle("Debug notification")
|
||||
.setSmallIcon(R.drawable.ic_stat_push)
|
||||
.setAutoCancel(true)
|
||||
.setColor(applicationContext.getCompatColor(R.color.colorPrimary))
|
||||
.setStyle(BigTextStyle().bigText("${SyncWorker::class.java.simpleName} result: $result"))
|
||||
.setPriority(PRIORITY_DEFAULT)
|
||||
.build())
|
||||
.build()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -57,7 +57,7 @@ class ErrorDialog : BaseDialogFragment<DialogErrorBinding>() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
return DialogErrorBinding.inflate(inflater).apply { binding = this }.root
|
||||
}
|
||||
|
||||
@ -114,11 +114,17 @@ class ErrorDialog : BaseDialogFragment<DialogErrorBinding>() {
|
||||
chooserTitle = getString(R.string.about_feedback),
|
||||
email = "wulkanowyinc@gmail.com",
|
||||
subject = "Zgłoszenie błędu",
|
||||
body = requireContext().getString(R.string.about_feedback_template,
|
||||
"${appInfo.systemManufacturer} ${appInfo.systemModel}", appInfo.systemVersion.toString(), appInfo.versionName
|
||||
body = requireContext().getString(
|
||||
R.string.about_feedback_template,
|
||||
"${appInfo.systemManufacturer} ${appInfo.systemModel}",
|
||||
appInfo.systemVersion.toString(),
|
||||
"${appInfo.versionName}-${appInfo.buildFlavor}"
|
||||
) + "\n" + content,
|
||||
onActivityNotFound = {
|
||||
requireContext().openInternetBrowser("https://github.com/wulkanowy/wulkanowy/issues", ::showMessage)
|
||||
requireContext().openInternetBrowser(
|
||||
"https://github.com/wulkanowy/wulkanowy/issues",
|
||||
::showMessage
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.databinding.ItemAccountBinding
|
||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||
import io.github.wulkanowy.utils.nickOrName
|
||||
import javax.inject.Inject
|
||||
|
||||
class WidgetConfigureAdapter @Inject constructor() : RecyclerView.Adapter<WidgetConfigureAdapter.ItemViewHolder>() {
|
||||
@ -28,7 +29,7 @@ class WidgetConfigureAdapter @Inject constructor() : RecyclerView.Adapter<Widget
|
||||
val (student, isCurrent) = items[position]
|
||||
|
||||
with(holder.binding) {
|
||||
accountItemName.text = "${student.studentName} ${student.className}"
|
||||
accountItemName.text = "${student.nickOrName} ${student.className}"
|
||||
accountItemSchool.text = student.schoolName
|
||||
|
||||
with(accountItemImage) {
|
||||
|
@ -18,6 +18,8 @@ import io.github.wulkanowy.utils.getCompatDrawable
|
||||
import io.github.wulkanowy.utils.openAppInMarket
|
||||
import io.github.wulkanowy.utils.openEmailClient
|
||||
import io.github.wulkanowy.utils.openInternetBrowser
|
||||
import io.github.wulkanowy.utils.toFormattedString
|
||||
import io.github.wulkanowy.utils.toLocalDateTime
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
@ -35,7 +37,9 @@ class AboutFragment : BaseFragment<FragmentAboutBinding>(R.layout.fragment_about
|
||||
|
||||
override val versionRes: Triple<String, String, Drawable?>?
|
||||
get() = context?.run {
|
||||
Triple(getString(R.string.about_version), "${appInfo.versionName} (${appInfo.versionCode})", getCompatDrawable(R.drawable.ic_all_about))
|
||||
val buildTimestamp = appInfo.buildTimestamp.toLocalDateTime().toFormattedString("yyyy-MM-dd")
|
||||
val versionSignature = "${appInfo.versionName}-${appInfo.buildFlavor} (${appInfo.versionCode}), $buildTimestamp"
|
||||
Triple(getString(R.string.about_version), versionSignature, getCompatDrawable(R.drawable.ic_all_about))
|
||||
}
|
||||
|
||||
override val creatorsRes: Triple<String, String, Drawable?>?
|
||||
@ -65,7 +69,11 @@ class AboutFragment : BaseFragment<FragmentAboutBinding>(R.layout.fragment_about
|
||||
|
||||
override val homepageRes: Triple<String, String, Drawable?>?
|
||||
get() = context?.run {
|
||||
Triple(getString(R.string.about_homepage), getString(R.string.about_homepage_summary), getCompatDrawable(R.drawable.ic_about_homepage))
|
||||
Triple(
|
||||
getString(R.string.about_homepage),
|
||||
getString(R.string.about_homepage_summary),
|
||||
getCompatDrawable(R.drawable.ic_all_home)
|
||||
)
|
||||
}
|
||||
|
||||
override val licensesRes: Triple<String, String, Drawable?>?
|
||||
@ -131,11 +139,17 @@ class AboutFragment : BaseFragment<FragmentAboutBinding>(R.layout.fragment_about
|
||||
chooserTitle = getString(R.string.about_feedback),
|
||||
email = "wulkanowyinc@gmail.com",
|
||||
subject = "Zgłoszenie błędu",
|
||||
body = getString(R.string.about_feedback_template,
|
||||
"${appInfo.systemManufacturer} ${appInfo.systemModel}", appInfo.systemVersion.toString(), appInfo.versionName
|
||||
body = getString(
|
||||
R.string.about_feedback_template,
|
||||
"${appInfo.systemManufacturer} ${appInfo.systemModel}",
|
||||
appInfo.systemVersion.toString(),
|
||||
"${appInfo.versionName}-${appInfo.buildFlavor}"
|
||||
),
|
||||
onActivityNotFound = {
|
||||
requireContext().openInternetBrowser("https://github.com/wulkanowy/wulkanowy/issues", ::showMessage)
|
||||
requireContext().openInternetBrowser(
|
||||
"https://github.com/wulkanowy/wulkanowy/issues",
|
||||
::showMessage
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -8,16 +8,17 @@ import android.view.View.VISIBLE
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
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.HeaderAccountBinding
|
||||
import io.github.wulkanowy.databinding.ItemAccountBinding
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||
import io.github.wulkanowy.utils.nickOrName
|
||||
import javax.inject.Inject
|
||||
|
||||
class AccountAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||
|
||||
var isAccountQuickDialogMode = false
|
||||
|
||||
var items = emptyList<AccountItem<*>>()
|
||||
|
||||
var onClickListener: (StudentWithSemesters) -> Unit = {}
|
||||
@ -30,53 +31,68 @@ class AccountAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView.V
|
||||
val inflater = LayoutInflater.from(parent.context)
|
||||
|
||||
return when (viewType) {
|
||||
AccountItem.ViewType.HEADER.id -> HeaderViewHolder(HeaderAccountBinding.inflate(inflater, parent, false))
|
||||
AccountItem.ViewType.ITEM.id -> ItemViewHolder(ItemAccountBinding.inflate(inflater, parent, false))
|
||||
AccountItem.ViewType.HEADER.id -> HeaderViewHolder(
|
||||
HeaderAccountBinding.inflate(inflater, parent, false)
|
||||
)
|
||||
AccountItem.ViewType.ITEM.id -> ItemViewHolder(
|
||||
ItemAccountBinding.inflate(inflater, parent, false)
|
||||
)
|
||||
else -> throw IllegalStateException()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||
when (holder) {
|
||||
is HeaderViewHolder -> bindHeaderViewHolder(holder.binding, items[position].value as Account)
|
||||
is ItemViewHolder -> bindItemViewHolder(holder.binding, items[position].value as StudentWithSemesters)
|
||||
is HeaderViewHolder -> bindHeaderViewHolder(
|
||||
holder.binding,
|
||||
items[position].value as Account,
|
||||
position
|
||||
)
|
||||
is ItemViewHolder -> bindItemViewHolder(
|
||||
holder.binding,
|
||||
items[position].value as StudentWithSemesters
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun bindHeaderViewHolder(binding: HeaderAccountBinding, account: Account) {
|
||||
private fun bindHeaderViewHolder(
|
||||
binding: HeaderAccountBinding,
|
||||
account: Account,
|
||||
position: Int
|
||||
) {
|
||||
with(binding) {
|
||||
accountHeaderDivider.visibility = if (position == 0) GONE else VISIBLE
|
||||
accountHeaderEmail.text = account.email
|
||||
accountHeaderType.setText(if (account.isParent) R.string.account_type_parent else R.string.account_type_student)
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
private fun bindItemViewHolder(binding: ItemAccountBinding, studentWithSemesters: StudentWithSemesters) {
|
||||
private fun bindItemViewHolder(
|
||||
binding: ItemAccountBinding,
|
||||
studentWithSemesters: StudentWithSemesters
|
||||
) {
|
||||
val student = studentWithSemesters.student
|
||||
val semesters = studentWithSemesters.semesters
|
||||
val diary = semesters.maxByOrNull { it.semesterId }
|
||||
val isDuplicatedStudent = items.filter {
|
||||
if (it.value !is StudentWithSemesters) return@filter false
|
||||
val studentToCompare = it.value.student
|
||||
|
||||
studentToCompare.studentId == student.studentId
|
||||
&& studentToCompare.schoolSymbol == student.schoolSymbol
|
||||
&& studentToCompare.symbol == student.symbol
|
||||
}.size > 1 && isAccountQuickDialogMode
|
||||
|
||||
with(binding) {
|
||||
accountItemName.text = "${student.studentName} ${diary?.diaryName.orEmpty()}"
|
||||
accountItemName.text = "${student.nickOrName} ${diary?.diaryName.orEmpty()}"
|
||||
accountItemSchool.text = studentWithSemesters.student.schoolName
|
||||
with(accountItemLoginMode) {
|
||||
visibility = when (Sdk.Mode.valueOf(student.loginMode)) {
|
||||
Sdk.Mode.API -> {
|
||||
setText(R.string.account_login_mobile_api)
|
||||
VISIBLE
|
||||
}
|
||||
Sdk.Mode.HYBRID -> {
|
||||
setText(R.string.account_login_hybrid)
|
||||
VISIBLE
|
||||
}
|
||||
Sdk.Mode.SCRAPPER -> {
|
||||
GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
accountItemAccountType.setText(if (student.isParent) R.string.account_type_parent else R.string.account_type_student)
|
||||
accountItemAccountType.visibility = if (isDuplicatedStudent) VISIBLE else GONE
|
||||
|
||||
with(accountItemImage) {
|
||||
val colorImage = if (student.isCurrent) context.getThemeAttrColor(R.attr.colorPrimary)
|
||||
val colorImage =
|
||||
if (student.isCurrent) context.getThemeAttrColor(R.attr.colorPrimary)
|
||||
else context.getThemeAttrColor(R.attr.colorOnSurface, 153)
|
||||
|
||||
setColorFilter(colorImage, PorterDuff.Mode.SRC_IN)
|
||||
|
@ -1,102 +0,0 @@
|
||||
package io.github.wulkanowy.ui.modules.account
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import android.widget.Toast.LENGTH_LONG
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.databinding.DialogAccountBinding
|
||||
import io.github.wulkanowy.ui.base.BaseDialogFragment
|
||||
import io.github.wulkanowy.ui.modules.login.LoginActivity
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class AccountDialog : BaseDialogFragment<DialogAccountBinding>(), AccountView {
|
||||
|
||||
@Inject
|
||||
lateinit var presenter: AccountPresenter
|
||||
|
||||
@Inject
|
||||
lateinit var accountAdapter: AccountAdapter
|
||||
|
||||
companion object {
|
||||
fun newInstance() = AccountDialog()
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setStyle(STYLE_NO_TITLE, 0)
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
return DialogAccountBinding.inflate(inflater).apply { binding = this }.root
|
||||
}
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
presenter.onAttachView(this)
|
||||
}
|
||||
|
||||
override fun initView() {
|
||||
accountAdapter.onClickListener = presenter::onItemSelected
|
||||
|
||||
with(binding) {
|
||||
accountDialogAdd.setOnClickListener { presenter.onAddSelected() }
|
||||
accountDialogRemove.setOnClickListener { presenter.onRemoveSelected() }
|
||||
accountDialogRecycler.apply {
|
||||
layoutManager = LinearLayoutManager(context)
|
||||
adapter = accountAdapter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateData(data: List<AccountItem<*>>) {
|
||||
with(accountAdapter) {
|
||||
items = data
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
override fun showError(text: String, error: Throwable) {
|
||||
showMessage(text)
|
||||
}
|
||||
|
||||
override fun showMessage(text: String) {
|
||||
Toast.makeText(context, text, LENGTH_LONG).show()
|
||||
}
|
||||
|
||||
override fun dismissView() {
|
||||
dismiss()
|
||||
}
|
||||
|
||||
override fun openLoginView() {
|
||||
activity?.let {
|
||||
startActivity(LoginActivity.getStartIntent(it))
|
||||
}
|
||||
}
|
||||
|
||||
override fun showConfirmDialog() {
|
||||
context?.let {
|
||||
AlertDialog.Builder(it)
|
||||
.setTitle(R.string.account_logout_student)
|
||||
.setMessage(R.string.account_confirm)
|
||||
.setPositiveButton(R.string.account_logout) { _, _ -> presenter.onLogoutConfirm() }
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
override fun recreateMainView() {
|
||||
activity?.recreate()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
presenter.onDetachView()
|
||||
super.onDestroy()
|
||||
}
|
||||
}
|
@ -0,0 +1,111 @@
|
||||
package io.github.wulkanowy.ui.modules.account
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.View
|
||||
import androidx.core.view.get
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||
import io.github.wulkanowy.databinding.FragmentAccountBinding
|
||||
import io.github.wulkanowy.ui.base.BaseFragment
|
||||
import io.github.wulkanowy.ui.modules.account.accountdetails.AccountDetailsFragment
|
||||
import io.github.wulkanowy.ui.modules.login.LoginActivity
|
||||
import io.github.wulkanowy.ui.modules.main.MainActivity
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class AccountFragment : BaseFragment<FragmentAccountBinding>(R.layout.fragment_account),
|
||||
AccountView, MainView.TitledView {
|
||||
|
||||
@Inject
|
||||
lateinit var presenter: AccountPresenter
|
||||
|
||||
@Inject
|
||||
lateinit var accountAdapter: AccountAdapter
|
||||
|
||||
companion object {
|
||||
|
||||
fun newInstance() = AccountFragment()
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
accountAdapter.onClickListener = presenter::onItemSelected
|
||||
|
||||
with(binding) {
|
||||
accountAdd.setOnClickListener { presenter.onAddSelected() }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
menu[0].isVisible = false
|
||||
}
|
||||
|
||||
override fun updateData(data: List<AccountItem<*>>) {
|
||||
with(accountAdapter) {
|
||||
items = data
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
override fun openLoginView() {
|
||||
activity?.let {
|
||||
startActivity(LoginActivity.getStartIntent(it))
|
||||
}
|
||||
}
|
||||
|
||||
override fun openAccountDetailsView(studentWithSemesters: StudentWithSemesters) {
|
||||
(activity as? MainActivity)?.pushView(
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
@ -3,7 +3,6 @@ package io.github.wulkanowy.ui.modules.account
|
||||
import io.github.wulkanowy.data.Status
|
||||
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||
import io.github.wulkanowy.services.sync.SyncManager
|
||||
import io.github.wulkanowy.ui.base.BasePresenter
|
||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||
import io.github.wulkanowy.utils.afterLoading
|
||||
@ -15,101 +14,91 @@ import javax.inject.Inject
|
||||
class AccountPresenter @Inject constructor(
|
||||
errorHandler: ErrorHandler,
|
||||
studentRepository: StudentRepository,
|
||||
private val syncManager: SyncManager
|
||||
) : BasePresenter<AccountView>(errorHandler, studentRepository) {
|
||||
|
||||
private lateinit var lastError: Throwable
|
||||
|
||||
override fun onAttachView(view: AccountView) {
|
||||
super.onAttachView(view)
|
||||
view.initView()
|
||||
Timber.i("Account dialog view was initialized")
|
||||
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()
|
||||
}
|
||||
|
||||
fun onRemoveSelected() {
|
||||
Timber.i("Select remove account")
|
||||
view?.showConfirmDialog()
|
||||
}
|
||||
|
||||
fun onLogoutConfirm() {
|
||||
flowWithResource {
|
||||
val student = studentRepository.getCurrentStudent(false)
|
||||
studentRepository.logoutStudent(student)
|
||||
|
||||
val students = studentRepository.getSavedStudents(false)
|
||||
if (students.isNotEmpty()) {
|
||||
studentRepository.switchStudent(students[0])
|
||||
}
|
||||
students
|
||||
}.onEach {
|
||||
when (it.status) {
|
||||
Status.LOADING -> Timber.i("Attempt to logout current user ")
|
||||
Status.SUCCESS -> view?.run {
|
||||
if (it.data!!.isEmpty()) {
|
||||
Timber.i("Logout result: Open login view")
|
||||
syncManager.stopSyncWorker()
|
||||
openClearLoginView()
|
||||
} else {
|
||||
Timber.i("Logout result: Switch to another student")
|
||||
recreateMainView()
|
||||
}
|
||||
}
|
||||
Status.ERROR -> {
|
||||
Timber.i("Logout result: An exception occurred")
|
||||
errorHandler.dispatch(it.error!!)
|
||||
}
|
||||
}
|
||||
}.afterLoading {
|
||||
view?.dismissView()
|
||||
}.launch("logout")
|
||||
}
|
||||
|
||||
fun onItemSelected(studentWithSemesters: StudentWithSemesters) {
|
||||
Timber.i("Select student item ${studentWithSemesters.student.id}")
|
||||
if (studentWithSemesters.student.isCurrent) {
|
||||
view?.dismissView()
|
||||
} else flowWithResource { studentRepository.switchStudent(studentWithSemesters) }.onEach {
|
||||
when (it.status) {
|
||||
Status.LOADING -> Timber.i("Attempt to change a student")
|
||||
Status.SUCCESS -> {
|
||||
Timber.i("Change a student result: Success")
|
||||
view?.recreateMainView()
|
||||
}
|
||||
Status.ERROR -> {
|
||||
Timber.i("Change a student result: An exception occurred")
|
||||
errorHandler.dispatch(it.error!!)
|
||||
}
|
||||
}
|
||||
}.afterLoading {
|
||||
view?.dismissView()
|
||||
}.launch("switch")
|
||||
view?.openAccountDetailsView(studentWithSemesters)
|
||||
}
|
||||
|
||||
private fun createAccountItems(items: List<StudentWithSemesters>): List<AccountItem<*>> {
|
||||
return items.groupBy { Account(it.student.email, it.student.isParent) }.map { (account, students) ->
|
||||
listOf(AccountItem(account, AccountItem.ViewType.HEADER)) + students.map { student ->
|
||||
return items.groupBy {
|
||||
Account("${it.student.userName} (${it.student.email})", it.student.isParent)
|
||||
}
|
||||
.map { (account, students) ->
|
||||
listOf(
|
||||
AccountItem(account, AccountItem.ViewType.HEADER)
|
||||
) + students.map { student ->
|
||||
AccountItem(student, AccountItem.ViewType.ITEM)
|
||||
}
|
||||
}.flatten()
|
||||
}
|
||||
.flatten()
|
||||
}
|
||||
|
||||
private fun loadData() {
|
||||
flowWithResource { studentRepository.getSavedStudents(false) }.onEach {
|
||||
flowWithResource { studentRepository.getSavedStudents(false) }
|
||||
.onEach {
|
||||
when (it.status) {
|
||||
Status.LOADING -> Timber.i("Loading account data started")
|
||||
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!!)
|
||||
}
|
||||
}
|
||||
}.launch()
|
||||
}
|
||||
.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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +1,26 @@
|
||||
package io.github.wulkanowy.ui.modules.account
|
||||
|
||||
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||
import io.github.wulkanowy.ui.base.BaseView
|
||||
|
||||
interface AccountView : BaseView {
|
||||
|
||||
val isViewEmpty: Boolean
|
||||
|
||||
fun initView()
|
||||
|
||||
fun updateData(data: List<AccountItem<*>>)
|
||||
|
||||
fun dismissView()
|
||||
|
||||
fun showConfirmDialog()
|
||||
|
||||
fun openLoginView()
|
||||
|
||||
fun recreateMainView()
|
||||
fun openAccountDetailsView(studentWithSemesters: StudentWithSemesters)
|
||||
|
||||
fun showErrorView(show: Boolean)
|
||||
|
||||
fun setErrorDetails(message: String)
|
||||
|
||||
fun showProgress(show: Boolean)
|
||||
|
||||
fun showContent(show: Boolean)
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,157 @@
|
||||
package io.github.wulkanowy.ui.modules.account.accountdetails
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.view.get
|
||||
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.FragmentAccountDetailsBinding
|
||||
import io.github.wulkanowy.ui.base.BaseFragment
|
||||
import io.github.wulkanowy.ui.modules.account.accountedit.AccountEditDialog
|
||||
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.nickOrName
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class AccountDetailsFragment :
|
||||
BaseFragment<FragmentAccountDetailsBinding>(R.layout.fragment_account_details),
|
||||
AccountDetailsView, MainView.TitledView {
|
||||
|
||||
@Inject
|
||||
lateinit var presenter: AccountDetailsPresenter
|
||||
|
||||
override val titleStringId = R.string.account_details_title
|
||||
|
||||
override var subtitleString = ""
|
||||
|
||||
companion object {
|
||||
|
||||
private const val ARGUMENT_KEY = "Data"
|
||||
|
||||
fun newInstance(studentWithSemesters: StudentWithSemesters) =
|
||||
AccountDetailsFragment().apply {
|
||||
arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, studentWithSemesters) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setHasOptionsMenu(true)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
binding = FragmentAccountDetailsBinding.bind(view)
|
||||
presenter.onAttachView(this, requireArguments()[ARGUMENT_KEY] as StudentWithSemesters)
|
||||
}
|
||||
|
||||
override fun initView() {
|
||||
binding.accountDetailsErrorRetry.setOnClickListener { presenter.onRetry() }
|
||||
binding.accountDetailsErrorDetails.setOnClickListener { presenter.onDetailsClick() }
|
||||
binding.accountDetailsLogout.setOnClickListener { presenter.onRemoveSelected() }
|
||||
binding.accountDetailsSelect.setOnClickListener { presenter.onStudentSelect() }
|
||||
|
||||
binding.accountDetailsPersonalData.setOnClickListener {
|
||||
presenter.onStudentInfoSelected(StudentInfoView.Type.PERSONAL)
|
||||
}
|
||||
binding.accountDetailsAddressData.setOnClickListener {
|
||||
presenter.onStudentInfoSelected(StudentInfoView.Type.ADDRESS)
|
||||
}
|
||||
binding.accountDetailsContactData.setOnClickListener {
|
||||
presenter.onStudentInfoSelected(StudentInfoView.Type.CONTACT)
|
||||
}
|
||||
binding.accountDetailsFamilyData.setOnClickListener {
|
||||
presenter.onStudentInfoSelected(StudentInfoView.Type.FAMILY)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
menu[0].isVisible = false
|
||||
inflater.inflate(R.menu.action_menu_account_details, menu)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return if (item.itemId == R.id.accountDetailsMenuEdit) {
|
||||
presenter.onAccountEditSelected()
|
||||
true
|
||||
} else false
|
||||
}
|
||||
|
||||
override fun showAccountData(student: Student) {
|
||||
with(binding) {
|
||||
accountDetailsName.text = student.nickOrName
|
||||
accountDetailsSchool.text = student.schoolName
|
||||
}
|
||||
}
|
||||
|
||||
override fun enableSelectStudentButton(enable: Boolean) {
|
||||
binding.accountDetailsSelect.isEnabled = enable
|
||||
}
|
||||
|
||||
override fun showAccountEditDetailsDialog(student: Student) {
|
||||
(requireActivity() as MainActivity).showDialogFragment(
|
||||
AccountEditDialog.newInstance(student)
|
||||
)
|
||||
}
|
||||
|
||||
override fun showLogoutConfirmDialog() {
|
||||
context?.let {
|
||||
AlertDialog.Builder(it)
|
||||
.setTitle(R.string.account_logout_student)
|
||||
.setMessage(R.string.account_confirm)
|
||||
.setPositiveButton(R.string.account_logout) { _, _ -> presenter.onLogoutConfirm() }
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
override fun popView() {
|
||||
(requireActivity() as MainActivity).popView(2)
|
||||
}
|
||||
|
||||
override fun recreateMainView() {
|
||||
requireActivity().recreate()
|
||||
}
|
||||
|
||||
override fun openStudentInfoView(
|
||||
infoType: StudentInfoView.Type,
|
||||
studentWithSemesters: StudentWithSemesters
|
||||
) {
|
||||
(requireActivity() as MainActivity).pushView(
|
||||
StudentInfoFragment.newInstance(
|
||||
infoType,
|
||||
studentWithSemesters
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun showErrorView(show: Boolean) {
|
||||
binding.accountDetailsError.visibility = if (show) View.VISIBLE else View.GONE
|
||||
}
|
||||
|
||||
override fun setErrorDetails(message: String) {
|
||||
binding.accountDetailsErrorMessage.text = message
|
||||
}
|
||||
|
||||
override fun showProgress(show: Boolean) {
|
||||
binding.accountDetailsProgress.visibility = if (show) View.VISIBLE else View.GONE
|
||||
}
|
||||
|
||||
override fun showContent(show: Boolean) {
|
||||
binding.accountDetailsContent.visibility = if (show) View.VISIBLE else View.GONE
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
presenter.onDetachView()
|
||||
super.onDestroyView()
|
||||
}
|
||||
}
|
@ -0,0 +1,172 @@
|
||||
package io.github.wulkanowy.ui.modules.account.accountdetails
|
||||
|
||||
import io.github.wulkanowy.data.Resource
|
||||
import io.github.wulkanowy.data.Status
|
||||
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||
import io.github.wulkanowy.services.sync.SyncManager
|
||||
import io.github.wulkanowy.ui.base.BasePresenter
|
||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||
import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoView
|
||||
import io.github.wulkanowy.utils.afterLoading
|
||||
import io.github.wulkanowy.utils.flowWithResource
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
class AccountDetailsPresenter @Inject constructor(
|
||||
errorHandler: ErrorHandler,
|
||||
studentRepository: StudentRepository,
|
||||
private val syncManager: SyncManager
|
||||
) : BasePresenter<AccountDetailsView>(errorHandler, studentRepository) {
|
||||
|
||||
private lateinit var studentWithSemesters: StudentWithSemesters
|
||||
|
||||
private lateinit var lastError: Throwable
|
||||
|
||||
private var studentId: Long? = null
|
||||
|
||||
fun onAttachView(view: AccountDetailsView, studentWithSemesters: StudentWithSemesters) {
|
||||
super.onAttachView(view)
|
||||
studentId = studentWithSemesters.student.id
|
||||
|
||||
view.initView()
|
||||
errorHandler.showErrorMessage = ::showErrorViewOnError
|
||||
Timber.i("Account details view was initialized")
|
||||
loadData()
|
||||
}
|
||||
|
||||
fun onRetry() {
|
||||
view?.run {
|
||||
showErrorView(false)
|
||||
showProgress(true)
|
||||
}
|
||||
loadData()
|
||||
}
|
||||
|
||||
fun onDetailsClick() {
|
||||
view?.showErrorDetailsDialog(lastError)
|
||||
}
|
||||
|
||||
private fun loadData() {
|
||||
flowWithResource { studentRepository.getSavedStudents() }
|
||||
.map { studentWithSemesters ->
|
||||
Resource(
|
||||
data = studentWithSemesters.data?.single { it.student.id == studentId },
|
||||
status = studentWithSemesters.status,
|
||||
error = studentWithSemesters.error
|
||||
)
|
||||
}
|
||||
.onEach {
|
||||
when (it.status) {
|
||||
Status.LOADING -> {
|
||||
view?.run {
|
||||
showProgress(true)
|
||||
showContent(false)
|
||||
}
|
||||
Timber.i("Loading account details view started")
|
||||
}
|
||||
Status.SUCCESS -> {
|
||||
Timber.i("Loading account details view result: Success")
|
||||
studentWithSemesters = it.data!!
|
||||
view?.run {
|
||||
showAccountData(studentWithSemesters.student)
|
||||
enableSelectStudentButton(!studentWithSemesters.student.isCurrent)
|
||||
showContent(true)
|
||||
showErrorView(false)
|
||||
}
|
||||
}
|
||||
Status.ERROR -> {
|
||||
Timber.i("Loading account details view result: An exception occurred")
|
||||
errorHandler.dispatch(it.error!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
.afterLoading { view?.showProgress(false) }
|
||||
.launch()
|
||||
}
|
||||
|
||||
fun onAccountEditSelected() {
|
||||
view?.showAccountEditDetailsDialog(studentWithSemesters.student)
|
||||
}
|
||||
|
||||
fun onStudentInfoSelected(infoType: StudentInfoView.Type) {
|
||||
view?.openStudentInfoView(infoType, studentWithSemesters)
|
||||
}
|
||||
|
||||
fun onStudentSelect() {
|
||||
Timber.i("Select student ${studentWithSemesters.student.id}")
|
||||
|
||||
flowWithResource { studentRepository.switchStudent(studentWithSemesters) }
|
||||
.onEach {
|
||||
when (it.status) {
|
||||
Status.LOADING -> Timber.i("Attempt to change a student")
|
||||
Status.SUCCESS -> {
|
||||
Timber.i("Change a student result: Success")
|
||||
view?.recreateMainView()
|
||||
}
|
||||
Status.ERROR -> {
|
||||
Timber.i("Change a student result: An exception occurred")
|
||||
errorHandler.dispatch(it.error!!)
|
||||
}
|
||||
}
|
||||
}.afterLoading {
|
||||
view?.popView()
|
||||
}.launch("switch")
|
||||
}
|
||||
|
||||
fun onRemoveSelected() {
|
||||
Timber.i("Select remove account")
|
||||
view?.showLogoutConfirmDialog()
|
||||
}
|
||||
|
||||
fun onLogoutConfirm() {
|
||||
flowWithResource {
|
||||
val studentToLogout = studentWithSemesters.student
|
||||
|
||||
studentRepository.logoutStudent(studentToLogout)
|
||||
val students = studentRepository.getSavedStudents(false)
|
||||
|
||||
if (studentToLogout.isCurrent && students.isNotEmpty()) {
|
||||
studentRepository.switchStudent(students[0])
|
||||
}
|
||||
|
||||
return@flowWithResource students
|
||||
}.onEach {
|
||||
when (it.status) {
|
||||
Status.LOADING -> Timber.i("Attempt to logout user")
|
||||
Status.SUCCESS -> view?.run {
|
||||
when {
|
||||
it.data!!.isEmpty() -> {
|
||||
Timber.i("Logout result: Open login view")
|
||||
syncManager.stopSyncWorker()
|
||||
openClearLoginView()
|
||||
}
|
||||
studentWithSemesters.student.isCurrent -> {
|
||||
Timber.i("Logout result: Logout student and switch to another")
|
||||
recreateMainView()
|
||||
}
|
||||
else -> Timber.i("Logout result: Logout student")
|
||||
}
|
||||
}
|
||||
Status.ERROR -> {
|
||||
Timber.i("Logout result: An exception occurred")
|
||||
errorHandler.dispatch(it.error!!)
|
||||
}
|
||||
}
|
||||
}.afterLoading {
|
||||
view?.popView()
|
||||
}.launch("logout")
|
||||
}
|
||||
|
||||
private fun showErrorViewOnError(message: String, error: Throwable) {
|
||||
view?.run {
|
||||
lastError = error
|
||||
setErrorDetails(message)
|
||||
showErrorView(true)
|
||||
showContent(false)
|
||||
showProgress(false)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package io.github.wulkanowy.ui.modules.account.accountdetails
|
||||
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||
import io.github.wulkanowy.ui.base.BaseView
|
||||
import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoView
|
||||
|
||||
interface AccountDetailsView : BaseView {
|
||||
|
||||
fun initView()
|
||||
|
||||
fun showAccountData(student: Student)
|
||||
|
||||
fun showAccountEditDetailsDialog(student: Student)
|
||||
|
||||
fun showLogoutConfirmDialog()
|
||||
|
||||
fun popView()
|
||||
|
||||
fun recreateMainView()
|
||||
|
||||
fun enableSelectStudentButton(enable: Boolean)
|
||||
|
||||
fun openStudentInfoView(
|
||||
infoType: StudentInfoView.Type,
|
||||
studentWithSemesters: StudentWithSemesters
|
||||
)
|
||||
|
||||
fun showErrorView(show: Boolean)
|
||||
|
||||
fun setErrorDetails(message: String)
|
||||
|
||||
fun showProgress(show: Boolean)
|
||||
|
||||
fun showContent(show: Boolean)
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
package io.github.wulkanowy.ui.modules.account.accountedit
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.databinding.DialogAccountEditBinding
|
||||
import io.github.wulkanowy.ui.base.BaseDialogFragment
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class AccountEditDialog : BaseDialogFragment<DialogAccountEditBinding>(), AccountEditView {
|
||||
|
||||
@Inject
|
||||
lateinit var presenter: AccountEditPresenter
|
||||
|
||||
companion object {
|
||||
|
||||
private const val ARGUMENT_KEY = "student_with_semesters"
|
||||
|
||||
fun newInstance(student: Student) =
|
||||
AccountEditDialog().apply {
|
||||
arguments = Bundle().apply {
|
||||
putSerializable(ARGUMENT_KEY, student)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setStyle(STYLE_NO_TITLE, 0)
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View = DialogAccountEditBinding.inflate(inflater).apply { binding = this }.root
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
presenter.onAttachView(this, requireArguments()[ARGUMENT_KEY] as Student)
|
||||
}
|
||||
|
||||
override fun initView() {
|
||||
with(binding) {
|
||||
accountEditDetailsCancel.setOnClickListener { dismiss() }
|
||||
accountEditDetailsSave.setOnClickListener {
|
||||
presenter.changeStudentNick(binding.accountEditDetailsNickText.text.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun showCurrentNick(nick: String) {
|
||||
binding.accountEditDetailsNickText.setText(nick)
|
||||
}
|
||||
|
||||
override fun popView() {
|
||||
dismiss()
|
||||
}
|
||||
|
||||
override fun recreateMainView() {
|
||||
activity?.recreate()
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
presenter.onDetachView()
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
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.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
|
||||
import javax.inject.Inject
|
||||
|
||||
class AccountEditPresenter @Inject constructor(
|
||||
errorHandler: ErrorHandler,
|
||||
studentRepository: StudentRepository
|
||||
) : BasePresenter<AccountEditView>(errorHandler, studentRepository) {
|
||||
|
||||
lateinit var student: Student
|
||||
|
||||
fun onAttachView(view: AccountEditView, student: Student) {
|
||||
super.onAttachView(view)
|
||||
this.student = student
|
||||
|
||||
with(view) {
|
||||
initView()
|
||||
showCurrentNick(student.nick.trim())
|
||||
}
|
||||
Timber.i("Account edit dialog view was initialized")
|
||||
}
|
||||
|
||||
fun changeStudentNick(nick: String) {
|
||||
flowWithResource {
|
||||
val studentNick =
|
||||
StudentNick(nick = nick.trim()).apply { id = student.id }
|
||||
studentRepository.updateStudentNick(studentNick)
|
||||
}.onEach {
|
||||
when (it.status) {
|
||||
Status.LOADING -> Timber.i("Attempt to change a student nick")
|
||||
Status.SUCCESS -> {
|
||||
Timber.i("Change a student nick result: Success")
|
||||
view?.recreateMainView()
|
||||
}
|
||||
Status.ERROR -> {
|
||||
Timber.i("Change a student result: An exception occurred")
|
||||
errorHandler.dispatch(it.error!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
.afterLoading { view?.popView() }
|
||||
.launch()
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package io.github.wulkanowy.ui.modules.account.accountedit
|
||||
|
||||
import io.github.wulkanowy.ui.base.BaseView
|
||||
|
||||
interface AccountEditView : BaseView {
|
||||
|
||||
fun initView()
|
||||
|
||||
fun popView()
|
||||
|
||||
fun recreateMainView()
|
||||
|
||||
fun showCurrentNick(nick: String)
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
package io.github.wulkanowy.ui.modules.account.accountquick
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.github.wulkanowy.databinding.DialogAccountQuickBinding
|
||||
import io.github.wulkanowy.ui.base.BaseDialogFragment
|
||||
import io.github.wulkanowy.ui.modules.account.AccountAdapter
|
||||
import io.github.wulkanowy.ui.modules.account.AccountFragment
|
||||
import io.github.wulkanowy.ui.modules.account.AccountItem
|
||||
import io.github.wulkanowy.ui.modules.main.MainActivity
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class AccountQuickDialog : BaseDialogFragment<DialogAccountQuickBinding>(), AccountQuickView {
|
||||
|
||||
@Inject
|
||||
lateinit var accountAdapter: AccountAdapter
|
||||
|
||||
@Inject
|
||||
lateinit var presenter: AccountQuickPresenter
|
||||
|
||||
companion object {
|
||||
fun newInstance() = AccountQuickDialog()
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setStyle(STYLE_NO_TITLE, 0)
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
) = DialogAccountQuickBinding.inflate(inflater).apply { binding = this }.root
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
presenter.onAttachView(this)
|
||||
}
|
||||
|
||||
override fun initView() {
|
||||
binding.accountQuickDialogManger.setOnClickListener { presenter.onManagerSelected() }
|
||||
|
||||
with(accountAdapter) {
|
||||
isAccountQuickDialogMode = true
|
||||
onClickListener = presenter::onStudentSelect
|
||||
}
|
||||
|
||||
with(binding.accountQuickDialogRecycler) {
|
||||
layoutManager = LinearLayoutManager(context)
|
||||
adapter = accountAdapter
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateData(data: List<AccountItem<*>>) {
|
||||
with(accountAdapter) {
|
||||
items = data
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
override fun popView() {
|
||||
dismiss()
|
||||
}
|
||||
|
||||
override fun recreateMainView() {
|
||||
activity?.recreate()
|
||||
}
|
||||
|
||||
override fun openAccountView() {
|
||||
(requireActivity() as MainActivity).pushView(AccountFragment.newInstance())
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
presenter.onDetachView()
|
||||
super.onDestroyView()
|
||||
}
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
package io.github.wulkanowy.ui.modules.account.accountquick
|
||||
|
||||
import io.github.wulkanowy.data.Status
|
||||
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.ui.modules.account.AccountItem
|
||||
import io.github.wulkanowy.utils.afterLoading
|
||||
import io.github.wulkanowy.utils.flowWithResource
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
class AccountQuickPresenter @Inject constructor(
|
||||
errorHandler: ErrorHandler,
|
||||
studentRepository: StudentRepository
|
||||
) : BasePresenter<AccountQuickView>(errorHandler, studentRepository) {
|
||||
|
||||
override fun onAttachView(view: AccountQuickView) {
|
||||
super.onAttachView(view)
|
||||
view.initView()
|
||||
Timber.i("Account quick dialog view was initialized")
|
||||
loadData()
|
||||
}
|
||||
|
||||
fun onManagerSelected() {
|
||||
view?.run {
|
||||
openAccountView()
|
||||
popView()
|
||||
}
|
||||
}
|
||||
|
||||
fun onStudentSelect(studentWithSemesters: StudentWithSemesters) {
|
||||
Timber.i("Select student ${studentWithSemesters.student.id}")
|
||||
|
||||
if (studentWithSemesters.student.isCurrent) {
|
||||
view?.popView()
|
||||
return
|
||||
}
|
||||
|
||||
flowWithResource { studentRepository.switchStudent(studentWithSemesters) }
|
||||
.onEach {
|
||||
when (it.status) {
|
||||
Status.LOADING -> Timber.i("Attempt to change a student")
|
||||
Status.SUCCESS -> {
|
||||
Timber.i("Change a student result: Success")
|
||||
view?.recreateMainView()
|
||||
}
|
||||
Status.ERROR -> {
|
||||
Timber.i("Change a student result: An exception occurred")
|
||||
errorHandler.dispatch(it.error!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
.afterLoading { view?.popView() }
|
||||
.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)
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package io.github.wulkanowy.ui.modules.account.accountquick
|
||||
|
||||
import io.github.wulkanowy.ui.base.BaseView
|
||||
import io.github.wulkanowy.ui.modules.account.AccountItem
|
||||
|
||||
interface AccountQuickView : BaseView {
|
||||
|
||||
fun initView()
|
||||
|
||||
fun updateData(data: List<AccountItem<*>>)
|
||||
|
||||
fun recreateMainView()
|
||||
|
||||
fun popView()
|
||||
|
||||
fun openAccountView()
|
||||
}
|
@ -26,6 +26,7 @@ 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
|
||||
|
||||
@ -60,6 +61,7 @@ class AttendanceFragment : BaseFragment<FragmentAttendanceBinding>(R.layout.frag
|
||||
override val excuseActionMode: Boolean get() = attendanceAdapter.excuseActionMode
|
||||
|
||||
private var actionMode: ActionMode? = null
|
||||
|
||||
private val actionModeCallback = object : ActionMode.Callback {
|
||||
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
|
||||
val inflater = mode.menuInflater
|
||||
@ -111,6 +113,8 @@ class AttendanceFragment : BaseFragment<FragmentAttendanceBinding>(R.layout.frag
|
||||
|
||||
with(binding) {
|
||||
attendanceSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
|
||||
attendanceSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary))
|
||||
attendanceSwipe.setProgressBackgroundColorSchemeColor(requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh))
|
||||
attendanceErrorRetry.setOnClickListener { presenter.onRetry() }
|
||||
attendanceErrorDetails.setOnClickListener { presenter.onDetailsClick() }
|
||||
|
||||
@ -222,6 +226,7 @@ class AttendanceFragment : BaseFragment<FragmentAttendanceBinding>(R.layout.frag
|
||||
setDateRangeLimiter(SchooldaysRangeLimiter())
|
||||
version = DatePickerDialog.Version.VERSION_2
|
||||
scrollOrientation = DatePickerDialog.ScrollOrientation.VERTICAL
|
||||
vibrate(false)
|
||||
show(this@AttendanceFragment.parentFragmentManager, null)
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ import io.github.wulkanowy.databinding.FragmentAttendanceSummaryBinding
|
||||
import io.github.wulkanowy.ui.base.BaseFragment
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import io.github.wulkanowy.utils.dpToPx
|
||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||
import io.github.wulkanowy.utils.setOnItemSelectedListener
|
||||
import javax.inject.Inject
|
||||
|
||||
@ -56,6 +57,8 @@ class AttendanceSummaryFragment :
|
||||
|
||||
with(binding) {
|
||||
attendanceSummarySwipe.setOnRefreshListener(presenter::onSwipeRefresh)
|
||||
attendanceSummarySwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary))
|
||||
attendanceSummarySwipe.setProgressBackgroundColorSchemeColor(requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh))
|
||||
attendanceSummaryErrorRetry.setOnClickListener { presenter.onRetry() }
|
||||
attendanceSummaryErrorDetails.setOnClickListener { presenter.onDetailsClick() }
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import io.github.wulkanowy.databinding.FragmentConferenceBinding
|
||||
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.getThemeAttrColor
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
@ -47,7 +48,9 @@ class ConferenceFragment : BaseFragment<FragmentConferenceBinding>(R.layout.frag
|
||||
}
|
||||
|
||||
with(binding) {
|
||||
conferenceSwipe.setOnRefreshListener { presenter.onSwipeRefresh() }
|
||||
conferenceSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
|
||||
conferenceSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary))
|
||||
conferenceSwipe.setProgressBackgroundColorSchemeColor(requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh))
|
||||
conferenceErrorRetry.setOnClickListener { presenter.onRetry() }
|
||||
conferenceErrorDetails.setOnClickListener { presenter.onDetailsClick() }
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ import io.github.wulkanowy.ui.modules.main.MainActivity
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import io.github.wulkanowy.ui.widgets.DividerItemDecoration
|
||||
import io.github.wulkanowy.utils.dpToPx
|
||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
@ -55,6 +56,8 @@ class ExamFragment : BaseFragment<FragmentExamBinding>(R.layout.fragment_exam),
|
||||
|
||||
with(binding) {
|
||||
examSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
|
||||
examSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary))
|
||||
examSwipe.setProgressBackgroundColorSchemeColor(requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh))
|
||||
examErrorRetry.setOnClickListener { presenter.onRetry() }
|
||||
examErrorDetails.setOnClickListener { presenter.onDetailsClick() }
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
package io.github.wulkanowy.ui.modules.grade
|
||||
|
||||
import io.github.wulkanowy.data.Resource
|
||||
import io.github.wulkanowy.data.Status
|
||||
import io.github.wulkanowy.data.db.entities.Grade
|
||||
import io.github.wulkanowy.data.db.entities.GradeSummary
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
@ -33,68 +34,144 @@ class GradeAverageProvider @Inject constructor(
|
||||
|
||||
private val minusModifier get() = preferencesRepository.gradeMinusModifier
|
||||
|
||||
fun getGradesDetailsWithAverage(student: Student, semesterId: Int, forceRefresh: Boolean) = flowWithResourceIn {
|
||||
fun getGradesDetailsWithAverage(student: Student, semesterId: Int, forceRefresh: Boolean) =
|
||||
flowWithResourceIn {
|
||||
val semesters = semesterRepository.getSemesters(student)
|
||||
|
||||
when (preferencesRepository.gradeAverageMode) {
|
||||
ONE_SEMESTER -> getSemesterDetailsWithAverage(student, semesters.single { it.semesterId == semesterId }, forceRefresh)
|
||||
BOTH_SEMESTERS -> calculateBothSemestersAverage(student, semesters, semesterId, forceRefresh)
|
||||
ALL_YEAR -> calculateAllYearAverage(student, semesters, semesterId, forceRefresh)
|
||||
ONE_SEMESTER -> getGradeSubjects(
|
||||
student = student,
|
||||
semester = semesters.single { it.semesterId == semesterId },
|
||||
forceRefresh = forceRefresh
|
||||
)
|
||||
BOTH_SEMESTERS -> calculateCombinedAverage(
|
||||
student = student,
|
||||
semesters = semesters,
|
||||
semesterId = semesterId,
|
||||
forceRefresh = forceRefresh,
|
||||
averageMode = BOTH_SEMESTERS
|
||||
)
|
||||
ALL_YEAR -> calculateCombinedAverage(
|
||||
student = student,
|
||||
semesters = semesters,
|
||||
semesterId = semesterId,
|
||||
forceRefresh = forceRefresh,
|
||||
averageMode = ALL_YEAR
|
||||
)
|
||||
}
|
||||
}.distinctUntilChanged()
|
||||
|
||||
private fun calculateBothSemestersAverage(student: Student, semesters: List<Semester>, semesterId: Int, forceRefresh: Boolean): Flow<Resource<List<GradeDetailsWithAverage>>> {
|
||||
private fun calculateCombinedAverage(
|
||||
student: Student,
|
||||
semesters: List<Semester>,
|
||||
semesterId: Int,
|
||||
forceRefresh: Boolean,
|
||||
averageMode: GradeAverageMode
|
||||
): Flow<Resource<List<GradeSubject>>> {
|
||||
val gradeAverageForceCalc = preferencesRepository.gradeAverageForceCalc
|
||||
val selectedSemester = semesters.single { it.semesterId == semesterId }
|
||||
val firstSemester = semesters.single { it.diaryId == selectedSemester.diaryId && it.semesterName == 1 }
|
||||
val firstSemester =
|
||||
semesters.single { it.diaryId == selectedSemester.diaryId && it.semesterName == 1 }
|
||||
|
||||
val selectedSemesterDetailsWithAverage = getSemesterDetailsWithAverage(student, selectedSemester, forceRefresh)
|
||||
val selectedSemesterGradeSubjects =
|
||||
getGradeSubjects(student, selectedSemester, forceRefresh)
|
||||
|
||||
return if (selectedSemester == firstSemester) selectedSemesterDetailsWithAverage else {
|
||||
val firstSemesterDetailsWithAverage = getSemesterDetailsWithAverage(student, firstSemester, forceRefresh)
|
||||
selectedSemesterDetailsWithAverage.combine(firstSemesterDetailsWithAverage) { selectedDetails, secondDetails ->
|
||||
val isAnyAverage = selectedDetails.data.orEmpty().any { it.average != .0 }
|
||||
secondDetails.copy(data = selectedDetails.data?.map { selected ->
|
||||
val second = secondDetails.data.orEmpty().singleOrNull { it.subject == selected.subject }
|
||||
selected.copy(average = if (!isAnyAverage || preferencesRepository.gradeAverageForceCalc) {
|
||||
val selectedGrades = selected.grades.updateModifiers(student).calcAverage()
|
||||
(selectedGrades + (second?.grades?.updateModifiers(student)?.calcAverage() ?: selectedGrades)) / 2
|
||||
} else (selected.average + (second?.average ?: selected.average)) / 2)
|
||||
})
|
||||
if (selectedSemester == firstSemester) return selectedSemesterGradeSubjects
|
||||
|
||||
val firstSemesterGradeSubjects =
|
||||
getGradeSubjects(student, firstSemester, forceRefresh)
|
||||
|
||||
return selectedSemesterGradeSubjects.combine(firstSemesterGradeSubjects) { secondSemesterGradeSubject, firstSemesterGradeSubject ->
|
||||
if (firstSemesterGradeSubject.status == Status.ERROR) {
|
||||
return@combine firstSemesterGradeSubject
|
||||
}
|
||||
|
||||
val isAnyAverage = secondSemesterGradeSubject.data.orEmpty().any { it.average != .0 }
|
||||
val updatedData = secondSemesterGradeSubject.data?.map { secondSemesterSubject ->
|
||||
val firstSemesterSubject = firstSemesterGradeSubject.data.orEmpty()
|
||||
.singleOrNull { it.subject == secondSemesterSubject.subject }
|
||||
|
||||
val updatedAverage = if (averageMode == ALL_YEAR) {
|
||||
calculateAllYearAverage(
|
||||
student = student,
|
||||
isAnyAverage = isAnyAverage,
|
||||
gradeAverageForceCalc = gradeAverageForceCalc,
|
||||
secondSemesterSubject = secondSemesterSubject,
|
||||
firstSemesterSubject = firstSemesterSubject
|
||||
)
|
||||
} else {
|
||||
calculateBothSemestersAverage(
|
||||
student = student,
|
||||
isAnyAverage = isAnyAverage,
|
||||
gradeAverageForceCalc = gradeAverageForceCalc,
|
||||
secondSemesterSubject = secondSemesterSubject,
|
||||
firstSemesterSubject = firstSemesterSubject
|
||||
)
|
||||
}
|
||||
secondSemesterSubject.copy(average = updatedAverage)
|
||||
}
|
||||
secondSemesterGradeSubject.copy(data = updatedData)
|
||||
}
|
||||
}
|
||||
|
||||
private fun calculateAllYearAverage(student: Student, semesters: List<Semester>, semesterId: Int, forceRefresh: Boolean): Flow<Resource<List<GradeDetailsWithAverage>>> {
|
||||
val selectedSemester = semesters.single { it.semesterId == semesterId }
|
||||
val firstSemester = semesters.single { it.diaryId == selectedSemester.diaryId && it.semesterName == 1 }
|
||||
private fun calculateAllYearAverage(
|
||||
student: Student,
|
||||
isAnyAverage: Boolean,
|
||||
gradeAverageForceCalc: Boolean,
|
||||
secondSemesterSubject: GradeSubject,
|
||||
firstSemesterSubject: GradeSubject?
|
||||
) = if (!isAnyAverage || gradeAverageForceCalc) {
|
||||
val updatedSecondSemesterGrades =
|
||||
secondSemesterSubject.grades.updateModifiers(student)
|
||||
val updatedFirstSemesterGrades =
|
||||
firstSemesterSubject?.grades?.updateModifiers(student).orEmpty()
|
||||
|
||||
val selectedSemesterDetailsWithAverage = getSemesterDetailsWithAverage(student, selectedSemester, forceRefresh)
|
||||
|
||||
return if (selectedSemester == firstSemester) selectedSemesterDetailsWithAverage else {
|
||||
val firstSemesterDetailsWithAverage = getSemesterDetailsWithAverage(student, firstSemester, forceRefresh)
|
||||
selectedSemesterDetailsWithAverage.combine(firstSemesterDetailsWithAverage) { selectedDetails, secondDetails ->
|
||||
val isAnyAverage = selectedDetails.data.orEmpty().any { it.average != .0 }
|
||||
secondDetails.copy(data = selectedDetails.data?.map { selected ->
|
||||
val second = secondDetails.data.orEmpty().singleOrNull { it.subject == selected.subject }
|
||||
selected.copy(average = if (!isAnyAverage || preferencesRepository.gradeAverageForceCalc) {
|
||||
(selected.grades.updateModifiers(student) + second?.grades?.updateModifiers(student).orEmpty()).calcAverage()
|
||||
} else selected.average)
|
||||
})
|
||||
}
|
||||
}
|
||||
(updatedSecondSemesterGrades + updatedFirstSemesterGrades).calcAverage()
|
||||
} else {
|
||||
secondSemesterSubject.average
|
||||
}
|
||||
|
||||
private fun getSemesterDetailsWithAverage(student: Student, semester: Semester, forceRefresh: Boolean): Flow<Resource<List<GradeDetailsWithAverage>>> {
|
||||
return gradeRepository.getGrades(student, semester, forceRefresh = forceRefresh).map { res ->
|
||||
private fun calculateBothSemestersAverage(
|
||||
student: Student,
|
||||
isAnyAverage: Boolean,
|
||||
gradeAverageForceCalc: Boolean,
|
||||
secondSemesterSubject: GradeSubject,
|
||||
firstSemesterSubject: GradeSubject?
|
||||
) = if (!isAnyAverage || 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
|
||||
}
|
||||
|
||||
private fun getGradeSubjects(
|
||||
student: Student,
|
||||
semester: Semester,
|
||||
forceRefresh: Boolean
|
||||
): Flow<Resource<List<GradeSubject>>> {
|
||||
val gradeAverageForceCalc = preferencesRepository.gradeAverageForceCalc
|
||||
|
||||
return gradeRepository.getGrades(student, semester, forceRefresh = forceRefresh)
|
||||
.map { res ->
|
||||
val (details, summaries) = res.data ?: null to null
|
||||
val isAnyAverage = summaries.orEmpty().any { it.average != .0 }
|
||||
val allGrades = details.orEmpty().groupBy { it.subject }
|
||||
|
||||
val items = summaries?.emulateEmptySummaries(student, semester, allGrades.toList(), isAnyAverage)?.map { summary ->
|
||||
val items = summaries?.emulateEmptySummaries(
|
||||
student,
|
||||
semester,
|
||||
allGrades.toList(),
|
||||
isAnyAverage
|
||||
)?.map { summary ->
|
||||
val grades = allGrades[summary.subject].orEmpty()
|
||||
GradeDetailsWithAverage(
|
||||
GradeSubject(
|
||||
subject = summary.subject,
|
||||
average = if (!isAnyAverage || preferencesRepository.gradeAverageForceCalc) {
|
||||
average = if (!isAnyAverage || gradeAverageForceCalc) {
|
||||
grades.updateModifiers(student).calcAverage()
|
||||
} else summary.average,
|
||||
points = summary.pointsSum,
|
||||
@ -107,7 +184,12 @@ class GradeAverageProvider @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun List<GradeSummary>.emulateEmptySummaries(student: Student, semester: Semester, grades: List<Pair<String, List<Grade>>>, calcAverage: Boolean): List<GradeSummary> {
|
||||
private fun List<GradeSummary>.emulateEmptySummaries(
|
||||
student: Student,
|
||||
semester: Semester,
|
||||
grades: List<Pair<String, List<Grade>>>,
|
||||
calcAverage: Boolean
|
||||
): List<GradeSummary> {
|
||||
if (isNotEmpty() && size > grades.size) return this
|
||||
|
||||
return grades.mapIndexed { i, (subject, details) ->
|
||||
|
@ -33,7 +33,6 @@ class GradeFragment : BaseFragment<FragmentGradeBinding>(R.layout.fragment_grade
|
||||
private var semesterSwitchMenu: MenuItem? = null
|
||||
|
||||
companion object {
|
||||
private const val SAVED_SEMESTER_KEY = "CURRENT_SEMESTER"
|
||||
|
||||
fun newInstance() = GradeFragment()
|
||||
}
|
||||
@ -52,7 +51,7 @@ class GradeFragment : BaseFragment<FragmentGradeBinding>(R.layout.fragment_grade
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
binding = FragmentGradeBinding.bind(view)
|
||||
presenter.onAttachView(this, savedInstanceState?.getInt(SAVED_SEMESTER_KEY))
|
||||
presenter.onAttachView(this)
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
@ -161,11 +160,6 @@ class GradeFragment : BaseFragment<FragmentGradeBinding>(R.layout.fragment_grade
|
||||
(pagerAdapter.getFragmentInstance(index) as? GradeView.GradeChildView)?.onParentChangeSemester()
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
outState.putInt(SAVED_SEMESTER_KEY, presenter.selectedIndex)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
presenter.onDetachView()
|
||||
super.onDestroyView()
|
||||
|
@ -21,8 +21,7 @@ class GradePresenter @Inject constructor(
|
||||
private val analytics: AnalyticsHelper
|
||||
) : BasePresenter<GradeView>(errorHandler, studentRepository) {
|
||||
|
||||
var selectedIndex = 0
|
||||
private set
|
||||
private var selectedIndex = 0
|
||||
|
||||
private var schoolYear = 0
|
||||
|
||||
@ -32,9 +31,8 @@ class GradePresenter @Inject constructor(
|
||||
|
||||
private lateinit var lastError: Throwable
|
||||
|
||||
fun onAttachView(view: GradeView, savedIndex: Int?) {
|
||||
override fun onAttachView(view: GradeView) {
|
||||
super.onAttachView(view)
|
||||
selectedIndex = savedIndex ?: 0
|
||||
view.initView()
|
||||
Timber.i("Grade view was initialized with $selectedIndex index")
|
||||
errorHandler.showErrorMessage = ::showErrorViewOnError
|
||||
|
@ -3,7 +3,7 @@ package io.github.wulkanowy.ui.modules.grade
|
||||
import io.github.wulkanowy.data.db.entities.Grade
|
||||
import io.github.wulkanowy.data.db.entities.GradeSummary
|
||||
|
||||
data class GradeDetailsWithAverage(
|
||||
data class GradeSubject(
|
||||
val subject: String,
|
||||
val average: Double,
|
||||
val points: String,
|
@ -17,6 +17,7 @@ import io.github.wulkanowy.ui.base.BaseFragment
|
||||
import io.github.wulkanowy.ui.modules.grade.GradeFragment
|
||||
import io.github.wulkanowy.ui.modules.grade.GradeView
|
||||
import io.github.wulkanowy.ui.modules.main.MainActivity
|
||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
@ -65,7 +66,9 @@ class GradeDetailsFragment :
|
||||
layoutManager = LinearLayoutManager(context)
|
||||
adapter = gradeDetailsAdapter
|
||||
}
|
||||
gradeDetailsSwipe.setOnRefreshListener { presenter.onSwipeRefresh() }
|
||||
gradeDetailsSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
|
||||
gradeDetailsSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary))
|
||||
gradeDetailsSwipe.setProgressBackgroundColorSchemeColor(requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh))
|
||||
gradeDetailsErrorRetry.setOnClickListener { presenter.onRetry() }
|
||||
gradeDetailsErrorDetails.setOnClickListener { presenter.onDetailsClick() }
|
||||
}
|
||||
|
@ -10,9 +10,9 @@ 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.ui.modules.grade.GradeAverageProvider
|
||||
import io.github.wulkanowy.ui.modules.grade.GradeDetailsWithAverage
|
||||
import io.github.wulkanowy.ui.modules.grade.GradeSortingMode.ALPHABETIC
|
||||
import io.github.wulkanowy.ui.modules.grade.GradeSortingMode.DATE
|
||||
import io.github.wulkanowy.ui.modules.grade.GradeSubject
|
||||
import io.github.wulkanowy.utils.AnalyticsHelper
|
||||
import io.github.wulkanowy.utils.afterLoading
|
||||
import io.github.wulkanowy.utils.flowWithResource
|
||||
@ -201,8 +201,9 @@ class GradeDetailsPresenter @Inject constructor(
|
||||
}.launch()
|
||||
}
|
||||
|
||||
private fun updateNewGradesAmount(grades: List<GradeDetailsWithAverage>) {
|
||||
newGradesAmount = grades.sumBy { item -> item.grades.sumBy { grade -> if (!grade.isRead) 1 else 0 } }
|
||||
private fun updateNewGradesAmount(grades: List<GradeSubject>) {
|
||||
newGradesAmount =
|
||||
grades.sumBy { item -> item.grades.sumBy { grade -> if (!grade.isRead) 1 else 0 } }
|
||||
}
|
||||
|
||||
private fun showErrorViewOnError(message: String, error: Throwable) {
|
||||
@ -217,7 +218,7 @@ class GradeDetailsPresenter @Inject constructor(
|
||||
}
|
||||
|
||||
@SuppressLint("DefaultLocale")
|
||||
private fun createGradeItems(items: List<GradeDetailsWithAverage>): List<GradeDetailsItem> {
|
||||
private fun createGradeItems(items: List<GradeSubject>): List<GradeDetailsItem> {
|
||||
return items
|
||||
.let { gradesWithAverages ->
|
||||
if (!preferencesRepository.showSubjectsWithoutGrades) {
|
||||
|
@ -78,18 +78,18 @@ class GradeStatisticsAdapter @Inject constructor() :
|
||||
|
||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||
when (holder) {
|
||||
is PartialViewHolder -> bindPartialChart(holder, items[position].partial!!)
|
||||
is SemesterViewHolder -> bindSemesterChart(holder, items[position].semester!!)
|
||||
is PointsViewHolder -> bindBarChart(holder, items[position].points!!)
|
||||
is PartialViewHolder -> bindPartialChart(holder.binding, items[position].partial!!)
|
||||
is SemesterViewHolder -> bindSemesterChart(holder.binding, items[position].semester!!)
|
||||
is PointsViewHolder -> bindBarChart(holder.binding, items[position].points!!)
|
||||
}
|
||||
}
|
||||
|
||||
private fun bindPartialChart(holder: PartialViewHolder, partials: GradePartialStatistics) {
|
||||
bindPieChart(holder.binding, partials.subject, partials.classAverage, partials.classAmounts)
|
||||
private fun bindPartialChart(binding: ItemGradeStatisticsPieBinding, partials: GradePartialStatistics) {
|
||||
bindPieChart(binding, partials.subject, partials.classAverage, partials.classAmounts)
|
||||
}
|
||||
|
||||
private fun bindSemesterChart(holder: SemesterViewHolder, semester: GradeSemesterStatistics) {
|
||||
bindPieChart(holder.binding, semester.subject, semester.average, semester.amounts)
|
||||
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>) {
|
||||
@ -103,9 +103,12 @@ class GradeStatisticsAdapter @Inject constructor() :
|
||||
else -> materialGradeColors
|
||||
}
|
||||
|
||||
val dataset = PieDataSet(amounts.mapIndexed { grade, amount ->
|
||||
val dataset = PieDataSet(
|
||||
amounts.mapIndexed { grade, amount ->
|
||||
PieEntry(amount.toFloat(), (grade + 1).toString())
|
||||
}.reversed().filterNot { it.value == 0f }, "Legenda")
|
||||
}.reversed().filterNot { it.value == 0f },
|
||||
binding.root.context.getString(R.string.grade_statistics_legend)
|
||||
)
|
||||
|
||||
with(dataset) {
|
||||
valueTextSize = 12f
|
||||
@ -138,11 +141,13 @@ 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)
|
||||
|
||||
minAngleForSlices = 25f
|
||||
description.isEnabled = false
|
||||
centerText = amounts.fold(0) { acc, it -> acc + it }
|
||||
.let { resources.getQuantityString(R.plurals.grade_number_item, it, it) } +
|
||||
("\n\nŚrednia: $average").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))
|
||||
@ -150,8 +155,8 @@ class GradeStatisticsAdapter @Inject constructor() :
|
||||
}
|
||||
}
|
||||
|
||||
private fun bindBarChart(holder: PointsViewHolder, points: GradePointsStatistics) {
|
||||
with(holder.binding.gradeStatisticsBarTitle) {
|
||||
private fun bindBarChart(binding: ItemGradeStatisticsBarBinding, points: GradePointsStatistics) {
|
||||
with(binding.gradeStatisticsBarTitle) {
|
||||
text = points.subject
|
||||
visibility = if (items.size == 1) GONE else VISIBLE
|
||||
}
|
||||
@ -159,18 +164,18 @@ class GradeStatisticsAdapter @Inject constructor() :
|
||||
val dataset = BarDataSet(listOf(
|
||||
BarEntry(1f, points.others.toFloat()),
|
||||
BarEntry(2f, points.student.toFloat())
|
||||
), "Legenda")
|
||||
), binding.root.context.getString(R.string.grade_statistics_legend))
|
||||
|
||||
with(dataset) {
|
||||
valueTextSize = 12f
|
||||
valueTextColor = holder.binding.root.context.getThemeAttrColor(android.R.attr.textColorPrimary)
|
||||
valueTextColor = binding.root.context.getThemeAttrColor(android.R.attr.textColorPrimary)
|
||||
valueFormatter = object : ValueFormatter() {
|
||||
override fun getBarLabel(barEntry: BarEntry) = "${barEntry.y}%"
|
||||
}
|
||||
colors = gradePointsColors
|
||||
}
|
||||
|
||||
with(holder.binding.gradeStatisticsBar) {
|
||||
with(binding.gradeStatisticsBar) {
|
||||
setTouchEnabled(false)
|
||||
if (items.size == 1) animateXY(1000, 1000)
|
||||
data = BarData(dataset).apply {
|
||||
@ -179,12 +184,12 @@ class GradeStatisticsAdapter @Inject constructor() :
|
||||
}
|
||||
legend.setCustom(listOf(
|
||||
LegendEntry().apply {
|
||||
label = "Średnia klasy"
|
||||
label = binding.root.context.getString(R.string.grade_statistics_average_class)
|
||||
formColor = gradePointsColors[0]
|
||||
form = Legend.LegendForm.SQUARE
|
||||
},
|
||||
LegendEntry().apply {
|
||||
label = "Uczeń"
|
||||
label = binding.root.context.getString(R.string.grade_statistics_average_student)
|
||||
formColor = gradePointsColors[1]
|
||||
form = Legend.LegendForm.SQUARE
|
||||
}
|
||||
@ -193,7 +198,7 @@ class GradeStatisticsAdapter @Inject constructor() :
|
||||
|
||||
description.isEnabled = false
|
||||
|
||||
holder.binding.root.context.getThemeAttrColor(android.R.attr.textColorPrimary).let {
|
||||
binding.root.context.getThemeAttrColor(android.R.attr.textColorPrimary).let {
|
||||
axisLeft.textColor = it
|
||||
axisRight.textColor = it
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ import io.github.wulkanowy.ui.base.BaseFragment
|
||||
import io.github.wulkanowy.ui.modules.grade.GradeFragment
|
||||
import io.github.wulkanowy.ui.modules.grade.GradeView
|
||||
import io.github.wulkanowy.utils.dpToPx
|
||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||
import io.github.wulkanowy.utils.setOnItemSelectedListener
|
||||
import javax.inject.Inject
|
||||
|
||||
@ -69,6 +70,8 @@ class GradeStatisticsFragment :
|
||||
gradeStatisticsSubjectsContainer.setElevationCompat(requireContext().dpToPx(1f))
|
||||
|
||||
gradeStatisticsSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
|
||||
gradeStatisticsSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary))
|
||||
gradeStatisticsSwipe.setProgressBackgroundColorSchemeColor(requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh))
|
||||
gradeStatisticsErrorRetry.setOnClickListener { presenter.onRetry() }
|
||||
gradeStatisticsErrorDetails.setOnClickListener { presenter.onDetailsClick() }
|
||||
}
|
||||
|
@ -172,6 +172,7 @@ class GradeStatisticsPresenter @Inject constructor(
|
||||
showErrorView(false)
|
||||
enableSwipe(true)
|
||||
showRefresh(true)
|
||||
showProgress(false)
|
||||
updateData(it.data!!, preferencesRepository.gradeColorTheme, preferencesRepository.showAllSubjectsOnStatisticsList)
|
||||
showSubjects(!preferencesRepository.showAllSubjectsOnStatisticsList)
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ import io.github.wulkanowy.databinding.FragmentGradeSummaryBinding
|
||||
import io.github.wulkanowy.ui.base.BaseFragment
|
||||
import io.github.wulkanowy.ui.modules.grade.GradeFragment
|
||||
import io.github.wulkanowy.ui.modules.grade.GradeView
|
||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
@ -52,7 +53,9 @@ class GradeSummaryFragment :
|
||||
adapter = gradeSummaryAdapter
|
||||
}
|
||||
with(binding) {
|
||||
gradeSummarySwipe.setOnRefreshListener { presenter.onSwipeRefresh() }
|
||||
gradeSummarySwipe.setOnRefreshListener(presenter::onSwipeRefresh)
|
||||
gradeSummarySwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary))
|
||||
gradeSummarySwipe.setProgressBackgroundColorSchemeColor(requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh))
|
||||
gradeSummaryErrorRetry.setOnClickListener { presenter.onRetry() }
|
||||
gradeSummaryErrorDetails.setOnClickListener { presenter.onDetailsClick() }
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ 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.ui.modules.grade.GradeAverageProvider
|
||||
import io.github.wulkanowy.ui.modules.grade.GradeDetailsWithAverage
|
||||
import io.github.wulkanowy.ui.modules.grade.GradeSubject
|
||||
import io.github.wulkanowy.utils.AnalyticsHelper
|
||||
import io.github.wulkanowy.utils.afterLoading
|
||||
import io.github.wulkanowy.utils.flowWithResourceIn
|
||||
@ -135,14 +135,14 @@ class GradeSummaryPresenter @Inject constructor(
|
||||
cancelJobs("load")
|
||||
}
|
||||
|
||||
private fun createGradeSummaryItems(items: List<GradeDetailsWithAverage>): List<GradeSummary> {
|
||||
private fun createGradeSummaryItems(items: List<GradeSubject>): List<GradeSummary> {
|
||||
return items
|
||||
.filter { !checkEmpty(it) }
|
||||
.sortedBy { it.subject }
|
||||
.map { it.summary.copy(average = it.average) }
|
||||
}
|
||||
|
||||
private fun checkEmpty(gradeSummary: GradeDetailsWithAverage): Boolean {
|
||||
private fun checkEmpty(gradeSummary: GradeSubject): Boolean {
|
||||
return gradeSummary.run {
|
||||
summary.finalGrade.isBlank()
|
||||
&& summary.predictedGrade.isBlank()
|
||||
|
@ -15,6 +15,7 @@ import io.github.wulkanowy.ui.modules.main.MainActivity
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import io.github.wulkanowy.ui.widgets.DividerItemDecoration
|
||||
import io.github.wulkanowy.utils.dpToPx
|
||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
@ -55,6 +56,8 @@ class HomeworkFragment : BaseFragment<FragmentHomeworkBinding>(R.layout.fragment
|
||||
|
||||
with(binding) {
|
||||
homeworkSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
|
||||
homeworkSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary))
|
||||
homeworkSwipe.setProgressBackgroundColorSchemeColor(requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh))
|
||||
homeworkErrorRetry.setOnClickListener { presenter.onRetry() }
|
||||
homeworkErrorDetails.setOnClickListener { presenter.onDetailsClick() }
|
||||
|
||||
|
@ -10,6 +10,7 @@ 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.main.MainView
|
||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
@ -38,7 +39,9 @@ class LuckyNumberFragment :
|
||||
|
||||
override fun initView() {
|
||||
with(binding) {
|
||||
luckyNumberSwipe.setOnRefreshListener { presenter.onSwipeRefresh() }
|
||||
luckyNumberSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
|
||||
luckyNumberSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary))
|
||||
luckyNumberSwipe.setProgressBackgroundColorSchemeColor(requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh))
|
||||
luckyNumberErrorRetry.setOnClickListener { presenter.onRetry() }
|
||||
luckyNumberErrorDetails.setOnClickListener { presenter.onDetailsClick() }
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ import android.os.Build.VERSION_CODES.LOLLIPOP
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.core.view.ViewCompat
|
||||
@ -28,7 +29,7 @@ import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.databinding.ActivityMainBinding
|
||||
import io.github.wulkanowy.ui.base.BaseActivity
|
||||
import io.github.wulkanowy.ui.modules.account.AccountDialog
|
||||
import io.github.wulkanowy.ui.modules.account.accountquick.AccountQuickDialog
|
||||
import io.github.wulkanowy.ui.modules.attendance.AttendanceFragment
|
||||
import io.github.wulkanowy.ui.modules.exam.ExamFragment
|
||||
import io.github.wulkanowy.ui.modules.grade.GradeFragment
|
||||
@ -65,25 +66,30 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
|
||||
|
||||
private val overlayProvider by lazy { ElevationOverlayProvider(this) }
|
||||
|
||||
private val navController = FragNavController(supportFragmentManager, R.id.mainFragmentContainer)
|
||||
private val navController =
|
||||
FragNavController(supportFragmentManager, R.id.mainFragmentContainer)
|
||||
|
||||
companion object {
|
||||
const val EXTRA_START_MENU = "extraStartMenu"
|
||||
|
||||
fun getStartIntent(context: Context, startMenu: MainView.Section? = null, clear: Boolean = false): Intent {
|
||||
return Intent(context, MainActivity::class.java)
|
||||
.apply {
|
||||
fun getStartIntent(
|
||||
context: Context,
|
||||
startMenu: MainView.Section? = null,
|
||||
clear: Boolean = false
|
||||
) = Intent(context, MainActivity::class.java).apply {
|
||||
if (clear) flags = FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TASK
|
||||
startMenu?.let { putExtra(EXTRA_START_MENU, it.id) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override val isRootView get() = navController.isRootFragment
|
||||
|
||||
override val currentStackSize get() = navController.currentStack?.size
|
||||
|
||||
override val currentViewTitle get() = (navController.currentFrag as? MainView.TitledView)?.titleStringId?.let { getString(it) }
|
||||
override val currentViewTitle
|
||||
get() = (navController.currentFrag as? MainView.TitledView)?.titleStringId?.let {
|
||||
getString(it)
|
||||
}
|
||||
|
||||
override val currentViewSubtitle get() = (navController.currentFrag as? MainView.TitledView)?.subtitleString
|
||||
|
||||
@ -106,7 +112,10 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
|
||||
messageContainer = binding.mainFragmentContainer
|
||||
updateHelper.messageContainer = binding.mainFragmentContainer
|
||||
|
||||
presenter.onAttachView(this, MainView.Section.values().singleOrNull { it.id == intent.getIntExtra(EXTRA_START_MENU, -1) })
|
||||
val section = MainView.Section.values()
|
||||
.singleOrNull { it.id == intent.getIntExtra(EXTRA_START_MENU, -1) }
|
||||
|
||||
presenter.onAttachView(this, section)
|
||||
|
||||
with(navController) {
|
||||
initialize(startMenuIndex, savedInstanceState)
|
||||
@ -132,21 +141,49 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
|
||||
val shortcutsList = mutableListOf<ShortcutInfo>()
|
||||
|
||||
listOf(
|
||||
Triple(getString(R.string.grade_title), R.drawable.ic_shortcut_grade, MainView.Section.GRADE),
|
||||
Triple(getString(R.string.attendance_title), R.drawable.ic_shortcut_attendance, MainView.Section.ATTENDANCE),
|
||||
Triple(getString(R.string.exam_title), R.drawable.ic_shortcut_exam, MainView.Section.EXAM),
|
||||
Triple(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)
|
||||
Triple(
|
||||
getString(R.string.grade_title),
|
||||
R.drawable.ic_shortcut_grade,
|
||||
MainView.Section.GRADE
|
||||
),
|
||||
Triple(
|
||||
getString(R.string.attendance_title),
|
||||
R.drawable.ic_shortcut_attendance,
|
||||
MainView.Section.ATTENDANCE
|
||||
),
|
||||
Triple(
|
||||
getString(R.string.exam_title),
|
||||
R.drawable.ic_shortcut_exam,
|
||||
MainView.Section.EXAM
|
||||
),
|
||||
Triple(
|
||||
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(ShortcutInfo.Builder(applicationContext, title)
|
||||
shortcutsList.add(
|
||||
ShortcutInfo.Builder(applicationContext, title)
|
||||
.setShortLabel(title)
|
||||
.setLongLabel(title)
|
||||
.setIcon(Icon.createWithResource(applicationContext, icon))
|
||||
.setIntents(arrayOf(
|
||||
Intent(applicationContext, MainActivity::class.java).setAction(Intent.ACTION_VIEW),
|
||||
Intent(applicationContext, MainActivity::class.java).putExtra(EXTRA_START_MENU, enum.id)
|
||||
.setAction(Intent.ACTION_VIEW).addFlags(FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TASK)))
|
||||
.build())
|
||||
.setIntents(
|
||||
arrayOf(
|
||||
Intent(applicationContext, MainActivity::class.java)
|
||||
.setAction(Intent.ACTION_VIEW),
|
||||
Intent(applicationContext, MainActivity::class.java)
|
||||
.putExtra(EXTRA_START_MENU, enum.id)
|
||||
.setAction(Intent.ACTION_VIEW)
|
||||
.addFlags(FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TASK)
|
||||
)
|
||||
)
|
||||
.build()
|
||||
)
|
||||
}
|
||||
|
||||
getSystemService<ShortcutManager>()?.dynamicShortcuts = shortcutsList
|
||||
@ -160,20 +197,33 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
|
||||
override fun initView() {
|
||||
with(binding.mainToolbar) {
|
||||
if (SDK_INT >= LOLLIPOP) stateListAnimator = null
|
||||
setBackgroundColor(overlayProvider.compositeOverlayWithThemeSurfaceColorIfNeeded(dpToPx(4f)))
|
||||
setBackgroundColor(
|
||||
overlayProvider.compositeOverlayWithThemeSurfaceColorIfNeeded(dpToPx(4f))
|
||||
)
|
||||
}
|
||||
|
||||
with(binding.mainBottomNav) {
|
||||
addItems(listOf(
|
||||
addItems(
|
||||
listOf(
|
||||
AHBottomNavigationItem(R.string.grade_title, R.drawable.ic_main_grade, 0),
|
||||
AHBottomNavigationItem(R.string.attendance_title, R.drawable.ic_main_attendance, 0),
|
||||
AHBottomNavigationItem(
|
||||
R.string.attendance_title,
|
||||
R.drawable.ic_main_attendance,
|
||||
0
|
||||
),
|
||||
AHBottomNavigationItem(R.string.exam_title, R.drawable.ic_main_exam, 0),
|
||||
AHBottomNavigationItem(R.string.timetable_title, R.drawable.ic_main_timetable, 0),
|
||||
AHBottomNavigationItem(
|
||||
R.string.timetable_title,
|
||||
R.drawable.ic_main_timetable,
|
||||
0
|
||||
),
|
||||
AHBottomNavigationItem(R.string.more_title, R.drawable.ic_main_more, 0)
|
||||
))
|
||||
)
|
||||
)
|
||||
accentColor = getThemeAttrColor(R.attr.colorPrimary)
|
||||
inactiveColor = getThemeAttrColor(R.attr.colorOnSurface, 153)
|
||||
defaultBackgroundColor = overlayProvider.compositeOverlayWithThemeSurfaceColorIfNeeded(dpToPx(8f))
|
||||
defaultBackgroundColor =
|
||||
overlayProvider.compositeOverlayWithThemeSurfaceColorIfNeeded(dpToPx(8f))
|
||||
titleState = ALWAYS_SHOW
|
||||
currentItem = startMenuIndex
|
||||
isBehaviorTranslationEnabled = false
|
||||
@ -183,6 +233,13 @@ 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
|
||||
} else {
|
||||
View.VISIBLE
|
||||
}
|
||||
|
||||
analytics.setCurrentScreen(this@MainActivity, name)
|
||||
presenter.onViewChange(section)
|
||||
}
|
||||
@ -224,7 +281,7 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
|
||||
}
|
||||
|
||||
override fun showAccountPicker() {
|
||||
navController.showDialogFragment(AccountDialog.newInstance())
|
||||
navController.showDialogFragment(AccountQuickDialog.newInstance())
|
||||
}
|
||||
|
||||
override fun showActionBarElevation(show: Boolean) {
|
||||
|
@ -64,6 +64,8 @@ interface MainView : BaseView {
|
||||
LUCKY_NUMBER(8),
|
||||
SETTINGS(9),
|
||||
ABOUT(10),
|
||||
SCHOOL(11)
|
||||
SCHOOL(11),
|
||||
ACCOUNT(12),
|
||||
STUDENT_INFO(13)
|
||||
}
|
||||
}
|
||||
|
@ -70,6 +70,7 @@ class MessagePreviewPresenter @Inject constructor(
|
||||
this@MessagePreviewPresenter.attachments = it.data.attachments
|
||||
view?.apply {
|
||||
setMessageWithAttachment(it.data)
|
||||
showContent(true)
|
||||
initOptions()
|
||||
}
|
||||
analytics.logEvent(
|
||||
|
@ -19,6 +19,7 @@ import io.github.wulkanowy.ui.modules.main.MainActivity
|
||||
import io.github.wulkanowy.ui.modules.message.MessageFragment
|
||||
import io.github.wulkanowy.ui.modules.message.preview.MessagePreviewFragment
|
||||
import io.github.wulkanowy.ui.widgets.DividerItemDecoration
|
||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import javax.inject.Inject
|
||||
|
||||
@ -74,7 +75,9 @@ class MessageTabFragment : BaseFragment<FragmentMessageTabBinding>(R.layout.frag
|
||||
addItemDecoration(DividerItemDecoration(context))
|
||||
}
|
||||
with(binding) {
|
||||
messageTabSwipe.setOnRefreshListener { presenter.onSwipeRefresh() }
|
||||
messageTabSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
|
||||
messageTabSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary))
|
||||
messageTabSwipe.setProgressBackgroundColorSchemeColor(requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh))
|
||||
messageTabErrorRetry.setOnClickListener { presenter.onRetry() }
|
||||
messageTabErrorDetails.setOnClickListener { presenter.onDetailsClick() }
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ import io.github.wulkanowy.ui.modules.main.MainActivity
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import io.github.wulkanowy.ui.modules.mobiledevice.token.MobileDeviceTokenDialog
|
||||
import io.github.wulkanowy.ui.widgets.DividerItemDecoration
|
||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
@ -56,7 +57,9 @@ class MobileDeviceFragment :
|
||||
}
|
||||
|
||||
with(binding) {
|
||||
mobileDevicesSwipe.setOnRefreshListener { presenter.onSwipeRefresh() }
|
||||
mobileDevicesSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
|
||||
mobileDevicesSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary))
|
||||
mobileDevicesSwipe.setProgressBackgroundColorSchemeColor(requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh))
|
||||
mobileDevicesErrorRetry.setOnClickListener { presenter.onRetry() }
|
||||
mobileDevicesErrorDetails.setOnClickListener { presenter.onDetailsClick() }
|
||||
mobileDeviceAddButton.setOnClickListener { presenter.onRegisterDevice() }
|
||||
|
@ -13,6 +13,7 @@ import io.github.wulkanowy.ui.base.BaseFragment
|
||||
import io.github.wulkanowy.ui.modules.main.MainActivity
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import io.github.wulkanowy.ui.widgets.DividerItemDecoration
|
||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
@ -50,7 +51,9 @@ class NoteFragment : BaseFragment<FragmentNoteBinding>(R.layout.fragment_note),
|
||||
addItemDecoration(DividerItemDecoration(context))
|
||||
}
|
||||
with(binding) {
|
||||
noteSwipe.setOnRefreshListener { presenter.onSwipeRefresh() }
|
||||
noteSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
|
||||
noteSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary))
|
||||
noteSwipe.setProgressBackgroundColorSchemeColor(requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh))
|
||||
noteErrorRetry.setOnClickListener { presenter.onRetry() }
|
||||
noteErrorDetails.setOnClickListener { presenter.onDetailsClick() }
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ import io.github.wulkanowy.ui.base.BaseFragment
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import io.github.wulkanowy.ui.modules.schoolandteachers.SchoolAndTeachersChildView
|
||||
import io.github.wulkanowy.ui.modules.schoolandteachers.SchoolAndTeachersFragment
|
||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||
import io.github.wulkanowy.utils.openDialer
|
||||
import io.github.wulkanowy.utils.openNavigation
|
||||
import javax.inject.Inject
|
||||
@ -39,7 +40,9 @@ class SchoolFragment : BaseFragment<FragmentSchoolBinding>(R.layout.fragment_sch
|
||||
|
||||
override fun initView() {
|
||||
with(binding) {
|
||||
schoolSwipe.setOnRefreshListener { presenter.onSwipeRefresh() }
|
||||
schoolSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
|
||||
schoolSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary))
|
||||
schoolSwipe.setProgressBackgroundColorSchemeColor(requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh))
|
||||
schoolErrorRetry.setOnClickListener { presenter.onRetry() }
|
||||
schoolErrorDetails.setOnClickListener { presenter.onDetailsClick() }
|
||||
|
||||
|
@ -81,10 +81,7 @@ class SchoolPresenter @Inject constructor(
|
||||
showEmpty(false)
|
||||
showErrorView(false)
|
||||
}
|
||||
analytics.logEvent(
|
||||
"load_item",
|
||||
"type" to "school"
|
||||
)
|
||||
analytics.logEvent("load_item", "type" to "school")
|
||||
} else view?.run {
|
||||
Timber.i("Loading school result: No school info found")
|
||||
showContent(!isViewEmpty)
|
||||
|
@ -14,6 +14,7 @@ import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import io.github.wulkanowy.ui.modules.schoolandteachers.SchoolAndTeachersChildView
|
||||
import io.github.wulkanowy.ui.modules.schoolandteachers.SchoolAndTeachersFragment
|
||||
import io.github.wulkanowy.ui.widgets.DividerItemDecoration
|
||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
@ -51,7 +52,9 @@ class TeacherFragment : BaseFragment<FragmentTeacherBinding>(R.layout.fragment_t
|
||||
addItemDecoration(DividerItemDecoration(context))
|
||||
}
|
||||
with(binding) {
|
||||
teacherSwipe.setOnRefreshListener { presenter.onSwipeRefresh() }
|
||||
teacherSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
|
||||
teacherSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary))
|
||||
teacherSwipe.setProgressBackgroundColorSchemeColor(requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh))
|
||||
teacherErrorRetry.setOnClickListener { presenter.onRetry() }
|
||||
teacherErrorDetails.setOnClickListener { presenter.onDetailsClick() }
|
||||
}
|
||||
|
@ -79,6 +79,10 @@ class SettingsFragment : PreferenceFragmentCompat(),
|
||||
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 ""
|
||||
|
@ -42,14 +42,18 @@ class SettingsPresenter @Inject constructor(
|
||||
when (key) {
|
||||
serviceEnableKey -> with(syncManager) { if (isServiceEnabled) startPeriodicSyncWorker() else stopSyncWorker() }
|
||||
servicesIntervalKey, servicesOnlyWifiKey -> syncManager.startPeriodicSyncWorker(true)
|
||||
isDebugNotificationEnableKey -> chuckerCollector.showNotification = isDebugNotificationEnable
|
||||
isDebugNotificationEnableKey -> chuckerCollector.showNotification =
|
||||
isDebugNotificationEnable
|
||||
appThemeKey -> view?.recreateView()
|
||||
isUpcomingLessonsNotificationsEnableKey -> if (!isUpcomingLessonsNotificationsEnable) timetableNotificationHelper.cancelNotification()
|
||||
appLanguageKey -> view?.run {
|
||||
val newLang = if (appLanguage == "system") appInfo.systemLanguage else appLanguage
|
||||
analytics.logEvent("language", "setting_changed" to newLang)
|
||||
|
||||
updateLanguage(newLang)
|
||||
if (appLanguage == "system") {
|
||||
updateLanguageToFollowSystem()
|
||||
analytics.logEvent("language", "setting_changed" to appInfo.systemLanguage)
|
||||
} else {
|
||||
updateLanguage(appLanguage)
|
||||
analytics.logEvent("language", "setting_changed" to appLanguage)
|
||||
}
|
||||
recreateView()
|
||||
}
|
||||
}
|
||||
@ -71,7 +75,10 @@ class SettingsPresenter @Inject constructor(
|
||||
analytics.logEvent("sync_now", "status" to "success")
|
||||
}
|
||||
WorkInfo.State.FAILED -> {
|
||||
showError(syncFailedString, Throwable(workInfo.outputData.getString("error")))
|
||||
showError(
|
||||
syncFailedString,
|
||||
Throwable(workInfo.outputData.getString("error"))
|
||||
)
|
||||
analytics.logEvent("sync_now", "status" to "failed")
|
||||
}
|
||||
else -> Timber.d("Sync now state: ${workInfo.state}")
|
||||
|
@ -14,6 +14,8 @@ interface SettingsView : BaseView {
|
||||
|
||||
fun updateLanguage(langCode: String)
|
||||
|
||||
fun updateLanguageToFollowSystem()
|
||||
|
||||
fun setServicesSuspended(serviceEnablesKey: String, isHolidays: Boolean)
|
||||
|
||||
fun setSyncInProgress(inProgress: Boolean)
|
||||
|
@ -0,0 +1,42 @@
|
||||
package io.github.wulkanowy.ui.modules.studentinfo
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import io.github.wulkanowy.databinding.ItemStudentInfoBinding
|
||||
import javax.inject.Inject
|
||||
|
||||
class StudentInfoAdapter @Inject constructor() :
|
||||
RecyclerView.Adapter<StudentInfoAdapter.ViewHolder>() {
|
||||
|
||||
var items = listOf<Pair<String, String>>()
|
||||
|
||||
var onItemClickListener: (position: Int) -> Unit = {}
|
||||
|
||||
var onItemLongClickListener: (text: String) -> Unit = {}
|
||||
|
||||
override fun getItemCount() = items.size
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(
|
||||
ItemStudentInfoBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
)
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
val item = items[position]
|
||||
|
||||
with(holder.binding) {
|
||||
studentInfoItemTitle.text = item.first
|
||||
studentInfoItemSubtitle.text = item.second
|
||||
|
||||
with(root) {
|
||||
setOnClickListener { onItemClickListener(position) }
|
||||
setOnLongClickListener {
|
||||
onItemLongClickListener(studentInfoItemSubtitle.text.toString())
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ViewHolder(val binding: ItemStudentInfoBinding) : RecyclerView.ViewHolder(binding.root)
|
||||
}
|
@ -0,0 +1,232 @@
|
||||
package io.github.wulkanowy.ui.modules.studentinfo
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.core.view.get
|
||||
import androidx.recyclerview.widget.DividerItemDecoration
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.StudentInfo
|
||||
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||
import io.github.wulkanowy.data.enums.Gender
|
||||
import io.github.wulkanowy.databinding.FragmentStudentInfoBinding
|
||||
import io.github.wulkanowy.ui.base.BaseFragment
|
||||
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
|
||||
|
||||
@AndroidEntryPoint
|
||||
class StudentInfoFragment :
|
||||
BaseFragment<FragmentStudentInfoBinding>(R.layout.fragment_student_info), StudentInfoView,
|
||||
MainView.TitledView {
|
||||
|
||||
@Inject
|
||||
lateinit var presenter: StudentInfoPresenter
|
||||
|
||||
@Inject
|
||||
lateinit var studentInfoAdapter: StudentInfoAdapter
|
||||
|
||||
override val titleStringId: Int
|
||||
get() = R.string.student_info_title
|
||||
|
||||
override val isViewEmpty get() = studentInfoAdapter.items.isEmpty()
|
||||
|
||||
companion object {
|
||||
|
||||
private const val INFO_TYPE_ARGUMENT_KEY = "info_type"
|
||||
|
||||
private const val STUDENT_ARGUMENT_KEY = "student_with_semesters"
|
||||
|
||||
fun newInstance(type: StudentInfoView.Type, studentWithSemesters: StudentWithSemesters) =
|
||||
StudentInfoFragment().apply {
|
||||
arguments = Bundle().apply {
|
||||
putSerializable(INFO_TYPE_ARGUMENT_KEY, type)
|
||||
putSerializable(STUDENT_ARGUMENT_KEY, studentWithSemesters)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setHasOptionsMenu(true)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
binding = FragmentStudentInfoBinding.bind(view)
|
||||
presenter.onAttachView(
|
||||
this,
|
||||
requireArguments().getSerializable(INFO_TYPE_ARGUMENT_KEY) as StudentInfoView.Type,
|
||||
requireArguments().getSerializable(STUDENT_ARGUMENT_KEY) as StudentWithSemesters
|
||||
)
|
||||
}
|
||||
|
||||
override fun initView() {
|
||||
with(binding) {
|
||||
studentInfoSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
|
||||
studentInfoSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary))
|
||||
studentInfoSwipe.setProgressBackgroundColorSchemeColor(requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh))
|
||||
studentInfoErrorRetry.setOnClickListener { presenter.onRetry() }
|
||||
studentInfoErrorDetails.setOnClickListener { presenter.onDetailsClick() }
|
||||
}
|
||||
|
||||
with(studentInfoAdapter) {
|
||||
onItemClickListener = presenter::onItemSelected
|
||||
onItemLongClickListener = presenter::onItemLongClick
|
||||
}
|
||||
|
||||
with(binding.studentInfoRecycler) {
|
||||
layoutManager = LinearLayoutManager(context)
|
||||
addItemDecoration(DividerItemDecoration(context, RecyclerView.VERTICAL))
|
||||
setHasFixedSize(true)
|
||||
adapter = studentInfoAdapter
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateData(data: List<Pair<String, String>>) {
|
||||
with(studentInfoAdapter) {
|
||||
items = data
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
menu[0].isVisible = false
|
||||
}
|
||||
|
||||
override fun showPersonalTypeData(studentInfo: StudentInfo) {
|
||||
updateData(
|
||||
listOf(
|
||||
getString(R.string.student_info_first_name) to studentInfo.firstName,
|
||||
getString(R.string.student_info_second_name) to studentInfo.secondName,
|
||||
getString(R.string.student_info_last_name) to studentInfo.surname,
|
||||
getString(R.string.student_info_gender) to getString(if (studentInfo.gender == Gender.MALE) R.string.student_info_male else R.string.student_info_female),
|
||||
getString(R.string.student_info_polish_citizenship) to getString(if (studentInfo.hasPolishCitizenship) R.string.all_yes else R.string.all_no),
|
||||
getString(R.string.student_info_family_name) to studentInfo.familyName,
|
||||
getString(R.string.student_info_parents_name) to studentInfo.parentsNames
|
||||
).map {
|
||||
if (it.second.isBlank()) it.copy(second = getString(R.string.all_no_data)) else it
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
override fun showContactTypeData(studentInfo: StudentInfo) {
|
||||
updateData(
|
||||
listOf(
|
||||
getString(R.string.student_info_phone) to studentInfo.phoneNumber,
|
||||
getString(R.string.student_info_cellphone) to studentInfo.cellPhoneNumber,
|
||||
getString(R.string.student_info_email) to studentInfo.email
|
||||
).map {
|
||||
if (it.second.isBlank()) it.copy(second = getString(R.string.all_no_data)) else it
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@SuppressLint("DefaultLocale")
|
||||
override fun showFamilyTypeData(studentInfo: StudentInfo) {
|
||||
updateData(
|
||||
listOf(
|
||||
studentInfo.firstGuardian.kinship.capitalize() to studentInfo.firstGuardian.fullName,
|
||||
studentInfo.secondGuardian.kinship.capitalize() to studentInfo.secondGuardian.fullName
|
||||
).map {
|
||||
if (it.second.isBlank()) it.copy(second = getString(R.string.all_no_data)) else it
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
override fun showAddressTypeData(studentInfo: StudentInfo) {
|
||||
updateData(
|
||||
listOf(
|
||||
getString(R.string.student_info_address) to studentInfo.address,
|
||||
getString(R.string.student_info_registered_address) to studentInfo.registeredAddress,
|
||||
getString(R.string.student_info_correspondence_address) to studentInfo.correspondenceAddress
|
||||
).map {
|
||||
if (it.second.isBlank()) it.copy(second = getString(R.string.all_no_data)) else it
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
override fun showFirstGuardianTypeData(studentInfo: StudentInfo) {
|
||||
updateData(
|
||||
listOf(
|
||||
getString(R.string.student_info_full_name) to studentInfo.firstGuardian.fullName,
|
||||
getString(R.string.student_info_kinship) to studentInfo.firstGuardian.kinship,
|
||||
getString(R.string.student_info_guardian_address) to studentInfo.firstGuardian.address,
|
||||
getString(R.string.student_info_phones) to studentInfo.firstGuardian.phones,
|
||||
getString(R.string.student_info_email) to studentInfo.firstGuardian.email
|
||||
).map {
|
||||
if (it.second.isBlank()) it.copy(second = getString(R.string.all_no_data)) else it
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
override fun showSecondGuardianTypeData(studentInfo: StudentInfo) {
|
||||
updateData(
|
||||
listOf(
|
||||
getString(R.string.student_info_full_name) to studentInfo.secondGuardian.fullName,
|
||||
getString(R.string.student_info_kinship) to studentInfo.secondGuardian.kinship,
|
||||
getString(R.string.student_info_guardian_address) to studentInfo.secondGuardian.address,
|
||||
getString(R.string.student_info_phones) to studentInfo.secondGuardian.phones,
|
||||
getString(R.string.student_info_email) to studentInfo.secondGuardian.email
|
||||
).map {
|
||||
if (it.second.isBlank()) it.copy(second = getString(R.string.all_no_data)) else it
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
override fun openStudentInfoView(
|
||||
infoType: StudentInfoView.Type,
|
||||
studentWithSemesters: StudentWithSemesters
|
||||
) {
|
||||
(requireActivity() as MainActivity).pushView(newInstance(infoType, studentWithSemesters))
|
||||
}
|
||||
|
||||
override fun showEmpty(show: Boolean) {
|
||||
binding.studentInfoEmpty.visibility = if (show) View.VISIBLE else View.GONE
|
||||
}
|
||||
|
||||
override fun showErrorView(show: Boolean) {
|
||||
binding.studentInfoError.visibility = if (show) View.VISIBLE else View.GONE
|
||||
}
|
||||
|
||||
override fun setErrorDetails(message: String) {
|
||||
binding.studentInfoErrorMessage.text = message
|
||||
}
|
||||
|
||||
override fun showProgress(show: Boolean) {
|
||||
binding.studentInfoProgress.visibility = if (show) View.VISIBLE else View.GONE
|
||||
}
|
||||
|
||||
override fun enableSwipe(enable: Boolean) {
|
||||
binding.studentInfoSwipe.isEnabled = enable
|
||||
}
|
||||
|
||||
override fun showContent(show: Boolean) {
|
||||
binding.studentInfoRecycler.visibility = if (show) View.VISIBLE else View.GONE
|
||||
}
|
||||
|
||||
override fun hideRefresh() {
|
||||
binding.studentInfoSwipe.isRefreshing = false
|
||||
}
|
||||
|
||||
override fun copyToClipboard(text: String) {
|
||||
val clipData = ClipData.newPlainText("student_info_wulkanowy", text)
|
||||
requireActivity().getSystemService<ClipboardManager>()?.setPrimaryClip(clipData)
|
||||
Toast.makeText(context, R.string.all_copied, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
presenter.onDetachView()
|
||||
super.onDestroyView()
|
||||
}
|
||||
}
|
@ -0,0 +1,142 @@
|
||||
package io.github.wulkanowy.ui.modules.studentinfo
|
||||
|
||||
import io.github.wulkanowy.data.Status
|
||||
import io.github.wulkanowy.data.db.entities.StudentInfo
|
||||
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||
import io.github.wulkanowy.data.repositories.StudentInfoRepository
|
||||
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.flowWithResourceIn
|
||||
import io.github.wulkanowy.utils.getCurrentOrLast
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
class StudentInfoPresenter @Inject constructor(
|
||||
errorHandler: ErrorHandler,
|
||||
studentRepository: StudentRepository,
|
||||
private val studentInfoRepository: StudentInfoRepository,
|
||||
private val analytics: AnalyticsHelper
|
||||
) : BasePresenter<StudentInfoView>(errorHandler, studentRepository) {
|
||||
|
||||
private lateinit var infoType: StudentInfoView.Type
|
||||
|
||||
private lateinit var studentWithSemesters: StudentWithSemesters
|
||||
|
||||
private lateinit var lastError: Throwable
|
||||
|
||||
fun onAttachView(
|
||||
view: StudentInfoView,
|
||||
type: StudentInfoView.Type,
|
||||
studentWithSemesters: StudentWithSemesters
|
||||
) {
|
||||
super.onAttachView(view)
|
||||
infoType = type
|
||||
this.studentWithSemesters = studentWithSemesters
|
||||
view.initView()
|
||||
Timber.i("Student info $infoType view was initialized")
|
||||
errorHandler.showErrorMessage = ::showErrorViewOnError
|
||||
loadData()
|
||||
}
|
||||
|
||||
fun onSwipeRefresh() {
|
||||
loadData(true)
|
||||
}
|
||||
|
||||
fun onRetry() {
|
||||
view?.run {
|
||||
showErrorView(false)
|
||||
showProgress(true)
|
||||
}
|
||||
loadData(true)
|
||||
}
|
||||
|
||||
fun onDetailsClick() {
|
||||
view?.showErrorDetailsDialog(lastError)
|
||||
}
|
||||
|
||||
fun onItemSelected(position: Int) {
|
||||
if (infoType != StudentInfoView.Type.FAMILY) return
|
||||
|
||||
if (position == 0) {
|
||||
view?.openStudentInfoView(StudentInfoView.Type.FIRST_GUARDIAN, studentWithSemesters)
|
||||
} else {
|
||||
view?.openStudentInfoView(StudentInfoView.Type.SECOND_GUARDIAN, studentWithSemesters)
|
||||
}
|
||||
}
|
||||
|
||||
fun onItemLongClick(text: String) {
|
||||
view?.copyToClipboard(text)
|
||||
}
|
||||
|
||||
private fun loadData(forceRefresh: Boolean = false) {
|
||||
flowWithResourceIn {
|
||||
val semester = studentWithSemesters.semesters.getCurrentOrLast()
|
||||
studentInfoRepository.getStudentInfo(
|
||||
studentWithSemesters.student,
|
||||
semester,
|
||||
forceRefresh
|
||||
)
|
||||
}.onEach {
|
||||
when (it.status) {
|
||||
Status.LOADING -> Timber.i("Loading student info $infoType started")
|
||||
Status.SUCCESS -> {
|
||||
if (it.data != null) {
|
||||
Timber.i("Loading student info $infoType result: Success")
|
||||
showCorrectData(it.data)
|
||||
view?.run {
|
||||
showContent(true)
|
||||
showEmpty(false)
|
||||
showErrorView(false)
|
||||
}
|
||||
analytics.logEvent("load_item", "type" to "student_info")
|
||||
} else {
|
||||
Timber.i("Loading student info $infoType result: No school info found")
|
||||
view?.run {
|
||||
showContent(!isViewEmpty)
|
||||
showEmpty(isViewEmpty)
|
||||
showErrorView(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
Status.ERROR -> {
|
||||
Timber.i("Loading student info $infoType result: An exception occurred")
|
||||
errorHandler.dispatch(it.error!!)
|
||||
}
|
||||
}
|
||||
}.afterLoading {
|
||||
view?.run {
|
||||
hideRefresh()
|
||||
showProgress(false)
|
||||
enableSwipe(true)
|
||||
}
|
||||
}.launch()
|
||||
}
|
||||
|
||||
private fun showCorrectData(studentInfo: StudentInfo) {
|
||||
when (infoType) {
|
||||
StudentInfoView.Type.PERSONAL -> view?.showPersonalTypeData(studentInfo)
|
||||
StudentInfoView.Type.CONTACT -> view?.showContactTypeData(studentInfo)
|
||||
StudentInfoView.Type.ADDRESS -> view?.showAddressTypeData(studentInfo)
|
||||
StudentInfoView.Type.FAMILY -> view?.showFamilyTypeData(studentInfo)
|
||||
StudentInfoView.Type.SECOND_GUARDIAN -> view?.showSecondGuardianTypeData(studentInfo)
|
||||
StudentInfoView.Type.FIRST_GUARDIAN -> view?.showFirstGuardianTypeData(studentInfo)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showErrorViewOnError(message: String, error: Throwable) {
|
||||
view?.run {
|
||||
if (isViewEmpty) {
|
||||
lastError = error
|
||||
setErrorDetails(message)
|
||||
showErrorView(true)
|
||||
showEmpty(false)
|
||||
showContent(false)
|
||||
showProgress(false)
|
||||
} else showError(message, error)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
package io.github.wulkanowy.ui.modules.studentinfo
|
||||
|
||||
import io.github.wulkanowy.data.db.entities.StudentInfo
|
||||
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||
import io.github.wulkanowy.ui.base.BaseView
|
||||
|
||||
interface StudentInfoView : BaseView {
|
||||
|
||||
enum class Type {
|
||||
PERSONAL, ADDRESS, CONTACT, FAMILY, FIRST_GUARDIAN, SECOND_GUARDIAN
|
||||
}
|
||||
|
||||
val isViewEmpty: Boolean
|
||||
|
||||
fun initView()
|
||||
|
||||
fun updateData(data: List<Pair<String, String>>)
|
||||
|
||||
fun showPersonalTypeData(studentInfo: StudentInfo)
|
||||
|
||||
fun showContactTypeData(studentInfo: StudentInfo)
|
||||
|
||||
fun showAddressTypeData(studentInfo: StudentInfo)
|
||||
|
||||
fun showFamilyTypeData(studentInfo: StudentInfo)
|
||||
|
||||
fun showFirstGuardianTypeData(studentInfo: StudentInfo)
|
||||
|
||||
fun showSecondGuardianTypeData(studentInfo: StudentInfo)
|
||||
|
||||
fun openStudentInfoView(infoType: Type, studentWithSemesters: StudentWithSemesters)
|
||||
|
||||
fun showEmpty(show: Boolean)
|
||||
|
||||
fun showErrorView(show: Boolean)
|
||||
|
||||
fun setErrorDetails(message: String)
|
||||
|
||||
fun showProgress(show: Boolean)
|
||||
|
||||
fun enableSwipe(enable: Boolean)
|
||||
|
||||
fun showContent(show: Boolean)
|
||||
|
||||
fun hideRefresh()
|
||||
|
||||
fun copyToClipboard(text: String)
|
||||
}
|
@ -21,6 +21,7 @@ import io.github.wulkanowy.ui.modules.timetable.completed.CompletedLessonsFragme
|
||||
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
|
||||
|
||||
@ -69,6 +70,8 @@ class TimetableFragment : BaseFragment<FragmentTimetableBinding>(R.layout.fragme
|
||||
|
||||
with(binding) {
|
||||
timetableSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
|
||||
timetableSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary))
|
||||
timetableSwipe.setProgressBackgroundColorSchemeColor(requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh))
|
||||
timetableErrorRetry.setOnClickListener { presenter.onRetry() }
|
||||
timetableErrorDetails.setOnClickListener { presenter.onDetailsClick() }
|
||||
|
||||
@ -176,6 +179,7 @@ class TimetableFragment : BaseFragment<FragmentTimetableBinding>(R.layout.fragme
|
||||
setDateRangeLimiter(SchooldaysRangeLimiter())
|
||||
version = DatePickerDialog.Version.VERSION_2
|
||||
scrollOrientation = DatePickerDialog.ScrollOrientation.VERTICAL
|
||||
vibrate(false)
|
||||
show(this@TimetableFragment.parentFragmentManager, null)
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ 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
|
||||
|
||||
@ -53,6 +54,8 @@ class AdditionalLessonsFragment :
|
||||
|
||||
with(binding) {
|
||||
additionalLessonsSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
|
||||
additionalLessonsSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary))
|
||||
additionalLessonsSwipe.setProgressBackgroundColorSchemeColor(requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh))
|
||||
additionalLessonsErrorRetry.setOnClickListener { presenter.onRetry() }
|
||||
|
||||
additionalLessonsPreviousButton.setOnClickListener { presenter.onPreviousDay() }
|
||||
@ -128,6 +131,7 @@ class AdditionalLessonsFragment :
|
||||
setDateRangeLimiter(SchooldaysRangeLimiter())
|
||||
version = DatePickerDialog.Version.VERSION_2
|
||||
scrollOrientation = DatePickerDialog.ScrollOrientation.VERTICAL
|
||||
vibrate(false)
|
||||
show(this@AdditionalLessonsFragment.parentFragmentManager, null)
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ import io.github.wulkanowy.ui.widgets.DividerItemDecoration
|
||||
import io.github.wulkanowy.utils.SchooldaysRangeLimiter
|
||||
import io.github.wulkanowy.utils.dpToPx
|
||||
import io.github.wulkanowy.utils.getCompatDrawable
|
||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||
import java.time.LocalDate
|
||||
import javax.inject.Inject
|
||||
|
||||
@ -60,6 +61,8 @@ class CompletedLessonsFragment :
|
||||
|
||||
with(binding) {
|
||||
completedLessonsSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
|
||||
completedLessonsSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary))
|
||||
completedLessonsSwipe.setProgressBackgroundColorSchemeColor(requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh))
|
||||
completedLessonErrorRetry.setOnClickListener { presenter.onRetry() }
|
||||
completedLessonErrorDetails.setOnClickListener { presenter.onDetailsClick() }
|
||||
|
||||
@ -147,6 +150,7 @@ class CompletedLessonsFragment :
|
||||
setDateRangeLimiter(SchooldaysRangeLimiter())
|
||||
version = DatePickerDialog.Version.VERSION_2
|
||||
scrollOrientation = DatePickerDialog.ScrollOrientation.VERTICAL
|
||||
vibrate(false)
|
||||
show(this@CompletedLessonsFragment.parentFragmentManager, null)
|
||||
}
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import io.github.wulkanowy.utils.AnalyticsHelper
|
||||
import io.github.wulkanowy.utils.nextOrSameSchoolDay
|
||||
import io.github.wulkanowy.utils.nextSchoolDay
|
||||
import io.github.wulkanowy.utils.nickOrName
|
||||
import io.github.wulkanowy.utils.previousSchoolDay
|
||||
import io.github.wulkanowy.utils.toFormattedString
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
@ -151,8 +152,14 @@ class TimetableWidgetProvider : HiltBroadcastReceiver() {
|
||||
|
||||
val remoteView = RemoteViews(context.packageName, layoutId).apply {
|
||||
setEmptyView(R.id.timetableWidgetList, R.id.timetableWidgetEmpty)
|
||||
setTextViewText(R.id.timetableWidgetDate, date.toFormattedString("EEEE, dd.MM").capitalize())
|
||||
setTextViewText(R.id.timetableWidgetName, student?.studentName ?: context.getString(R.string.all_no_data))
|
||||
setTextViewText(
|
||||
R.id.timetableWidgetDate,
|
||||
date.toFormattedString("EEEE, dd.MM").capitalize()
|
||||
)
|
||||
setTextViewText(
|
||||
R.id.timetableWidgetName,
|
||||
student?.nickOrName ?: context.getString(R.string.all_no_data)
|
||||
)
|
||||
setRemoteAdapter(R.id.timetableWidgetList, adapterIntent)
|
||||
setOnClickPendingIntent(R.id.timetableWidgetNext, nextNavIntent)
|
||||
setOnClickPendingIntent(R.id.timetableWidgetPrev, prevNavIntent)
|
||||
|
@ -4,7 +4,9 @@ import android.content.res.Resources
|
||||
import android.os.Build.MANUFACTURER
|
||||
import android.os.Build.MODEL
|
||||
import android.os.Build.VERSION.SDK_INT
|
||||
import io.github.wulkanowy.BuildConfig.BUILD_TIMESTAMP
|
||||
import io.github.wulkanowy.BuildConfig.DEBUG
|
||||
import io.github.wulkanowy.BuildConfig.FLAVOR
|
||||
import io.github.wulkanowy.BuildConfig.VERSION_CODE
|
||||
import io.github.wulkanowy.BuildConfig.VERSION_NAME
|
||||
import javax.inject.Inject
|
||||
@ -17,6 +19,10 @@ open class AppInfo @Inject constructor() {
|
||||
|
||||
open val versionCode get() = VERSION_CODE
|
||||
|
||||
open val buildTimestamp get() = BUILD_TIMESTAMP
|
||||
|
||||
open val buildFlavor get() = FLAVOR
|
||||
|
||||
open val versionName get() = VERSION_NAME
|
||||
|
||||
open val systemVersion get() = SDK_INT
|
||||
@ -28,4 +34,9 @@ open class AppInfo @Inject constructor() {
|
||||
@Suppress("DEPRECATION")
|
||||
open val systemLanguage: String
|
||||
get() = Resources.getSystem().configuration.locale.language
|
||||
|
||||
open val defaultColorsForAvatar = listOf(
|
||||
0xe57373, 0xf06292, 0xba68c8, 0x9575cd, 0x7986cb, 0x64b5f6, 0x4fc3f7, 0x4dd0e1, 0x4db6ac,
|
||||
0x81c784, 0xaed581, 0xff8a65, 0xd4e157, 0xffd54f, 0xffb74d, 0xa1887f, 0x90a4ae
|
||||
)
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package io.github.wulkanowy.utils
|
||||
|
||||
import io.github.wulkanowy.data.Resource
|
||||
import io.github.wulkanowy.data.Status
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.collect
|
||||
@ -71,24 +72,14 @@ inline fun <ResultType, RequestType, T> networkBoundResource(
|
||||
|
||||
fun <T> flowWithResource(block: suspend () -> T) = flow {
|
||||
emit(Resource.loading())
|
||||
emit(try {
|
||||
Resource.success(block())
|
||||
} catch (e: Throwable) {
|
||||
Resource.error(e)
|
||||
})
|
||||
}
|
||||
emit(Resource.success(block()))
|
||||
}.catch { emit(Resource.error(it)) }
|
||||
|
||||
@OptIn(FlowPreview::class)
|
||||
fun <T> flowWithResourceIn(block: suspend () -> Flow<Resource<T>>) = flow {
|
||||
emit(Resource.loading())
|
||||
|
||||
block()
|
||||
.catch { emit(Resource.error(it)) }
|
||||
.collect {
|
||||
if (it.status != Status.LOADING || (it.status == Status.LOADING && it.data != null)) { // LOADING without data is already emitted
|
||||
emit(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
emitAll(block().filter { it.status != Status.LOADING || (it.status == Status.LOADING && it.data != null) })
|
||||
}.catch { emit(Resource.error(it)) }
|
||||
|
||||
fun <T> Flow<Resource<T>>.afterLoading(callback: () -> Unit) = onEach {
|
||||
if (it.status != Status.LOADING) callback()
|
||||
@ -96,4 +87,5 @@ fun <T> Flow<Resource<T>>.afterLoading(callback: () -> Unit) = onEach {
|
||||
|
||||
suspend fun <T> Flow<Resource<T>>.toFirstResult() = filter { it.status != Status.LOADING }.first()
|
||||
|
||||
suspend fun <T> Flow<Resource<T>>.waitForResult() = takeWhile { it.status == Status.LOADING }.collect()
|
||||
suspend fun <T> Flow<Resource<T>>.waitForResult() =
|
||||
takeWhile { it.status == Status.LOADING }.collect()
|
||||
|
@ -2,6 +2,8 @@ package io.github.wulkanowy.utils
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
import io.github.wulkanowy.ui.modules.about.AboutFragment
|
||||
import io.github.wulkanowy.ui.modules.account.AccountFragment
|
||||
import io.github.wulkanowy.ui.modules.account.accountdetails.AccountDetailsFragment
|
||||
import io.github.wulkanowy.ui.modules.attendance.AttendanceFragment
|
||||
import io.github.wulkanowy.ui.modules.exam.ExamFragment
|
||||
import io.github.wulkanowy.ui.modules.grade.GradeFragment
|
||||
@ -13,6 +15,7 @@ import io.github.wulkanowy.ui.modules.more.MoreFragment
|
||||
import io.github.wulkanowy.ui.modules.note.NoteFragment
|
||||
import io.github.wulkanowy.ui.modules.schoolandteachers.SchoolAndTeachersFragment
|
||||
import io.github.wulkanowy.ui.modules.settings.SettingsFragment
|
||||
import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoFragment
|
||||
import io.github.wulkanowy.ui.modules.timetable.TimetableFragment
|
||||
|
||||
fun Fragment.toSection(): MainView.Section? {
|
||||
@ -29,6 +32,9 @@ fun Fragment.toSection(): MainView.Section? {
|
||||
is SettingsFragment -> MainView.Section.SETTINGS
|
||||
is AboutFragment -> MainView.Section.ABOUT
|
||||
is SchoolAndTeachersFragment -> MainView.Section.SCHOOL
|
||||
is AccountFragment -> MainView.Section.ACCOUNT
|
||||
is AccountDetailsFragment -> MainView.Section.ACCOUNT
|
||||
is StudentInfoFragment -> MainView.Section.STUDENT_INFO
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
@ -16,14 +16,16 @@ fun List<Grade>.calcAverage(): Double {
|
||||
}
|
||||
|
||||
@JvmName("calcSummaryAverage")
|
||||
fun List<GradeSummary>.calcAverage(): Double {
|
||||
return asSequence().mapNotNull {
|
||||
if (it.finalGrade.matches("[0-6]".toRegex())) it.finalGrade.toDouble() else null
|
||||
}.average().let { if (it.isNaN()) 0.0 else it }
|
||||
}
|
||||
fun List<GradeSummary>.calcAverage() = asSequence()
|
||||
.mapNotNull {
|
||||
if (it.finalGrade.matches("[0-6]".toRegex())) {
|
||||
it.finalGrade.toDouble()
|
||||
} else null
|
||||
}
|
||||
.average()
|
||||
.let { if (it.isNaN()) 0.0 else it }
|
||||
|
||||
fun Grade.getBackgroundColor(theme: String): Int {
|
||||
return when (theme) {
|
||||
fun Grade.getBackgroundColor(theme: String) = when (theme) {
|
||||
"grade_color" -> getGradeColor()
|
||||
"material" -> when (value.toInt()) {
|
||||
6 -> R.color.grade_material_six
|
||||
@ -43,23 +45,19 @@ fun Grade.getBackgroundColor(theme: String): Int {
|
||||
1 -> R.color.grade_vulcan_one
|
||||
else -> R.color.grade_vulcan_default
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Grade.getGradeColor(): Int {
|
||||
return when (color) {
|
||||
fun Grade.getGradeColor() = when (color) {
|
||||
"000000" -> R.color.grade_black
|
||||
"F04C4C" -> R.color.grade_red
|
||||
"20A4F7" -> R.color.grade_blue
|
||||
"6ECD07" -> R.color.grade_green
|
||||
"B16CF1" -> R.color.grade_purple
|
||||
else -> R.color.grade_material_default
|
||||
}
|
||||
}
|
||||
|
||||
inline val Grade.colorStringId: Int
|
||||
get() {
|
||||
return when (color) {
|
||||
get() = when (color) {
|
||||
"000000" -> R.string.all_black
|
||||
"F04C4C" -> R.string.all_red
|
||||
"20A4F7" -> R.string.all_blue
|
||||
@ -67,12 +65,9 @@ inline val Grade.colorStringId: Int
|
||||
"B16CF1" -> R.string.all_purple
|
||||
else -> R.string.all_empty_color
|
||||
}
|
||||
}
|
||||
|
||||
fun Grade.changeModifier(plusModifier: Double, minusModifier: Double): Grade {
|
||||
return when {
|
||||
fun Grade.changeModifier(plusModifier: Double, minusModifier: Double) = when {
|
||||
modifier > 0 -> copy(modifier = plusModifier)
|
||||
modifier < 0 -> copy(modifier = -minusModifier)
|
||||
else -> this
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,5 @@
|
||||
package io.github.wulkanowy.utils
|
||||
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
|
||||
inline val Student.nickOrName get() = if (nick.isBlank()) studentName else nick
|
@ -1,6 +1,7 @@
|
||||
package io.github.wulkanowy.utils
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import java.text.SimpleDateFormat
|
||||
import java.time.DayOfWeek.FRIDAY
|
||||
import java.time.DayOfWeek.MONDAY
|
||||
import java.time.DayOfWeek.SATURDAY
|
||||
@ -8,12 +9,12 @@ import java.time.DayOfWeek.SUNDAY
|
||||
import java.time.Instant.ofEpochMilli
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDateTime
|
||||
import java.time.LocalDateTime.now
|
||||
import java.time.LocalDateTime.ofInstant
|
||||
import java.time.Month
|
||||
import java.time.ZoneId
|
||||
import java.time.ZoneOffset
|
||||
import java.time.format.DateTimeFormatter.ofPattern
|
||||
import java.time.format.TextStyle.FULL
|
||||
import java.time.temporal.TemporalAdjusters.firstInMonth
|
||||
import java.time.temporal.TemporalAdjusters.next
|
||||
import java.time.temporal.TemporalAdjusters.previous
|
||||
@ -33,24 +34,10 @@ fun LocalDateTime.toFormattedString(format: String = DATE_PATTERN): String = for
|
||||
|
||||
@SuppressLint("DefaultLocale")
|
||||
fun Month.getFormattedName(): String {
|
||||
return getDisplayName(FULL, Locale.getDefault())
|
||||
.let {
|
||||
when (it) {
|
||||
"stycznia" -> "Styczeń"
|
||||
"lutego" -> "Luty"
|
||||
"marca" -> "Marzec"
|
||||
"kwietnia" -> "Kwiecień"
|
||||
"maja" -> "Maj"
|
||||
"czerwca" -> "Czerwiec"
|
||||
"lipca" -> "Lipiec"
|
||||
"sierpnia" -> "Sierpień"
|
||||
"września" -> "Wrzesień"
|
||||
"października" -> "Październik"
|
||||
"listopada" -> "Listopad"
|
||||
"grudnia" -> "Grudzień"
|
||||
else -> it
|
||||
}
|
||||
}.capitalize()
|
||||
val formatter = SimpleDateFormat("LLLL", Locale.getDefault())
|
||||
|
||||
val date = now().withMonth(value)
|
||||
return formatter.format(date.toInstant(ZoneOffset.UTC).toEpochMilli()).capitalize()
|
||||
}
|
||||
|
||||
inline val LocalDate.nextSchoolDay: LocalDate
|
||||
|
@ -1,9 +1,7 @@
|
||||
Wersja 0.24.3
|
||||
- naprawiliśmy odczytywanie wiadomości
|
||||
- naprawiliśmy niekończące się ładowanie w ocenach na drugim semestrze
|
||||
- naprawiliśmy ciemny motyw na MIUI 12
|
||||
- dodaliśmy automatyczne odświeżanie danych w aplikacji
|
||||
- naprawiliśmy wysyłanie wiadomości kiedy uczeń zalogowany był/jest przez konto ucznia i rodzica
|
||||
- dodaliśmy zakładkę lekcji dodatkowych (na górnym pasku w planie lekcji)
|
||||
Wersja 0.25.0
|
||||
- naprawiliśmy przełączanie semestrów przy przełączaniu uczniów
|
||||
- naprawiliśmy błąd przy odświeżaniu ocen gdy włączony był inny niż domyślny tryb liczenia średniej
|
||||
- dodaliśmy menadżer kont, gdzie można podejrzeć informacje o uczniu oraz zmienić pseudonim
|
||||
- zmieniliśmy ikonę planu lekcji oraz kolor animacji odświeżania w ciemnym motywie
|
||||
|
||||
Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases
|
||||
|
16
app/src/main/res/drawable-anydpi-v24/ic_stat_timetable.xml
Normal file
@ -0,0 +1,16 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="#FFFFFF"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<group
|
||||
android:scaleX="0.92"
|
||||
android:scaleY="0.92"
|
||||
android:translateX="0.96"
|
||||
android:translateY="0.96">
|
||||
<path
|
||||
android:fillColor="#FFF"
|
||||
android:pathData="M14,12H15.5V14.82L17.94,16.23L17.19,17.53L14,15.69V12M4,2H18A2,2 0 0,1 20,4V10.1C21.24,11.36 22,13.09 22,15A7,7 0 0,1 15,22C13.09,22 11.36,21.24 10.1,20H4A2,2 0 0,1 2,18V4A2,2 0 0,1 4,2M4,15V18H8.67C8.24,17.09 8,16.07 8,15H4M4,8H10V5H4V8M18,8V5H12V8H18M4,13H8.29C8.63,11.85 9.26,10.82 10.1,10H4V13M15,10.15A4.85,4.85 0 0,0 10.15,15C10.15,17.68 12.32,19.85 15,19.85A4.85,4.85 0 0,0 19.85,15C19.85,12.32 17.68,10.15 15,10.15Z" />
|
||||
</group>
|
||||
</vector>
|
Before Width: | Height: | Size: 312 B After Width: | Height: | Size: 386 B |
Before Width: | Height: | Size: 275 B After Width: | Height: | Size: 315 B |
Before Width: | Height: | Size: 358 B After Width: | Height: | Size: 499 B |
Before Width: | Height: | Size: 459 B After Width: | Height: | Size: 671 B |
9
app/src/main/res/drawable/ic_account_details_family.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M16,4c0,-1.11 0.89,-2 2,-2s2,0.89 2,2s-0.89,2 -2,2S16,5.11 16,4zM20,22v-6h2.5l-2.54,-7.63C19.68,7.55 18.92,7 18.06,7h-0.12c-0.86,0 -1.63,0.55 -1.9,1.37l-0.86,2.58C16.26,11.55 17,12.68 17,14v8H20zM12.5,11.5c0.83,0 1.5,-0.67 1.5,-1.5s-0.67,-1.5 -1.5,-1.5S11,9.17 11,10S11.67,11.5 12.5,11.5zM5.5,6c1.11,0 2,-0.89 2,-2s-0.89,-2 -2,-2s-2,0.89 -2,2S4.39,6 5.5,6zM7.5,22v-7H9V9c0,-1.1 -0.9,-2 -2,-2H4C2.9,7 2,7.9 2,9v6h1.5v7H7.5zM14,22v-4h1v-4c0,-0.82 -0.68,-1.5 -1.5,-1.5h-2c-0.82,0 -1.5,0.68 -1.5,1.5v4h1v4H14z" />
|
||||
</vector>
|
@ -5,5 +5,5 @@
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FFF"
|
||||
android:pathData="M19,3h-1L18,1h-2v2L8,3L8,1L6,1v2L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM19,19L5,19L5,9h14v10zM5,7L5,5h14v2L5,7zM7,11h10v2L7,13zM7,15h7v2L7,17z" />
|
||||
android:pathData="M14,12H15.5V14.82L17.94,16.23L17.19,17.53L14,15.69V12M4,2H18A2,2 0 0,1 20,4V10.1C21.24,11.36 22,13.09 22,15A7,7 0 0,1 15,22C13.09,22 11.36,21.24 10.1,20H4A2,2 0 0,1 2,18V4A2,2 0 0,1 4,2M4,15V18H8.67C8.24,17.09 8,16.07 8,15H4M4,8H10V5H4V8M18,8V5H12V8H18M4,13H8.29C8.63,11.85 9.26,10.82 10.1,10H4V13M15,10.15A4.85,4.85 0 0,0 10.15,15C10.15,17.68 12.32,19.85 15,19.85A4.85,4.85 0 0,0 19.85,15C19.85,12.32 17.68,10.15 15,10.15Z" />
|
||||
</vector>
|
||||
|
@ -5,5 +5,5 @@
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FFF"
|
||||
android:pathData="M19,19V8H5V19H19M16,1H18V3H19C20.11,3 21,3.9 21,5V19C21,20.11 20.11,21 19,21H5C3.89,21 3,20.1 3,19V5C3,3.89 3.89,3 5,3H6V1H8V3H16V1M11,9.5H13V12.5H16V14.5H13V17.5H11V14.5H8V12.5H11V9.5Z" />
|
||||
android:pathData="M6,1L6,3L5,3C3.89,3 3,3.89 3,5v14c0,1.1 0.89,2 2,2h14c1.11,0 2,-0.89 2,-2L21,5C21,3.9 20.11,3 19,3L18,3L18,1L16,1L16,3L8,3L8,1ZM5,5L19,5L19,7L5,7ZM5,9L19,9L19,19L5,19ZM11,10v3L8,13v2h3v3h2v-3h3v-2h-3v-3z" />
|
||||
</vector>
|
||||
|