diff --git a/app/build.gradle b/app/build.gradle index 4f1b37b3d..d2089beb0 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,15 +1,15 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' -apply plugin: 'kotlin-kapt'// sync warning probably caused by bug https://issuetracker.google.com/issues/74537216, fix in AS 3.2 +apply plugin: 'kotlin-kapt' apply plugin: 'kotlin-android-extensions' apply plugin: 'io.fabric' +apply plugin: 'com.github.triplet.play' apply from: 'jacoco.gradle' apply from: 'sonarqube.gradle' -apply plugin: 'com.github.triplet.play' android { compileSdkVersion 28 - buildToolsVersion '28.0.2' + buildToolsVersion '28.0.3' playAccountConfigs { defaultAccountConfig { @@ -75,13 +75,15 @@ dependencies { implementation('com.github.wulkanowy:api:07201a4') { exclude module: "threetenbp" } + implementation "com.android.support:support-v4:$supportVersion" + implementation "com.android.support:appcompat-v7:$supportVersion" implementation "com.android.support:design:$supportVersion" implementation "com.android.support:cardview-v7:$supportVersion" implementation "com.android.support:preference-v14:$supportVersion" implementation 'com.android.support:multidex:1.0.3' - implementation "com.google.android.gms:play-services-oss-licenses:16.0.0" + implementation "com.google.android.gms:play-services-oss-licenses:16.0.1" implementation "com.firebase:firebase-jobdispatcher:0.8.5" implementation "com.google.dagger:dagger-android-support:2.17" @@ -92,7 +94,7 @@ dependencies { implementation "android.arch.persistence.room:rxjava2:1.1.1" kapt "android.arch.persistence.room:compiler:1.1.1" - implementation "eu.davidea:flexible-adapter:5.0.5" + implementation "eu.davidea:flexible-adapter:5.0.6" implementation "eu.davidea:flexible-adapter-ui:1.0.0-b5" implementation "com.aurelhubert:ahbottomnavigation:2.2.0" implementation 'com.ncapdevi:frag-nav:3.0.0-RC3' @@ -101,8 +103,6 @@ dependencies { implementation 'io.reactivex.rxjava2:rxandroid:2.0.2' implementation "io.reactivex.rxjava2:rxjava:2.2.1" - implementation "org.apache.commons:commons-lang3:3.8" - implementation "org.apache.commons:commons-collections4:4.2" implementation "com.jakewharton.threetenabp:threetenabp:1.1.0" implementation "com.jakewharton.timber:timber:4.7.1" diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/local/AttendanceLocalTest.kt b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/local/AttendanceLocalTest.kt index c8293b275..3af246eb8 100644 --- a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/local/AttendanceLocalTest.kt +++ b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/local/AttendanceLocalTest.kt @@ -34,13 +34,13 @@ class AttendanceLocalTest { @Test fun saveAndReadTest() { attendanceLocal.saveAttendance(listOf( - Attendance(studentId = "1", diaryId = "2", date = LocalDate.of(2018, 9, 10)), - Attendance(studentId = "1", diaryId = "2", date = LocalDate.of(2018, 9, 14)), - Attendance(studentId = "1", diaryId = "2", date = LocalDate.of(2018, 9, 17)) // in next week + Attendance(0, "1", "2", LocalDate.of(2018, 9, 10), 0, "", ""), + Attendance(0, "1", "2", LocalDate.of(2018, 9, 14), 0, "", ""), + Attendance(0, "1", "2", LocalDate.of(2018, 9, 17), 0, "", "") )) val attendance = attendanceLocal - .getAttendance(Semester(studentId = "1", diaryId = "2", semesterId = "3"), + .getAttendance(Semester(1, "1", "2", "", "3", 1), LocalDate.of(2018, 9, 10), LocalDate.of(2018, 9, 14) ) diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/local/ExamLocalTest.kt b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/local/ExamLocalTest.kt index 52edf5593..8a6b4bcc9 100644 --- a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/local/ExamLocalTest.kt +++ b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/local/ExamLocalTest.kt @@ -34,13 +34,13 @@ class ExamLocalTest { @Test fun saveAndReadTest() { examLocal.saveExams(listOf( - Exam(studentId = "1", diaryId = "2", date = LocalDate.of(2018, 9, 10)), - Exam(studentId = "1", diaryId = "2", date = LocalDate.of(2018, 9, 14)), - Exam(studentId = "1", diaryId = "2", date = LocalDate.of(2018, 9, 17)) // in next week + Exam(0, "1", "2", LocalDate.of(2018, 9, 10), LocalDate.now(), "", "", "", "", "", ""), + Exam(0, "1", "2", LocalDate.of(2018, 9, 14), LocalDate.now(), "", "", "", "", "", ""), + Exam(0, "1", "2", LocalDate.of(2018, 9, 17), LocalDate.now(), "", "", "", "", "", "") )) val exams = examLocal - .getExams(Semester(studentId = "1", diaryId = "2", semesterId = "3"), + .getExams(Semester(1, "1", "2", "", "3", 1), LocalDate.of(2018, 9, 10), LocalDate.of(2018, 9, 14) ) diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/local/SessionLocalTest.kt b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/local/SessionLocalTest.kt index 17264c956..6c13b25c4 100644 --- a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/local/SessionLocalTest.kt +++ b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/local/SessionLocalTest.kt @@ -16,7 +16,7 @@ import kotlin.test.assertEquals @RunWith(AndroidJUnit4::class) class SessionLocalTest { - private lateinit var studentLocal: SessionLocal + private lateinit var sessionLocal: SessionLocal private lateinit var testDb: AppDatabase @@ -28,7 +28,7 @@ class SessionLocalTest { testDb = Room.inMemoryDatabaseBuilder(context, AppDatabase::class.java) .build() sharedHelper = SharedPrefHelper(context.getSharedPreferences("TEST", Context.MODE_PRIVATE)) - studentLocal = SessionLocal(testDb.studentDao(), testDb.semesterDao(), sharedHelper, context) + sessionLocal = SessionLocal(testDb.studentDao(), testDb.semesterDao(), sharedHelper, context) } @After @@ -38,12 +38,12 @@ class SessionLocalTest { @Test fun saveAndReadTest() { - studentLocal.saveStudent(Student(email = "test", password = "test123", schoolId = "23")).blockingAwait() + sessionLocal.saveStudent(Student(email = "test", password = "test123", schoolId = "23")).blockingAwait() assert(sharedHelper.getLong(SessionLocal.LAST_USER_KEY, 0) == 1L) - assert(studentLocal.isSessionSaved) + assert(sessionLocal.isSessionSaved) - val student = studentLocal.getLastStudent().blockingGet() + val student = sessionLocal.getLastStudent().blockingGet() assertEquals("23", student.schoolId) } } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 21c7bc994..e4d5f780a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,6 +1,5 @@ @@ -15,13 +14,11 @@ android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:theme="@style/WulkanowyTheme" - android:usesCleartextTraffic="true" - tools:targetApi="m"> + android:usesCleartextTraffic="true"> + android:screenOrientation="portrait" + android:theme="@style/WulkanowyTheme.SplashScreen"> @@ -32,39 +29,13 @@ android:name=".ui.login.LoginActivity" android:configChanges="orientation|screenSize" android:label="@string/login_title" - android:theme="@style/WulkanowyTheme.DarkActionBar" android:windowSoftInputMode="adjustResize" /> - - - - - - - - - - - - - - - - + android:launchMode="singleTop" + android:theme="@style/WulkanowyTheme.NoActionBar" /> = - DaggerAppComponent.builder().create(this) + override fun applicationInjector(): AndroidInjector { + return DaggerAppComponent.builder().create(this) + } } diff --git a/app/src/main/java/io/github/wulkanowy/data/RepositoryModule.kt b/app/src/main/java/io/github/wulkanowy/data/RepositoryModule.kt index 4cba8eda5..cf1031e5d 100644 --- a/app/src/main/java/io/github/wulkanowy/data/RepositoryModule.kt +++ b/app/src/main/java/io/github/wulkanowy/data/RepositoryModule.kt @@ -1,15 +1,14 @@ package io.github.wulkanowy.data -import android.arch.persistence.room.Room import android.content.Context import android.content.SharedPreferences import android.support.v7.preference.PreferenceManager import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings +import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.strategy.SocketInternetObservingStrategy import dagger.Module import dagger.Provides import io.github.wulkanowy.api.Api import io.github.wulkanowy.data.db.AppDatabase -import io.github.wulkanowy.utils.DATABASE_NAME import javax.inject.Singleton @Module @@ -18,7 +17,10 @@ internal class RepositoryModule { @Singleton @Provides fun provideInternetObservingSettings(): InternetObservingSettings { - return InternetObservingSettings.create() + return InternetObservingSettings + .strategy(SocketInternetObservingStrategy()) + .host("www.google.com") + .build() } @Singleton @@ -27,10 +29,7 @@ internal class RepositoryModule { @Singleton @Provides - fun provideDatabase(context: Context): AppDatabase { - return Room.databaseBuilder(context, AppDatabase::class.java, DATABASE_NAME) - .build() - } + fun provideDatabase(context: Context) = AppDatabase.newInstance(context) @Provides fun provideErrorHandler(context: Context) = ErrorHandler(context.resources) @@ -49,6 +48,14 @@ internal class RepositoryModule { @Provides fun provideSemesterDao(database: AppDatabase) = database.semesterDao() + @Singleton + @Provides + fun provideGradeDao(database: AppDatabase) = database.gradeDao() + + @Singleton + @Provides + fun provideGradeSummaryDao(database: AppDatabase) = database.gradeSummaryDao() + @Singleton @Provides fun provideExamDao(database: AppDatabase) = database.examsDao() diff --git a/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt b/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt index 3e186f48d..cc39b58e1 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt @@ -1,16 +1,12 @@ package io.github.wulkanowy.data.db import android.arch.persistence.room.Database +import android.arch.persistence.room.Room import android.arch.persistence.room.RoomDatabase import android.arch.persistence.room.TypeConverters -import io.github.wulkanowy.data.db.dao.AttendanceDao -import io.github.wulkanowy.data.db.dao.ExamDao -import io.github.wulkanowy.data.db.dao.SemesterDao -import io.github.wulkanowy.data.db.dao.StudentDao -import io.github.wulkanowy.data.db.entities.Attendance -import io.github.wulkanowy.data.db.entities.Exam -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student +import android.content.Context +import io.github.wulkanowy.data.db.dao.* +import io.github.wulkanowy.data.db.entities.* import javax.inject.Singleton @Singleton @@ -19,7 +15,9 @@ import javax.inject.Singleton Student::class, Semester::class, Exam::class, - Attendance::class + Attendance::class, + Grade::class, + GradeSummary::class ], version = 1, exportSchema = false @@ -27,6 +25,13 @@ import javax.inject.Singleton @TypeConverters(Converters::class) abstract class AppDatabase : RoomDatabase() { + companion object { + fun newInstance(context: Context): AppDatabase { + return Room.databaseBuilder(context, AppDatabase::class.java, "wulkanowy_database") + .build() + } + } + abstract fun studentDao(): StudentDao abstract fun semesterDao(): SemesterDao @@ -34,4 +39,8 @@ abstract class AppDatabase : RoomDatabase() { abstract fun examsDao(): ExamDao abstract fun attendanceDao(): AttendanceDao + + abstract fun gradeDao(): GradeDao + + abstract fun gradeSummaryDao(): GradeSummaryDao } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeDao.kt new file mode 100644 index 000000000..5149e4732 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeDao.kt @@ -0,0 +1,21 @@ +package io.github.wulkanowy.data.db.dao + +import android.arch.persistence.room.* +import io.github.wulkanowy.data.db.entities.Grade +import io.reactivex.Maybe + +@Dao +interface GradeDao { + + @Insert + fun insertAll(grades: List) + + @Update + fun update(grade: Grade) + + @Delete + fun deleteAll(grades: List) + + @Query("SELECT * FROM Grades WHERE semester_id = :semesterId AND student_id = :studentId") + fun getGrades(semesterId: String, studentId: String): Maybe> +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeSummaryDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeSummaryDao.kt new file mode 100644 index 000000000..6dab2690d --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeSummaryDao.kt @@ -0,0 +1,18 @@ +package io.github.wulkanowy.data.db.dao + +import android.arch.persistence.room.Dao +import android.arch.persistence.room.Insert +import android.arch.persistence.room.OnConflictStrategy.REPLACE +import android.arch.persistence.room.Query +import io.github.wulkanowy.data.db.entities.GradeSummary +import io.reactivex.Maybe + +@Dao +interface GradeSummaryDao { + + @Insert(onConflict = REPLACE) + fun insertAll(gradesSummary: List) + + @Query("SELECT * FROM grades_summary WHERE student_id = :studentId AND semester_id = :semesterId") + fun getGradesSummary(semesterId: String, studentId: String): Maybe> +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/Attendance.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/Attendance.kt index a3bfa2aa4..be8911a57 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/entities/Attendance.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/Attendance.kt @@ -13,18 +13,18 @@ data class Attendance( var id: Long = 0, @ColumnInfo(name = "student_id") - var studentId: String = "", + var studentId: String, @ColumnInfo(name = "diary_id") - var diaryId: String = "", + var diaryId: String, var date: LocalDate, - var number: Int = 0, + var number: Int, - var subject: String = "", + var subject: String, - var name: String = "", + var name: String, var presence: Boolean = false, diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/Exam.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/Exam.kt index 9dd81a3d5..807c8f393 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/entities/Exam.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/Exam.kt @@ -13,26 +13,26 @@ data class Exam( var id: Long = 0, @ColumnInfo(name = "student_id") - var studentId: String = "", + var studentId: String, @ColumnInfo(name = "diary_id") - var diaryId: String = "", + var diaryId: String, var date: LocalDate, @ColumnInfo(name = "entry_date") var entryDate: LocalDate = LocalDate.now(), - var subject: String = "", + var subject: String, - var group: String = "", + var group: String, - var type: String = "", + var type: String, - var description: String = "", + var description: String, - var teacher: String = "", + var teacher: String, @ColumnInfo(name = "teacher_symbol") - var teacherSymbol: String = "" + var teacherSymbol: String ) : Serializable diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/Grade.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/Grade.kt new file mode 100644 index 000000000..1fee97a84 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/Grade.kt @@ -0,0 +1,49 @@ +package io.github.wulkanowy.data.db.entities + +import android.arch.persistence.room.ColumnInfo +import android.arch.persistence.room.Entity +import android.arch.persistence.room.PrimaryKey +import org.threeten.bp.LocalDate +import java.io.Serializable + +@Entity(tableName = "Grades") +data class Grade( + + @ColumnInfo(name = "semester_id") + var semesterId: String, + + @ColumnInfo(name = "student_id") + var studentId: String, + + var subject: String, + + var entry: String, + + var value: Int, + + var modifier: Double, + + var comment: String, + + var color: String, + + @ColumnInfo(name = "grade_symbol") + var gradeSymbol: String, + + var description: String, + + var weight: String, + + var weightValue: Int, + + var date: LocalDate, + + var teacher: String +) : Serializable { + + @PrimaryKey(autoGenerate = true) + var id: Long = 0 + + @ColumnInfo(name = "is_new") + var isNew: Boolean = false +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/GradeSummary.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/GradeSummary.kt new file mode 100644 index 000000000..58528a04e --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/GradeSummary.kt @@ -0,0 +1,26 @@ +package io.github.wulkanowy.data.db.entities + +import android.arch.persistence.room.ColumnInfo +import android.arch.persistence.room.Entity +import android.arch.persistence.room.Index +import android.arch.persistence.room.PrimaryKey + +@Entity(tableName = "Grades_Summary", + indices = [Index(value = ["semester_id", "student_id", "subject"], unique = true)]) +data class GradeSummary( + + @PrimaryKey(autoGenerate = true) + var id: Long = 0, + + @ColumnInfo(name = "semester_id") + var semesterId: String, + + @ColumnInfo(name = "student_id") + var studentId: String, + + var subject: String, + + var predictedGrade: String, + + var finalGrade: String +) diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/Semester.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/Semester.kt index 5ef1f359b..2bad1cba1 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/entities/Semester.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/Semester.kt @@ -19,13 +19,13 @@ data class Semester( var diaryId: String, @ColumnInfo(name = "diary_name") - var diaryName: String = "", + var diaryName: String, @ColumnInfo(name = "semester_id") var semesterId: String, @ColumnInfo(name = "semester_name") - var semesterName: Int = 0, + var semesterName: Int, @ColumnInfo(name = "is_current") var current: Boolean = false diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt index 4ed56d07c..91986826d 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt @@ -6,7 +6,7 @@ import io.github.wulkanowy.data.db.entities.Attendance import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.repositories.local.AttendanceLocal import io.github.wulkanowy.data.repositories.remote.AttendanceRemote -import io.github.wulkanowy.utils.extension.getWeekFirstDayAlwaysCurrent +import io.github.wulkanowy.utils.weekFirstDayAlwaysCurrent import io.reactivex.Single import org.threeten.bp.DayOfWeek import org.threeten.bp.LocalDate @@ -21,7 +21,7 @@ class AttendanceRepository @Inject constructor( ) { fun getAttendance(semester: Semester, startDate: LocalDate, endDate: LocalDate, forceRefresh: Boolean = false): Single> { - val start = startDate.getWeekFirstDayAlwaysCurrent() + val start = startDate.weekFirstDayAlwaysCurrent val end = endDate.with(TemporalAdjusters.nextOrSame(DayOfWeek.FRIDAY)) return local.getAttendance(semester, start, end).filter { !forceRefresh } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/ExamRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/ExamRepository.kt index e2932900b..a334719ec 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/ExamRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/ExamRepository.kt @@ -6,8 +6,7 @@ import io.github.wulkanowy.data.db.entities.Exam import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.repositories.local.ExamLocal import io.github.wulkanowy.data.repositories.remote.ExamRemote -import io.github.wulkanowy.utils.extension.getWeekFirstDayAlwaysCurrent -import io.github.wulkanowy.utils.extension.toDate +import io.github.wulkanowy.utils.weekFirstDayAlwaysCurrent import io.reactivex.Single import org.threeten.bp.DayOfWeek import org.threeten.bp.LocalDate @@ -24,7 +23,7 @@ class ExamRepository @Inject constructor( ) { fun getExams(semester: Semester, startDate: LocalDate, endDate: LocalDate, forceRefresh: Boolean = false): Single> { - val start = startDate.getWeekFirstDayAlwaysCurrent() + val start = startDate.weekFirstDayAlwaysCurrent val end = endDate.with(TemporalAdjusters.nextOrSame(DayOfWeek.FRIDAY)) return local.getExams(semester, start, end).filter { !forceRefresh } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/GradeRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/GradeRepository.kt new file mode 100644 index 000000000..9e4e2ab89 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/GradeRepository.kt @@ -0,0 +1,42 @@ +package io.github.wulkanowy.data.repositories + +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.Grade +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.repositories.local.GradeLocal +import io.github.wulkanowy.data.repositories.remote.GradeRemote +import io.reactivex.Completable +import io.reactivex.Single +import java.net.UnknownHostException +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class GradeRepository @Inject constructor( + private val settings: InternetObservingSettings, + private val local: GradeLocal, + private val remote: GradeRemote +) { + + fun getGrades(semester: Semester, forceRefresh: Boolean = false): Single> { + return local.getGrades(semester).filter { !forceRefresh } + .switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings) + .flatMap { + if (it) remote.getGrades(semester) + else Single.error(UnknownHostException()) + }.flatMap { newGrades -> + local.getGrades(semester).toSingle(emptyList()) + .doOnSuccess { oldGrades -> + local.deleteGrades(oldGrades - newGrades) + local.saveGrades((newGrades - oldGrades) + .onEach { if (oldGrades.isNotEmpty()) it.isNew = true }) + } + }.flatMap { local.getGrades(semester).toSingle(emptyList()) }) + + } + + fun updateGrade(grade: Grade): Completable { + return local.updateGrade(grade) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/GradeSummaryRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/GradeSummaryRepository.kt new file mode 100644 index 000000000..58afe3ecb --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/GradeSummaryRepository.kt @@ -0,0 +1,30 @@ +package io.github.wulkanowy.data.repositories + +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.GradeSummary +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.repositories.local.GradeSummaryLocal +import io.github.wulkanowy.data.repositories.remote.GradeSummaryRemote +import io.reactivex.Single +import java.net.UnknownHostException +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class GradeSummaryRepository @Inject constructor( + private val settings: InternetObservingSettings, + private val local: GradeSummaryLocal, + private val remote: GradeSummaryRemote +) { + + fun getGradesSummary(semester: Semester, forceRefresh: Boolean = false): Single> { + return local.getGradesSummary(semester).filter { !forceRefresh } + .switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings) + .flatMap { + if (it) remote.getGradeSummary(semester) + else Single.error(UnknownHostException()) + } + ).doOnSuccess { local.saveGradesSummary(it) } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/SessionRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/SessionRepository.kt index 6d158d29b..ae7e201ef 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/SessionRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/SessionRepository.kt @@ -42,9 +42,8 @@ class SessionRepository @Inject constructor( } fun saveStudent(student: Student): Completable { - return remote.getSemesters(student).flatMapCompletable { - local.saveSemesters(it) - }.concatWith(local.saveStudent(student)) + return remote.getSemesters(student).flatMapCompletable { local.saveSemesters(it) } + .concatWith(local.saveStudent(student)) } fun clearCache() { diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/local/ExamLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/local/ExamLocal.kt index 0074cd0fc..b8c3c0e27 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/local/ExamLocal.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/local/ExamLocal.kt @@ -3,11 +3,8 @@ package io.github.wulkanowy.data.repositories.local import io.github.wulkanowy.data.db.dao.ExamDao import io.github.wulkanowy.data.db.entities.Exam import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.utils.extension.toDate import io.reactivex.Maybe -import org.threeten.bp.DayOfWeek import org.threeten.bp.LocalDate -import org.threeten.bp.temporal.TemporalAdjusters import javax.inject.Inject class ExamLocal @Inject constructor(private val examDb: ExamDao) { diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/local/GradeLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/local/GradeLocal.kt new file mode 100644 index 000000000..5689ee4bb --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/local/GradeLocal.kt @@ -0,0 +1,29 @@ +package io.github.wulkanowy.data.repositories.local + +import io.github.wulkanowy.data.db.dao.GradeDao +import io.github.wulkanowy.data.db.entities.Grade +import io.github.wulkanowy.data.db.entities.Semester +import io.reactivex.Completable +import io.reactivex.Maybe +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class GradeLocal @Inject constructor(private val gradeDb: GradeDao) { + + fun getGrades(semester: Semester): Maybe> { + return gradeDb.getGrades(semester.semesterId, semester.studentId).filter { !it.isEmpty() } + } + + fun saveGrades(grades: List) { + gradeDb.insertAll(grades) + } + + fun updateGrade(grade: Grade): Completable { + return Completable.fromCallable { gradeDb.update(grade) } + } + + fun deleteGrades(grades: List) { + gradeDb.deleteAll(grades) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/local/GradeSummaryLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/local/GradeSummaryLocal.kt new file mode 100644 index 000000000..d06808b15 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/local/GradeSummaryLocal.kt @@ -0,0 +1,21 @@ +package io.github.wulkanowy.data.repositories.local + +import io.github.wulkanowy.data.db.dao.GradeSummaryDao +import io.github.wulkanowy.data.db.entities.GradeSummary +import io.github.wulkanowy.data.db.entities.Semester +import io.reactivex.Maybe +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class GradeSummaryLocal @Inject constructor(private val gradeSummaryDb: GradeSummaryDao) { + + fun getGradesSummary(semester: Semester): Maybe> { + return gradeSummaryDb.getGradesSummary(semester.semesterId, semester.studentId) + .filter { !it.isEmpty() } + } + + fun saveGradesSummary(gradesSummary: List) { + gradeSummaryDb.insertAll(gradesSummary) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/remote/AttendanceRemote.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/remote/AttendanceRemote.kt index 1a1657842..4d17c24c5 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/remote/AttendanceRemote.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/remote/AttendanceRemote.kt @@ -3,7 +3,7 @@ package io.github.wulkanowy.data.repositories.remote import io.github.wulkanowy.api.Api import io.github.wulkanowy.data.db.entities.Attendance import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.utils.extension.toLocalDate +import io.github.wulkanowy.utils.toLocalDate import io.reactivex.Single import org.threeten.bp.LocalDate import javax.inject.Inject diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/remote/ExamRemote.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/remote/ExamRemote.kt index ea4000a3c..719d561dc 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/remote/ExamRemote.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/remote/ExamRemote.kt @@ -3,7 +3,7 @@ package io.github.wulkanowy.data.repositories.remote import io.github.wulkanowy.api.Api import io.github.wulkanowy.data.db.entities.Exam import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.utils.extension.toLocalDate +import io.github.wulkanowy.utils.toLocalDate import io.reactivex.Single import org.threeten.bp.LocalDate import javax.inject.Inject diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/remote/GradeRemote.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/remote/GradeRemote.kt new file mode 100644 index 000000000..2f4c9a0ec --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/remote/GradeRemote.kt @@ -0,0 +1,42 @@ +package io.github.wulkanowy.data.repositories.remote + +import io.github.wulkanowy.api.Api +import io.github.wulkanowy.data.db.entities.Grade +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.utils.toLocalDate +import io.reactivex.Single +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class GradeRemote @Inject constructor(private val api: Api) { + + fun getGrades(semester: Semester): Single> { + return Single.just(api.run { + if (diaryId != semester.diaryId) { + diaryId = semester.diaryId + notifyDataChanged() + } + }).flatMap { api.getGrades(semester.semesterId.toInt()) } + .map { grades -> + grades.map { + Grade( + semesterId = semester.semesterId, + studentId = semester.studentId, + subject = it.subject, + entry = it.entry, + value = it.value, + modifier = it.modifier.toDouble(), + comment = it.comment, + color = it.color, + gradeSymbol = it.symbol, + description = it.description, + weight = it.weight, + weightValue = it.weightValue, + date = it.date.toLocalDate(), + teacher = it.teacher + ) + } + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/remote/GradeSummaryRemote.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/remote/GradeSummaryRemote.kt new file mode 100644 index 000000000..b08e8f590 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/remote/GradeSummaryRemote.kt @@ -0,0 +1,32 @@ +package io.github.wulkanowy.data.repositories.remote + +import io.github.wulkanowy.api.Api +import io.github.wulkanowy.data.db.entities.GradeSummary +import io.github.wulkanowy.data.db.entities.Semester +import io.reactivex.Single +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class GradeSummaryRemote @Inject constructor(private val api: Api) { + + fun getGradeSummary(semester: Semester): Single> { + return Single.just(api.run { + if (diaryId != semester.diaryId) { + diaryId = semester.diaryId + notifyDataChanged() + } + }).flatMap { api.getGradesSummary(semester.semesterId.toInt()) } + .map { gradesSummary -> + gradesSummary.map { + GradeSummary( + semesterId = semester.semesterId, + studentId = semester.studentId, + subject = it.name, + predictedGrade = it.predicted, + finalGrade = it.final + ) + } + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/di/AppModule.kt b/app/src/main/java/io/github/wulkanowy/di/AppModule.kt index 6eff9e971..dbed90bb9 100644 --- a/app/src/main/java/io/github/wulkanowy/di/AppModule.kt +++ b/app/src/main/java/io/github/wulkanowy/di/AppModule.kt @@ -8,13 +8,16 @@ import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import io.github.wulkanowy.WulkanowyApp import io.github.wulkanowy.utils.schedulers.SchedulersManager import io.github.wulkanowy.utils.schedulers.SchedulersProvider +import javax.inject.Singleton @Module internal class AppModule { + @Singleton @Provides fun provideContext(app: WulkanowyApp): Context = app + @Singleton @Provides fun provideSchedulers(): SchedulersManager = SchedulersProvider() diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BaseActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/base/BaseActivity.kt index 0946b251d..69e9beb0f 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/BaseActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/BaseActivity.kt @@ -6,11 +6,10 @@ import android.support.design.widget.Snackbar.LENGTH_LONG import android.support.v7.app.AppCompatDelegate import android.view.View import dagger.android.support.DaggerAppCompatActivity -import io.github.wulkanowy.R abstract class BaseActivity : DaggerAppCompatActivity(), BaseView { - protected lateinit var messageView: View + protected lateinit var messageContainer: View public override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -18,12 +17,7 @@ abstract class BaseActivity : DaggerAppCompatActivity(), BaseView { } override fun showMessage(text: String) { - Snackbar.make(messageView, text, LENGTH_LONG).show() - - } - - override fun showNoNetworkMessage() { - showMessage(getString(R.string.all_no_internet)) + Snackbar.make(messageContainer, text, LENGTH_LONG).show() } override fun onDestroy() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BaseFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/base/BaseFragment.kt index 72c21da96..8c6adb2cc 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/BaseFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/BaseFragment.kt @@ -1,24 +1,10 @@ package io.github.wulkanowy.ui.base -import android.support.annotation.StringRes - import dagger.android.support.DaggerFragment abstract class BaseFragment : DaggerFragment(), BaseView { - fun setTitle(title: String) { - activity?.title = title - } - override fun showMessage(text: String) { (activity as BaseActivity?)?.showMessage(text) } - - fun showMessage(@StringRes stringId: Int) { - showMessage(getString(stringId)) - } - - override fun showNoNetworkMessage() { - (activity as BaseActivity?)?.showNoNetworkMessage() - } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BasePagerAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/base/BasePagerAdapter.kt index 8b37da85b..12c6ee333 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/BasePagerAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/BasePagerAdapter.kt @@ -3,28 +3,30 @@ package io.github.wulkanowy.ui.base import android.support.v4.app.Fragment import android.support.v4.app.FragmentManager import android.support.v4.app.FragmentStatePagerAdapter +import android.view.ViewGroup class BasePagerAdapter(fragmentManager: FragmentManager) : FragmentStatePagerAdapter(fragmentManager) { - private val fragmentList = mutableListOf() + val fragments = mutableMapOf() - private val titleList = mutableListOf() + val registeredFragments = mutableMapOf() - fun addFragment(fragment: Fragment, title: String) { - fragmentList.add(fragment) - titleList.add(title) - } + override fun getItem(position: Int) = fragments.values.elementAt(position) - fun addFragments(vararg fragments: Fragment) { - fragmentList.addAll(fragments) - } - - override fun getItem(position: Int): Fragment = fragmentList[position] - - override fun getCount(): Int = fragmentList.size + override fun getCount() = fragments.size override fun getPageTitle(position: Int): CharSequence? { - return if (!titleList.isEmpty() && titleList.size == fragmentList.size) titleList[position] - else null + return fragments.keys.elementAtOrNull(position) + } + + override fun instantiateItem(container: ViewGroup, position: Int): Any { + return super.instantiateItem(container, position).also { + registeredFragments[position] = it as Fragment + } + } + + override fun destroyItem(container: ViewGroup, position: Int, fragment: Any) { + registeredFragments.remove(position) + super.destroyItem(container, position, fragment) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BaseView.kt b/app/src/main/java/io/github/wulkanowy/ui/base/BaseView.kt index 4c384ab24..6352ca83e 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/BaseView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/BaseView.kt @@ -3,6 +3,4 @@ package io.github.wulkanowy.ui.base interface BaseView { fun showMessage(text: String) - - fun showNoNetworkMessage() } diff --git a/app/src/main/java/io/github/wulkanowy/ui/login/LoginActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/login/LoginActivity.kt index 0ab172ace..4fb1ee519 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/login/LoginActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/login/LoginActivity.kt @@ -8,10 +8,9 @@ import io.github.wulkanowy.ui.base.BaseActivity import io.github.wulkanowy.ui.base.BasePagerAdapter import io.github.wulkanowy.ui.login.form.LoginFormFragment import io.github.wulkanowy.ui.login.options.LoginOptionsFragment -import io.github.wulkanowy.utils.extension.setOnSelectPageListener +import io.github.wulkanowy.utils.setOnSelectPageListener import kotlinx.android.synthetic.main.activity_login.* import javax.inject.Inject -import javax.inject.Named class LoginActivity : BaseActivity(), LoginView, LoginSwitchListener { @@ -19,7 +18,6 @@ class LoginActivity : BaseActivity(), LoginView, LoginSwitchListener { lateinit var presenter: LoginPresenter @Inject - @field:Named("Login") lateinit var loginAdapter: BasePagerAdapter companion object { @@ -30,7 +28,7 @@ class LoginActivity : BaseActivity(), LoginView, LoginSwitchListener { super.onCreate(savedInstanceState) setContentView(R.layout.activity_login) presenter.attachView(this) - messageView = loginContainer + messageContainer = loginContainer } override fun onBackPressed() { @@ -38,7 +36,10 @@ class LoginActivity : BaseActivity(), LoginView, LoginSwitchListener { } override fun initAdapter() { - loginAdapter.addFragments(LoginFormFragment(), LoginOptionsFragment()) + loginAdapter.fragments.putAll(mapOf( + "1" to LoginFormFragment.newInstance(), + "2" to LoginOptionsFragment.newInstance() + )) loginViewpager.run { adapter = loginAdapter setOnSelectPageListener { presenter.onPageSelected(it) } @@ -61,7 +62,7 @@ class LoginActivity : BaseActivity(), LoginView, LoginSwitchListener { (loginAdapter.getItem(index) as LoginOptionsFragment).loadData() } - override fun currentViewPosition(): Int = loginViewpager.currentItem + override fun currentViewPosition() = loginViewpager.currentItem public override fun onDestroy() { presenter.detachView() diff --git a/app/src/main/java/io/github/wulkanowy/ui/login/LoginModule.kt b/app/src/main/java/io/github/wulkanowy/ui/login/LoginModule.kt index 5d1e4323b..ea5734116 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/login/LoginModule.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/login/LoginModule.kt @@ -9,8 +9,6 @@ import io.github.wulkanowy.di.scopes.PerFragment import io.github.wulkanowy.ui.base.BasePagerAdapter import io.github.wulkanowy.ui.login.form.LoginFormFragment import io.github.wulkanowy.ui.login.options.LoginOptionsFragment -import io.github.wulkanowy.ui.login.options.LoginOptionsModule -import javax.inject.Named @Module internal abstract class LoginModule { @@ -19,8 +17,8 @@ internal abstract class LoginModule { companion object { @JvmStatic + @PerActivity @Provides - @Named("Login") fun provideLoginAdapter(activity: LoginActivity) = BasePagerAdapter(activity.supportFragmentManager) @JvmStatic @@ -34,6 +32,6 @@ internal abstract class LoginModule { abstract fun bindLoginFormFragment(): LoginFormFragment @PerFragment - @ContributesAndroidInjector(modules = [LoginOptionsModule::class]) + @ContributesAndroidInjector() abstract fun bindLoginOptionsFragment(): LoginOptionsFragment } diff --git a/app/src/main/java/io/github/wulkanowy/ui/login/form/LoginFormFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/login/form/LoginFormFragment.kt index b2e3145bc..e27526aa2 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/login/form/LoginFormFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/login/form/LoginFormFragment.kt @@ -11,8 +11,8 @@ import android.widget.ArrayAdapter import io.github.wulkanowy.R import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.login.LoginSwitchListener -import io.github.wulkanowy.utils.extension.hideSoftInput -import io.github.wulkanowy.utils.extension.showSoftInput +import io.github.wulkanowy.utils.hideSoftInput +import io.github.wulkanowy.utils.showSoftInput import kotlinx.android.synthetic.main.fragment_login_form.* import javax.inject.Inject @@ -21,6 +21,10 @@ class LoginFormFragment : BaseFragment(), LoginFormView { @Inject lateinit var presenter: LoginFormPresenter + companion object { + fun newInstance() = LoginFormFragment() + } + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { return inflater.inflate(R.layout.fragment_login_form, container, false) } @@ -132,8 +136,8 @@ class LoginFormFragment : BaseFragment(), LoginFormView { loginFormProgressContainer.visibility = if (show) VISIBLE else GONE } - override fun onDestroy() { - super.onDestroy() + override fun onDestroyView() { + super.onDestroyView() presenter.detachView() } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/login/form/LoginFormPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/login/form/LoginFormPresenter.kt index 3cc25a84d..b4245497a 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/login/form/LoginFormPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/login/form/LoginFormPresenter.kt @@ -3,7 +3,6 @@ package io.github.wulkanowy.ui.login.form import io.github.wulkanowy.data.repositories.SessionRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.login.LoginErrorHandler -import io.github.wulkanowy.utils.DEFAULT_SYMBOL import io.github.wulkanowy.utils.schedulers.SchedulersManager import javax.inject.Inject @@ -83,6 +82,6 @@ class LoginFormPresenter @Inject constructor( } private fun normalizeSymbol(symbol: String): String { - return if (symbol.isEmpty()) DEFAULT_SYMBOL else symbol + return if (symbol.isEmpty()) "Default" else symbol } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/login/options/LoginOptionsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/login/options/LoginOptionsFragment.kt index cedf47f6e..b318daac2 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/login/options/LoginOptionsFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/login/options/LoginOptionsFragment.kt @@ -13,7 +13,7 @@ import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import io.github.wulkanowy.R import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.main.MainActivity -import io.github.wulkanowy.utils.extension.setOnItemClickListener +import io.github.wulkanowy.utils.setOnItemClickListener import kotlinx.android.synthetic.main.fragment_login_options.* import javax.inject.Inject diff --git a/app/src/main/java/io/github/wulkanowy/ui/login/options/LoginOptionsModule.kt b/app/src/main/java/io/github/wulkanowy/ui/login/options/LoginOptionsModule.kt deleted file mode 100644 index 711db2171..000000000 --- a/app/src/main/java/io/github/wulkanowy/ui/login/options/LoginOptionsModule.kt +++ /dev/null @@ -1,14 +0,0 @@ -package io.github.wulkanowy.ui.login.options - -import dagger.Module -import dagger.Provides -import eu.davidea.flexibleadapter.FlexibleAdapter -import io.github.wulkanowy.di.scopes.PerFragment - -@Module -internal class LoginOptionsModule { - - @Provides - @PerFragment - fun provideLoginOptionsAdapter() = FlexibleAdapter(null) -} \ No newline at end of file diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/MainActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/main/MainActivity.kt index 4ec418fb3..803b8729b 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/MainActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/main/MainActivity.kt @@ -3,7 +3,6 @@ package io.github.wulkanowy.ui.main import android.content.Context import android.content.Intent import android.os.Bundle -import android.support.v4.app.Fragment import android.support.v4.content.ContextCompat import com.aurelhubert.ahbottomnavigation.AHBottomNavigation import com.aurelhubert.ahbottomnavigation.AHBottomNavigationItem @@ -16,10 +15,12 @@ import io.github.wulkanowy.ui.main.exam.ExamFragment import io.github.wulkanowy.ui.main.grade.GradeFragment import io.github.wulkanowy.ui.main.more.MoreFragment import io.github.wulkanowy.ui.main.timetable.TimetableFragment +import io.github.wulkanowy.utils.setOnTabTransactionListener import kotlinx.android.synthetic.main.activity_main.* import javax.inject.Inject -class MainActivity : BaseActivity(), MainView, FragNavController.TransactionListener { +class MainActivity : BaseActivity(), MainView { + @Inject lateinit var presenter: MainPresenter @@ -27,7 +28,7 @@ class MainActivity : BaseActivity(), MainView, FragNavController.TransactionList lateinit var navController: FragNavController companion object { - const val DEFAULT_TAB = 0 + const val DEFAULT_TAB = 2 fun getStartIntent(context: Context) = Intent(context, MainActivity::class.java) } @@ -35,27 +36,19 @@ class MainActivity : BaseActivity(), MainView, FragNavController.TransactionList override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) - messageView = mainContainer + setSupportActionBar(mainToolbar) + messageContainer = mainFragmentContainer + presenter.attachView(this) navController.initialize(DEFAULT_TAB, savedInstanceState) } - override fun initFragmentController() { - navController.run { - rootFragments = listOf( - GradeFragment.newInstance(), - AttendanceFragment.newInstance(), - ExamFragment.newInstance(), - TimetableFragment.newInstance(), - MoreFragment.newInstance() - ) - fragmentHideStrategy = DETACH_ON_NAVIGATE_HIDE_ON_SWITCH - createEager = true - transactionListener = this@MainActivity - } + override fun onStart() { + super.onStart() + presenter.onStartView() } - override fun initBottomNav() { + override fun initView() { mainBottomNav.run { addItems(mutableListOf( AHBottomNavigationItem(R.string.grade_title, R.drawable.ic_menu_main_grade_26dp, 0), @@ -69,39 +62,63 @@ class MainActivity : BaseActivity(), MainView, FragNavController.TransactionList titleState = AHBottomNavigation.TitleState.ALWAYS_SHOW currentItem = DEFAULT_TAB isBehaviorTranslationEnabled = false - setOnTabSelectedListener { position, _ -> - presenter.onTabSelected(position) + setTitleTextSizeInSp(10f, 10f) + + setOnTabSelectedListener { position, wasSelected -> + presenter.onTabSelected(position, wasSelected) } } + + navController.run { + setOnTabTransactionListener { presenter.onMenuViewChange(it) } + fragmentHideStrategy = DETACH_ON_NAVIGATE_HIDE_ON_SWITCH + rootFragments = listOf( + GradeFragment.newInstance(), + AttendanceFragment.newInstance(), + ExamFragment.newInstance(), + TimetableFragment.newInstance(), + MoreFragment.newInstance() + ) + } } - override fun onFragmentTransaction(fragment: Fragment?, transactionType: FragNavController.TransactionType) {} - - override fun onTabTransaction(fragment: Fragment?, index: Int) { - presenter.onMenuFragmentChange(index) - } - - override fun switchMenuFragment(position: Int) { + override fun switchMenuView(position: Int) { navController.switchTab(position) } override fun setViewTitle(title: String) { - setTitle(title) + supportActionBar?.title = title } - override fun defaultTitle(): String = getString(R.string.main_title) + override fun expandActionBar(show: Boolean) { + mainAppBarContainer.setExpanded(show, true) + } - override fun mapOfTitles(): Map { - return mapOf(0 to R.string.grade_title, - 1 to R.string.attendance_title, - 2 to R.string.exam_title, - 3 to R.string.timetable_title, - 4 to R.string.more_title - ).mapValues { getString(it.value) } + override fun viewTitle(index: Int): String { + return getString(listOf(R.string.grade_title, + R.string.attendance_title, + R.string.exam_title, + R.string.timetable_title, + R.string.more_title)[index]) + } + + override fun currentMenuIndex() = navController.currentStackIndex + + override fun notifyMenuViewReselected() { + (navController.currentFrag as? MainView.MenuFragmentView)?.onFragmentReselected() + } + + override fun onBackPressed() { + navController.apply { if (isRootFragment) super.onBackPressed() else popFragment() } } override fun onSaveInstanceState(outState: Bundle?) { super.onSaveInstanceState(outState) navController.onSaveInstanceState(outState) } + + override fun onDestroy() { + super.onDestroy() + presenter.detachView() + } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/MainModule.kt b/app/src/main/java/io/github/wulkanowy/ui/main/MainModule.kt index ea37ef4ea..f750a3014 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/MainModule.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/main/MainModule.kt @@ -10,6 +10,7 @@ import io.github.wulkanowy.di.scopes.PerFragment import io.github.wulkanowy.ui.main.attendance.AttendanceFragment import io.github.wulkanowy.ui.main.exam.ExamFragment import io.github.wulkanowy.ui.main.grade.GradeFragment +import io.github.wulkanowy.ui.main.grade.GradeModule import io.github.wulkanowy.ui.main.more.MoreFragment import io.github.wulkanowy.ui.main.timetable.TimetableFragment @@ -36,7 +37,7 @@ abstract class MainModule { abstract fun bindExamFragment(): ExamFragment @PerFragment - @ContributesAndroidInjector + @ContributesAndroidInjector(modules = [GradeModule::class]) abstract fun bindGradeFragment(): GradeFragment @PerFragment @@ -47,4 +48,3 @@ abstract class MainModule { @ContributesAndroidInjector abstract fun bindTimetableFragment(): TimetableFragment } - diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/MainPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/main/MainPresenter.kt index 5041b9892..27dc9425d 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/MainPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/main/MainPresenter.kt @@ -9,21 +9,27 @@ class MainPresenter @Inject constructor(errorHandler: ErrorHandler) override fun attachView(view: MainView) { super.attachView(view) - view.run { - initFragmentController() - initBottomNav() - } + view.initView() } - fun onTabSelected(position: Int): Boolean { - view?.switchMenuFragment(position) - return true + fun onStartView() { + view?.run { setViewTitle(viewTitle(currentMenuIndex())) } } - fun onMenuFragmentChange(position: Int) { - view?.run { - setViewTitle(mapOfTitles()[position] ?: defaultTitle()) - } + fun onMenuViewChange(index: Int) { + view?.run { setViewTitle(viewTitle(index)) } + } + + fun onTabSelected(index: Int, wasSelected: Boolean): Boolean { + return view?.run { + expandActionBar(true) + if (wasSelected) { + notifyMenuViewReselected() + false + } else { + switchMenuView(index) + true + } + } == true } } - diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/MainView.kt b/app/src/main/java/io/github/wulkanowy/ui/main/MainView.kt index c5b3d7c22..fee8630e7 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/MainView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/main/MainView.kt @@ -4,15 +4,22 @@ import io.github.wulkanowy.ui.base.BaseView interface MainView : BaseView { - fun initFragmentController() + fun initView() - fun initBottomNav() - - fun switchMenuFragment(position: Int) + fun switchMenuView(position: Int) fun setViewTitle(title: String) - fun defaultTitle(): String + fun expandActionBar(show: Boolean) - fun mapOfTitles(): Map -} \ No newline at end of file + fun viewTitle(index: Int): String + + fun currentMenuIndex(): Int + + fun notifyMenuViewReselected() + + interface MenuFragmentView { + + fun onFragmentReselected() + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceDialog.kt index 91c36594d..648acf3b9 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceDialog.kt @@ -7,7 +7,7 @@ import android.view.View import android.view.ViewGroup import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Attendance -import io.github.wulkanowy.utils.extension.toFormat +import io.github.wulkanowy.utils.toFormattedString import kotlinx.android.synthetic.main.dialog_attendance.* class AttendanceDialog : DialogFragment() { @@ -42,7 +42,7 @@ class AttendanceDialog : DialogFragment() { attendanceDialogSubject.text = attendance.subject attendanceDialogDescription.text = attendance.name - attendanceDialogDate.text = attendance.date.toFormat() + attendanceDialogDate.text = attendance.date.toFormattedString() attendanceDialogNumber.text = attendance.number.toString() attendanceDialogClose.setOnClickListener { dismiss() } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceFragment.kt index 179909aae..3efd61ba8 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceFragment.kt @@ -10,7 +10,7 @@ import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Attendance import io.github.wulkanowy.ui.base.BaseFragment -import io.github.wulkanowy.utils.extension.setOnItemClickListener +import io.github.wulkanowy.utils.setOnItemClickListener import kotlinx.android.synthetic.main.fragment_attendance.* import javax.inject.Inject diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendancePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendancePresenter.kt index 7444055d2..20da58c69 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendancePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendancePresenter.kt @@ -7,7 +7,7 @@ import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.repositories.AttendanceRepository import io.github.wulkanowy.data.repositories.SessionRepository import io.github.wulkanowy.ui.base.BasePresenter -import io.github.wulkanowy.utils.extension.* +import io.github.wulkanowy.utils.* import io.github.wulkanowy.utils.schedulers.SchedulersManager import org.threeten.bp.LocalDate import javax.inject.Inject @@ -19,7 +19,7 @@ class AttendancePresenter @Inject constructor( private val sessionRepository: SessionRepository ) : BasePresenter(errorHandler) { - var currentDate: LocalDate = LocalDate.now().getNearSchoolDayPrevOnWeekEnd() + var currentDate: LocalDate = LocalDate.now().nearSchoolDayPrevOnWeekEnd private set override fun attachView(view: AttendanceView) { @@ -27,13 +27,14 @@ class AttendancePresenter @Inject constructor( view.initView() } - fun loadAttendanceForPreviousDay() = loadData(currentDate.getPreviousWorkDay().toEpochDay()) + fun loadAttendanceForPreviousDay() = loadData(currentDate.previousWorkDay.toEpochDay()) - fun loadAttendanceForNextDay() = loadData(currentDate.getNextWorkDay().toEpochDay()) + fun loadAttendanceForNextDay() = loadData(currentDate.nextWorkDay.toEpochDay()) fun loadData(date: Long?, forceRefresh: Boolean = false) { - this.currentDate = LocalDate.ofEpochDay(date ?: currentDate.getNearSchoolDayPrevOnWeekEnd().toEpochDay()) - if (currentDate.isHolidays()) return + this.currentDate = LocalDate.ofEpochDay(date + ?: currentDate.nearSchoolDayPrevOnWeekEnd.toEpochDay()) + if (currentDate.isHolidays) return disposable.clear() disposable.add(sessionRepository.getSemesters() @@ -50,9 +51,9 @@ class AttendancePresenter @Inject constructor( showEmpty(false) clearData() } - showPreButton(!currentDate.minusDays(1).isHolidays()) - showNextButton(!currentDate.plusDays(1).isHolidays()) - updateNavigationDay(currentDate.toFormat("EEEE \n dd.MM.YYYY").capitalize()) + showPreButton(!currentDate.minusDays(1).isHolidays) + showNextButton(!currentDate.plusDays(1).isHolidays) + updateNavigationDay(currentDate.toFormattedString("EEEE \n dd.MM.YYYY").capitalize()) } } .doFinally { diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/exam/ExamDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/main/exam/ExamDialog.kt index 7f15267d4..619b29e11 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/exam/ExamDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/main/exam/ExamDialog.kt @@ -7,7 +7,7 @@ import android.view.View import android.view.ViewGroup import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Exam -import io.github.wulkanowy.utils.extension.toFormat +import io.github.wulkanowy.utils.toFormattedString import kotlinx.android.synthetic.main.dialog_exam.* class ExamDialog : DialogFragment() { @@ -43,7 +43,7 @@ class ExamDialog : DialogFragment() { examDialogSubjectValue.text = exam.subject examDialogTypeValue.text = exam.type examDialogTeacherValue.text = exam.teacher - examDialogDateValue.text = exam.entryDate.toFormat() + examDialogDateValue.text = exam.entryDate.toFormattedString() examDialogDescriptionValue.text = exam.description examDialogClose.setOnClickListener { dismiss() } diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/exam/ExamFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/main/exam/ExamFragment.kt index 957bb94f8..5bd94d187 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/exam/ExamFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/main/exam/ExamFragment.kt @@ -11,7 +11,7 @@ import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Exam import io.github.wulkanowy.ui.base.BaseFragment -import io.github.wulkanowy.utils.extension.setOnItemClickListener +import io.github.wulkanowy.utils.setOnItemClickListener import kotlinx.android.synthetic.main.fragment_exam.* import javax.inject.Inject diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/exam/ExamHeader.kt b/app/src/main/java/io/github/wulkanowy/ui/main/exam/ExamHeader.kt index 5c752b441..57a8e778b 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/exam/ExamHeader.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/main/exam/ExamHeader.kt @@ -6,8 +6,8 @@ import eu.davidea.flexibleadapter.items.AbstractHeaderItem import eu.davidea.flexibleadapter.items.IFlexible import eu.davidea.viewholders.ExpandableViewHolder import io.github.wulkanowy.R -import io.github.wulkanowy.utils.extension.getWeekDayName -import io.github.wulkanowy.utils.extension.toFormat +import io.github.wulkanowy.utils.toFormattedString +import io.github.wulkanowy.utils.weekDayName import kotlinx.android.extensions.LayoutContainer import kotlinx.android.synthetic.main.header_exam.* import org.threeten.bp.LocalDate @@ -40,8 +40,8 @@ class ExamHeader : AbstractHeaderItem() { override fun bindViewHolder(adapter: FlexibleAdapter>?, holder: ViewHolder, position: Int, payloads: MutableList?) { holder.run { - examHeaderDay.text = date.getWeekDayName().capitalize() - examHeaderDate.text = date.toFormat() + examHeaderDay.text = date.weekDayName.capitalize() + examHeaderDate.text = date.toFormattedString() } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/exam/ExamPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/main/exam/ExamPresenter.kt index 174cd2257..a1b96b13f 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/exam/ExamPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/main/exam/ExamPresenter.kt @@ -7,10 +7,10 @@ import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.repositories.ExamRepository import io.github.wulkanowy.data.repositories.SessionRepository import io.github.wulkanowy.ui.base.BasePresenter -import io.github.wulkanowy.utils.extension.getWeekFirstDayNextOnWeekEnd -import io.github.wulkanowy.utils.extension.isHolidays -import io.github.wulkanowy.utils.extension.toFormat +import io.github.wulkanowy.utils.isHolidays import io.github.wulkanowy.utils.schedulers.SchedulersManager +import io.github.wulkanowy.utils.toFormattedString +import io.github.wulkanowy.utils.weekFirstDayNextOnWeekEnd import org.threeten.bp.LocalDate import javax.inject.Inject @@ -21,7 +21,7 @@ class ExamPresenter @Inject constructor( private val sessionRepository: SessionRepository ) : BasePresenter(errorHandler) { - var currentDate: LocalDate = LocalDate.now().getWeekFirstDayNextOnWeekEnd() + var currentDate: LocalDate = LocalDate.now().weekFirstDayNextOnWeekEnd private set override fun attachView(view: ExamView) { @@ -34,8 +34,9 @@ class ExamPresenter @Inject constructor( fun loadExamsForNextWeek() = loadData(currentDate.plusDays(7).toEpochDay()) fun loadData(date: Long?, forceRefresh: Boolean = false) { - this.currentDate = LocalDate.ofEpochDay(date ?: currentDate.getWeekFirstDayNextOnWeekEnd().toEpochDay()) - if (currentDate.isHolidays()) return + this.currentDate = LocalDate.ofEpochDay(date + ?: currentDate.weekFirstDayNextOnWeekEnd.toEpochDay()) + if (currentDate.isHolidays) return disposable.clear() disposable.add(sessionRepository.getSemesters() @@ -51,9 +52,10 @@ class ExamPresenter @Inject constructor( showProgress(!forceRefresh) if (!forceRefresh) showEmpty(false) showContent(null == date && forceRefresh) - showPreButton(!currentDate.minusDays(7).isHolidays()) - showNextButton(!currentDate.plusDays(7).isHolidays()) - updateNavigationWeek("${currentDate.toFormat("dd.MM")}-${currentDate.plusDays(4).toFormat("dd.MM")}") + showPreButton(!currentDate.minusDays(7).isHolidays) + showNextButton(!currentDate.plusDays(7).isHolidays) + updateNavigationWeek(currentDate.toFormattedString("dd.MM") + + "-${currentDate.plusDays(4).toFormattedString("dd.MM")}") } } .doAfterSuccess { diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/grade/GradeFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/main/grade/GradeFragment.kt index 961afb1e0..97144f5ce 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/grade/GradeFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/main/grade/GradeFragment.kt @@ -1,19 +1,120 @@ package io.github.wulkanowy.ui.main.grade import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup +import android.support.v7.app.AlertDialog +import android.view.* +import android.view.View.INVISIBLE +import android.view.View.VISIBLE import io.github.wulkanowy.R import io.github.wulkanowy.ui.base.BaseFragment +import io.github.wulkanowy.ui.base.BasePagerAdapter +import io.github.wulkanowy.ui.main.MainView +import io.github.wulkanowy.ui.main.grade.details.GradeDetailsFragment +import io.github.wulkanowy.ui.main.grade.summary.GradeSummaryFragment +import io.github.wulkanowy.utils.setOnSelectPageListener +import kotlinx.android.synthetic.main.fragment_grade.* +import javax.inject.Inject -class GradeFragment : BaseFragment() { +class GradeFragment : BaseFragment(), GradeView, MainView.MenuFragmentView { + + @Inject + lateinit var presenter: GradePresenter + + @Inject + lateinit var pagerAdapter: BasePagerAdapter companion object { fun newInstance() = GradeFragment() } + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setHasOptionsMenu(true) + } + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { return inflater.inflate(R.layout.fragment_grade, container, false) } -} + + override fun onActivityCreated(savedInstanceState: Bundle?) { + super.onActivityCreated(savedInstanceState) + presenter.attachView(this) + } + + override fun onCreateOptionsMenu(menu: Menu?, inflater: MenuInflater?) { + inflater?.inflate(R.menu.action_menu_grade, menu) + } + + override fun initView() { + pagerAdapter.fragments.putAll(mapOf( + getString(R.string.all_details) to GradeDetailsFragment.newInstance(), + getString(R.string.grade_menu_summary) to GradeSummaryFragment.newInstance() + )) + gradeViewPager.run { + adapter = pagerAdapter + setOnSelectPageListener { presenter.onPageSelected(it) } + } + gradeTabLayout.setupWithViewPager(gradeViewPager) + } + + override fun onOptionsItemSelected(item: MenuItem?): Boolean { + return if (item?.itemId == R.id.gradeMenuSemester) presenter.onSemesterSwitch() + else false + } + + override fun onFragmentReselected() { + presenter.onViewReselected() + } + + override fun showContent(show: Boolean) { + gradeViewPager.visibility = if (show) VISIBLE else INVISIBLE + gradeTabLayout.visibility = if (show) VISIBLE else INVISIBLE + } + + override fun showProgress(show: Boolean) { + gradeProgress.visibility = if (show) VISIBLE else INVISIBLE + } + + override fun showSemesterDialog(selectedIndex: Int) { + arrayOf(getString(R.string.grade_semester, 1), + getString(R.string.grade_semester, 2)).also { array -> + context?.let { + AlertDialog.Builder(it) + .setSingleChoiceItems(array, selectedIndex) { dialog, which -> + presenter.onSemesterSelected(which) + dialog.dismiss() + } + .setTitle(R.string.grade_switch_semester) + .setNegativeButton(R.string.all_cancel) { dialog, _ -> dialog.dismiss() } + .show() + } + } + } + + override fun currentPageIndex() = gradeViewPager.currentItem + + fun onChildRefresh() { + presenter.onChildViewRefresh() + } + + fun onChildFragmentLoaded(semesterId: String) { + presenter.onChildViewLoaded(semesterId) + } + + override fun notifyChildLoadData(index: Int, semesterId: String, forceRefresh: Boolean) { + (childFragmentManager.fragments[index] as GradeView.GradeChildView).onParentLoadData(semesterId, forceRefresh) + } + + override fun notifyChildParentReselected(index: Int) { + (pagerAdapter.registeredFragments[index] as? GradeView.GradeChildView)?.onParentReselected() + } + + override fun notifyChildSemesterChange(index: Int) { + (pagerAdapter.registeredFragments[index] as? GradeView.GradeChildView)?.onParentChangeSemester() + } + + override fun onDestroyView() { + super.onDestroyView() + presenter.detachView() + } +} \ No newline at end of file diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/grade/GradeModule.kt b/app/src/main/java/io/github/wulkanowy/ui/main/grade/GradeModule.kt new file mode 100644 index 000000000..d9312f07a --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/grade/GradeModule.kt @@ -0,0 +1,32 @@ +package io.github.wulkanowy.ui.main.grade + +import dagger.Module +import dagger.Provides +import dagger.android.ContributesAndroidInjector +import io.github.wulkanowy.di.scopes.PerChildFragment +import io.github.wulkanowy.di.scopes.PerFragment +import io.github.wulkanowy.ui.base.BasePagerAdapter +import io.github.wulkanowy.ui.main.grade.details.GradeDetailsFragment +import io.github.wulkanowy.ui.main.grade.summary.GradeSummaryFragment + +@Module +abstract class GradeModule { + + @Module + companion object { + + @JvmStatic + @PerFragment + @Provides + fun provideGradePagerAdapter(fragment: GradeFragment) = BasePagerAdapter(fragment.childFragmentManager) + } + + @PerChildFragment + @ContributesAndroidInjector() + abstract fun bindGradeDetailsFragment(): GradeDetailsFragment + + @PerChildFragment + @ContributesAndroidInjector + abstract fun binGradeSummaryFragment(): GradeSummaryFragment +} + diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/grade/GradePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/main/grade/GradePresenter.kt new file mode 100644 index 000000000..3d79156a3 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/grade/GradePresenter.kt @@ -0,0 +1,94 @@ +package io.github.wulkanowy.ui.main.grade + +import io.github.wulkanowy.data.ErrorHandler +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.repositories.SessionRepository +import io.github.wulkanowy.ui.base.BasePresenter +import io.github.wulkanowy.utils.schedulers.SchedulersManager +import io.reactivex.Completable +import java.util.concurrent.TimeUnit +import javax.inject.Inject + +class GradePresenter @Inject constructor( + private val errorHandler: ErrorHandler, + private val schedulers: SchedulersManager, + private val sessionRepository: SessionRepository) : BasePresenter(errorHandler) { + + private var semesters = emptyList() + + private var selectedIndex = 0 + + private val loadedSemesterId = mutableMapOf() + + override fun attachView(view: GradeView) { + super.attachView(view) + disposable.add(Completable.timer(150, TimeUnit.MILLISECONDS, schedulers.mainThread()) + .subscribe { + view.initView() + loadData() + }) + } + + fun onViewReselected() { + view?.run { notifyChildParentReselected(currentPageIndex()) } + } + + fun onSemesterSwitch(): Boolean { + if (semesters.isNotEmpty()) view?.showSemesterDialog(selectedIndex) + return true + } + + fun onSemesterSelected(index: Int) { + if (selectedIndex != index) { + selectedIndex = index + loadedSemesterId.clear() + view?.let { + notifyChildrenSemesterChange() + loadChild(it.currentPageIndex()) + } + } + } + + fun onChildViewRefresh() { + view?.let { loadChild(it.currentPageIndex(), forceRefresh = true) } + } + + fun onChildViewLoaded(semesterId: String) { + view?.apply { + showContent(true) + showProgress(false) + loadedSemesterId[currentPageIndex()] = semesterId + } + } + + fun onPageSelected(index: Int) { + loadChild(index) + } + + private fun loadData() { + disposable.add(sessionRepository.getSemesters() + .map { + it.first { item -> item.current }.also { current -> + selectedIndex = current.semesterName - 1 + semesters = it.filter { semester -> semester.diaryId == current.diaryId } + } + } + .subscribeOn(schedulers.backgroundThread()) + .observeOn(schedulers.mainThread()) + .subscribe({ _ -> + view?.let { loadChild(it.currentPageIndex()) } + }) { errorHandler.proceed(it) }) + } + + private fun loadChild(index: Int, forceRefresh: Boolean = false) { + semesters.first { it.semesterName == selectedIndex + 1 }.semesterId.also { + if (forceRefresh || loadedSemesterId[index] != it) { + view?.notifyChildLoadData(index, it, forceRefresh) + } + } + } + + private fun notifyChildrenSemesterChange() { + for (i in 0..1) view?.notifyChildSemesterChange(i) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/grade/GradeView.kt b/app/src/main/java/io/github/wulkanowy/ui/main/grade/GradeView.kt new file mode 100644 index 000000000..fa2d4f51c --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/grade/GradeView.kt @@ -0,0 +1,31 @@ +package io.github.wulkanowy.ui.main.grade + +import io.github.wulkanowy.ui.base.BaseView + +interface GradeView : BaseView { + + fun initView() + + fun currentPageIndex(): Int + + fun showContent(show: Boolean) + + fun showProgress(show: Boolean) + + fun showSemesterDialog(selectedIndex: Int) + + fun notifyChildLoadData(index: Int, semesterId: String, forceRefresh: Boolean) + + fun notifyChildParentReselected(index: Int) + + fun notifyChildSemesterChange(index: Int) + + interface GradeChildView { + + fun onParentChangeSemester() + + fun onParentLoadData(semesterId: String, forceRefresh: Boolean) + + fun onParentReselected() + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/grade/details/GradeDetailsDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/main/grade/details/GradeDetailsDialog.kt new file mode 100644 index 000000000..3fda90d93 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/grade/details/GradeDetailsDialog.kt @@ -0,0 +1,79 @@ +package io.github.wulkanowy.ui.main.grade.details + +import android.os.Bundle +import android.support.v4.app.DialogFragment +import android.view.LayoutInflater +import android.view.View +import android.view.View.GONE +import android.view.ViewGroup +import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.Grade +import io.github.wulkanowy.utils.colorStringId +import io.github.wulkanowy.utils.toFormattedString +import io.github.wulkanowy.utils.valueColor +import kotlinx.android.synthetic.main.dialog_grade.* + + +class GradeDetailsDialog : DialogFragment() { + + private lateinit var grade: Grade + + companion object { + private const val ARGUMENT_KEY = "Item" + + fun newInstance(grade: Grade): GradeDetailsDialog { + return GradeDetailsDialog().apply { + arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, grade) } + } + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + arguments?.run { + grade = getSerializable(ARGUMENT_KEY) as Grade + } + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + return inflater.inflate(R.layout.dialog_grade, container, false) + } + + override fun onActivityCreated(savedInstanceState: Bundle?) { + super.onActivityCreated(savedInstanceState) + + gradeDialogSubject.text = grade.subject + gradeDialogWeightValue.text = grade.weight + gradeDialogDateValue.text = grade.date.toFormattedString() + gradeDialogColorValue.text = getString(grade.colorStringId) + + gradeDialogCommentValue.apply { + if (grade.comment.isEmpty()) { + visibility = GONE + gradeDialogComment.visibility = GONE + } else text = grade.comment + } + + gradeDialogValue.run { + text = grade.entry + setBackgroundResource(grade.valueColor) + } + + gradeDialogTeacherValue.text = if (grade.teacher.isEmpty()) { + getString(R.string.all_no_data) + } else grade.teacher + + gradeDialogDescriptionValue.text = grade.run { + when { + description.isEmpty() && gradeSymbol.isNotEmpty() -> gradeSymbol + description.isEmpty() && gradeSymbol.isEmpty() -> getString(R.string.all_no_description) + gradeSymbol.isNotEmpty() && description.isNotEmpty() -> "$gradeSymbol - $description" + else -> description + } + } + + gradeDialogClose.setOnClickListener { dismiss() } + } + +} + diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/grade/details/GradeDetailsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/main/grade/details/GradeDetailsFragment.kt new file mode 100644 index 000000000..a5f3409c1 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/grade/details/GradeDetailsFragment.kt @@ -0,0 +1,134 @@ +package io.github.wulkanowy.ui.main.grade.details + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.View.* +import android.view.ViewGroup +import eu.davidea.flexibleadapter.FlexibleAdapter +import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager +import eu.davidea.flexibleadapter.items.AbstractFlexibleItem +import eu.davidea.flexibleadapter.items.IExpandable +import eu.davidea.flexibleadapter.items.IFlexible +import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.Grade +import io.github.wulkanowy.ui.base.BaseFragment +import io.github.wulkanowy.ui.main.grade.GradeFragment +import io.github.wulkanowy.ui.main.grade.GradeView +import io.github.wulkanowy.utils.setOnItemClickListener +import kotlinx.android.synthetic.main.fragment_grade_details.* +import javax.inject.Inject + +class GradeDetailsFragment : BaseFragment(), GradeDetailsView, GradeView.GradeChildView { + + @Inject + lateinit var presenter: GradeDetailsPresenter + + @Inject + lateinit var gradeDetailsAdapter: FlexibleAdapter> + + companion object { + fun newInstance() = GradeDetailsFragment() + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + return inflater.inflate(R.layout.fragment_grade_details, container, false) + } + + override fun onActivityCreated(savedInstanceState: Bundle?) { + super.onActivityCreated(savedInstanceState) + presenter.attachView(this) + } + + override fun initView() { + gradeDetailsAdapter.run { + isAutoCollapseOnExpand = true + isAutoScrollOnExpand = true + setOnItemClickListener { presenter.onGradeItemSelected(getItem(it)) } + } + + gradeDetailsRecycler.run { + layoutManager = SmoothScrollLinearLayoutManager(context) + adapter = gradeDetailsAdapter + } + gradeDetailsSwipe.setOnRefreshListener { presenter.onSwipeRefresh() } + } + + override fun updateData(data: List) { + gradeDetailsAdapter.updateDataSet(data, true) + } + + override fun updateItem(item: AbstractFlexibleItem<*>) { + gradeDetailsAdapter.updateItem(item) + } + + override fun clearView() { + gradeDetailsAdapter.clear() + } + + override fun resetView() { + gradeDetailsAdapter.apply { + smoothScrollToPosition(0) + collapseAll() + } + } + + override fun getHeaderOfItem(item: AbstractFlexibleItem<*>): IExpandable<*, out IFlexible<*>>? { + return gradeDetailsAdapter.getExpandableOf(item) + } + + override fun isViewEmpty() = gradeDetailsAdapter.isEmpty + + override fun showProgress(show: Boolean) { + gradeDetailsProgress.visibility = if (show) VISIBLE else GONE + } + + override fun showContent(show: Boolean) { + gradeDetailsRecycler.visibility = if (show) VISIBLE else INVISIBLE + } + + override fun showEmpty(show: Boolean) { + gradeDetailsEmpty.visibility = if (show) VISIBLE else INVISIBLE + } + + override fun showRefresh(show: Boolean) { + gradeDetailsSwipe.isRefreshing = show + } + + override fun showGradeDialog(grade: Grade) { + GradeDetailsDialog.newInstance(grade).show(fragmentManager, grade.toString()) + } + + override fun onParentLoadData(semesterId: String, forceRefresh: Boolean) { + presenter.loadData(semesterId, forceRefresh) + } + + override fun onParentReselected() { + presenter.onParentViewReselected() + } + + override fun onParentChangeSemester() { + presenter.onParentChangeSemester() + } + + override fun notifyParentDataLoaded(semesterId: String) { + (parentFragment as? GradeFragment)?.onChildFragmentLoaded(semesterId) + } + + override fun notifyParentRefresh() { + (parentFragment as? GradeFragment)?.onChildRefresh() + } + + override fun emptyAverageString(): String = getString(R.string.grade_no_average) + + override fun averageString(): String = getString(R.string.grade_average) + + override fun gradeNumberString(number: Int): String = resources.getQuantityString(R.plurals.grade_number_item, number, number) + + override fun weightString(): String = getString(R.string.grade_weight) + + override fun onDestroyView() { + super.onDestroyView() + presenter.detachView() + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/grade/details/GradeDetailsHeader.kt b/app/src/main/java/io/github/wulkanowy/ui/main/grade/details/GradeDetailsHeader.kt new file mode 100644 index 000000000..92636e490 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/grade/details/GradeDetailsHeader.kt @@ -0,0 +1,73 @@ +package io.github.wulkanowy.ui.main.grade.details + +import android.view.View +import android.view.View.GONE +import android.view.View.VISIBLE +import eu.davidea.flexibleadapter.FlexibleAdapter +import eu.davidea.flexibleadapter.items.AbstractExpandableItem +import eu.davidea.flexibleadapter.items.IFlexible +import eu.davidea.viewholders.ExpandableViewHolder +import io.github.wulkanowy.R +import kotlinx.android.extensions.LayoutContainer +import kotlinx.android.synthetic.main.header_grade_details.* + +class GradeDetailsHeader( + private val subject: String, + private val number: String, + private val average: String, + var newGrades: Int) + : AbstractExpandableItem() { + + override fun getLayoutRes() = R.layout.header_grade_details + + override fun createViewHolder(view: View?, adapter: FlexibleAdapter>?): ViewHolder { + return ViewHolder(view, adapter) + } + + override fun bindViewHolder(adapter: FlexibleAdapter>?, holder: ViewHolder, + position: Int, payloads: MutableList?) { + holder.run { + gradeHeaderSubject.text = subject + gradeHeaderAverage.text = average + gradeHeaderNumber.text = number + gradeHeaderPredicted.visibility = GONE + gradeHeaderFinal.visibility = GONE + + gradeHeaderNote.visibility = if (newGrades > 0) VISIBLE else GONE + } + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as GradeDetailsHeader + + if (subject != other.subject) return false + if (number != other.number) return false + if (average != other.average) return false + + return true + } + + override fun hashCode(): Int { + var result = subject.hashCode() + result = 31 * result + number.hashCode() + result = 31 * result + average.hashCode() + return result + } + + + class ViewHolder(view: View?, adapter: FlexibleAdapter>?) : ExpandableViewHolder(view, adapter), + LayoutContainer { + + init { + contentView.setOnClickListener(this) + } + + override fun shouldNotifyParentOnClick() = true + + override val containerView: View + get() = contentView + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/grade/details/GradeDetailsItem.kt b/app/src/main/java/io/github/wulkanowy/ui/main/grade/details/GradeDetailsItem.kt new file mode 100644 index 000000000..1a553b1e7 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/grade/details/GradeDetailsItem.kt @@ -0,0 +1,68 @@ +package io.github.wulkanowy.ui.main.grade.details + +import android.annotation.SuppressLint +import android.view.View +import android.view.View.GONE +import android.view.View.VISIBLE +import eu.davidea.flexibleadapter.FlexibleAdapter +import eu.davidea.flexibleadapter.items.AbstractFlexibleItem +import eu.davidea.flexibleadapter.items.IFlexible +import eu.davidea.viewholders.FlexibleViewHolder +import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.Grade +import io.github.wulkanowy.utils.toFormattedString +import kotlinx.android.extensions.LayoutContainer +import kotlinx.android.synthetic.main.item_grade_details.* + +class GradeDetailsItem(val grade: Grade, private val weightString: String, private val valueColor: Int) + : AbstractFlexibleItem() { + + override fun getLayoutRes() = R.layout.item_grade_details + + override fun createViewHolder(view: View, adapter: FlexibleAdapter>): ViewHolder { + return ViewHolder(view, adapter) + } + + @SuppressLint("SetTextI18n") + override fun bindViewHolder(adapter: FlexibleAdapter>, holder: ViewHolder, + position: Int, payloads: MutableList?) { + holder.run { + gradeItemValue.run { + text = grade.entry + setBackgroundResource(valueColor) + } + gradeItemDescription.text = if (grade.description.isNotEmpty()) grade.description else grade.gradeSymbol + gradeItemDate.text = grade.date.toFormattedString() + gradeItemWeight.text = "$weightString: ${grade.weight}" + gradeItemNote.visibility = if (grade.isNew) VISIBLE else GONE + } + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as GradeDetailsItem + + if (grade != other.grade) return false + if (weightString != other.weightString) return false + if (valueColor != other.valueColor) return false + + return true + } + + override fun hashCode(): Int { + var result = grade.hashCode() + result = 31 * result + weightString.hashCode() + result = 31 * result + valueColor + return result + } + + + class ViewHolder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter), + LayoutContainer { + + override val containerView: View + get() = contentView + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/grade/details/GradeDetailsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/main/grade/details/GradeDetailsPresenter.kt new file mode 100644 index 000000000..8add69a9e --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/grade/details/GradeDetailsPresenter.kt @@ -0,0 +1,124 @@ +package io.github.wulkanowy.ui.main.grade.details + +import eu.davidea.flexibleadapter.items.AbstractFlexibleItem +import io.github.wulkanowy.data.ErrorHandler +import io.github.wulkanowy.data.db.entities.Grade +import io.github.wulkanowy.data.repositories.GradeRepository +import io.github.wulkanowy.data.repositories.SessionRepository +import io.github.wulkanowy.ui.base.BasePresenter +import io.github.wulkanowy.utils.calcAverage +import io.github.wulkanowy.utils.schedulers.SchedulersManager +import io.github.wulkanowy.utils.valueColor +import javax.inject.Inject + +class GradeDetailsPresenter @Inject constructor( + private val errorHandler: ErrorHandler, + private val schedulers: SchedulersManager, + private val gradeRepository: GradeRepository, + private val sessionRepository: SessionRepository) : BasePresenter(errorHandler) { + + override fun attachView(view: GradeDetailsView) { + super.attachView(view) + view.initView() + } + + fun loadData(semesterId: String, forceRefresh: Boolean) { + disposable.add(sessionRepository.getSemesters() + .flatMap { gradeRepository.getGrades(it.first { item -> item.semesterId == semesterId }, forceRefresh) } + .map { createGradeItems(it.groupBy { grade -> grade.subject }.toSortedMap()) } + .subscribeOn(schedulers.backgroundThread()) + .observeOn(schedulers.mainThread()) + .doFinally { + view?.run { + showRefresh(false) + showProgress(false) + notifyParentDataLoaded(semesterId) + } + } + .subscribe({ + view?.run { + showEmpty(it.isEmpty()) + showContent(it.isNotEmpty()) + updateData(it) + } + }) { + view?.run { showEmpty(isViewEmpty()) } + errorHandler.proceed(it) + }) + } + + fun onGradeItemSelected(item: AbstractFlexibleItem<*>?) { + if (item is GradeDetailsItem) { + view?.apply { + showGradeDialog(item.grade) + if (item.grade.isNew) { + item.grade.isNew = false + updateItem(item) + getHeaderOfItem(item)?.let { header -> + if (header is GradeDetailsHeader) { + header.newGrades-- + updateItem(header) + } + } + updateGrade(item.grade) + } + } + } + } + + fun onSwipeRefresh() { + view?.notifyParentRefresh() + } + + fun onParentViewReselected() { + view?.run { + if (!isViewEmpty()) resetView() + } + } + + fun onParentChangeSemester() { + view?.run { + showProgress(true) + showRefresh(false) + showContent(false) + showEmpty(false) + clearView() + } + disposable.clear() + } + + private fun createGradeItems(items: Map>): List { + return items.map { + it.value.calcAverage().let { average -> + GradeDetailsHeader( + subject = it.key, + average = formatAverage(average), + number = view?.gradeNumberString(it.value.size).orEmpty(), + newGrades = it.value.filter { grade -> grade.isNew }.size + ).apply { + subItems = it.value.map { item -> + GradeDetailsItem( + grade = item, + weightString = view?.weightString().orEmpty(), + valueColor = item.valueColor + ) + } + } + } + } + } + + private fun formatAverage(average: Double): String { + return view?.run { + if (average == 0.0) emptyAverageString() + else averageString().format(average) + }.orEmpty() + } + + private fun updateGrade(grade: Grade) { + disposable.add(gradeRepository.updateGrade(grade) + .subscribeOn(schedulers.backgroundThread()) + .observeOn(schedulers.mainThread()) + .subscribe({}) { error -> errorHandler.proceed(error) }) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/grade/details/GradeDetailsView.kt b/app/src/main/java/io/github/wulkanowy/ui/main/grade/details/GradeDetailsView.kt new file mode 100644 index 000000000..0091fe999 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/grade/details/GradeDetailsView.kt @@ -0,0 +1,46 @@ +package io.github.wulkanowy.ui.main.grade.details + +import eu.davidea.flexibleadapter.items.AbstractFlexibleItem +import eu.davidea.flexibleadapter.items.IExpandable +import eu.davidea.flexibleadapter.items.IFlexible +import io.github.wulkanowy.data.db.entities.Grade +import io.github.wulkanowy.ui.base.BaseView + +interface GradeDetailsView : BaseView { + + fun initView() + + fun updateData(data: List) + + fun updateItem(item: AbstractFlexibleItem<*>) + + fun getHeaderOfItem(item: AbstractFlexibleItem<*>): IExpandable<*, out IFlexible<*>>? + + fun resetView() + + fun clearView() + + fun isViewEmpty(): Boolean + + fun showGradeDialog(grade: Grade) + + fun showContent(show: Boolean) + + fun showEmpty(show: Boolean) + + fun showProgress(show: Boolean) + + fun showRefresh(show: Boolean) + + fun emptyAverageString(): String + + fun averageString(): String + + fun gradeNumberString(number: Int): String + + fun weightString(): String + + fun notifyParentDataLoaded(semesterId: String) + + fun notifyParentRefresh() +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/grade/summary/GradeSummaryFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/main/grade/summary/GradeSummaryFragment.kt new file mode 100644 index 000000000..f64c1f363 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/grade/summary/GradeSummaryFragment.kt @@ -0,0 +1,111 @@ +package io.github.wulkanowy.ui.main.grade.summary + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.View.* +import android.view.ViewGroup +import eu.davidea.flexibleadapter.FlexibleAdapter +import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager +import eu.davidea.flexibleadapter.items.AbstractFlexibleItem +import io.github.wulkanowy.R +import io.github.wulkanowy.ui.base.BaseFragment +import io.github.wulkanowy.ui.main.grade.GradeFragment +import io.github.wulkanowy.ui.main.grade.GradeView +import kotlinx.android.synthetic.main.fragment_grade_summary.* +import javax.inject.Inject + +class GradeSummaryFragment : BaseFragment(), GradeSummaryView, GradeView.GradeChildView { + + @Inject + lateinit var presenter: GradeSummaryPresenter + + @Inject + lateinit var gradeSummaryAdapter: FlexibleAdapter> + + companion object { + fun newInstance() = GradeSummaryFragment() + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + return inflater.inflate(R.layout.fragment_grade_summary, container, false) + } + + override fun onActivityCreated(savedInstanceState: Bundle?) { + super.onActivityCreated(savedInstanceState) + presenter.attachView(this) + } + + override fun initView() { + gradeSummaryAdapter.setDisplayHeadersAtStartUp(true) + + gradeSummaryRecycler.run { + layoutManager = SmoothScrollLinearLayoutManager(context) + adapter = gradeSummaryAdapter + } + gradeSummarySwipe.setOnRefreshListener { presenter.onSwipeRefresh() } + } + + override fun updateDataSet(data: List, header: GradeSummaryScrollableHeader) { + gradeSummaryAdapter.apply { + updateDataSet(data, true) + removeAllScrollableHeaders() + addScrollableHeader(header) + } + } + + override fun clearView() { + gradeSummaryAdapter.clear() + } + + override fun resetView() { + gradeSummaryAdapter.smoothScrollToPosition(0) + } + + override fun isViewEmpty() = gradeSummaryAdapter.isEmpty + + override fun showContent(show: Boolean) { + gradeSummaryRecycler.visibility = if (show) VISIBLE else INVISIBLE + } + + override fun showEmpty(show: Boolean) { + gradeSummaryEmpty.visibility = if (show) VISIBLE else INVISIBLE + } + + override fun showProgress(show: Boolean) { + gradeSummaryProgress.visibility = if (show) VISIBLE else GONE + } + + override fun showRefresh(show: Boolean) { + gradeSummarySwipe.isRefreshing = show + } + + override fun onParentLoadData(semesterId: String, forceRefresh: Boolean) { + presenter.loadData(semesterId, forceRefresh) + } + + override fun onParentReselected() { + presenter.onParentViewReselected() + } + + override fun onParentChangeSemester() { + presenter.onParentChangeSemester() + } + + override fun notifyParentDataLoaded(semesterId: String) { + (parentFragment as? GradeFragment)?.onChildFragmentLoaded(semesterId) + } + + override fun notifyParentRefresh() { + (parentFragment as? GradeFragment)?.onChildRefresh() + } + + override fun predictedString() = getString(R.string.grade_summary_predicted_grade) + + override fun finalString() = getString(R.string.grade_summary_final_grade) + + override fun onDestroyView() { + super.onDestroyView() + presenter.detachView() + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/grade/summary/GradeSummaryHeader.kt b/app/src/main/java/io/github/wulkanowy/ui/main/grade/summary/GradeSummaryHeader.kt new file mode 100644 index 000000000..c3dd6b3c6 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/grade/summary/GradeSummaryHeader.kt @@ -0,0 +1,52 @@ +package io.github.wulkanowy.ui.main.grade.summary + +import android.view.View +import eu.davidea.flexibleadapter.FlexibleAdapter +import eu.davidea.flexibleadapter.items.AbstractHeaderItem +import eu.davidea.flexibleadapter.items.IFlexible +import eu.davidea.viewholders.FlexibleViewHolder +import io.github.wulkanowy.R +import kotlinx.android.extensions.LayoutContainer +import kotlinx.android.synthetic.main.header_grade_summary.* + +class GradeSummaryHeader(private val name: String, private val average: String) : AbstractHeaderItem() { + + override fun getLayoutRes() = R.layout.header_grade_summary + + override fun createViewHolder(view: View?, adapter: FlexibleAdapter>?): ViewHolder { + return ViewHolder(view, adapter) + } + + override fun bindViewHolder(adapter: FlexibleAdapter>?, holder: ViewHolder?, + position: Int, payloads: MutableList?) { + holder?.run { + gradeSummaryHeaderName.text = name + gradeSummaryHeaderAverage.text = average + } + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as GradeSummaryHeader + + if (name != other.name) return false + if (average != other.average) return false + + return true + } + + override fun hashCode(): Int { + var result = name.hashCode() + result = 31 * result + average.hashCode() + return result + } + + class ViewHolder(view: View?, adapter: FlexibleAdapter>?) : + FlexibleViewHolder(view, adapter), LayoutContainer { + + override val containerView: View? + get() = contentView + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/grade/summary/GradeSummaryItem.kt b/app/src/main/java/io/github/wulkanowy/ui/main/grade/summary/GradeSummaryItem.kt new file mode 100644 index 000000000..d2ad9cac4 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/grade/summary/GradeSummaryItem.kt @@ -0,0 +1,56 @@ +package io.github.wulkanowy.ui.main.grade.summary + +import android.view.View +import eu.davidea.flexibleadapter.FlexibleAdapter +import eu.davidea.flexibleadapter.items.AbstractSectionableItem +import eu.davidea.flexibleadapter.items.IFlexible +import eu.davidea.viewholders.FlexibleViewHolder +import io.github.wulkanowy.R +import kotlinx.android.extensions.LayoutContainer +import kotlinx.android.synthetic.main.item_grade_summary.* + +class GradeSummaryItem(header: GradeSummaryHeader, private val grade: String, private val title: String) + : AbstractSectionableItem(header) { + + override fun getLayoutRes() = R.layout.item_grade_summary + + override fun createViewHolder(view: View?, adapter: FlexibleAdapter>?): ViewHolder { + return ViewHolder(view, adapter) + } + + override fun bindViewHolder(adapter: FlexibleAdapter>?, holder: ViewHolder?, + position: Int, payloads: MutableList?) { + holder?.run { + gradeSummaryItemGrade.text = grade + gradeSummaryItemTitle.text = title + } + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as GradeSummaryItem + + if (grade != other.grade) return false + if (title != other.title) return false + if (header != other.header) return false + + return true + } + + override fun hashCode(): Int { + var result = header.hashCode() + result = 31 * result + grade.hashCode() + result = 31 * result + title.hashCode() + return result + } + + + class ViewHolder(view: View?, adapter: FlexibleAdapter>?) + : FlexibleViewHolder(view, adapter), LayoutContainer { + + override val containerView: View? + get() = contentView + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/grade/summary/GradeSummaryPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/main/grade/summary/GradeSummaryPresenter.kt new file mode 100644 index 000000000..0c3f135df --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/grade/summary/GradeSummaryPresenter.kt @@ -0,0 +1,121 @@ +package io.github.wulkanowy.ui.main.grade.summary + +import io.github.wulkanowy.data.ErrorHandler +import io.github.wulkanowy.data.db.entities.GradeSummary +import io.github.wulkanowy.data.repositories.GradeRepository +import io.github.wulkanowy.data.repositories.GradeSummaryRepository +import io.github.wulkanowy.data.repositories.SessionRepository +import io.github.wulkanowy.ui.base.BasePresenter +import io.github.wulkanowy.utils.calcAverage +import io.github.wulkanowy.utils.schedulers.SchedulersManager +import java.lang.String.format +import java.util.Locale.FRANCE +import javax.inject.Inject + +class GradeSummaryPresenter @Inject constructor( + private val errorHandler: ErrorHandler, + private val gradeSummaryRepository: GradeSummaryRepository, + private val gradeRepository: GradeRepository, + private val sessionRepository: SessionRepository, + private val schedulers: SchedulersManager) + : BasePresenter(errorHandler) { + + override fun attachView(view: GradeSummaryView) { + super.attachView(view) + view.initView() + } + + fun loadData(semesterId: String, forceRefresh: Boolean) { + disposable.add(sessionRepository.getSemesters() + .map { semester -> semester.first { it.semesterId == semesterId } } + .flatMap { + gradeSummaryRepository.getGradesSummary(it, forceRefresh) + .flatMap { gradesSummary -> + gradeRepository.getGrades(it, forceRefresh) + .map { grades -> + grades.groupBy { grade -> grade.subject } + .mapValues { entry -> entry.value.calcAverage() } + .filterValues { value -> value != 0.0 } + .let { averages -> + createGradeSummaryItems(gradesSummary, averages) to + GradeSummaryScrollableHeader( + formatAverage(gradesSummary.calcAverage()), + formatAverage(averages.values.average()) + ) + } + } + } + } + .subscribeOn(schedulers.backgroundThread()) + .observeOn(schedulers.mainThread()) + .doFinally { + view?.run { + showRefresh(false) + showProgress(false) + notifyParentDataLoaded(semesterId) + } + }.subscribe({ + view?.run { + showEmpty(it.first.isEmpty()) + showContent(it.first.isNotEmpty()) + updateDataSet(it.first, it.second) + } + }) { + view?.run { showEmpty(isViewEmpty()) } + errorHandler.proceed(it) + }) + } + + fun onSwipeRefresh() { + view?.notifyParentRefresh() + } + + fun onParentViewReselected() { + view?.run { + if (!isViewEmpty()) resetView() + } + } + + fun onParentChangeSemester() { + view?.run { + showProgress(true) + showRefresh(false) + showContent(false) + showEmpty(false) + clearView() + } + disposable.clear() + } + + private fun createGradeSummaryItems(gradesSummary: List, averages: Map) + : List { + return gradesSummary.filter { !checkEmpty(it, averages) } + .flatMap { gradeSummary -> + GradeSummaryHeader( + name = gradeSummary.subject, + average = formatAverage(averages.getOrElse(gradeSummary.subject) { 0.0 }, "") + ).let { + listOf(GradeSummaryItem( + header = it, + title = view?.predictedString().orEmpty(), + grade = gradeSummary.predictedGrade + ), GradeSummaryItem( + header = it, + title = view?.finalString().orEmpty(), + grade = gradeSummary.finalGrade + )) + } + } + } + + private fun checkEmpty(gradeSummary: GradeSummary, averages: Map): Boolean { + return gradeSummary.run { + finalGrade.isEmpty() && predictedGrade.isEmpty() && averages[subject] == null + } + } + + private fun formatAverage(average: Double, defaultValue: String = "-- --"): String { + return if (average == 0.0 || average.isNaN()) defaultValue + else format(FRANCE, "%.2f", average) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/grade/summary/GradeSummaryScrollableHeader.kt b/app/src/main/java/io/github/wulkanowy/ui/main/grade/summary/GradeSummaryScrollableHeader.kt new file mode 100644 index 000000000..d0c6ab1b6 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/grade/summary/GradeSummaryScrollableHeader.kt @@ -0,0 +1,53 @@ +package io.github.wulkanowy.ui.main.grade.summary + +import android.view.View +import eu.davidea.flexibleadapter.FlexibleAdapter +import eu.davidea.flexibleadapter.items.AbstractFlexibleItem +import eu.davidea.flexibleadapter.items.IFlexible +import eu.davidea.viewholders.FlexibleViewHolder +import io.github.wulkanowy.R +import kotlinx.android.extensions.LayoutContainer +import kotlinx.android.synthetic.main.scrollable_header_grade_summary.* + +class GradeSummaryScrollableHeader(private val finalAverage: String, private val calculatedAverage: String) + : AbstractFlexibleItem() { + + override fun getLayoutRes() = R.layout.scrollable_header_grade_summary + + override fun createViewHolder(view: View?, adapter: FlexibleAdapter>?): ViewHolder { + return ViewHolder(view, adapter) + } + + override fun bindViewHolder(adapter: FlexibleAdapter>?, holder: ViewHolder?, + position: Int, payloads: MutableList?) { + holder?.apply { + gradeSummaryScrollableHeaderFinal.text = finalAverage + gradeSummaryScrollableHeaderCalculated.text = calculatedAverage + } + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as GradeSummaryScrollableHeader + + if (calculatedAverage != other.calculatedAverage) return false + if (finalAverage != other.finalAverage) return false + + return true + } + + override fun hashCode(): Int { + var result = calculatedAverage.hashCode() + result = 31 * result + finalAverage.hashCode() + return result + } + + class ViewHolder(view: View?, adapter: FlexibleAdapter>?) : FlexibleViewHolder(view, adapter), + LayoutContainer { + + override val containerView: View? + get() = contentView + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/grade/summary/GradeSummaryView.kt b/app/src/main/java/io/github/wulkanowy/ui/main/grade/summary/GradeSummaryView.kt new file mode 100644 index 000000000..6ddfb86d4 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/grade/summary/GradeSummaryView.kt @@ -0,0 +1,32 @@ +package io.github.wulkanowy.ui.main.grade.summary + +import io.github.wulkanowy.ui.base.BaseView + +interface GradeSummaryView : BaseView { + + fun initView() + + fun updateDataSet(data: List, header: GradeSummaryScrollableHeader) + + fun resetView() + + fun clearView() + + fun isViewEmpty(): Boolean + + fun showProgress(show: Boolean) + + fun showRefresh(show: Boolean) + + fun showContent(show: Boolean) + + fun showEmpty(show: Boolean) + + fun predictedString(): String + + fun finalString(): String + + fun notifyParentDataLoaded(semesterId: String) + + fun notifyParentRefresh() +} diff --git a/app/src/main/java/io/github/wulkanowy/utils/extension/ActivityExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/ActivityExtension.kt similarity index 91% rename from app/src/main/java/io/github/wulkanowy/utils/extension/ActivityExtension.kt rename to app/src/main/java/io/github/wulkanowy/utils/ActivityExtension.kt index 1a48c115f..c0314d02e 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/extension/ActivityExtension.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/ActivityExtension.kt @@ -1,4 +1,4 @@ -package io.github.wulkanowy.utils.extension +package io.github.wulkanowy.utils import android.app.Activity import android.content.Context.INPUT_METHOD_SERVICE diff --git a/app/src/main/java/io/github/wulkanowy/utils/AnimationUtils.java b/app/src/main/java/io/github/wulkanowy/utils/AnimationUtils.java deleted file mode 100644 index 041164231..000000000 --- a/app/src/main/java/io/github/wulkanowy/utils/AnimationUtils.java +++ /dev/null @@ -1,40 +0,0 @@ -package io.github.wulkanowy.utils; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.view.View; - -public final class AnimationUtils { - - public static void slideDown(final View view) { - view.setVisibility(View.VISIBLE); - view.setAlpha(0.f); - - view.setTranslationY(-(view.getHeight() / 2)); - view.animate() - .translationY(0) - .alpha(1.f) - .setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - view.setVisibility(View.VISIBLE); - view.setAlpha(1.f); - } - }); - } - - public static void slideUp(final View view) { - view.animate() - .translationY(-(view.getHeight() / 2)) - .alpha(0.f) - .setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - // superfluous restoration - view.setVisibility(View.GONE); - view.setAlpha(1.f); - view.setTranslationY(0.f); - } - }); - } -} diff --git a/app/src/main/java/io/github/wulkanowy/utils/AppConstant.kt b/app/src/main/java/io/github/wulkanowy/utils/AppConstant.kt deleted file mode 100644 index 055d7f143..000000000 --- a/app/src/main/java/io/github/wulkanowy/utils/AppConstant.kt +++ /dev/null @@ -1,11 +0,0 @@ -package io.github.wulkanowy.utils - -const val APP_NAME = "Wulkanowy" - -const val DATABASE_NAME = "wulkanowy_db" - -const val DEFAULT_SYMBOL = "Default" - -const val DATE_PATTERN = "yyyy-MM-dd" - -const val REPO_URL = "https://github.com/wulkanowy/wulkanowy" diff --git a/app/src/main/java/io/github/wulkanowy/utils/CommonUtils.java b/app/src/main/java/io/github/wulkanowy/utils/CommonUtils.java deleted file mode 100644 index d84770e54..000000000 --- a/app/src/main/java/io/github/wulkanowy/utils/CommonUtils.java +++ /dev/null @@ -1,44 +0,0 @@ -package io.github.wulkanowy.utils; - -import android.content.Context; -import android.content.res.TypedArray; -import android.support.annotation.AttrRes; -import android.support.annotation.ColorInt; - -import io.github.wulkanowy.R; - -public final class CommonUtils { - - private CommonUtils() { - throw new IllegalStateException("Utility class"); - } - - public static int colorHexToColorName(String hexColor) { - switch (hexColor) { - case "000000": - return R.string.all_black; - - case "F04C4C": - return R.string.all_red; - - case "20A4F7": - return R.string.all_blue; - - case "6ECD07": - return R.string.all_green; - - default: - return R.string.all_empty_color; - } - } - - @ColorInt - public static int getThemeAttrColor(Context context, @AttrRes int colorAttr) { - final TypedArray array = context.obtainStyledAttributes(null, new int[]{colorAttr}); - try { - return array.getColor(0, 0); - } finally { - array.recycle(); - } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/utils/FabricUtils.java b/app/src/main/java/io/github/wulkanowy/utils/FabricUtils.java deleted file mode 100644 index 69897caa0..000000000 --- a/app/src/main/java/io/github/wulkanowy/utils/FabricUtils.java +++ /dev/null @@ -1,37 +0,0 @@ -package io.github.wulkanowy.utils; - -import com.crashlytics.android.answers.Answers; -import com.crashlytics.android.answers.CustomEvent; -import com.crashlytics.android.answers.LoginEvent; -import com.crashlytics.android.answers.SignUpEvent; - -public final class FabricUtils { - - private FabricUtils() { - throw new IllegalStateException("Utility class"); - } - - public static void logLogin(String method, boolean result) { - Answers.getInstance().logLogin(new LoginEvent() - .putMethod(method) - .putSuccess(result) - ); - } - - public static void logRegister(boolean result, String symbol, String message) { - Answers.getInstance().logSignUp(new SignUpEvent() - .putMethod("Login activity") - .putSuccess(result) - .putCustomAttribute("symbol", symbol) - .putCustomAttribute("message", message) - ); - } - - public static void logRefresh(String name, boolean result, String date) { - Answers.getInstance().logCustom( - new CustomEvent(name + " refresh") - .putCustomAttribute("Success", result ? "true" : "false") - .putCustomAttribute("Date", date) - ); - } -} diff --git a/app/src/main/java/io/github/wulkanowy/utils/FlexibleAdapterExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/FlexibleAdapterExtension.kt new file mode 100644 index 000000000..27b5f94f9 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/utils/FlexibleAdapterExtension.kt @@ -0,0 +1,10 @@ +package io.github.wulkanowy.utils + +import eu.davidea.flexibleadapter.FlexibleAdapter + +inline fun FlexibleAdapter<*>.setOnItemClickListener(crossinline listener: (position: Int) -> Unit) { + addListener(FlexibleAdapter.OnItemClickListener { _, position -> + listener(position) + true + }) +} \ No newline at end of file diff --git a/app/src/main/java/io/github/wulkanowy/utils/FragNavControlerExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/FragNavControlerExtension.kt new file mode 100644 index 000000000..588a2cf99 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/utils/FragNavControlerExtension.kt @@ -0,0 +1,13 @@ +package io.github.wulkanowy.utils + +import android.support.v4.app.Fragment +import com.ncapdevi.fragnav.FragNavController + +inline fun FragNavController.setOnTabTransactionListener(crossinline listener: (index: Int) -> Unit) { + transactionListener = object : FragNavController.TransactionListener { + override fun onFragmentTransaction(fragment: Fragment?, transactionType: FragNavController.TransactionType) {} + override fun onTabTransaction(fragment: Fragment?, index: Int) { + listener(index) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/io/github/wulkanowy/utils/GradeExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/GradeExtension.kt new file mode 100644 index 000000000..ee89de03e --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/utils/GradeExtension.kt @@ -0,0 +1,48 @@ +package io.github.wulkanowy.utils + +import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.Grade +import io.github.wulkanowy.data.db.entities.GradeSummary + +fun List.calcAverage(): Double { + var counter = 0.0 + var denominator = 0.0 + + forEach { + counter += (it.value + it.modifier) * it.weightValue + denominator += it.weightValue + } + return if (denominator != 0.0) counter / denominator else 0.0 +} + +@JvmName("calcSummaryAverage") +fun List.calcAverage(): Double { + return asSequence().mapNotNull { + if (it.finalGrade.matches("[0-6]".toRegex())) it.finalGrade.toDouble() else null + }.average() +} + +inline val Grade.valueColor: Int + get() { + return when (value) { + 6 -> R.color.grade_six + 5 -> R.color.grade_five + 4 -> R.color.grade_four + 3 -> R.color.grade_three + 2 -> R.color.grade_two + 1 -> R.color.grade_one + else -> R.color.grade_default + } + + } + +inline val Grade.colorStringId: Int + get() { + return when (color) { + "000000" -> R.string.all_black + "F04C4C" -> R.string.all_red + "20A4F7" -> R.string.all_blue + "6ECD07" -> R.string.all_green + else -> R.string.all_empty_color + } + } \ No newline at end of file diff --git a/app/src/main/java/io/github/wulkanowy/utils/GradeUtils.java b/app/src/main/java/io/github/wulkanowy/utils/GradeUtils.java deleted file mode 100644 index b45bdcc6f..000000000 --- a/app/src/main/java/io/github/wulkanowy/utils/GradeUtils.java +++ /dev/null @@ -1,166 +0,0 @@ -package io.github.wulkanowy.utils; - -import java.util.regex.Pattern; - -public final class GradeUtils { - - private final static Pattern validGradePattern = Pattern.compile("^(\\++|-|--|=)?[0-6](\\++|-|--|=)?$"); - private final static Pattern simpleGradeValuePattern = Pattern.compile("([0-6])"); - - private GradeUtils() { - throw new IllegalStateException("Utility class"); - } - - /*public static float calculateWeightedAverage(List gradeList) { - - float counter = 0f; - float denominator = 0f; - - for (Grade grade : gradeList) { - int weight = getWeightValue(grade.getWeight()); - float value = getWeightedGradeValue(grade.getValue()); - - if (value != -1.0f) { - counter += value * weight; - denominator += weight; - } - } - - if (counter == 0f) { - return -1.0f; - } - return counter / denominator; - } - - public static float calculateSubjectsAverage(List subjectList, boolean usePredicted) { - return calculateSubjectsAverage(subjectList, usePredicted, false); - } - - public static float calculateDetailedSubjectsAverage(List subjectList) { - return calculateSubjectsAverage(subjectList, false, true); - } - - public static int getValueColor(String value) { - Matcher m1 = validGradePattern.matcher(value); - if (!m1.find()) { - return R.color.grade_default; - } - - Matcher m2 = simpleGradeValuePattern.matcher(m1.group()); - if (!m2.find()) { - return R.color.grade_default; - } - - switch (Integer.parseInt(m2.group())) { - case 6: - return R.color.grade_six; - case 5: - return R.color.grade_five; - case 4: - return R.color.grade_four; - case 3: - return R.color.grade_three; - case 2: - return R.color.grade_two; - case 1: - return R.color.grade_one; - default: - return R.color.grade_default; - } - } - - private static float calculateSubjectsAverage(List subjectList, boolean usePredicted, boolean useSubjectsAverages) { - float counter = 0f; - float denominator = 0f; - - for (Subject subject : subjectList) { - float value; - - if (useSubjectsAverages) { - value = calculateWeightedAverage(subject.getGradeList()); - } else { - value = getGradeValue(usePredicted ? subject.getPredictedRating() : subject.getFinalRating()); - } - - if (value != -1.0f) { - counter += Math.round(value); - denominator++; - } - } - - if (counter == 0) { - return -1.0f; - } - - return counter / denominator; - } - - public static float getGradeValue(String grade) { - if (validGradePattern.matcher(grade).matches()) { - return getWeightedGradeValue(grade); - } - - return getVerbalGradeValue(grade); - } - - private static float getVerbalGradeValue(String grade) { - switch (grade) { - case "celujący": - return 6f; - case "bardzo dobry": - return 5f; - case "dobry": - return 4f; - case "dostateczny": - return 3f; - case "dopuszczający": - return 2f; - case "niedostateczny": - return 1f; - default: - return -1f; - } - } - - public static String getShortGradeValue(String grade) { - switch (grade) { - case "celujący": - return "6"; - case "bardzo dobry": - return "5"; - case "dobry": - return "4"; - case "dostateczny": - return "3"; - case "dopuszczający": - return "2"; - case "niedostateczny": - return "1"; - default: - return grade; - } - } - - private static float getWeightedGradeValue(String value) { - if (validGradePattern.matcher(value).matches()) { - if (value.matches("[-][0-6]") || value.matches("[0-6][-]")) { - String replacedValue = value.replaceAll("[-]", ""); - return Float.valueOf(replacedValue) - 0.33f; - } else if (value.matches("[+][0-6]") || value.matches("[0-6][+]")) { - String replacedValue = value.replaceAll("[+]", ""); - return Float.valueOf((replacedValue)) + 0.33f; - } else if (value.matches("[-|=]{1,2}[0-6]") || value.matches("[0-6][-|=]{1,2}")) { - String replacedValue = value.replaceAll("[-|=]{1,2}", ""); - return Float.valueOf((replacedValue)) - 0.5f; - } else { - return Float.valueOf(value); - } - } else { - return -1; - } - } - - private static int getWeightValue(String weightOfGrade) { - return Integer.valueOf(weightOfGrade.substring(0, weightOfGrade.length() - 3)); - }*/ -} diff --git a/app/src/main/java/io/github/wulkanowy/utils/LoggerUtils.java b/app/src/main/java/io/github/wulkanowy/utils/LoggerUtils.java deleted file mode 100644 index 2b64e14e1..000000000 --- a/app/src/main/java/io/github/wulkanowy/utils/LoggerUtils.java +++ /dev/null @@ -1,47 +0,0 @@ -package io.github.wulkanowy.utils; - -import android.os.Build; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.util.Log; - -import com.crashlytics.android.Crashlytics; - -import timber.log.Timber; - -public final class LoggerUtils { - - public static class CrashlyticsTree extends Timber.Tree { - - @Override - protected void log(int priority, @Nullable String tag, @Nullable String message, @Nullable Throwable t) { - Crashlytics.setInt("priority", priority); - Crashlytics.setString("tag", tag); - - if (t == null) { - Crashlytics.log(message); - } else { - Crashlytics.setString("message", message); - Crashlytics.logException(t); - } - } - } - - public static class DebugLogTree extends Timber.DebugTree { - - @Override - protected void log(int priority, String tag, @NonNull String message, Throwable t) { - if ("HUAWEI".equals(Build.MANUFACTURER) || "samsung".equals(Build.MANUFACTURER)) { - if (priority == Log.VERBOSE || priority == Log.DEBUG || priority == Log.INFO) { - priority = Log.ERROR; - } - } - super.log(priority, AppConstantKt.APP_NAME, message, t); - } - - @Override - protected String createStackElementTag(@NonNull StackTraceElement element) { - return super.createStackElementTag(element) + " - " + element.getLineNumber(); - } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/utils/LoggerUtils.kt b/app/src/main/java/io/github/wulkanowy/utils/LoggerUtils.kt new file mode 100644 index 000000000..532209ea3 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/utils/LoggerUtils.kt @@ -0,0 +1,27 @@ +package io.github.wulkanowy.utils + +import com.crashlytics.android.Crashlytics +import timber.log.Timber + +object CrashlyticsTree : Timber.Tree() { + + override fun log(priority: Int, tag: String?, message: String, t: Throwable?) { + Crashlytics.setInt("priority", priority) + Crashlytics.setString("tag", tag) + + if (t == null) { + Crashlytics.log(message) + } else { + Crashlytics.setString("message", message) + Crashlytics.logException(t) + } + } +} + +object DebugLogTree : Timber.DebugTree() { + + override fun createStackElementTag(element: StackTraceElement): String? { + return super.createStackElementTag(element) + " - ${element.lineNumber}" + } +} + diff --git a/app/src/main/java/io/github/wulkanowy/utils/TimeExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/TimeExtension.kt new file mode 100644 index 000000000..04cf01127 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/utils/TimeExtension.kt @@ -0,0 +1,87 @@ +package io.github.wulkanowy.utils + +import org.threeten.bp.DayOfWeek.* +import org.threeten.bp.LocalDate +import org.threeten.bp.format.DateTimeFormatter +import org.threeten.bp.format.DateTimeFormatter.ofPattern +import org.threeten.bp.temporal.TemporalAdjusters +import org.threeten.bp.temporal.TemporalAdjusters.* +import java.text.SimpleDateFormat +import java.util.* + +private const val DATE_PATTERN = "yyyy-MM-dd" + +fun Date.toLocalDate(): LocalDate { + return LocalDate.parse(SimpleDateFormat(DATE_PATTERN, Locale.getDefault()).format(this)) +} + +fun String.toLocalDate(format: String = DATE_PATTERN): LocalDate { + return LocalDate.parse(this, DateTimeFormatter.ofPattern(format)) +} + +fun LocalDate.toFormattedString(format: String): String = this.format(ofPattern(format)) + +fun LocalDate.toFormattedString(): String = this.toFormattedString(DATE_PATTERN) + +inline val LocalDate.nextWorkDay: LocalDate + get() { + return when (this.dayOfWeek) { + FRIDAY, SATURDAY, SUNDAY -> this.with(next(MONDAY)) + else -> this.plusDays(1) + } + } + +inline val LocalDate.previousWorkDay: LocalDate + get() { + return when (this.dayOfWeek) { + SATURDAY, SUNDAY, MONDAY -> this.with(previous(FRIDAY)) + else -> this.minusDays(1) + } + } + +inline val LocalDate.nearSchoolDayPrevOnWeekEnd: LocalDate + get() { + return when (this.dayOfWeek) { + SATURDAY, SUNDAY -> this.with(previous(FRIDAY)) + else -> this + } + } + +inline val LocalDate.nearSchoolDayNextOnWeekEnd: LocalDate + get() { + return when (this.dayOfWeek) { + SATURDAY, SUNDAY -> this.with(next(MONDAY)) + else -> this + } + } + +inline val LocalDate.weekDayName: String + get() = this.format(ofPattern("EEEE", Locale.getDefault())) + +inline val LocalDate.weekFirstDayAlwaysCurrent: LocalDate + get() = this.with(TemporalAdjusters.previousOrSame(MONDAY)) + +inline val LocalDate.weekFirstDayNextOnWeekEnd: LocalDate + get() { + return when (this.dayOfWeek) { + SATURDAY, SUNDAY -> this.with(next(MONDAY)) + else -> this.with(previousOrSame(MONDAY)) + } + } + +/** + * [Dz.U. 2016 poz. 1335](http://prawo.sejm.gov.pl/isap.nsf/DocDetails.xsp?id=WDU20160001335) + */ +inline val LocalDate.isHolidays: Boolean + get() { + return LocalDate.of(this.year, 9, 1).run { + when (dayOfWeek) { + FRIDAY, SATURDAY, SUNDAY -> with(firstInMonth(MONDAY)) + else -> this + } + }.let { firstSchoolDay -> + LocalDate.of(this.year, 6, 20) + .with(next(FRIDAY)) + .let { lastSchoolDay -> this.isBefore(firstSchoolDay) && this.isAfter(lastSchoolDay) } + } + } diff --git a/app/src/main/java/io/github/wulkanowy/utils/extension/ViewPagerExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/ViewPagerExtension.kt similarity index 74% rename from app/src/main/java/io/github/wulkanowy/utils/extension/ViewPagerExtension.kt rename to app/src/main/java/io/github/wulkanowy/utils/ViewPagerExtension.kt index 6d2763dd2..1e51da09c 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/extension/ViewPagerExtension.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/ViewPagerExtension.kt @@ -1,13 +1,12 @@ -package io.github.wulkanowy.utils.extension +package io.github.wulkanowy.utils import android.support.v4.view.ViewPager -fun ViewPager.setOnSelectPageListener(selectListener: (position: Int) -> Unit) { +inline fun ViewPager.setOnSelectPageListener(crossinline selectListener: (position: Int) -> Unit) { addOnPageChangeListener(object : ViewPager.OnPageChangeListener { override fun onPageSelected(position: Int) { selectListener(position) } - override fun onPageScrollStateChanged(state: Int) {} override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {} }) diff --git a/app/src/main/java/io/github/wulkanowy/utils/extension/FlexibleAdapterExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/extension/FlexibleAdapterExtension.kt deleted file mode 100644 index b89060035..000000000 --- a/app/src/main/java/io/github/wulkanowy/utils/extension/FlexibleAdapterExtension.kt +++ /dev/null @@ -1,15 +0,0 @@ -package io.github.wulkanowy.utils.extension - -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem - -fun FlexibleAdapter<*>.setOnItemClickListener(listener: (position: Int) -> Unit) { - addListener(FlexibleAdapter.OnItemClickListener { _, position -> - listener(position) - true - }) -} - -fun FlexibleAdapter<*>.setOnUpdateListener(listener: (size: Int) -> Unit) { - addListener(FlexibleAdapter.OnUpdateListener { listener(it) }) -} diff --git a/app/src/main/java/io/github/wulkanowy/utils/extension/TimeExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/extension/TimeExtension.kt deleted file mode 100644 index 6b463c86b..000000000 --- a/app/src/main/java/io/github/wulkanowy/utils/extension/TimeExtension.kt +++ /dev/null @@ -1,79 +0,0 @@ -package io.github.wulkanowy.utils.extension - -import io.github.wulkanowy.utils.DATE_PATTERN -import org.threeten.bp.* -import org.threeten.bp.format.DateTimeFormatter -import org.threeten.bp.temporal.TemporalAdjusters -import java.text.SimpleDateFormat -import java.util.* - -fun Date.toLocalDate(): LocalDate = LocalDate.parse(SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(this)) - -fun String.toDate(format: String = "yyyy-MM-dd"): LocalDate = LocalDate.parse(this, DateTimeFormatter.ofPattern(format)) - -fun LocalDate.toFormat(format: String): String = this.format(DateTimeFormatter.ofPattern(format)) - -fun LocalDate.toFormat(): String = this.toFormat(DATE_PATTERN) - -fun LocalDate.getNextWorkDay(): LocalDate { - return when(this.dayOfWeek) { - DayOfWeek.FRIDAY, DayOfWeek.SATURDAY, DayOfWeek.SUNDAY -> this.with(TemporalAdjusters.next(DayOfWeek.MONDAY)) - else -> this.plusDays(1) - } -} - -fun LocalDate.getPreviousWorkDay(): LocalDate { - return when(this.dayOfWeek) { - DayOfWeek.SATURDAY, DayOfWeek.SUNDAY, DayOfWeek.MONDAY -> this.with(TemporalAdjusters.previous(DayOfWeek.FRIDAY)) - else -> this.minusDays(1) - } -} - -fun LocalDate.getNearSchoolDayPrevOnWeekEnd(): LocalDate { - return when(this.dayOfWeek) { - DayOfWeek.SATURDAY, DayOfWeek.SUNDAY -> this.with(TemporalAdjusters.previous(DayOfWeek.FRIDAY)) - else -> this - } -} - -fun LocalDate.getNearSchoolDayNextOnWeekEnd(): LocalDate { - return when(this.dayOfWeek) { - DayOfWeek.SATURDAY, DayOfWeek.SUNDAY -> this.with(TemporalAdjusters.next(DayOfWeek.MONDAY)) - else -> this - } -} - -fun LocalDate.getWeekDayName(): String = this.format(DateTimeFormatter.ofPattern("EEEE", Locale.getDefault())) - -fun LocalDate.getWeekFirstDayAlwaysCurrent(): LocalDate { - return this.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY)) -} - -fun LocalDate.getWeekFirstDayNextOnWeekEnd(): LocalDate { - return when(this.dayOfWeek) { - DayOfWeek.SATURDAY, DayOfWeek.SUNDAY -> this.with(TemporalAdjusters.next(DayOfWeek.MONDAY)) - else -> this.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY)) - } -} - -/** - * [Dz.U. 2016 poz. 1335](http://prawo.sejm.gov.pl/isap.nsf/DocDetails.xsp?id=WDU20160001335) - */ -fun LocalDate.isHolidays(): Boolean = this.isAfter(this.getLastSchoolDay()) && this.isBefore(this.getFirstSchoolDay()) - -fun LocalDate.getSchoolYear(): Int = if (this.monthValue <= 8) this.year - 1 else this.year - -fun LocalDate.getFirstSchoolDay(): LocalDate { - return LocalDate.of(this.year, 9, 1).run { - when (dayOfWeek) { - DayOfWeek.FRIDAY, DayOfWeek.SATURDAY, DayOfWeek.SUNDAY -> with(TemporalAdjusters.firstInMonth(DayOfWeek.MONDAY)) - else -> this - } - } -} - -fun LocalDate.getLastSchoolDay(): LocalDate { - return LocalDate - .of(this.year, 6, 20) - .with(TemporalAdjusters.next(DayOfWeek.FRIDAY)) -} diff --git a/app/src/main/java/io/github/wulkanowy/utils/security/Scrambler.kt b/app/src/main/java/io/github/wulkanowy/utils/security/Scrambler.kt index d3d53c54e..577409da5 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/security/Scrambler.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/security/Scrambler.kt @@ -12,7 +12,6 @@ import android.security.keystore.KeyGenParameterSpec import android.security.keystore.KeyProperties.* import android.util.Base64 import android.util.Base64.DEFAULT -import org.apache.commons.lang3.StringUtils import timber.log.Timber import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream @@ -76,7 +75,7 @@ object Scrambler { @JvmStatic fun decrypt(cipherText: String): String { - if (StringUtils.isEmpty(cipherText)) throw ScramblerException("Text to be encrypted is empty") + if (cipherText.isEmpty()) throw ScramblerException("Text to be encrypted is empty") if (SDK_INT < JELLY_BEAN_MR2 || cipherText.length < 250) { return String(Base64.decode(cipherText.toByteArray(KEY_CHARSET), DEFAULT), KEY_CHARSET) diff --git a/app/src/main/res/drawable-v15/img_splash_logo.png b/app/src/main/res/drawable-v15/img_splash_logo.png new file mode 100644 index 000000000..28dbd51dc Binary files /dev/null and b/app/src/main/res/drawable-v15/img_splash_logo.png differ diff --git a/app/src/main/res/drawable/layer_splash_background.xml b/app/src/main/res/drawable-v15/layer_splash_background.xml similarity index 67% rename from app/src/main/res/drawable/layer_splash_background.xml rename to app/src/main/res/drawable-v15/layer_splash_background.xml index 22c1be470..6509b2f80 100644 --- a/app/src/main/res/drawable/layer_splash_background.xml +++ b/app/src/main/res/drawable-v15/layer_splash_background.xml @@ -1,5 +1,9 @@ - + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/img_splash_logo.png b/app/src/main/res/drawable/img_splash_logo.png deleted file mode 100644 index d50ab8b2d..000000000 Binary files a/app/src/main/res/drawable/img_splash_logo.png and /dev/null differ diff --git a/app/src/main/res/drawable/img_timetable_widget_preview.png b/app/src/main/res/drawable/img_timetable_widget_preview.png deleted file mode 100644 index d9f3538c4..000000000 Binary files a/app/src/main/res/drawable/img_timetable_widget_preview.png and /dev/null differ diff --git a/app/src/main/res/drawable/img_timetable_widget_preview.webp b/app/src/main/res/drawable/img_timetable_widget_preview.webp new file mode 100644 index 000000000..e0b4a9735 Binary files /dev/null and b/app/src/main/res/drawable/img_timetable_widget_preview.webp differ diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index af892ed4e..507fa3b1b 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,19 +1,33 @@ - + android:layout_height="match_parent"> + + + + + + + android:layout_height="match_parent" + android:layout_marginBottom="@dimen/bottom_navigation_height" + app:layout_behavior="@string/appbar_scrolling_view_behavior" /> - \ No newline at end of file + diff --git a/app/src/main/res/layout/dialog_grade.xml b/app/src/main/res/layout/dialog_grade.xml index 3f6943519..13224afb3 100644 --- a/app/src/main/res/layout/dialog_grade.xml +++ b/app/src/main/res/layout/dialog_grade.xml @@ -5,45 +5,42 @@ android:layout_height="match_parent"> - + android:minWidth="300dp"> - + + + +