From f2b7c0e781084db0b6b20690f9777e17c728b09b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Wed, 3 Oct 2018 21:28:23 +0200 Subject: [PATCH] Refactor grade module (#156) --- app/build.gradle | 14 +- .../repositories/local/AttendanceLocalTest.kt | 8 +- .../data/repositories/local/ExamLocalTest.kt | 8 +- .../repositories/local/SessionLocalTest.kt | 10 +- app/src/main/AndroidManifest.xml | 39 +--- .../java/io/github/wulkanowy/WulkanowyApp.kt | 31 ++- .../github/wulkanowy/data/RepositoryModule.kt | 21 +- .../github/wulkanowy/data/db/AppDatabase.kt | 27 ++- .../github/wulkanowy/data/db/dao/GradeDao.kt | 21 ++ .../wulkanowy/data/db/dao/GradeSummaryDao.kt | 18 ++ .../wulkanowy/data/db/entities/Attendance.kt | 10 +- .../github/wulkanowy/data/db/entities/Exam.kt | 16 +- .../wulkanowy/data/db/entities/Grade.kt | 49 +++++ .../data/db/entities/GradeSummary.kt | 26 +++ .../wulkanowy/data/db/entities/Semester.kt | 4 +- .../data/repositories/AttendanceRepository.kt | 4 +- .../data/repositories/ExamRepository.kt | 5 +- .../data/repositories/GradeRepository.kt | 42 ++++ .../repositories/GradeSummaryRepository.kt | 30 +++ .../data/repositories/SessionRepository.kt | 5 +- .../data/repositories/local/ExamLocal.kt | 3 - .../data/repositories/local/GradeLocal.kt | 29 +++ .../repositories/local/GradeSummaryLocal.kt | 21 ++ .../repositories/remote/AttendanceRemote.kt | 2 +- .../data/repositories/remote/ExamRemote.kt | 2 +- .../data/repositories/remote/GradeRemote.kt | 42 ++++ .../repositories/remote/GradeSummaryRemote.kt | 32 +++ .../java/io/github/wulkanowy/di/AppModule.kt | 3 + .../github/wulkanowy/ui/base/BaseActivity.kt | 10 +- .../github/wulkanowy/ui/base/BaseFragment.kt | 14 -- .../wulkanowy/ui/base/BasePagerAdapter.kt | 32 +-- .../io/github/wulkanowy/ui/base/BaseView.kt | 2 - .../wulkanowy/ui/login/LoginActivity.kt | 13 +- .../github/wulkanowy/ui/login/LoginModule.kt | 6 +- .../ui/login/form/LoginFormFragment.kt | 12 +- .../ui/login/form/LoginFormPresenter.kt | 3 +- .../ui/login/options/LoginOptionsFragment.kt | 2 +- .../ui/login/options/LoginOptionsModule.kt | 14 -- .../github/wulkanowy/ui/main/MainActivity.kt | 89 ++++---- .../io/github/wulkanowy/ui/main/MainModule.kt | 4 +- .../github/wulkanowy/ui/main/MainPresenter.kt | 30 +-- .../io/github/wulkanowy/ui/main/MainView.kt | 21 +- .../ui/main/attendance/AttendanceDialog.kt | 4 +- .../ui/main/attendance/AttendanceFragment.kt | 2 +- .../ui/main/attendance/AttendancePresenter.kt | 19 +- .../wulkanowy/ui/main/exam/ExamDialog.kt | 4 +- .../wulkanowy/ui/main/exam/ExamFragment.kt | 2 +- .../wulkanowy/ui/main/exam/ExamHeader.kt | 8 +- .../wulkanowy/ui/main/exam/ExamPresenter.kt | 20 +- .../wulkanowy/ui/main/grade/GradeFragment.kt | 111 +++++++++- .../wulkanowy/ui/main/grade/GradeModule.kt | 32 +++ .../wulkanowy/ui/main/grade/GradePresenter.kt | 94 +++++++++ .../wulkanowy/ui/main/grade/GradeView.kt | 31 +++ .../main/grade/details/GradeDetailsDialog.kt | 79 +++++++ .../grade/details/GradeDetailsFragment.kt | 134 ++++++++++++ .../main/grade/details/GradeDetailsHeader.kt | 73 +++++++ .../ui/main/grade/details/GradeDetailsItem.kt | 68 ++++++ .../grade/details/GradeDetailsPresenter.kt | 124 +++++++++++ .../ui/main/grade/details/GradeDetailsView.kt | 46 +++++ .../grade/summary/GradeSummaryFragment.kt | 111 ++++++++++ .../main/grade/summary/GradeSummaryHeader.kt | 52 +++++ .../ui/main/grade/summary/GradeSummaryItem.kt | 56 +++++ .../grade/summary/GradeSummaryPresenter.kt | 121 +++++++++++ .../summary/GradeSummaryScrollableHeader.kt | 53 +++++ .../ui/main/grade/summary/GradeSummaryView.kt | 32 +++ .../{extension => }/ActivityExtension.kt | 2 +- .../wulkanowy/utils/AnimationUtils.java | 40 ---- .../io/github/wulkanowy/utils/AppConstant.kt | 11 - .../github/wulkanowy/utils/CommonUtils.java | 44 ---- .../github/wulkanowy/utils/FabricUtils.java | 37 ---- .../utils/FlexibleAdapterExtension.kt | 10 + .../utils/FragNavControlerExtension.kt | 13 ++ .../github/wulkanowy/utils/GradeExtension.kt | 48 +++++ .../io/github/wulkanowy/utils/GradeUtils.java | 166 --------------- .../github/wulkanowy/utils/LoggerUtils.java | 47 ----- .../io/github/wulkanowy/utils/LoggerUtils.kt | 27 +++ .../github/wulkanowy/utils/TimeExtension.kt | 87 ++++++++ .../{extension => }/ViewPagerExtension.kt | 5 +- .../extension/FlexibleAdapterExtension.kt | 15 -- .../utils/extension/TimeExtension.kt | 79 ------- .../wulkanowy/utils/security/Scrambler.kt | 3 +- .../main/res/drawable-v15/img_splash_logo.png | Bin 0 -> 4469 bytes .../layer_splash_background.xml | 8 +- .../main/res/drawable-v23/img_splash_logo.png | Bin 0 -> 4163 bytes .../drawable-v23/layer_splash_background.xml | 12 ++ app/src/main/res/drawable/img_splash_logo.png | Bin 28838 -> 0 bytes .../drawable/img_timetable_widget_preview.png | Bin 50108 -> 0 bytes .../img_timetable_widget_preview.webp | Bin 0 -> 10948 bytes app/src/main/res/layout/activity_main.xml | 26 ++- app/src/main/res/layout/dialog_grade.xml | 114 +++++----- app/src/main/res/layout/fragment_grade.xml | 195 +++--------------- .../res/layout/fragment_grade_details.xml | 50 +++++ .../res/layout/fragment_grade_summary.xml | 50 +++++ .../main/res/layout/fragment_login_form.xml | 4 +- .../main/res/layout/fragment_timetable.xml | 22 +- app/src/main/res/layout/header_attendance.xml | 8 +- ...der_grade.xml => header_grade_details.xml} | 47 ++--- .../main/res/layout/header_grade_summary.xml | 8 +- app/src/main/res/layout/header_timetable.xml | 8 +- app/src/main/res/layout/item_grade.xml | 88 -------- .../main/res/layout/item_grade_details.xml | 80 +++++++ .../main/res/layout/item_grade_summary.xml | 94 +++------ .../scrollable_header_grade_summary.xml | 56 +++++ app/src/main/res/layout/widget_timetable.xml | 6 +- app/src/main/res/menu/action_menu_grade.xml | 9 +- app/src/main/res/values-night/styles.xml | 17 -- app/src/main/res/values-pl/strings.xml | 5 +- app/src/main/res/values-w820dp/dimens.xml | 5 - app/src/main/res/values/dimens.xml | 12 +- app/src/main/res/values/strings.xml | 5 +- app/src/main/res/values/styles.xml | 25 +-- ...dentRemoteTest.kt => SessionRemoteTest.kt} | 2 +- .../wulkanowy/ui/main/MainPresenterTest.kt | 20 +- .../ui/splash/SplashPresenterTest.kt | 8 +- .../wulkanowy/utils/GradeExtensionTest.kt | 47 +++++ .../wulkanowy/utils/TimeExtensionTest.kt | 144 +++++++++++++ .../utils/extension/TimeExtensionTest.kt | 150 -------------- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- 119 files changed, 2629 insertions(+), 1384 deletions(-) create mode 100644 app/src/main/java/io/github/wulkanowy/data/db/dao/GradeDao.kt create mode 100644 app/src/main/java/io/github/wulkanowy/data/db/dao/GradeSummaryDao.kt create mode 100644 app/src/main/java/io/github/wulkanowy/data/db/entities/Grade.kt create mode 100644 app/src/main/java/io/github/wulkanowy/data/db/entities/GradeSummary.kt create mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/GradeRepository.kt create mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/GradeSummaryRepository.kt create mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/local/GradeLocal.kt create mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/local/GradeSummaryLocal.kt create mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/remote/GradeRemote.kt create mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/remote/GradeSummaryRemote.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/ui/login/options/LoginOptionsModule.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/main/grade/GradeModule.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/main/grade/GradePresenter.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/main/grade/GradeView.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/main/grade/details/GradeDetailsDialog.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/main/grade/details/GradeDetailsFragment.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/main/grade/details/GradeDetailsHeader.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/main/grade/details/GradeDetailsItem.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/main/grade/details/GradeDetailsPresenter.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/main/grade/details/GradeDetailsView.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/main/grade/summary/GradeSummaryFragment.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/main/grade/summary/GradeSummaryHeader.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/main/grade/summary/GradeSummaryItem.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/main/grade/summary/GradeSummaryPresenter.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/main/grade/summary/GradeSummaryScrollableHeader.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/main/grade/summary/GradeSummaryView.kt rename app/src/main/java/io/github/wulkanowy/utils/{extension => }/ActivityExtension.kt (91%) delete mode 100644 app/src/main/java/io/github/wulkanowy/utils/AnimationUtils.java delete mode 100644 app/src/main/java/io/github/wulkanowy/utils/AppConstant.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/utils/CommonUtils.java delete mode 100644 app/src/main/java/io/github/wulkanowy/utils/FabricUtils.java create mode 100644 app/src/main/java/io/github/wulkanowy/utils/FlexibleAdapterExtension.kt create mode 100644 app/src/main/java/io/github/wulkanowy/utils/FragNavControlerExtension.kt create mode 100644 app/src/main/java/io/github/wulkanowy/utils/GradeExtension.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/utils/GradeUtils.java delete mode 100644 app/src/main/java/io/github/wulkanowy/utils/LoggerUtils.java create mode 100644 app/src/main/java/io/github/wulkanowy/utils/LoggerUtils.kt create mode 100644 app/src/main/java/io/github/wulkanowy/utils/TimeExtension.kt rename app/src/main/java/io/github/wulkanowy/utils/{extension => }/ViewPagerExtension.kt (74%) delete mode 100644 app/src/main/java/io/github/wulkanowy/utils/extension/FlexibleAdapterExtension.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/utils/extension/TimeExtension.kt create mode 100644 app/src/main/res/drawable-v15/img_splash_logo.png rename app/src/main/res/{drawable => drawable-v15}/layer_splash_background.xml (67%) create mode 100644 app/src/main/res/drawable-v23/img_splash_logo.png create mode 100644 app/src/main/res/drawable-v23/layer_splash_background.xml delete mode 100644 app/src/main/res/drawable/img_splash_logo.png delete mode 100644 app/src/main/res/drawable/img_timetable_widget_preview.png create mode 100644 app/src/main/res/drawable/img_timetable_widget_preview.webp create mode 100644 app/src/main/res/layout/fragment_grade_details.xml create mode 100644 app/src/main/res/layout/fragment_grade_summary.xml rename app/src/main/res/layout/{header_grade.xml => header_grade_details.xml} (63%) delete mode 100644 app/src/main/res/layout/item_grade.xml create mode 100644 app/src/main/res/layout/item_grade_details.xml create mode 100644 app/src/main/res/layout/scrollable_header_grade_summary.xml delete mode 100644 app/src/main/res/values-night/styles.xml delete mode 100644 app/src/main/res/values-w820dp/dimens.xml rename app/src/test/java/io/github/wulkanowy/data/repositories/remote/{StudentRemoteTest.kt => SessionRemoteTest.kt} (96%) create mode 100644 app/src/test/java/io/github/wulkanowy/utils/GradeExtensionTest.kt create mode 100644 app/src/test/java/io/github/wulkanowy/utils/TimeExtensionTest.kt delete mode 100644 app/src/test/java/io/github/wulkanowy/utils/extension/TimeExtensionTest.kt diff --git a/app/build.gradle b/app/build.gradle index 4f1b37b3..d2089beb 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 c8293b27..3af246eb 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 52edf559..8a6b4bcc 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 17264c95..6c13b25c 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 21c7bc99..e4d5f780 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 4cba8eda..cf1031e5 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 3e186f48..cc39b58e 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 00000000..5149e473 --- /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 00000000..6dab2690 --- /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 a3bfa2aa..be8911a5 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 9dd81a3d..807c8f39 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 00000000..1fee97a8 --- /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 00000000..58528a04 --- /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 5ef1f359..2bad1cba 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 4ed56d07..91986826 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 e2932900..a334719e 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 00000000..9e4e2ab8 --- /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 00000000..58afe3ec --- /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 6d158d29..ae7e201e 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 0074cd0f..b8c3c0e2 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 00000000..5689ee4b --- /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 00000000..d06808b1 --- /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 1a165784..4d17c24c 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 ea4000a3..719d561d 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 00000000..2f4c9a0e --- /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 00000000..b08e8f59 --- /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 6eff9e97..dbed90bb 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 0946b251..69e9beb0 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 72c21da9..8c6adb2c 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 8b37da85..12c6ee33 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 4c384ab2..6352ca83 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 0ab172ac..4fb1ee51 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 5d1e4323..ea573411 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 b2e3145b..e27526aa 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 3cc25a84..b4245497 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 cedf47f6..b318daac 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 711db217..00000000 --- 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 4ec418fb..803b8729 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 ea37ef4e..f750a301 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 5041b989..27dc9425 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 c5b3d7c2..fee8630e 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 91c36594..648acf3b 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 179909aa..3efd61ba 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 7444055d..20da58c6 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 7f15267d..619b29e1 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 957bb94f..5bd94d18 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 5c752b44..57a8e778 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 174cd225..a1b96b13 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 961afb1e..97144f5c 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 00000000..d9312f07 --- /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 00000000..3d79156a --- /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 00000000..fa2d4f51 --- /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 00000000..3fda90d9 --- /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 00000000..a5f3409c --- /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 00000000..92636e49 --- /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 00000000..1a553b1e --- /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 00000000..8add69a9 --- /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 00000000..0091fe99 --- /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 00000000..f64c1f36 --- /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 00000000..c3dd6b3c --- /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 00000000..d2ad9cac --- /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 00000000..0c3f135d --- /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 00000000..d0c6ab1b --- /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 00000000..6ddfb86d --- /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 1a48c115..c0314d02 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 04116423..00000000 --- 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 055d7f14..00000000 --- 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 d84770e5..00000000 --- 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 69897caa..00000000 --- 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 00000000..27b5f94f --- /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 00000000..588a2cf9 --- /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 00000000..ee89de03 --- /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 b45bdcc6..00000000 --- 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 2b64e14e..00000000 --- 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 00000000..532209ea --- /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 00000000..04cf0112 --- /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 6d2763dd..1e51da09 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 b8906003..00000000 --- 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 6b463c86..00000000 --- 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 d3d53c54..577409da 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 0000000000000000000000000000000000000000..28dbd51dc8a545e8f0a8f5d9333c9988359d95b4 GIT binary patch literal 4469 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 literal 0 HcmV?d00001 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 22c1be47..6509b2f8 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 @@ - + + + + + + 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-v23/layer_splash_background.xml b/app/src/main/res/drawable-v23/layer_splash_background.xml new file mode 100644 index 00000000..ca7afbe1 --- /dev/null +++ b/app/src/main/res/drawable-v23/layer_splash_background.xml @@ -0,0 +1,12 @@ + + + + + + + + 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 d50ab8b2d0faf253dd903c252e5f22b70ff0d788..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 28838 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%0A=2)yUSuz;>EV?WCbX*q|}JD@0_ zA^5(Z6Fu$LCn$G5a|6lXJ_Tx6JK2f8fJURekae}fVKEY7Y&~EfSs-X->>NMJ*2QGo zo%s6apDvIbWNKUs7yz6wCnqP$$)XtCt8O4YY`cBY%aB|BLF4#<2LccV5!$51@`n?v z2!f(`$-;sz>;+1RM1PIVxr)aET*)oEB`YJhBHxGjE*P!Dk!aiobxEJLxr6sVefqRf zzSGGN(NnPNgPZ^XH_2NjiZXmrLsRn&vLd^SW<%5)xY%%8OpD1nA+<(sZmKFOH82Yk z_yrl8#M#-mUZ6}>i!-F9$GFJDS>af%p7E}|dzX-_jI*sU3h+GUur(jw0HY)goju5T zWKA+pS?x|4|EMs~qHvhOq(}PXNa<}}wziLgn})U%2SNLN?D~*;GuEQ%wejD3CJoY^ z0R=%n&%qd!d(3-BiW?a_Yvm)Z3=;y{Ol?^>V$N{EKzlKCV!`q8j;R5dgCft7>G#*H7)HbA|4$| zqs9DVTm^${EBKSttlY2U0c}8w(ct#ov3qwfxcLzB8Yio@omPMs(fH2O%E~a2{UH?e z)oxAz1~95*;32e3j9cU3e=5wbZma3 z7@}cKWcAl(HwuheL`ftf{p5_Q6vV{(&YdOk()1$FE3zg5TrKJYV}l{qOVf|5%;!Vn zaX##TLGD$9lP7P)9M(ZNqVyRRxFiaic7WBP>*;E1_aE|?N9yu>iJP!zkCN!ABJQa$ zjKkDKAJbEe+8C{U6we1R8i}_9O}w2TysD!!AW1A`D0G{BwBzr z6tRLv#PW^EH5Zq07}YY|`#(AQz$(tkb3mQgSBJZBm%n=R#s$=ZZQ|wWPxiuMV%X!q z*h01MMsX04p-sJm(}I){)v^>MTQa&u9M-`6gF5^~{XXtPhUYTo{c!VZ^IL|mvfpGg ze;ff5(-iZ5o+m?gdN{9R@A`FfS_y9@srnd9!1bkgHlWpZ35uvKeVO?jclynV!^;=BL{~JYA{;MthaBq z)!NoDmkeRw#}k9JVQ%v8Kn*HQC@kC~EPPIM7ojq1iN7E+%KTU*Ns(=7f|R6NVNMwK}_B<$8}(sK7T#~yZxTz*(cc$BglS1%EO0_qB=(lcEsGhdp9D2 zw|zIK5lZH~A&kB_Ysl=Gm5;JK#=1bHx3_Teu|IzCqR6Hp9*j3*Y*=Ge&gCx~G6vcI z`(rS#RH9VB30!pS8hLfu(i>tjfSG*aR&uUt z_yL1)xCs9|WJ@3%PlhqD&-FmvNxqFS2E;%gri32L{39bOnhq7|U!J~=wPuAEjO+H7 ziL(ZNB(Jd`ipv~xF!=hFJgCP|$UjG?`eJ~BG4ki(W`e&tftuHf*|zlPg;jYwckD2S z5{9ne@cVKBD@x>f|J2Nx0f|F;c?OpiykY%N`yVB0A_YL_`wwd9j^SdE8 zsFmkK@C!d++>+omjoGAu{R&zq2_YF&|K;k~i^bp(0N*hLK(}~VZo$prr0k|z3mp)B zpsGDB0ku*2z{Tso-mi#D-_ERq^$TLvhm|>TXiiS=z6w|ef>N-Ywe@jWFh`QNPL2`Vvkt}bf}A4L%!+|**teS0@cu19x^XiO{@8-|f3 z$yQ;Z8wc8`HI9=7^X-Xaieys<#e5CSfbE^`Ws`gUId;qosTK!`Cc8;1FC8H-EX;ss z?aYM>jar}HfVaZFyh=dgZEMU^PIZ-iq zIuWYiQ^{+NXup`@wYw_1MwPer_9 zkal^0%G_&QUMv@*hgj}*EcYeE865K#OgL4F9|C0okU9ruTSQoRlBP;cNx2sk^cO>E zH2L^sMhN+}sBz)5?;-I!px3NRksLyWF~xAsg2PPLC+HKvL$1}MKkuvna zDG19rB*(J2Dm%R_PsvD;1%GjK1!q@xH*=E;9n7RSv_&kp-#HqQFbpOEay0qsvV*22 zMbOVUw;b%p?9n;_8>hn(hf%8NXF|;1qw}$@iHP$xmG&}o9Cn+*??%54Z^8j6HJCgq zdVMqC9_W2+##_gZ5<=O^N?ggs#hjS<&K8vIJv$D#KpTrrl$4YN%RU3TLbd1PGt=it z_DC*kJi^u!fkJT#aT8n1+v3a$daaGqA4A_g!? zAY^eQh}z_lgqQ^q)`ugDmUfJpBaM zgFbi%PE@$s5XUX>MwM(v~zgeXdzY!`EEA#dKRsZk->?T12!iHu{{`u|L@z7kH?R?jdkZ7=%T zZ2)#IG&Ipyi@LTTgZ}^$+W90Y@9*EgqbNDPaePu_aQygjVuA+slWo~DhoaL2A02w& z$dO4ffno0K?mE1XQi;)Ka_zlgGY4H(ueL)x|koEr!(ZXvf z;K@(L9Y%Jv9PcJvpup}G040LABzis z4Io~?lVXr%a+BFTh}Lh=CSfeC`qI}+$3eJYA$M-yraYNf=(IhZF7cN<`W-BIw7Tem6{prfL3yFT!(xAE)7#71#uaEc(ho+mOM81yL-4iJR1o$iTVqcJ<5558o0 zvKe~b-C8Nj`Y>>?LbJM^Nc=U=H*VRIoO~NBx;a`CVNL+4WW&nWTLK3=N|y#ROWT3L zbG8qpL^Z($gIRTde-^qz04c@-f&w8`9{Hn+L0Lj#Ecda8y+tT*IyriesgpL#%*Eo_9yQe$@a{xN_g!( z4+J*|Ll~6kW2bDS`#L+tF)FNQ>XU;fWc<`xDuK2?rzSumgC9EYx1N!P&KAbi6@ldW zKkru`0G>Lp1t%T4dBA(V;UlIXG`-NK!JBFqG6Vg`5g^FMrXR|X@q z0-$;OQ+_6d9fILD=N0aEN1vS#SYb#U$)@CiMA-J2nFGzoMr<&{5fydO$Vi~pnG|>Sm|EB3wSlQBEMX^gMhx4;4GS6l4)~m2UyRk^Kk<%n?IpFolJAfNbd$ zw85C_?E^cE6gv5A2Ss=shK<_C(a}ED;RAY6ouF*BbK*w&GS`Y|*waulq1~+Q%U=Oj zhH)stR~h|T-<{3+yo84y!;(Pv#0aE?xoxj6=pcGPN!3~svy>JYm&?SEUxGtbO-)gA z?LtfO9%vE%7!Qg$`~tuTuS{@f&eX_y(vgnwFb5eE8ue zWUzl4f`Wo%#`F>U7Fu<+h?G=gtJt$q@Ofa|fhUGFhHWP;Q1j(3MI*rFhFw)|Pu`dUZ-3v_n(h*GkIRBYtH*P z+CtRhM8g3xjuKNGhiK*h%z&<7rSd(*zJ0Tmh+u7Jq($j|%&xg~#B|6Tm$a$3#e5Kc2 z?5J2J2EQ;W^6_IYSQ7$2PHoX%puCNg^L%4Sc4VAU(&0y^1aVYHG9kGp5S(}_$|cd* zn{=IC=)x7!UQRJJ9o}+APo$P&-_wfKqO5wC%W zGW5}L^&pe4V{TE@7-RIM_7BfCQRs>x!HS|*#A`eFQz;ba(H|VAT`9Dg%%wT8(6y!B zf91+J@47QmcV=b12;1_9AcEkKa@E2p&Ig^JeU({bD5PCAFGEYfS5ux97 zVJ&hSPTA|%oh?u&9fr4UWQ*@*D)WNX!S_;sW(App0r>Ecn^Z}39ev1n=e z(q%yt>D;24a*3st!T%}lhyYL3O@~pZuz|pRJ2D>{)OZFNp+%;-zjXZh`?2EeEm-86)`1%!$KxS%c zYRs}4DCQDLQb)Fknao44EhkGWAi$Y2$SVKP#f`|Nlx#){hBUomI>UM{#c zA`w$HxUfZ_yjcTPmAfZYcKKy!%sffjyc0v z&py6e0`>KeQu-h~!yG^Y(`0TQ7bD5V(3^)JIAy55n_u&M$y^mG=Frc)(cjX{Gw(nM zP=ks5XY*alf3|b?<_IC?Ji$u9v}h923kR42F;&QFs&eHsv<9@@ZKc=>DSRmI*9>~o z0q1QL9pZI00QIi~`9Gr6(QYld*VVB{$iVbl^B)iAhoWX%t*N$Zu)X-5;e8Aceby0O z^Rc1=#$*uP78oOmq3XdS@)uE`i34gMG8duB!smk06?}cyRnUh>z~HnUslhB-W!PfA zoYRQ8t1wcN52&RRlY?e^b6ElO;G6#o=^lmPyB5j8gT}ywC(K7oJGz#VI}F7%6Yi<&?bC51XOP`d>4`yD!9=pI zCXUl~?Ph^=!dBH~++=akSQf#Cy$wS3FfMR=T{U~f;JeLt!xe^FjuKIghgk%xvF zp9ZYjunlKMc27-jBH%cG6EoP`Eh>D#7ijw;te@)^YaeR za{-9)BUkS)Al9ubOZ628C0}y_6erlfMzGD5?=fkDaocOf z)$Zk;Fq`>!dxP$H0K$r}8=>9q2ZS|my2A9=sC9;$1r5T$e!`rCBZ6E9kOIYFW94~a zaW~jKAb6T0Qw=X@x)4B9JZ`7c5h3pe4^co-KL^l4Jg)s5I>#>dJ08X z%UxOR3&MUBw3&r%vi%BQfcUeR8V31+>)H%}Hu+4P0IOT^E+odG2dC_#18?6hLQOz= ztJk_T0%Opxia^fj#1iNSaJEQ{4=5v97Qrlsj)=srvOz4R>=ot+d@;2Ng1N!M6Gr<7 zEvMYv-K{xt9;XQ918oNf*P{LJ`Ff%SBoD%?*#}9G_79N*+n{y6(0(f@d-N#r zQUTEyXtSh;?3M%VQ1|H6^Zt5LDy)zGH}Op$Xj=R@Syvz4^~_91pqcUzRn|2(_v%Ja z;DP`O=bO-P*a0Urrbk*R?yRh=T1hH=Fn$U0pF!9m%KL_g({VKf1Ox$73E?B8*BI~m z!dp5({OhZ#2FEN@Syh)VPTT_G^)PR39yeT=NTlSH+gRJb=c4o>WBtV_0kx8S9Pke& z)bdFP_jsLu&y3yiQ~WD5`n55@mOzXYeZDkPYgvOO{Z7pnSXw?gFwC~FPQsg zBR0UAJKt&zDhxtPiTxoHZy>3C2yLyLTtTM~-gdMbX+lcd*1A;G1v; zNN>%<2gIfAkck16X#uM=OP^t2L?!YF5aDPmfs)D8DS#t%ElI~3rd3>l zLwD@Du+v8zM_}zFUR6TcEq3UT22)pEeckAjJNLHBm?4co8he>iY5-vqaosvLRWAK4Ar1D<(gIzrO?_-Uur1V0b97G$?f>4-cRthf#8k_7AkH|k5lrjsX5l%e-%r|O81q*&qeh7dej zA9qWz8cEO(q9U%lARF^O5(lOqO+llg`$j_!xFioF0@?HTG6!Ac8awVj%HMmM*o}Jp z9Ptt3)m2GsDz z1`ECHg4b`}P)QQX5GK)LGlxcr`GYItpfXd{CZBy#zwr}w{pnsclfr9f)mCZkFU5#` zXcN+5Ps77Gu;MYGv|<6>9sT}ZJa_-`g%IBV-e`ojE#lTdP0*YVwl)cT_wnN<=?Ua1 zX}A;d;tR3oLqV26{)k&#aku&jfvqR%!yCtk@?ml>M093oh%j4?=C~62y zWc69(=1Ki_xUVp|wBy3v%O%9{Ef|`Bz`(&V;_XdEC_fO5(x<1(+au7TQQMJ^2eUN1 z@j5IJ;B3i4ilnaHn`u!1d0?;R2Thlb68+|ezM-Lc*t^$mJ#sfYA}SNFn)CAXOnH?L zaVWFhFHUnD%`(Qrnk+W<;cYYn1D>$JBosgKfSQlXh?t$&2wP+9;^bZ^1$@LSUvjTL$LmMl zd8sJAq6XucqZ)Q=!nFYu2I~VHIIw(9roDoTVsi4DynHE|QF>FTW&NYE2!(~a!NJBD zJ_o}m$Z6M6vJi2|;*^2{lh-N(s*-JvFW&wzQaE@}(i@;z$6n^xv8Hz0gW7vZz>vxZDOb0U&v) zf{KpCOkK=ZH7~f(rPCrSu)qkh1JtU#oE)OEfjo~{_5c)5R!XlqF-%x8o77<24NZ&~ z2&r{G`93(s)CL{ed%#fzh1$t9VX73Sq=$!wFs5StVvFuGfLP2(Qc7303#_%k(evuK zxiKL(LGj6`9H(g#ucOG>qruN});=lKG{c~D_;Yi!C%n-+l^*<`_Rjt*>MD=pg9z#h zSjR1gr@`3O6(5H-8`y2oN>R!ckxYn%Y-wE=dCEf~1Hk|Z0J-CSuI#| zxbRku{~tMi!BcFDu;)+Ue9Gq!F~fKgIT<%^z<2QnX0>mrX>(9e zI1DR5P({i6DJqp{%_w4vb9mAT@HA8XM-$@HE@uc~th9-W1YN$b-{49?j_s6h9dW2e z%a_^_nS9u5T~xv0zcCQ8NhcV16;fkw;*vcB+uJ#@u|AH1N6HfC1+(VBC9J(b$7|wY0?iwxXrg1UhtRg z&j!s+~d8S37 zs5d+XVtj|SBbH0k<$lHq{EOn~s)*OLMTLd5cdr}@%VZ(F;V`PeUXf;7E9Df{hAB%0 zIC-AHr)C+7fRMh~JKDKy_jeke(blN2OahkVFO0qR<@Tn+q3oa9iktJET#)$rnlM>H z!VnwwD>j2_p#yFZ0DG(g|SGdrIv@rJFq|avFCR8tpCilJj=YO1u&Izo1`1Me}*ShjKGw+hn zVZnF{U6`OiKam4k<0cAOo6A*4V51r=a2*MtVz7u#q2C?nya}h6v~I`jIZlfaW-wm_ zrh1(&D(Z$K^1?$y^d9aOr?NA^jzYanUf-Zk%5p!na1l{q`Z#aD8O;_Ha0mn7hz9Bk+mu_caf=vS)H z4y6I}BTQDkQV2vTdK(6ewQ;7`g6$O9=Ei@m@bO_yEnt@-5~Ab|a8Eht#=YC??RQ2- zexcQ-V+4uTsxMIaG9KUTmU$^nwLuG&4(+b`%!uTv@ds#PTCZH#?5>_P0<6P#C)O%& z&C2g-T*WnWC@)XV@cNvZ55-FigLxBE(x`NO6F3vAswxgdNIdYytE~H(K*Bgbq8@`im*t=|fyC;7S z-Lc~=VX04f;GaTU)wB?z9PWz9J(4}<@&aA$Zc(7IB5J{wgYEWwM4Q1y28|uCf1iks zZU0pD1{Q+|Hb2c23L3|I_#7}X1a01|^WO%>FeAW1=Rw%lMVbAc;UCBbfJ)bBJd}>* z1?Kf%l&&g7TMC_eGR{ruAW6PeNzZcD^U}Lr!?}FG2d1oWKo2G{gx}>j&_(}sthjhg zT8D`v10v{cI$6lnj%dmFK@b;)#Xmq26=WjjF)tt~4g*>Y=S9;$zi|k2lt!6aYg!so zG~i=a%ypT{#5qDmpoDBk&iJU*O!!>yXmK5srD8h^#LdIDl8($8l^EJYI8^MvpDnlM zSfBArjEs48V_ZcV&=g+_C2V;DEE^huI|rYdTAgLzL85aN*5acl30H#ZqKxY}k(*2A}>j7C#9;7DAitP~9h+PVZSz6*Kl)3xS%F*(StPUqq zwg&c$!o+Znc|dvzUX%;F@$RM<`B>4U;0W5V;eCt}J!vD~Do|ebeHHKQq0gL7{=9;X z&u+Qg+-&uc$TbWV-yKy9fi`%o?WA|8`yB#1ys_N#ZOG+C+%_8)!Ax*L+wc<}bwUk= z|5IH`i;uUUSb~<~Ly8!36i~<@C1y@V_!9*jCcElC}f+>&!j2Gh{nMwlicq nLuT71_`li&m+sf~P1}2Q1m7@j`#710BMI8Lbwl|}yR!ZZ;mm^N 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 d9f3538c4b3db7d16fb8008e12517214d0d52d49..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 50108 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

#1|G1 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 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 0000000000000000000000000000000000000000..e0b4a9735a3295c7e56d97669cd34208fba9a977 GIT binary patch literal 10948 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 i*C_)nSRki!zL% - + 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 3f694351..13224afb 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"> - + + + +