forked from github/wulkanowy-mirror
Add grade statistics (#251)
This commit is contained in:
parent
cae4f140e6
commit
dcab8df4b9
@ -97,6 +97,9 @@ dependencies {
|
||||
implementation "com.aurelhubert:ahbottomnavigation:2.3.4"
|
||||
implementation 'com.ncapdevi:frag-nav:3.1.0'
|
||||
|
||||
implementation "com.hootsuite.android:nachos:1.1.1"
|
||||
implementation 'com.github.PhilJay:MPAndroidChart:971640b29d'
|
||||
|
||||
implementation 'com.github.pwittchen:reactivenetwork-rx2:3.0.2'
|
||||
implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
|
||||
implementation "io.reactivex.rxjava2:rxjava:2.2.5"
|
||||
@ -125,8 +128,6 @@ dependencies {
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.0'
|
||||
androidTestImplementation 'org.mockito:mockito-android:2.23.4'
|
||||
androidTestImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
|
||||
|
||||
implementation "com.hootsuite.android:nachos:1.1.1"
|
||||
}
|
||||
|
||||
apply plugin: 'com.google.gms.google-services'
|
||||
|
3
app/proguard-rules.pro
vendored
3
app/proguard-rules.pro
vendored
@ -32,5 +32,8 @@
|
||||
-dontwarn rx.internal.util.**
|
||||
-dontwarn sun.misc.Unsafe
|
||||
|
||||
#Config for MPAndroidChart
|
||||
-keep class com.github.mikephil.charting.** { *; }
|
||||
|
||||
#Config for API
|
||||
-keep class io.github.wulkanowy.api.** {*;}
|
||||
|
@ -0,0 +1,69 @@
|
||||
package io.github.wulkanowy.data.repositories.grade
|
||||
|
||||
import androidx.room.Room
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import io.github.wulkanowy.data.db.AppDatabase
|
||||
import io.github.wulkanowy.data.db.entities.GradeStatistics
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.repositories.gradestatistics.GradeStatisticsLocal
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class GradeStatisticsLocalTest {
|
||||
|
||||
private lateinit var gradeStatisticsLocal: GradeStatisticsLocal
|
||||
|
||||
private lateinit var testDb: AppDatabase
|
||||
|
||||
@Before
|
||||
fun createDb() {
|
||||
testDb = Room.inMemoryDatabaseBuilder(ApplicationProvider.getApplicationContext(), AppDatabase::class.java)
|
||||
.build()
|
||||
gradeStatisticsLocal = GradeStatisticsLocal(testDb.gradeStatistics)
|
||||
}
|
||||
|
||||
@After
|
||||
fun closeDb() {
|
||||
testDb.close()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun saveAndRead_subject() {
|
||||
gradeStatisticsLocal.saveGradesStatistics(listOf(
|
||||
getGradeStatistics("Matematyka", 2, 1),
|
||||
getGradeStatistics("Fizyka", 1, 2)
|
||||
))
|
||||
|
||||
val stats = gradeStatisticsLocal.getGradesStatistics(
|
||||
Semester(2, 2, "", 1, 2, true, 1 ,1), false,
|
||||
"Matematyka"
|
||||
).blockingGet()
|
||||
assertEquals(1, stats.size)
|
||||
assertEquals(stats[0].subject, "Matematyka")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun saveAndRead_all() {
|
||||
gradeStatisticsLocal.saveGradesStatistics(listOf(
|
||||
getGradeStatistics("Matematyka", 2, 1),
|
||||
getGradeStatistics("Chemia", 2, 1),
|
||||
getGradeStatistics("Fizyka", 1, 2)
|
||||
))
|
||||
|
||||
val stats = gradeStatisticsLocal.getGradesStatistics(
|
||||
Semester(2, 2, "", 1, 2, true, 1, 1), false,
|
||||
"Wszystkie"
|
||||
).blockingGet()
|
||||
assertEquals(1, stats.size)
|
||||
assertEquals(stats[0].subject, "Wszystkie")
|
||||
}
|
||||
|
||||
private fun getGradeStatistics(subject: String, studentId: Int, semesterId: Int): GradeStatistics {
|
||||
return GradeStatistics(studentId, semesterId, subject, 5, 5, false)
|
||||
}
|
||||
}
|
@ -4,7 +4,7 @@
|
||||
package="io.github.wulkanowy"
|
||||
android:installLocation="internalOnly">
|
||||
|
||||
<uses-sdk tools:overrideLibrary="com.readystatesoftware.chuck"/>
|
||||
<uses-sdk tools:overrideLibrary="com.readystatesoftware.chuck" />
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
@ -20,7 +20,7 @@
|
||||
android:supportsRtl="false"
|
||||
android:theme="@style/WulkanowyTheme"
|
||||
android:usesCleartextTraffic="true"
|
||||
tools:ignore="GoogleAppIndexingWarning">
|
||||
tools:ignore="GoogleAppIndexingWarning,UnusedAttribute">
|
||||
<activity
|
||||
android:name=".ui.modules.splash.SplashActivity"
|
||||
android:screenOrientation="portrait"
|
||||
|
@ -82,6 +82,10 @@ internal class RepositoryModule {
|
||||
@Provides
|
||||
fun provideGradeSummaryDao(database: AppDatabase) = database.gradeSummaryDao
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideGradeStatisticsDao(database: AppDatabase) = database.gradeStatistics
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideMessagesDao(database: AppDatabase) = database.messagesDao
|
||||
|
@ -11,6 +11,7 @@ import io.github.wulkanowy.data.db.dao.AttendanceSummaryDao
|
||||
import io.github.wulkanowy.data.db.dao.CompletedLessonsDao
|
||||
import io.github.wulkanowy.data.db.dao.ExamDao
|
||||
import io.github.wulkanowy.data.db.dao.GradeDao
|
||||
import io.github.wulkanowy.data.db.dao.GradeStatisticsDao
|
||||
import io.github.wulkanowy.data.db.dao.GradeSummaryDao
|
||||
import io.github.wulkanowy.data.db.dao.HomeworkDao
|
||||
import io.github.wulkanowy.data.db.dao.LuckyNumberDao
|
||||
@ -27,6 +28,7 @@ import io.github.wulkanowy.data.db.entities.AttendanceSummary
|
||||
import io.github.wulkanowy.data.db.entities.CompletedLesson
|
||||
import io.github.wulkanowy.data.db.entities.Exam
|
||||
import io.github.wulkanowy.data.db.entities.Grade
|
||||
import io.github.wulkanowy.data.db.entities.GradeStatistics
|
||||
import io.github.wulkanowy.data.db.entities.GradeSummary
|
||||
import io.github.wulkanowy.data.db.entities.Homework
|
||||
import io.github.wulkanowy.data.db.entities.LuckyNumber
|
||||
@ -43,6 +45,7 @@ import io.github.wulkanowy.data.db.migrations.Migration3
|
||||
import io.github.wulkanowy.data.db.migrations.Migration4
|
||||
import io.github.wulkanowy.data.db.migrations.Migration5
|
||||
import io.github.wulkanowy.data.db.migrations.Migration6
|
||||
import io.github.wulkanowy.data.db.migrations.Migration7
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
@ -56,6 +59,7 @@ import javax.inject.Singleton
|
||||
AttendanceSummary::class,
|
||||
Grade::class,
|
||||
GradeSummary::class,
|
||||
GradeStatistics::class,
|
||||
Message::class,
|
||||
Note::class,
|
||||
Homework::class,
|
||||
@ -72,7 +76,7 @@ import javax.inject.Singleton
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
|
||||
companion object {
|
||||
const val VERSION_SCHEMA = 6
|
||||
const val VERSION_SCHEMA = 7
|
||||
|
||||
fun newInstance(context: Context): AppDatabase {
|
||||
return Room.databaseBuilder(context, AppDatabase::class.java, "wulkanowy_database")
|
||||
@ -84,7 +88,8 @@ abstract class AppDatabase : RoomDatabase() {
|
||||
Migration3(),
|
||||
Migration4(),
|
||||
Migration5(),
|
||||
Migration6()
|
||||
Migration6(),
|
||||
Migration7()
|
||||
)
|
||||
.build()
|
||||
}
|
||||
@ -106,6 +111,8 @@ abstract class AppDatabase : RoomDatabase() {
|
||||
|
||||
abstract val gradeSummaryDao: GradeSummaryDao
|
||||
|
||||
abstract val gradeStatistics: GradeStatisticsDao
|
||||
|
||||
abstract val messagesDao: MessagesDao
|
||||
|
||||
abstract val noteDao: NoteDao
|
||||
|
@ -0,0 +1,26 @@
|
||||
package io.github.wulkanowy.data.db.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Delete
|
||||
import androidx.room.Insert
|
||||
import androidx.room.Query
|
||||
import io.github.wulkanowy.data.db.entities.GradeStatistics
|
||||
import io.reactivex.Maybe
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
@Dao
|
||||
interface GradeStatisticsDao {
|
||||
|
||||
@Insert
|
||||
fun insertAll(gradesStatistics: List<GradeStatistics>)
|
||||
|
||||
@Delete
|
||||
fun deleteAll(gradesStatistics: List<GradeStatistics>)
|
||||
|
||||
@Query("SELECT * FROM GradesStatistics WHERE student_id = :studentId AND semester_id = :semesterId AND subject = :subjectName AND is_semester = :isSemester")
|
||||
fun loadSubject(semesterId: Int, studentId: Int, subjectName: String, isSemester: Boolean): Maybe<List<GradeStatistics>>
|
||||
|
||||
@Query("SELECT * FROM GradesStatistics WHERE student_id = :studentId AND semester_id = :semesterId AND is_semester = :isSemester")
|
||||
fun loadAll(semesterId: Int, studentId: Int, isSemester: Boolean): Maybe<List<GradeStatistics>>
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package io.github.wulkanowy.data.db.entities
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
@Entity(tableName = "GradesStatistics")
|
||||
data class GradeStatistics(
|
||||
|
||||
@ColumnInfo(name = "student_id")
|
||||
val studentId: Int,
|
||||
|
||||
@ColumnInfo(name = "semester_id")
|
||||
val semesterId: Int,
|
||||
|
||||
val subject: String,
|
||||
|
||||
val grade: Int,
|
||||
|
||||
val amount: Int,
|
||||
|
||||
@ColumnInfo(name = "is_semester")
|
||||
val semester: Boolean
|
||||
) {
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
var id: Long = 0
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package io.github.wulkanowy.data.db.migrations
|
||||
|
||||
import androidx.room.migration.Migration
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
|
||||
class Migration7 : Migration(6, 7) {
|
||||
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL("CREATE TABLE IF NOT EXISTS `GradesStatistics` (" +
|
||||
"`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," +
|
||||
"`student_id` INTEGER NOT NULL," +
|
||||
"`semester_id` INTEGER NOT NULL," +
|
||||
"`subject` TEXT NOT NULL," +
|
||||
"`grade` INTEGER NOT NULL," +
|
||||
"`amount` INTEGER NOT NULL," +
|
||||
"`is_semester` INTEGER NOT NULL)")
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package io.github.wulkanowy.data.repositories.gradestatistics
|
||||
|
||||
import io.github.wulkanowy.data.db.dao.GradeStatisticsDao
|
||||
import io.github.wulkanowy.data.db.entities.GradeStatistics
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.reactivex.Maybe
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class GradeStatisticsLocal @Inject constructor(private val gradeStatisticsDb: GradeStatisticsDao) {
|
||||
|
||||
fun getGradesStatistics(semester: Semester, isSemester: Boolean): Maybe<List<GradeStatistics>> {
|
||||
return gradeStatisticsDb.loadAll(semester.semesterId, semester.studentId, isSemester)
|
||||
.filter { !it.isEmpty() }
|
||||
}
|
||||
|
||||
fun getGradesStatistics(semester: Semester, isSemester: Boolean, subjectName: String): Maybe<List<GradeStatistics>> {
|
||||
return (if ("Wszystkie" == subjectName) gradeStatisticsDb.loadAll(semester.semesterId, semester.studentId, isSemester).map { list ->
|
||||
list.groupBy { it.grade }.map {
|
||||
GradeStatistics(semester.studentId, semester.semesterId, subjectName, it.key, it.value.fold(0) { acc, e -> acc + e.amount }, false)
|
||||
}
|
||||
}
|
||||
else gradeStatisticsDb.loadSubject(semester.semesterId, semester.studentId, subjectName, isSemester)).filter { !it.isEmpty() }
|
||||
}
|
||||
|
||||
fun saveGradesStatistics(gradesStatistics: List<GradeStatistics>) {
|
||||
gradeStatisticsDb.insertAll(gradesStatistics)
|
||||
}
|
||||
|
||||
fun deleteGradesStatistics(gradesStatistics: List<GradeStatistics>) {
|
||||
gradeStatisticsDb.deleteAll(gradesStatistics)
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package io.github.wulkanowy.data.repositories.gradestatistics
|
||||
|
||||
import io.github.wulkanowy.api.Api
|
||||
import io.github.wulkanowy.data.db.entities.GradeStatistics
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.reactivex.Single
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class GradeStatisticsRemote @Inject constructor(private val api: Api) {
|
||||
|
||||
fun getGradeStatistics(semester: Semester, isSemester: Boolean): Single<List<GradeStatistics>> {
|
||||
return Single.just(api.apply { diaryId = semester.diaryId })
|
||||
.flatMap { it.getGradesStatistics(semester.semesterId, isSemester) }
|
||||
.map { gradeStatistics ->
|
||||
gradeStatistics.map {
|
||||
GradeStatistics(
|
||||
semesterId = semester.semesterId,
|
||||
studentId = semester.studentId,
|
||||
subject = it.subject,
|
||||
grade = it.gradeValue,
|
||||
amount = it.amount ?: 0,
|
||||
semester = isSemester
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package io.github.wulkanowy.data.repositories.gradestatistics
|
||||
|
||||
import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork
|
||||
import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings
|
||||
import io.github.wulkanowy.data.db.entities.GradeStatistics
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.reactivex.Single
|
||||
import java.net.UnknownHostException
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class GradeStatisticsRepository @Inject constructor(
|
||||
private val settings: InternetObservingSettings,
|
||||
private val local: GradeStatisticsLocal,
|
||||
private val remote: GradeStatisticsRemote
|
||||
) {
|
||||
|
||||
fun getGradesStatistics(semester: Semester, subjectName: String, isSemester: Boolean, forceRefresh: Boolean = false): Single<List<GradeStatistics>> {
|
||||
return local.getGradesStatistics(semester, isSemester, subjectName).filter { !forceRefresh }
|
||||
.switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings)
|
||||
.flatMap {
|
||||
if (it) remote.getGradeStatistics(semester, isSemester)
|
||||
else Single.error(UnknownHostException())
|
||||
}.flatMap { newGradesStats ->
|
||||
local.getGradesStatistics(semester, isSemester).toSingle(emptyList())
|
||||
.doOnSuccess { oldGradesStats ->
|
||||
local.deleteGradesStatistics(oldGradesStats - newGradesStats)
|
||||
local.saveGradesStatistics(newGradesStats - oldGradesStats)
|
||||
}
|
||||
}.flatMap { local.getGradesStatistics(semester, isSemester, subjectName).toSingle(emptyList()) })
|
||||
}
|
||||
}
|
@ -10,7 +10,7 @@ import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class SubjectRepostory @Inject constructor(
|
||||
class SubjectRepository @Inject constructor(
|
||||
private val settings: InternetObservingSettings,
|
||||
private val local: SubjectLocal,
|
||||
private val remote: SubjectRemote
|
@ -5,7 +5,7 @@ import io.github.wulkanowy.data.db.entities.Subject
|
||||
import io.github.wulkanowy.data.repositories.attendancesummary.AttendanceSummaryRepository
|
||||
import io.github.wulkanowy.data.repositories.semester.SemesterRepository
|
||||
import io.github.wulkanowy.data.repositories.student.StudentRepository
|
||||
import io.github.wulkanowy.data.repositories.subject.SubjectRepostory
|
||||
import io.github.wulkanowy.data.repositories.subject.SubjectRepository
|
||||
import io.github.wulkanowy.ui.base.session.BaseSessionPresenter
|
||||
import io.github.wulkanowy.ui.base.session.SessionErrorHandler
|
||||
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
|
||||
@ -21,7 +21,7 @@ import javax.inject.Inject
|
||||
class AttendanceSummaryPresenter @Inject constructor(
|
||||
private val errorHandler: SessionErrorHandler,
|
||||
private val attendanceSummaryRepository: AttendanceSummaryRepository,
|
||||
private val subjectRepository: SubjectRepostory,
|
||||
private val subjectRepository: SubjectRepository,
|
||||
private val studentRepository: StudentRepository,
|
||||
private val semesterRepository: SemesterRepository,
|
||||
private val schedulers: SchedulersProvider,
|
||||
|
@ -0,0 +1,34 @@
|
||||
package io.github.wulkanowy.ui.modules.grade
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.ViewGroup
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
|
||||
/**
|
||||
* @see <a href="https://stackoverflow.com/a/50382854">Tabs don't fit to screen with tabmode=scrollable, Even with a Custom Tab Layout</a>
|
||||
*/
|
||||
class CustomTabLayout : TabLayout {
|
||||
|
||||
constructor(context: Context) : super(context)
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
|
||||
|
||||
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||
setMeasuredDimension(widthMeasureSpec, heightMeasureSpec)
|
||||
val tabLayout = getChildAt(0) as ViewGroup
|
||||
val childCount = tabLayout.childCount
|
||||
|
||||
if (childCount == 0) return
|
||||
|
||||
val tabMinWidth = context.resources.displayMetrics.widthPixels / childCount
|
||||
|
||||
for (i in 0 until childCount) {
|
||||
tabLayout.getChildAt(i).minimumWidth = tabMinWidth
|
||||
}
|
||||
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
|
||||
}
|
||||
}
|
@ -14,6 +14,7 @@ import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.ui.base.BaseFragmentPagerAdapter
|
||||
import io.github.wulkanowy.ui.base.session.BaseSessionFragment
|
||||
import io.github.wulkanowy.ui.modules.grade.details.GradeDetailsFragment
|
||||
import io.github.wulkanowy.ui.modules.grade.statistics.GradeStatisticsFragment
|
||||
import io.github.wulkanowy.ui.modules.grade.summary.GradeSummaryFragment
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import io.github.wulkanowy.utils.setOnSelectPageListener
|
||||
@ -63,12 +64,14 @@ class GradeFragment : BaseSessionFragment(), GradeView, MainView.MainChildView,
|
||||
containerId = gradeViewPager.id
|
||||
addFragmentsWithTitle(mapOf(
|
||||
GradeDetailsFragment.newInstance() to getString(R.string.all_details),
|
||||
GradeSummaryFragment.newInstance() to getString(R.string.grade_menu_summary)
|
||||
GradeSummaryFragment.newInstance() to getString(R.string.grade_menu_summary),
|
||||
GradeStatisticsFragment.newInstance() to getString(R.string.grade_menu_statistics)
|
||||
))
|
||||
}
|
||||
|
||||
gradeViewPager.run {
|
||||
adapter = pagerAdapter
|
||||
offscreenPageLimit = 3
|
||||
setOnSelectPageListener { presenter.onPageSelected(it) }
|
||||
}
|
||||
gradeTabLayout.setupWithViewPager(gradeViewPager)
|
||||
|
@ -7,6 +7,7 @@ import io.github.wulkanowy.di.scopes.PerChildFragment
|
||||
import io.github.wulkanowy.di.scopes.PerFragment
|
||||
import io.github.wulkanowy.ui.base.BaseFragmentPagerAdapter
|
||||
import io.github.wulkanowy.ui.modules.grade.details.GradeDetailsFragment
|
||||
import io.github.wulkanowy.ui.modules.grade.statistics.GradeStatisticsFragment
|
||||
import io.github.wulkanowy.ui.modules.grade.summary.GradeSummaryFragment
|
||||
|
||||
@Module
|
||||
@ -28,4 +29,8 @@ abstract class GradeModule {
|
||||
@PerChildFragment
|
||||
@ContributesAndroidInjector
|
||||
abstract fun binGradeSummaryFragment(): GradeSummaryFragment
|
||||
|
||||
@PerChildFragment
|
||||
@ContributesAndroidInjector
|
||||
abstract fun binGradeStatisticsFragment(): GradeStatisticsFragment
|
||||
}
|
||||
|
@ -114,6 +114,6 @@ class GradePresenter @Inject constructor(
|
||||
}
|
||||
|
||||
private fun notifyChildrenSemesterChange() {
|
||||
for (i in 0..1) view?.notifyChildSemesterChange(i)
|
||||
for (i in 0..2) view?.notifyChildSemesterChange(i)
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,191 @@
|
||||
package io.github.wulkanowy.ui.modules.grade.statistics
|
||||
|
||||
import android.graphics.Color.WHITE
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.TextView
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.github.mikephil.charting.components.Legend
|
||||
import com.github.mikephil.charting.components.LegendEntry
|
||||
import com.github.mikephil.charting.data.PieData
|
||||
import com.github.mikephil.charting.data.PieDataSet
|
||||
import com.github.mikephil.charting.data.PieEntry
|
||||
import com.github.mikephil.charting.formatter.ValueFormatter
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.GradeStatistics
|
||||
import io.github.wulkanowy.ui.base.session.BaseSessionFragment
|
||||
import io.github.wulkanowy.ui.modules.grade.GradeFragment
|
||||
import io.github.wulkanowy.ui.modules.grade.GradeView
|
||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||
import io.github.wulkanowy.utils.setOnItemSelectedListener
|
||||
import kotlinx.android.synthetic.main.fragment_grade_statistics.*
|
||||
import javax.inject.Inject
|
||||
|
||||
class GradeStatisticsFragment : BaseSessionFragment(), GradeStatisticsView, GradeView.GradeChildView {
|
||||
|
||||
@Inject
|
||||
lateinit var presenter: GradeStatisticsPresenter
|
||||
|
||||
private lateinit var subjectsAdapter: ArrayAdapter<String>
|
||||
|
||||
companion object {
|
||||
private const val SAVED_CHART_TYPE = "CURRENT_TYPE"
|
||||
|
||||
fun newInstance() = GradeStatisticsFragment()
|
||||
}
|
||||
|
||||
override val isViewEmpty
|
||||
get() = gradeStatisticsChart.isEmpty
|
||||
|
||||
private val gradeColors = listOf(
|
||||
6 to R.color.grade_material_six,
|
||||
5 to R.color.grade_material_five,
|
||||
4 to R.color.grade_material_four,
|
||||
3 to R.color.grade_material_three,
|
||||
2 to R.color.grade_material_two,
|
||||
1 to R.color.grade_material_one
|
||||
)
|
||||
|
||||
private val gradeLabels = listOf(
|
||||
"6, 6-", "5, 5-, 5+", "4, 4-, 4+", "3, 3-, 3+", "2, 2-, 2+", "1, 1+"
|
||||
)
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
return inflater.inflate(R.layout.fragment_grade_statistics, container, false)
|
||||
}
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
messageContainer = gradeStatisticsChart
|
||||
presenter.onAttachView(this, savedInstanceState?.getBoolean(SAVED_CHART_TYPE))
|
||||
}
|
||||
|
||||
override fun initView() {
|
||||
gradeStatisticsChart.run {
|
||||
description.isEnabled = false
|
||||
setHoleColor(context.getThemeAttrColor(android.R.attr.windowBackground))
|
||||
setCenterTextColor(context.getThemeAttrColor(android.R.attr.textColorPrimary))
|
||||
animateXY(1000, 1000)
|
||||
minAngleForSlices = 25f
|
||||
legend.apply {
|
||||
textColor = context.getThemeAttrColor(android.R.attr.textColorPrimary)
|
||||
setCustom(gradeLabels.mapIndexed { i, it ->
|
||||
LegendEntry().apply {
|
||||
label = it
|
||||
formColor = ContextCompat.getColor(context, gradeColors[i].second)
|
||||
form = Legend.LegendForm.SQUARE
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
context?.let {
|
||||
subjectsAdapter = ArrayAdapter(it, android.R.layout.simple_spinner_item, ArrayList<String>())
|
||||
subjectsAdapter.setDropDownViewResource(R.layout.item_attendance_summary_subject)
|
||||
}
|
||||
|
||||
gradeStatisticsSubjects.run {
|
||||
adapter = subjectsAdapter
|
||||
setOnItemSelectedListener { presenter.onSubjectSelected((it as TextView).text.toString()) }
|
||||
}
|
||||
|
||||
gradeStatisticsSwipe.setOnRefreshListener { presenter.onSwipeRefresh() }
|
||||
}
|
||||
|
||||
override fun updateSubjects(data: ArrayList<String>) {
|
||||
subjectsAdapter.run {
|
||||
clear()
|
||||
addAll(data)
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateData(items: List<GradeStatistics>) {
|
||||
gradeStatisticsChart.run {
|
||||
data = PieData(PieDataSet(items.map {
|
||||
PieEntry(it.amount.toFloat(), it.grade.toString())
|
||||
}, "Legenda").apply {
|
||||
valueTextSize = 12f
|
||||
sliceSpace = 1f
|
||||
valueTextColor = WHITE
|
||||
setColors(items.map {
|
||||
gradeColors.single { color -> color.first == it.grade }.second
|
||||
}.toIntArray(), context)
|
||||
}).apply {
|
||||
setTouchEnabled(false)
|
||||
setValueFormatter(object : ValueFormatter() {
|
||||
override fun getPieLabel(value: Float, pieEntry: PieEntry): String {
|
||||
return resources.getQuantityString(R.plurals.grade_number_item, value.toInt(), value.toInt())
|
||||
}
|
||||
})
|
||||
centerText = items.fold(0) { acc, it -> acc + it.amount }
|
||||
.let { resources.getQuantityString(R.plurals.grade_number_item, it, it) }
|
||||
}
|
||||
invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
override fun showSubjects(show: Boolean) {
|
||||
gradeStatisticsSubjectsContainer.visibility = if (show) View.VISIBLE else View.INVISIBLE
|
||||
}
|
||||
|
||||
override fun clearView() {
|
||||
gradeStatisticsChart.clear()
|
||||
}
|
||||
|
||||
override fun showContent(show: Boolean) {
|
||||
gradeStatisticsChart.visibility = if (show) View.VISIBLE else View.GONE
|
||||
}
|
||||
|
||||
override fun showEmpty(show: Boolean) {
|
||||
gradeStatisticsEmpty.visibility = if (show) View.VISIBLE else View.INVISIBLE
|
||||
}
|
||||
|
||||
override fun showProgress(show: Boolean) {
|
||||
gradeStatisticsProgress.visibility = if (show) View.VISIBLE else View.GONE
|
||||
}
|
||||
|
||||
override fun showRefresh(show: Boolean) {
|
||||
gradeStatisticsSwipe.isRefreshing = show
|
||||
}
|
||||
|
||||
override fun onParentLoadData(semesterId: Int, forceRefresh: Boolean) {
|
||||
presenter.onParentViewLoadData(semesterId, forceRefresh)
|
||||
}
|
||||
|
||||
override fun onParentReselected() {
|
||||
//
|
||||
}
|
||||
|
||||
override fun onParentChangeSemester() {
|
||||
presenter.onParentViewChangeSemester()
|
||||
}
|
||||
|
||||
override fun notifyParentDataLoaded(semesterId: Int) {
|
||||
(parentFragment as? GradeFragment)?.onChildFragmentLoaded(semesterId)
|
||||
}
|
||||
|
||||
override fun notifyParentRefresh() {
|
||||
(parentFragment as? GradeFragment)?.onChildRefresh()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
gradeStatisticsTypeSwitch.setOnCheckedChangeListener { _, checkedId ->
|
||||
presenter.onTypeChange(checkedId == R.id.gradeStatisticsTypeSemester)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
outState.putBoolean(GradeStatisticsFragment.SAVED_CHART_TYPE, presenter.currentIsSemester)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
presenter.onDetachView()
|
||||
}
|
||||
}
|
@ -0,0 +1,140 @@
|
||||
package io.github.wulkanowy.ui.modules.grade.statistics
|
||||
|
||||
import io.github.wulkanowy.data.db.entities.Subject
|
||||
import io.github.wulkanowy.data.repositories.gradestatistics.GradeStatisticsRepository
|
||||
import io.github.wulkanowy.data.repositories.semester.SemesterRepository
|
||||
import io.github.wulkanowy.data.repositories.student.StudentRepository
|
||||
import io.github.wulkanowy.data.repositories.subject.SubjectRepository
|
||||
import io.github.wulkanowy.ui.base.session.BaseSessionPresenter
|
||||
import io.github.wulkanowy.ui.base.session.SessionErrorHandler
|
||||
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
|
||||
import io.github.wulkanowy.utils.SchedulersProvider
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
class GradeStatisticsPresenter @Inject constructor(
|
||||
private val errorHandler: SessionErrorHandler,
|
||||
private val gradeStatisticsRepository: GradeStatisticsRepository,
|
||||
private val subjectRepository: SubjectRepository,
|
||||
private val studentRepository: StudentRepository,
|
||||
private val semesterRepository: SemesterRepository,
|
||||
private val schedulers: SchedulersProvider,
|
||||
private val analytics: FirebaseAnalyticsHelper
|
||||
) : BaseSessionPresenter<GradeStatisticsView>(errorHandler) {
|
||||
|
||||
private var subjects = emptyList<Subject>()
|
||||
|
||||
private var currentSemesterId = 0
|
||||
|
||||
private var currentSubjectName: String = "Wszystkie"
|
||||
|
||||
var currentIsSemester = false
|
||||
private set
|
||||
|
||||
fun onAttachView(view: GradeStatisticsView, isSemester: Boolean?) {
|
||||
super.onAttachView(view)
|
||||
currentIsSemester = isSemester ?: false
|
||||
view.initView()
|
||||
}
|
||||
|
||||
fun onParentViewLoadData(semesterId: Int, forceRefresh: Boolean) {
|
||||
currentSemesterId = semesterId
|
||||
loadSubjects()
|
||||
loadData(semesterId, currentSubjectName, currentIsSemester, forceRefresh)
|
||||
}
|
||||
|
||||
fun onParentViewChangeSemester() {
|
||||
view?.run {
|
||||
showProgress(true)
|
||||
showRefresh(false)
|
||||
showContent(false)
|
||||
showEmpty(false)
|
||||
clearView()
|
||||
}
|
||||
disposable.clear()
|
||||
}
|
||||
|
||||
fun onSwipeRefresh() {
|
||||
Timber.i("Force refreshing the grade stats")
|
||||
view?.notifyParentRefresh()
|
||||
}
|
||||
|
||||
fun onSubjectSelected(name: String) {
|
||||
Timber.i("Select attendance stats subject $name")
|
||||
view?.run {
|
||||
showContent(false)
|
||||
showProgress(true)
|
||||
showEmpty(false)
|
||||
clearView()
|
||||
}
|
||||
(subjects.singleOrNull { it.name == name }?.name).let {
|
||||
if (it != currentSubjectName) loadData(currentSemesterId, name, currentIsSemester)
|
||||
}
|
||||
}
|
||||
|
||||
fun onTypeChange(isSemester: Boolean) {
|
||||
Timber.i("Select attendance stats semester: $isSemester")
|
||||
view?.run {
|
||||
showContent(false)
|
||||
showProgress(true)
|
||||
showEmpty(false)
|
||||
clearView()
|
||||
}
|
||||
loadData(currentSemesterId, currentSubjectName, isSemester)
|
||||
}
|
||||
|
||||
private fun loadSubjects() {
|
||||
Timber.i("Loading grade stats subjects started")
|
||||
disposable.add(studentRepository.getCurrentStudent()
|
||||
.flatMap { semesterRepository.getCurrentSemester(it) }
|
||||
.flatMap { subjectRepository.getSubjects(it) }
|
||||
.doOnSuccess { subjects = it }
|
||||
.map { ArrayList(it.map { subject -> subject.name }) }
|
||||
.subscribeOn(schedulers.backgroundThread)
|
||||
.observeOn(schedulers.mainThread)
|
||||
.subscribe({
|
||||
Timber.i("Loading grade stats subjects result: Success")
|
||||
view?.run {
|
||||
updateSubjects(it)
|
||||
showSubjects(true)
|
||||
}
|
||||
}, {
|
||||
Timber.e("Loading grade stats subjects result: An exception occurred")
|
||||
errorHandler.dispatch(it)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
private fun loadData(semesterId: Int, subjectName: String, isSemester: Boolean, forceRefresh: Boolean = false) {
|
||||
Timber.i("Loading grade stats data started")
|
||||
currentSubjectName = subjectName
|
||||
currentIsSemester = isSemester
|
||||
disposable.add(studentRepository.getCurrentStudent()
|
||||
.flatMap { semesterRepository.getSemesters(it) }
|
||||
.flatMap { gradeStatisticsRepository.getGradesStatistics(it.first { item -> item.semesterId == semesterId }, subjectName, isSemester, forceRefresh) }
|
||||
.map { list -> list.sortedByDescending { it.grade } }
|
||||
.map { list -> list.filter { it.amount != 0 } }
|
||||
.subscribeOn(schedulers.backgroundThread)
|
||||
.observeOn(schedulers.mainThread)
|
||||
.doFinally {
|
||||
view?.run {
|
||||
showRefresh(false)
|
||||
showProgress(false)
|
||||
notifyParentDataLoaded(semesterId)
|
||||
}
|
||||
}
|
||||
.subscribe({
|
||||
Timber.i("Loading grade stats result: Success")
|
||||
view?.run {
|
||||
showEmpty(it.isEmpty())
|
||||
showContent(it.isNotEmpty())
|
||||
updateData(it)
|
||||
}
|
||||
analytics.logEvent("load_grade_statistics", "items" to it.size, "force_refresh" to forceRefresh)
|
||||
}) {
|
||||
Timber.e("Loading grade stats result: An exception occurred")
|
||||
view?.run { showEmpty(isViewEmpty) }
|
||||
errorHandler.dispatch(it)
|
||||
})
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package io.github.wulkanowy.ui.modules.grade.statistics
|
||||
|
||||
import io.github.wulkanowy.data.db.entities.GradeStatistics
|
||||
import io.github.wulkanowy.ui.base.session.BaseSessionView
|
||||
|
||||
interface GradeStatisticsView : BaseSessionView {
|
||||
|
||||
val isViewEmpty: Boolean
|
||||
|
||||
fun initView()
|
||||
|
||||
fun updateSubjects(data: ArrayList<String>)
|
||||
|
||||
fun updateData(items: List<GradeStatistics>)
|
||||
|
||||
fun showSubjects(show: Boolean)
|
||||
|
||||
fun notifyParentDataLoaded(semesterId: Int)
|
||||
|
||||
fun notifyParentRefresh()
|
||||
|
||||
fun clearView()
|
||||
|
||||
fun showContent(show: Boolean)
|
||||
|
||||
fun showEmpty(show: Boolean)
|
||||
|
||||
fun showProgress(show: Boolean)
|
||||
|
||||
fun showRefresh(show: Boolean)
|
||||
}
|
@ -4,11 +4,19 @@ import android.view.View
|
||||
import android.widget.AdapterView
|
||||
import android.widget.Spinner
|
||||
|
||||
/**
|
||||
* @see <a href="https://stackoverflow.com/a/29602298">How to keep onItemSelected from firing off on a newly instantiated Spinner?</a>
|
||||
*/
|
||||
inline fun Spinner.setOnItemSelectedListener(crossinline listener: (view: View?) -> Unit) {
|
||||
onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
||||
override fun onNothingSelected(parent: AdapterView<*>?) {}
|
||||
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
|
||||
listener(view)
|
||||
onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
||||
override fun onNothingSelected(parent: AdapterView<*>?) {}
|
||||
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
|
||||
listener(view)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,32 +5,33 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.google.android.material.tabs.TabLayout
|
||||
<io.github.wulkanowy.ui.modules.grade.CustomTabLayout
|
||||
android:id="@+id/gradeTabLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:background="@color/colorPrimary"
|
||||
android:elevation="5dp"
|
||||
android:visibility="invisible"
|
||||
app:tabGravity="fill"
|
||||
app:tabIndicatorColor="@android:color/white"
|
||||
app:tabMaxWidth="0dp"
|
||||
app:tabMode="fixed"
|
||||
app:tabTextColor="@android:color/white" />
|
||||
app:tabMode="scrollable"
|
||||
app:tabTextColor="@android:color/white"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<androidx.viewpager.widget.ViewPager
|
||||
android:id="@+id/gradeViewPager"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="48dp"
|
||||
android:visibility="invisible" />
|
||||
android:visibility="invisible"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/gradeProgress"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:indeterminate="true" />
|
||||
android:indeterminate="true"
|
||||
tools:visibility="invisible" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/gradeEmpty"
|
||||
@ -38,7 +39,8 @@
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:visibility="invisible">
|
||||
android:visibility="invisible"
|
||||
tools:visibility="visible">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:layout_width="wrap_content"
|
||||
|
@ -2,7 +2,8 @@
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".ui.modules.grade.details.GradeDetailsFragment">
|
||||
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
android:id="@+id/gradeDetailsSwipe"
|
||||
@ -20,7 +21,8 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:indeterminate="true" />
|
||||
android:indeterminate="true"
|
||||
tools:visibility="gone" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/gradeDetailsEmpty"
|
||||
|
128
app/src/main/res/layout/fragment_grade_statistics.xml
Normal file
128
app/src/main/res/layout/fragment_grade_statistics.xml
Normal file
@ -0,0 +1,128 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
tools:context=".ui.modules.grade.statistics.GradeStatisticsFragment">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/gradeStatisticsSubjectsContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?android:attr/windowBackground"
|
||||
android:elevation="5dp"
|
||||
android:padding="5dp"
|
||||
android:visibility="invisible"
|
||||
tools:listitem="@layout/item_attendance_summary"
|
||||
tools:targetApi="lollipop"
|
||||
tools:visibility="visible">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatSpinner
|
||||
android:id="@+id/gradeStatisticsSubjects"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="middle"
|
||||
android:entries="@array/endpoints_keys"
|
||||
android:paddingStart="10dp"
|
||||
android:paddingLeft="10dp"
|
||||
android:paddingTop="10dp"
|
||||
android:paddingEnd="30dp"
|
||||
android:paddingRight="30dp"
|
||||
android:paddingBottom="10dp"
|
||||
android:spinnerMode="dialog" />
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
android:id="@+id/gradeStatisticsSwipe"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fillViewport="true">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<RadioGroup
|
||||
android:id="@+id/gradeStatisticsTypeSwitch"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal"
|
||||
android:paddingTop="5dp">
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/gradeStatisticsTypePartial"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="true"
|
||||
android:tag="partial"
|
||||
android:text="@string/grade_statistics_partial" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/gradeStatisticsTypeSemester"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:tag="annual"
|
||||
android:text="@string/grade_statistics_semester" />
|
||||
</RadioGroup>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.github.mikephil.charting.charts.PieChart
|
||||
android:id="@+id/gradeStatisticsChart"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?android:attr/windowBackground"
|
||||
android:minHeight="400dp"
|
||||
android:visibility="gone"
|
||||
android:layout_margin="10dp"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/gradeStatisticsProgress"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:indeterminate="true"
|
||||
tools:visibility="gone" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/gradeStatisticsEmpty"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:visibility="invisible">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:minWidth="100dp"
|
||||
android:minHeight="100dp"
|
||||
app:srcCompat="@drawable/ic_menu_main_grade_26dp"
|
||||
app:tint="?android:attr/textColorPrimary"
|
||||
tools:ignore="contentDescription" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/grade_no_items"
|
||||
android:textSize="20sp" />
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
</LinearLayout>
|
@ -2,7 +2,8 @@
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".ui.modules.grade.summary.GradeSummaryFragment">
|
||||
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
android:id="@+id/gradeSummarySwipe"
|
||||
@ -20,7 +21,8 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:indeterminate="true" />
|
||||
android:indeterminate="true"
|
||||
tools:visibility="gone" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/gradeSummaryEmpty"
|
||||
|
@ -16,7 +16,7 @@
|
||||
android:layout_height="40dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:tint="?android:attr/textColorPrimary"
|
||||
android:tint="@android:color/black"
|
||||
app:srcCompat="@drawable/ic_all_account_24dp" />
|
||||
|
||||
<TextView
|
||||
|
@ -60,7 +60,10 @@
|
||||
<string name="grade_summary_calculated_average">Obliczona średnia</string>
|
||||
<string name="grade_summary_final_average">Końcowa średnia</string>
|
||||
<string name="grade_menu_summary">Podsumowanie</string>
|
||||
<string name="grade_menu_statistics">Oceny klasy</string>
|
||||
<string name="grade_menu_read">Oznacz jako przeczytane</string>
|
||||
<string name="grade_statistics_partial">Oceny cząstkowe</string>
|
||||
<string name="grade_statistics_semester">Oceny semestralne</string>
|
||||
<plurals name="grade_number_item">
|
||||
<item quantity="one">%d ocena</item>
|
||||
<item quantity="few">%d oceny</item>
|
||||
|
@ -60,7 +60,10 @@
|
||||
<string name="grade_summary_calculated_average">Calculated average</string>
|
||||
<string name="grade_summary_final_average">Final average</string>
|
||||
<string name="grade_menu_summary">Summary</string>
|
||||
<string name="grade_menu_statistics">Class grades</string>
|
||||
<string name="grade_menu_read">Mark as read</string>
|
||||
<string name="grade_statistics_partial">Partial grades</string>
|
||||
<string name="grade_statistics_semester">Semester grades</string>
|
||||
<plurals name="grade_number_item">
|
||||
<item quantity="one">%d grade</item>
|
||||
<item quantity="other">%d grades</item>
|
||||
|
@ -0,0 +1,53 @@
|
||||
package io.github.wulkanowy.data.repositories.remote
|
||||
|
||||
import io.github.wulkanowy.api.Api
|
||||
import io.github.wulkanowy.api.grades.GradeStatistics
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.repositories.gradestatistics.GradeStatisticsRemote
|
||||
import io.mockk.MockKAnnotations
|
||||
import io.mockk.every
|
||||
import io.mockk.impl.annotations.MockK
|
||||
import io.mockk.impl.annotations.SpyK
|
||||
import io.reactivex.Single
|
||||
import org.junit.Assert
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
class GradeStatisticsRemoteTest {
|
||||
|
||||
@SpyK
|
||||
private var mockApi = Api()
|
||||
|
||||
@MockK
|
||||
private lateinit var semesterMock: Semester
|
||||
|
||||
@Before
|
||||
fun initApi() {
|
||||
MockKAnnotations.init(this)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getGradeStatisticsTest() {
|
||||
every { mockApi.getGradesStatistics(1, any()) } returns Single.just(listOf(
|
||||
getGradeStatistics("Fizyka"),
|
||||
getGradeStatistics("Matematyka")
|
||||
))
|
||||
|
||||
every { mockApi.diaryId } returns 1
|
||||
every { semesterMock.studentId } returns 1
|
||||
every { semesterMock.semesterId } returns 1
|
||||
every { semesterMock.semesterName } returns 2
|
||||
every { semesterMock.diaryId } returns 1
|
||||
|
||||
val stats = GradeStatisticsRemote(mockApi).getGradeStatistics(semesterMock, false).blockingGet()
|
||||
Assert.assertEquals(2, stats.size)
|
||||
}
|
||||
|
||||
private fun getGradeStatistics(subjectName: String): GradeStatistics {
|
||||
return GradeStatistics().apply {
|
||||
subject = subjectName
|
||||
gradeValue = 5
|
||||
amount = 10
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user