From 155f0cc3478c2cd5ad0f9a8bae6e306606440120 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Tue, 28 Apr 2020 20:56:55 +0000 Subject: [PATCH 01/85] Bump threetenbp from 1.4.3 to 1.4.4 (#784) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 45d20f96d..60ce9da3d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -200,7 +200,7 @@ dependencies { testImplementation "junit:junit:4.13" testImplementation "io.mockk:mockk:$mockk" - testImplementation "org.threeten:threetenbp:1.4.3" + testImplementation "org.threeten:threetenbp:1.4.4" testImplementation "org.mockito:mockito-inline:3.3.3" androidTestImplementation "androidx.test:core:1.2.0" From 87af3da1ad9143a18d32c8c1dffaa59da8616f08 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Tue, 28 Apr 2020 20:57:14 +0000 Subject: [PATCH 02/85] Bump threetenabp from 1.2.3 to 1.2.4 (#783) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 60ce9da3d..d3967cec9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -178,7 +178,7 @@ dependencies { implementation "io.reactivex.rxjava2:rxjava:2.2.19" implementation "com.google.code.gson:gson:2.8.6" - implementation "com.jakewharton.threetenabp:threetenabp:1.2.3" + implementation "com.jakewharton.threetenabp:threetenabp:1.2.4" implementation "com.jakewharton.timber:timber:4.7.1" implementation "at.favre.lib:slf4j-timber:1.0.1" implementation "fr.bipi.treessence:treessence:0.3.2" From 6fe62edd637fedb08fce9b87347729041667f5c2 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Tue, 28 Apr 2020 21:04:54 +0000 Subject: [PATCH 03/85] Bump firebase-inappmessaging-display-ktx from 19.0.5 to 19.0.6 (#786) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index d3967cec9..bc9df9482 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -187,7 +187,7 @@ dependencies { implementation "io.coil-kt:coil:0.9.5" playImplementation 'com.google.firebase:firebase-analytics:17.3.0' - playImplementation 'com.google.firebase:firebase-inappmessaging-display-ktx:19.0.5' + playImplementation 'com.google.firebase:firebase-inappmessaging-display-ktx:19.0.6' playImplementation "com.google.firebase:firebase-inappmessaging-ktx:19.0.5" playImplementation "com.google.firebase:firebase-messaging:20.1.0" playImplementation "com.crashlytics.sdk.android:crashlytics:2.10.1" From 5fba3d577546a02892462962788007b3f15f2e69 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Tue, 28 Apr 2020 21:25:27 +0000 Subject: [PATCH 04/85] Bump firebase-inappmessaging-ktx from 19.0.5 to 19.0.6 (#782) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index bc9df9482..733d8d63f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -188,7 +188,7 @@ dependencies { playImplementation 'com.google.firebase:firebase-analytics:17.3.0' playImplementation 'com.google.firebase:firebase-inappmessaging-display-ktx:19.0.6' - playImplementation "com.google.firebase:firebase-inappmessaging-ktx:19.0.5" + playImplementation "com.google.firebase:firebase-inappmessaging-ktx:19.0.6" playImplementation "com.google.firebase:firebase-messaging:20.1.0" playImplementation "com.crashlytics.sdk.android:crashlytics:2.10.1" playImplementation 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava' From c6c2b1c6a344d67150be8cf7164f4985a1b8c39d Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Wed, 29 Apr 2020 14:40:12 +0000 Subject: [PATCH 05/85] Bump coil from 0.9.5 to 0.10.1 (#785) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 733d8d63f..6c5d79362 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -184,7 +184,7 @@ dependencies { implementation "fr.bipi.treessence:treessence:0.3.2" implementation "com.mikepenz:aboutlibraries-core:$about_libraries" implementation 'com.wdullaer:materialdatetimepicker:4.2.3' - implementation "io.coil-kt:coil:0.9.5" + implementation "io.coil-kt:coil:0.10.1" playImplementation 'com.google.firebase:firebase-analytics:17.3.0' playImplementation 'com.google.firebase:firebase-inappmessaging-display-ktx:19.0.6' From 17ac3cfd52f0577a6a2f24f6c634b395d95d75f8 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Thu, 30 Apr 2020 22:40:38 +0000 Subject: [PATCH 06/85] Bump firebase-analytics from 17.3.0 to 17.4.0 (#787) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 6c5d79362..17cc6d958 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -186,7 +186,7 @@ dependencies { implementation 'com.wdullaer:materialdatetimepicker:4.2.3' implementation "io.coil-kt:coil:0.10.1" - playImplementation 'com.google.firebase:firebase-analytics:17.3.0' + playImplementation 'com.google.firebase:firebase-analytics:17.4.0' playImplementation 'com.google.firebase:firebase-inappmessaging-display-ktx:19.0.6' playImplementation "com.google.firebase:firebase-inappmessaging-ktx:19.0.6" playImplementation "com.google.firebase:firebase-messaging:20.1.0" From a1f864b35e85743d843ba2b20e06d2c18f8283e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Fri, 1 May 2020 12:54:01 +0200 Subject: [PATCH 07/85] Add importantForAutofill to login fields (#788) --- app/src/main/res/layout/fragment_login_advanced.xml | 2 ++ app/src/main/res/layout/fragment_login_form.xml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/app/src/main/res/layout/fragment_login_advanced.xml b/app/src/main/res/layout/fragment_login_advanced.xml index 55bc62ae3..65af0b543 100644 --- a/app/src/main/res/layout/fragment_login_advanced.xml +++ b/app/src/main/res/layout/fragment_login_advanced.xml @@ -118,6 +118,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:autofillHints="username|emailAddress" + android:importantForAutofill="yes" android:inputType="textEmailAddress" android:maxLines="1" tools:targetApi="o" /> @@ -151,6 +152,7 @@ android:autofillHints="password" android:imeActionLabel="@string/login_sign_in" android:imeOptions="actionDone" + android:importantForAutofill="yes" android:inputType="textPassword" android:maxLines="1" app:fontFamily="sans-serif" diff --git a/app/src/main/res/layout/fragment_login_form.xml b/app/src/main/res/layout/fragment_login_form.xml index ba261e095..e4a5fab78 100644 --- a/app/src/main/res/layout/fragment_login_form.xml +++ b/app/src/main/res/layout/fragment_login_form.xml @@ -134,6 +134,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:autofillHints="username|emailAddress" + android:importantForAutofill="yes" android:inputType="textEmailAddress" android:maxLines="1" tools:targetApi="o" /> @@ -167,6 +168,7 @@ android:autofillHints="password" android:imeActionLabel="@string/login_sign_in" android:imeOptions="actionDone" + android:importantForAutofill="yes" android:inputType="textPassword" android:maxLines="1" app:fontFamily="sans-serif" From 4a3b746d4894dc290bea7227aabec9d5f8b7a26f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Fri, 1 May 2020 17:38:19 +0200 Subject: [PATCH 08/85] Remove flexible adapter (#781) --- app/build.gradle | 11 +- .../java/io/github/wulkanowy/WulkanowyApp.kt | 3 - .../github/wulkanowy/data/pojos/AppCreator.kt | 3 - .../wulkanowy/data/pojos/Contributor.kt | 3 + .../appcreator/AppCreatorRepository.kt | 8 +- .../java/io/github/wulkanowy/di/AppModule.kt | 5 - .../ui/base/BaseExpandableAdapter.kt | 58 +++++ .../ui/base/WidgetConfigureAdapter.kt | 46 ++++ .../ui/modules/about/AboutAdapter.kt | 72 +++++++ .../ui/modules/about/AboutFragment.kt | 18 +- .../wulkanowy/ui/modules/about/AboutItem.kt | 56 ----- .../ui/modules/about/AboutPresenter.kt | 25 ++- .../ui/modules/about/AboutScrollableHeader.kt | 41 ---- .../wulkanowy/ui/modules/about/AboutView.kt | 2 +- .../about/contributor/ContributorAdapter.kt | 41 ++++ .../about/contributor/ContributorFragment.kt | 25 ++- .../about/contributor/ContributorItem.kt | 51 ----- .../about/contributor/ContributorPresenter.kt | 8 +- .../about/contributor/ContributorView.kt | 3 +- .../modules/about/license/LicenseAdapter.kt | 34 +++ .../modules/about/license/LicenseFragment.kt | 19 +- .../ui/modules/about/license/LicenseItem.kt | 44 ---- .../modules/about/license/LicensePresenter.kt | 10 +- .../ui/modules/about/license/LicenseView.kt | 2 +- .../ui/modules/account/AccountAdapter.kt | 46 ++++ .../ui/modules/account/AccountDialog.kt | 19 +- .../ui/modules/account/AccountItem.kt | 59 ------ .../ui/modules/account/AccountPresenter.kt | 40 ++-- .../ui/modules/account/AccountView.kt | 3 +- .../modules/attendance/AttendanceAdapter.kt | 74 ++++++- .../modules/attendance/AttendanceFragment.kt | 34 +-- .../ui/modules/attendance/AttendanceItem.kt | 97 --------- .../ui/modules/attendance/AttendanceModule.kt | 12 -- .../modules/attendance/AttendancePresenter.kt | 16 +- .../ui/modules/attendance/AttendanceView.kt | 2 +- .../summary/AttendanceSummaryAdapter.kt | 101 +++++++++ .../summary/AttendanceSummaryFragment.kt | 25 ++- .../summary/AttendanceSummaryItem.kt | 80 ------- .../summary/AttendanceSummaryPresenter.kt | 55 +---- .../AttendanceSummaryScrollableHeader.kt | 46 ---- .../summary/AttendanceSummaryView.kt | 5 +- .../wulkanowy/ui/modules/exam/ExamAdapter.kt | 65 ++++++ .../wulkanowy/ui/modules/exam/ExamFragment.kt | 31 +-- .../wulkanowy/ui/modules/exam/ExamHeader.kt | 52 ----- .../wulkanowy/ui/modules/exam/ExamItem.kt | 49 +---- .../ui/modules/exam/ExamPresenter.kt | 22 +- .../wulkanowy/ui/modules/exam/ExamView.kt | 2 +- .../grade/details/GradeDetailsAdapter.kt | 176 ++++++++++++++++ .../grade/details/GradeDetailsFragment.kt | 64 ++---- .../grade/details/GradeDetailsHeader.kt | 94 --------- .../GradeDetailsHeaderItemDecoration.kt | 38 ---- .../modules/grade/details/GradeDetailsItem.kt | 88 ++------ .../grade/details/GradeDetailsPresenter.kt | 112 ++++------ .../modules/grade/details/GradeDetailsView.kt | 23 +- .../grade/summary/GradeSummaryAdapter.kt | 85 ++++++++ .../grade/summary/GradeSummaryFragment.kt | 27 ++- .../modules/grade/summary/GradeSummaryItem.kt | 64 ------ .../grade/summary/GradeSummaryPresenter.kt | 39 +--- .../summary/GradeSummaryScrollableHeader.kt | 53 ----- .../modules/grade/summary/GradeSummaryView.kt | 3 +- .../ui/modules/homework/HomeworkAdapter.kt | 67 ++++++ .../ui/modules/homework/HomeworkFragment.kt | 31 +-- .../ui/modules/homework/HomeworkHeader.kt | 54 ----- .../ui/modules/homework/HomeworkItem.kt | 52 +---- .../ui/modules/homework/HomeworkPresenter.kt | 22 +- .../ui/modules/homework/HomeworkView.kt | 2 +- .../LoginStudentSelectAdapter.kt | 51 +++++ .../LoginStudentSelectFragment.kt | 18 +- .../studentselect/LoginStudentSelectItem.kt | 71 ------- .../LoginStudentSelectPresenter.kt | 22 +- .../studentselect/LoginStudentSelectView.kt | 3 +- .../LuckyNumberWidgetConfigureActivity.kt | 24 ++- .../LuckyNumberWidgetConfigureItem.kt | 60 ------ .../LuckyNumberWidgetConfigurePresenter.kt | 13 +- .../LuckyNumberWidgetConfigureView.kt | 3 +- .../wulkanowy/ui/modules/main/MainActivity.kt | 3 +- .../wulkanowy/ui/modules/main/MainModule.kt | 6 +- .../ui/modules/message/MessageItem.kt | 67 ------ .../modules/message/tab/MessageTabAdapter.kt | 53 +++++ .../modules/message/tab/MessageTabFragment.kt | 42 ++-- .../message/tab/MessageTabPresenter.kt | 20 +- .../ui/modules/message/tab/MessageTabView.kt | 10 +- .../mobiledevice/MobileDeviceAdapter.kt | 34 ++- .../mobiledevice/MobileDeviceFragment.kt | 84 ++++---- .../modules/mobiledevice/MobileDeviceItem.kt | 53 ----- .../mobiledevice/MobileDeviceModule.kt | 12 -- .../mobiledevice/MobileDevicePresenter.kt | 9 +- .../modules/mobiledevice/MobileDeviceView.kt | 12 +- .../wulkanowy/ui/modules/more/MoreAdapter.kt | 34 +++ .../wulkanowy/ui/modules/more/MoreFragment.kt | 18 +- .../wulkanowy/ui/modules/more/MoreItem.kt | 47 ----- .../ui/modules/more/MorePresenter.kt | 26 ++- .../wulkanowy/ui/modules/more/MoreView.kt | 2 +- .../wulkanowy/ui/modules/note/NoteAdapter.kt | 60 ++++++ .../wulkanowy/ui/modules/note/NoteFragment.kt | 41 ++-- .../wulkanowy/ui/modules/note/NoteItem.kt | 77 ------- .../ui/modules/note/NotePresenter.kt | 22 +- .../wulkanowy/ui/modules/note/NoteView.kt | 5 +- .../teacher/TeacherAdapter.kt | 40 ++++ .../teacher/TeacherFragment.kt | 33 ++- .../schoolandteachers/teacher/TeacherItem.kt | 59 ------ .../teacher/TeacherPresenter.kt | 1 - .../schoolandteachers/teacher/TeacherView.kt | 8 +- .../ui/modules/timetable/TimetableAdapter.kt | 199 ++++++++++++++++++ .../ui/modules/timetable/TimetableFragment.kt | 33 +-- .../ui/modules/timetable/TimetableItem.kt | 197 ----------------- .../modules/timetable/TimetablePresenter.kt | 23 +- .../ui/modules/timetable/TimetableView.kt | 2 +- .../completed/CompletedLessonItem.kt | 58 ----- .../completed/CompletedLessonsAdapter.kt | 45 ++++ .../completed/CompletedLessonsFragment.kt | 27 ++- .../completed/CompletedLessonsPresenter.kt | 15 +- .../completed/CompletedLessonsView.kt | 2 +- .../TimetableWidgetConfigureActivity.kt | 20 +- .../TimetableWidgetConfigureItem.kt | 59 ------ .../TimetableWidgetConfigurePresenter.kt | 17 +- .../TimetableWidgetConfigureView.kt | 3 +- .../ui/widgets/DividerItemDecoration.kt | 27 +++ .../wulkanowy/utils/ContextExtension.kt | 7 + .../utils/FlexibleAdapterExtension.kt | 11 - app/src/main/res/layout/dialog_account.xml | 3 +- app/src/main/res/layout/header_exam.xml | 3 +- .../main/res/layout/header_grade_details.xml | 154 +++++++------- app/src/main/res/layout/header_homework.xml | 3 +- app/src/main/res/layout/item_about.xml | 11 +- app/src/main/res/layout/item_account.xml | 28 +-- app/src/main/res/layout/item_attendance.xml | 12 +- .../res/layout/item_attendance_summary.xml | 25 +-- .../main/res/layout/item_completed_lesson.xml | 16 +- app/src/main/res/layout/item_contributor.xml | 25 ++- app/src/main/res/layout/item_exam.xml | 8 +- .../main/res/layout/item_grade_details.xml | 146 ++++++------- .../main/res/layout/item_grade_summary.xml | 12 +- app/src/main/res/layout/item_homework.xml | 2 +- app/src/main/res/layout/item_license.xml | 3 +- .../res/layout/item_login_student_select.xml | 23 +- app/src/main/res/layout/item_message.xml | 3 +- .../main/res/layout/item_mobile_device.xml | 7 +- app/src/main/res/layout/item_more.xml | 3 +- app/src/main/res/layout/item_note.xml | 2 +- app/src/main/res/layout/item_teacher.xml | 7 +- app/src/main/res/layout/item_timetable.xml | 63 +++--- .../main/res/layout/item_timetable_small.xml | 14 +- .../res/layout/scrollable_header_about.xml | 4 +- .../scrollable_header_attendance_summary.xml | 3 +- .../scrollable_header_grade_summary.xml | 6 +- .../LoginStudentSelectPresenterTest.kt | 4 +- 147 files changed, 2231 insertions(+), 2864 deletions(-) delete mode 100644 app/src/main/java/io/github/wulkanowy/data/pojos/AppCreator.kt create mode 100644 app/src/main/java/io/github/wulkanowy/data/pojos/Contributor.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/base/BaseExpandableAdapter.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/base/WidgetConfigureAdapter.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutAdapter.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutItem.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutScrollableHeader.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/about/contributor/ContributorAdapter.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/about/contributor/ContributorItem.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/about/license/LicenseAdapter.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/about/license/LicenseItem.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountAdapter.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountItem.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceItem.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceModule.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryAdapter.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryItem.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryScrollableHeader.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamAdapter.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamHeader.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsAdapter.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsHeader.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsHeaderItemDecoration.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryAdapter.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryItem.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryScrollableHeader.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkAdapter.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkHeader.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectAdapter.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectItem.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigureItem.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/message/MessageItem.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabAdapter.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDeviceItem.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDeviceModule.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreAdapter.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreItem.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteAdapter.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteItem.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherAdapter.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherItem.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableAdapter.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableItem.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonItem.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsAdapter.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigureItem.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/widgets/DividerItemDecoration.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/utils/FlexibleAdapterExtension.kt diff --git a/app/build.gradle b/app/build.gradle index 17cc6d958..7c311dfc2 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -80,6 +80,10 @@ android { } } + viewBinding { + enabled = true + } + lintOptions { disable 'HardwareIds' } @@ -118,7 +122,6 @@ ext { work_manager = "2.3.4" room = "2.2.5" dagger = "2.27" - // don't update https://github.com/ChuckerTeam/chucker/issues/242 chucker = "3.2.0" mockk = "1.9.2" } @@ -167,13 +170,11 @@ dependencies { implementation "com.squareup.inject:assisted-inject-annotations-dagger2:0.5.2" kapt "com.squareup.inject:assisted-inject-processor-dagger2:0.5.2" - implementation "eu.davidea:flexible-adapter:5.1.0" - implementation "eu.davidea:flexible-adapter-ui:1.0.0" implementation "com.aurelhubert:ahbottomnavigation:2.3.4" implementation "com.ncapdevi:frag-nav:3.3.0" - implementation "com.github.YarikSOffice:lingver:1.2.1" + implementation "com.github.YarikSOffice:lingver:1.2.2" - implementation "com.github.pwittchen:reactivenetwork-rx2:3.0.6" + implementation "com.github.pwittchen:reactivenetwork-rx2:3.0.8" implementation "io.reactivex.rxjava2:rxandroid:2.1.1" implementation "io.reactivex.rxjava2:rxjava:2.2.19" diff --git a/app/src/main/java/io/github/wulkanowy/WulkanowyApp.kt b/app/src/main/java/io/github/wulkanowy/WulkanowyApp.kt index 96ec7cb84..3fab98563 100644 --- a/app/src/main/java/io/github/wulkanowy/WulkanowyApp.kt +++ b/app/src/main/java/io/github/wulkanowy/WulkanowyApp.kt @@ -10,8 +10,6 @@ import com.jakewharton.threetenabp.AndroidThreeTen import com.yariksoffice.lingver.Lingver import dagger.android.AndroidInjector import dagger.android.support.DaggerApplication -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.utils.Log import fr.bipi.tressence.file.FileLoggerTree import io.github.wulkanowy.di.DaggerAppComponent import io.github.wulkanowy.services.sync.SyncWorkerFactory @@ -57,7 +55,6 @@ class WulkanowyApp : DaggerApplication(), Configuration.Provider { private fun initLogging() { if (appInfo.isDebug) { - FlexibleAdapter.enableLogs(Log.Level.DEBUG) Timber.plant(DebugLogTree()) Timber.plant(FileLoggerTree.Builder() .withFileName("wulkanowy.%g.log") diff --git a/app/src/main/java/io/github/wulkanowy/data/pojos/AppCreator.kt b/app/src/main/java/io/github/wulkanowy/data/pojos/AppCreator.kt deleted file mode 100644 index d67aa2a7f..000000000 --- a/app/src/main/java/io/github/wulkanowy/data/pojos/AppCreator.kt +++ /dev/null @@ -1,3 +0,0 @@ -package io.github.wulkanowy.data.pojos - -class AppCreator(val displayName: String, val githubUsername: String) diff --git a/app/src/main/java/io/github/wulkanowy/data/pojos/Contributor.kt b/app/src/main/java/io/github/wulkanowy/data/pojos/Contributor.kt new file mode 100644 index 000000000..e792bde46 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/pojos/Contributor.kt @@ -0,0 +1,3 @@ +package io.github.wulkanowy.data.pojos + +class Contributor(val displayName: String, val githubUsername: String) diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/appcreator/AppCreatorRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/appcreator/AppCreatorRepository.kt index fa752ed29..6a0b2d32e 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/appcreator/AppCreatorRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/appcreator/AppCreatorRepository.kt @@ -2,18 +2,18 @@ package io.github.wulkanowy.data.repositories.appcreator import android.content.res.AssetManager import com.google.gson.Gson -import io.github.wulkanowy.data.pojos.AppCreator +import io.github.wulkanowy.data.pojos.Contributor import io.reactivex.Single import javax.inject.Inject import javax.inject.Singleton @Singleton class AppCreatorRepository @Inject constructor(private val assets: AssetManager) { - fun getAppCreators(): Single> { - return Single.fromCallable> { + fun getAppCreators(): Single> { + return Single.fromCallable> { Gson().fromJson( assets.open("contributors.json").bufferedReader().use { it.readText() }, - Array::class.java + Array::class.java ).toList() } } 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 4f5683850..db5ff59b3 100644 --- a/app/src/main/java/io/github/wulkanowy/di/AppModule.kt +++ b/app/src/main/java/io/github/wulkanowy/di/AppModule.kt @@ -5,8 +5,6 @@ import android.content.Context import com.yariksoffice.lingver.Lingver import dagger.Module import dagger.Provides -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import io.github.wulkanowy.WulkanowyApp import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.SchedulersProvider @@ -23,9 +21,6 @@ internal class AppModule { @Provides fun provideSchedulersProvider() = SchedulersProvider() - @Provides - fun provideFlexibleAdapter() = FlexibleAdapter>(null, null, true) - @Singleton @Provides fun provideAppWidgetManager(context: Context): AppWidgetManager = AppWidgetManager.getInstance(context) diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BaseExpandableAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/base/BaseExpandableAdapter.kt new file mode 100644 index 000000000..eee4625c9 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/base/BaseExpandableAdapter.kt @@ -0,0 +1,58 @@ +package io.github.wulkanowy.ui.base + +import android.util.DisplayMetrics +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.LinearSmoothScroller +import androidx.recyclerview.widget.RecyclerView +import kotlin.math.max +import kotlin.math.min + +abstract class BaseExpandableAdapter : RecyclerView.Adapter() { + + companion object { + private const val MILLISECONDS_PER_INCH = 100f + private const val AUTO_SCROLL_DELAY = 150L + } + + private var recyclerView: RecyclerView? = null + + override fun onAttachedToRecyclerView(recyclerView: RecyclerView) { + super.onAttachedToRecyclerView(recyclerView) + this.recyclerView = recyclerView + } + + override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) { + super.onDetachedFromRecyclerView(recyclerView) + this.recyclerView = null + } + + // original: https://github.com/davideas/FlexibleAdapter/blob/5.1.0/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java#L4984-L5011 + protected fun scrollToHeaderWithSubItems(position: Int, subItemsCount: Int) { + val layoutManager = recyclerView!!.layoutManager as LinearLayoutManager + val firstVisibleItem = layoutManager.findFirstCompletelyVisibleItemPosition() + val lastVisibleItem = layoutManager.findLastCompletelyVisibleItemPosition() + val itemsToShow = position + subItemsCount - lastVisibleItem + if (itemsToShow > 0) { + val scrollMax: Int = position - firstVisibleItem + val scrollMin = max(0, position + subItemsCount - lastVisibleItem) + val scrollBy = min(scrollMax, scrollMin) + val scrollTo = firstVisibleItem + scrollBy + scrollToPosition(scrollTo) + } else if (position < firstVisibleItem) { + scrollToPosition(position) + } + } + + private fun scrollToPosition(position: Int) { + recyclerView?.run { + postDelayed({ + layoutManager?.startSmoothScroll(object : LinearSmoothScroller(context) { + override fun getVerticalSnapPreference() = SNAP_TO_START + override fun calculateSpeedPerPixel(displayMetrics: DisplayMetrics) = MILLISECONDS_PER_INCH / displayMetrics.densityDpi + }.apply { + targetPosition = position + }) + }, AUTO_SCROLL_DELAY) + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/WidgetConfigureAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/base/WidgetConfigureAdapter.kt new file mode 100644 index 000000000..cefe6ed75 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/base/WidgetConfigureAdapter.kt @@ -0,0 +1,46 @@ +package io.github.wulkanowy.ui.base + +import android.annotation.SuppressLint +import android.graphics.PorterDuff +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.databinding.ItemAccountBinding +import io.github.wulkanowy.utils.getThemeAttrColor +import javax.inject.Inject + +class WidgetConfigureAdapter @Inject constructor() : RecyclerView.Adapter() { + + var items = emptyList>() + + var onClickListener: (Student) -> Unit = {} + + override fun getItemCount() = items.size + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder( + ItemAccountBinding.inflate(LayoutInflater.from(parent.context), parent, false) + ) + + @SuppressLint("SetTextI18n") + override fun onBindViewHolder(holder: ItemViewHolder, position: Int) { + val (student, isCurrent) = items[position] + + with(holder.binding) { + accountItemName.text = "${student.studentName} ${student.className}" + accountItemSchool.text = student.schoolName + + with(accountItemImage) { + val colorImage = if (isCurrent) context.getThemeAttrColor(R.attr.colorPrimary) + else context.getThemeAttrColor(R.attr.colorOnSurface, 153) + + setColorFilter(colorImage, PorterDuff.Mode.SRC_IN) + } + + root.setOnClickListener { onClickListener(student) } + } + } + + class ItemViewHolder(val binding: ItemAccountBinding) : RecyclerView.ViewHolder(binding.root) +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutAdapter.kt new file mode 100644 index 000000000..35dec3b4f --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutAdapter.kt @@ -0,0 +1,72 @@ +package io.github.wulkanowy.ui.modules.about + +import android.graphics.drawable.Drawable +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.core.content.res.ResourcesCompat +import androidx.recyclerview.widget.RecyclerView +import io.github.wulkanowy.databinding.ItemAboutBinding +import io.github.wulkanowy.databinding.ScrollableHeaderAboutBinding +import javax.inject.Inject + +class AboutAdapter @Inject constructor() : RecyclerView.Adapter() { + + private enum class ViewType(val id: Int) { + ITEM_HEADER(1), + ITEM_ELEMENT(2) + } + + var items = emptyList>() + + var onClickListener: (name: String) -> Unit = {} + + override fun getItemCount() = items.size + 1 + + override fun getItemViewType(position: Int) = when (position) { + 0 -> ViewType.ITEM_HEADER.id + else -> ViewType.ITEM_ELEMENT.id + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + val inflater = LayoutInflater.from(parent.context) + + return when (viewType) { + ViewType.ITEM_HEADER.id -> HeaderViewHolder(ScrollableHeaderAboutBinding.inflate(inflater, parent, false)) + ViewType.ITEM_ELEMENT.id -> ItemViewHolder(ItemAboutBinding.inflate(inflater, parent, false)) + else -> throw IllegalStateException() + } + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + when (holder) { + is HeaderViewHolder -> bindHeaderViewHolder(holder.binding) + is ItemViewHolder -> bindItemViewHolder(holder.binding, position - 1) + } + } + + private fun bindHeaderViewHolder(binding: ScrollableHeaderAboutBinding) { + with(binding.aboutScrollableHeaderIcon) { + setImageDrawable(ResourcesCompat.getDrawableForDensity( + context.resources, context.applicationInfo.icon, 640, null) + ) + } + } + + private fun bindItemViewHolder(binding: ItemAboutBinding, position: Int) { + val (title, summary, image) = items[position] + + with(binding) { + aboutItemImage.setImageDrawable(image) + aboutItemTitle.text = title + aboutItemSummary.text = summary + + root.setOnClickListener { onClickListener(title) } + } + } + + private class HeaderViewHolder(val binding: ScrollableHeaderAboutBinding) : + RecyclerView.ViewHolder(binding.root) + + private class ItemViewHolder(val binding: ItemAboutBinding) : + RecyclerView.ViewHolder(binding.root) +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutFragment.kt index 5a32ac837..b2893c1e1 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutFragment.kt @@ -5,9 +5,7 @@ import android.os.Bundle import android.view.LayoutInflater 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 androidx.recyclerview.widget.LinearLayoutManager import io.github.wulkanowy.R import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.modules.about.contributor.ContributorFragment @@ -19,7 +17,6 @@ import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.getCompatDrawable import io.github.wulkanowy.utils.openEmailClient import io.github.wulkanowy.utils.openInternetBrowser -import io.github.wulkanowy.utils.setOnItemClickListener import kotlinx.android.synthetic.main.fragment_about.* import javax.inject.Inject @@ -29,7 +26,7 @@ class AboutFragment : BaseFragment(), AboutView, MainView.TitledView { lateinit var presenter: AboutPresenter @Inject - lateinit var aboutAdapter: FlexibleAdapter> + lateinit var aboutAdapter: AboutAdapter @Inject lateinit var appInfo: AppInfo @@ -90,19 +87,18 @@ class AboutFragment : BaseFragment(), AboutView, MainView.TitledView { } override fun initView() { - aboutAdapter.setOnItemClickListener(presenter::onItemSelected) + aboutAdapter.onClickListener = presenter::onItemSelected with(aboutRecycler) { - layoutManager = SmoothScrollLinearLayoutManager(context) + layoutManager = LinearLayoutManager(context) adapter = aboutAdapter } } - override fun updateData(header: AboutScrollableHeader, items: List) { + override fun updateData(data: List>) { with(aboutAdapter) { - removeAllScrollableHeaders() - addScrollableHeader(header) - updateDataSet(items) + items = data + notifyDataSetChanged() } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutItem.kt deleted file mode 100644 index 29f1cd8c8..000000000 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutItem.kt +++ /dev/null @@ -1,56 +0,0 @@ -package io.github.wulkanowy.ui.modules.about - -import android.graphics.drawable.Drawable -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.item_about.* - -class AboutItem( - val title: String, - private val summary: String, - private val image: Drawable? -) : AbstractFlexibleItem() { - - override fun getLayoutRes() = R.layout.item_about - - override fun createViewHolder(view: View, adapter: FlexibleAdapter>) = ViewHolder(view, adapter) - - override fun bindViewHolder(adapter: FlexibleAdapter>, holder: ViewHolder, position: Int, payloads: MutableList) { - with(holder) { - aboutItemImage.setImageDrawable(image) - aboutItemTitle.text = title - aboutItemSummary.text = summary - } - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as AboutItem - - if (title != other.title) return false - if (summary != other.summary) return false - if (image != other.image) return false - - return true - } - - override fun hashCode(): Int { - var result = title.hashCode() - result = 31 * result + summary.hashCode() - result = 31 * result + (image?.hashCode() ?: 0) - 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/modules/about/AboutPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutPresenter.kt index 7e740b32b..27237ea6f 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutPresenter.kt @@ -1,6 +1,5 @@ package io.github.wulkanowy.ui.modules.about -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler @@ -23,10 +22,9 @@ class AboutPresenter @Inject constructor( loadData() } - fun onItemSelected(item: AbstractFlexibleItem<*>) { - if (item !is AboutItem) return + fun onItemSelected(name: String) { view?.run { - when (item.title) { + when (name) { versionRes?.first -> { Timber.i("Opening log viewer") openLogViewer() @@ -73,15 +71,16 @@ class AboutPresenter @Inject constructor( private fun loadData() { view?.run { - updateData(AboutScrollableHeader(), listOfNotNull( - versionRes?.let { (title, summary, image) -> AboutItem(title, summary, image) }, - creatorsRes?.let { (title, summary, image) -> AboutItem(title, summary, image) }, - feedbackRes?.let { (title, summary, image) -> AboutItem(title, summary, image) }, - faqRes?.let { (title, summary, image) -> AboutItem(title, summary, image) }, - discordRes?.let { (title, summary, image) -> AboutItem(title, summary, image) }, - homepageRes?.let { (title, summary, image) -> AboutItem(title, summary, image) }, - licensesRes?.let { (title, summary, image) -> AboutItem(title, summary, image) }, - privacyRes?.let { (title, summary, image) -> AboutItem(title, summary, image) })) + updateData(listOfNotNull( + versionRes, + creatorsRes, + feedbackRes, + faqRes, + discordRes, + homepageRes, + licensesRes, + privacyRes + )) } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutScrollableHeader.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutScrollableHeader.kt deleted file mode 100644 index 07bb41249..000000000 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutScrollableHeader.kt +++ /dev/null @@ -1,41 +0,0 @@ -package io.github.wulkanowy.ui.modules.about - -import android.view.View -import androidx.core.content.res.ResourcesCompat -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_about.* - -class AboutScrollableHeader : AbstractFlexibleItem() { - - override fun getLayoutRes() = R.layout.scrollable_header_about - - override fun createViewHolder(view: View, adapter: FlexibleAdapter>) = ViewHolder(view, adapter) - - override fun bindViewHolder(adapter: FlexibleAdapter>, holder: ViewHolder, position: Int, payloads: MutableList) { - with(holder) { - val context = contentView.context - val drawable = ResourcesCompat.getDrawableForDensity(context.resources, context.applicationInfo.icon, 640, null) - - aboutScrollableHeaderIcon.setImageDrawable(drawable) - } - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - return true - } - - override fun hashCode() = javaClass.hashCode() - - 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/modules/about/AboutView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutView.kt index 4bc0c3fe0..79b700ea3 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutView.kt @@ -23,7 +23,7 @@ interface AboutView : BaseView { fun initView() - fun updateData(header: AboutScrollableHeader, items: List) + fun updateData(data: List>) fun openLogViewer() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/about/contributor/ContributorAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/about/contributor/ContributorAdapter.kt new file mode 100644 index 000000000..215cd27db --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/about/contributor/ContributorAdapter.kt @@ -0,0 +1,41 @@ +package io.github.wulkanowy.ui.modules.about.contributor + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import coil.api.load +import coil.transform.RoundedCornersTransformation +import io.github.wulkanowy.data.pojos.Contributor +import io.github.wulkanowy.databinding.ItemContributorBinding +import javax.inject.Inject + +class ContributorAdapter @Inject constructor() : + RecyclerView.Adapter() { + + var items = emptyList() + + var onClickListener: (Contributor) -> Unit = {} + + override fun getItemCount() = items.size + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder( + ItemContributorBinding.inflate(LayoutInflater.from(parent.context), parent, false) + ) + + override fun onBindViewHolder(holder: ItemViewHolder, position: Int) { + val item = items[position] + + with(holder.binding) { + creatorItemName.text = item.displayName + creatorItemAvatar.load("https://github.com/${item.githubUsername}.png") { + transformations(RoundedCornersTransformation(8f)) + crossfade(true) + } + + root.setOnClickListener { onClickListener(item) } + } + } + + class ItemViewHolder(val binding: ItemContributorBinding) : + RecyclerView.ViewHolder(binding.root) +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/about/contributor/ContributorFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/about/contributor/ContributorFragment.kt index c181c3d38..2544836cb 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/about/contributor/ContributorFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/about/contributor/ContributorFragment.kt @@ -6,15 +6,13 @@ import android.view.View import android.view.View.GONE import android.view.View.VISIBLE import android.view.ViewGroup -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.common.FlexibleItemDecoration -import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem +import androidx.recyclerview.widget.LinearLayoutManager import io.github.wulkanowy.R +import io.github.wulkanowy.data.pojos.Contributor import io.github.wulkanowy.ui.base.BaseFragment +import io.github.wulkanowy.ui.widgets.DividerItemDecoration import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.utils.openInternetBrowser -import io.github.wulkanowy.utils.setOnItemClickListener import kotlinx.android.synthetic.main.fragment_creator.* import javax.inject.Inject @@ -24,7 +22,7 @@ class ContributorFragment : BaseFragment(), ContributorView, MainView.TitledView lateinit var presenter: ContributorPresenter @Inject - lateinit var creatorsAdapter: FlexibleAdapter> + lateinit var creatorsAdapter: ContributorAdapter override val titleStringId get() = R.string.contributors_title @@ -43,18 +41,19 @@ class ContributorFragment : BaseFragment(), ContributorView, MainView.TitledView override fun initView() { with(creatorRecycler) { - layoutManager = SmoothScrollLinearLayoutManager(context) + layoutManager = LinearLayoutManager(context) adapter = creatorsAdapter - addItemDecoration(FlexibleItemDecoration(context) - .withDefaultDivider() - .withDrawDividerOnLastItem(false)) + addItemDecoration(DividerItemDecoration(context)) } - creatorsAdapter.setOnItemClickListener(presenter::onItemSelected) + creatorsAdapter.onClickListener = presenter::onItemSelected creatorSeeMore.setOnClickListener { presenter.onSeeMoreClick() } } - override fun updateData(data: List) { - creatorsAdapter.updateDataSet(data) + override fun updateData(data: List) { + with(creatorsAdapter) { + items = data + notifyDataSetChanged() + } } override fun openUserGithubPage(username: String) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/about/contributor/ContributorItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/about/contributor/ContributorItem.kt deleted file mode 100644 index 844b5bd8d..000000000 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/about/contributor/ContributorItem.kt +++ /dev/null @@ -1,51 +0,0 @@ -package io.github.wulkanowy.ui.modules.about.contributor - -import android.view.View -import coil.api.load -import coil.transform.RoundedCornersTransformation -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.pojos.AppCreator -import kotlinx.android.extensions.LayoutContainer -import kotlinx.android.synthetic.main.item_contributor.* - -class ContributorItem(val creator: AppCreator) : - AbstractFlexibleItem() { - - override fun getLayoutRes() = R.layout.item_contributor - - override fun createViewHolder(view: View, adapter: FlexibleAdapter>) = ViewHolder(view, adapter) - - override fun bindViewHolder(adapter: FlexibleAdapter>, holder: ViewHolder, position: Int, payloads: MutableList) { - with(holder) { - creatorItemName.text = creator.displayName - - creatorItemAvatar.load("https://github.com/${creator.githubUsername}.png") { - transformations(RoundedCornersTransformation(8f)) - crossfade(true) - } - } - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as ContributorItem - - if (creator != other.creator) return false - - return true - } - - override fun hashCode() = creator.hashCode() - - 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/modules/about/contributor/ContributorPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/about/contributor/ContributorPresenter.kt index 721b25007..416a59ce5 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/about/contributor/ContributorPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/about/contributor/ContributorPresenter.kt @@ -1,6 +1,6 @@ package io.github.wulkanowy.ui.modules.about.contributor -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem +import io.github.wulkanowy.data.pojos.Contributor import io.github.wulkanowy.data.repositories.appcreator.AppCreatorRepository import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter @@ -21,9 +21,8 @@ class ContributorPresenter @Inject constructor( loadData() } - fun onItemSelected(item: AbstractFlexibleItem<*>) { - if (item !is ContributorItem) return - view?.openUserGithubPage(item.creator.githubUsername) + fun onItemSelected(contributor: Contributor) { + view?.openUserGithubPage(contributor.githubUsername) } fun onSeeMoreClick() { @@ -32,7 +31,6 @@ class ContributorPresenter @Inject constructor( private fun loadData() { disposable.add(appCreatorRepository.getAppCreators() - .map { it.map { creator -> ContributorItem(creator) } } .subscribeOn(schedulers.backgroundThread) .observeOn(schedulers.mainThread) .doFinally { view?.showProgress(false) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/about/contributor/ContributorView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/about/contributor/ContributorView.kt index 18ec3a8ec..8007e4e3f 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/about/contributor/ContributorView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/about/contributor/ContributorView.kt @@ -1,12 +1,13 @@ package io.github.wulkanowy.ui.modules.about.contributor +import io.github.wulkanowy.data.pojos.Contributor import io.github.wulkanowy.ui.base.BaseView interface ContributorView : BaseView { fun initView() - fun updateData(data: List) + fun updateData(data: List) fun openUserGithubPage(username: String) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/about/license/LicenseAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/about/license/LicenseAdapter.kt new file mode 100644 index 000000000..07025c09f --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/about/license/LicenseAdapter.kt @@ -0,0 +1,34 @@ +package io.github.wulkanowy.ui.modules.about.license + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.mikepenz.aboutlibraries.entity.Library +import io.github.wulkanowy.databinding.ItemLicenseBinding +import javax.inject.Inject + +class LicenseAdapter @Inject constructor() : RecyclerView.Adapter() { + + var items = emptyList() + + var onClickListener: (Library) -> Unit = {} + + override fun getItemCount() = items.size + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder( + ItemLicenseBinding.inflate(LayoutInflater.from(parent.context), parent, false) + ) + + override fun onBindViewHolder(holder: ItemViewHolder, position: Int) { + val item = items[position] + + with(holder.binding) { + licenseItemName.text = item.libraryName + licenseItemSummary.text = item.license?.licenseName?.takeIf { it.isNotBlank() } ?: item.libraryVersion + + root.setOnClickListener { onClickListener(item) } + } + } + + class ItemViewHolder(val binding: ItemLicenseBinding) : RecyclerView.ViewHolder(binding.root) +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/about/license/LicenseFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/about/license/LicenseFragment.kt index 2681680b1..d64c6225c 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/about/license/LicenseFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/about/license/LicenseFragment.kt @@ -8,16 +8,13 @@ import android.view.View.VISIBLE import android.view.ViewGroup import androidx.appcompat.app.AlertDialog import androidx.core.text.parseAsHtml +import androidx.recyclerview.widget.LinearLayoutManager import com.mikepenz.aboutlibraries.Libs import com.mikepenz.aboutlibraries.entity.Library import dagger.Lazy -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.modules.main.MainView -import io.github.wulkanowy.utils.setOnItemClickListener import kotlinx.android.synthetic.main.fragment_license.* import javax.inject.Inject @@ -27,7 +24,7 @@ class LicenseFragment : BaseFragment(), LicenseView, MainView.TitledView { lateinit var presenter: LicensePresenter @Inject - lateinit var licenseAdapter: FlexibleAdapter> + lateinit var licenseAdapter: LicenseAdapter @Inject lateinit var libs: Lazy @@ -53,15 +50,19 @@ class LicenseFragment : BaseFragment(), LicenseView, MainView.TitledView { } override fun initView() { + licenseAdapter.onClickListener = presenter::onItemSelected + with(licenseRecycler) { - layoutManager = SmoothScrollLinearLayoutManager(context) + layoutManager = LinearLayoutManager(context) adapter = licenseAdapter } - licenseAdapter.setOnItemClickListener(presenter::onItemSelected) } - override fun updateData(data: List) { - licenseAdapter.updateDataSet(data) + override fun updateData(data: List) { + with(licenseAdapter) { + items = data + notifyDataSetChanged() + } } override fun openLicense(licenseHtml: String) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/about/license/LicenseItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/about/license/LicenseItem.kt deleted file mode 100644 index 8dcb89224..000000000 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/about/license/LicenseItem.kt +++ /dev/null @@ -1,44 +0,0 @@ -package io.github.wulkanowy.ui.modules.about.license - -import android.view.View -import com.mikepenz.aboutlibraries.entity.Library -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.item_license.* - -class LicenseItem(val library: Library) : AbstractFlexibleItem() { - - override fun getLayoutRes() = R.layout.item_license - - override fun createViewHolder(view: View, adapter: FlexibleAdapter>) = ViewHolder(view, adapter) - - override fun bindViewHolder(adapter: FlexibleAdapter>, holder: ViewHolder, position: Int, payloads: MutableList) { - with(holder) { - licenseItemName.text = library.libraryName - licenseItemSummary.text = library.license?.licenseName?.takeIf { it.isNotBlank() } ?: library.libraryVersion - } - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as LicenseItem - - if (library != other.library) return false - - return true - } - - override fun hashCode() = library.hashCode() - - 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/modules/about/license/LicensePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/about/license/LicensePresenter.kt index dc48b098b..d0f6d69e0 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/about/license/LicensePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/about/license/LicensePresenter.kt @@ -1,6 +1,6 @@ package io.github.wulkanowy.ui.modules.about.license -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem +import com.mikepenz.aboutlibraries.entity.Library import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler @@ -20,14 +20,12 @@ class LicensePresenter @Inject constructor( loadData() } - fun onItemSelected(item: AbstractFlexibleItem<*>) { - if (item !is LicenseItem) return - view?.run { item.library.license?.licenseDescription?.let { openLicense(it) } } + fun onItemSelected(library: Library) { + view?.run { library.license?.licenseDescription?.let { openLicense(it) } } } private fun loadData() { - disposable.add(Single.fromCallable { view?.appLibraries } - .map { it.map { library -> LicenseItem(library) } } + disposable.add(Single.fromCallable { view?.appLibraries.orEmpty() } .subscribeOn(schedulers.backgroundThread) .observeOn(schedulers.mainThread) .doOnEvent { _, _ -> view?.showProgress(false) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/about/license/LicenseView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/about/license/LicenseView.kt index 3939d3e80..0680dbb73 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/about/license/LicenseView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/about/license/LicenseView.kt @@ -9,7 +9,7 @@ interface LicenseView : BaseView { fun initView() - fun updateData(data: List) + fun updateData(data: List) fun openLicense(licenseHtml: String) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountAdapter.kt new file mode 100644 index 000000000..7df0ca378 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountAdapter.kt @@ -0,0 +1,46 @@ +package io.github.wulkanowy.ui.modules.account + +import android.annotation.SuppressLint +import android.graphics.PorterDuff +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.databinding.ItemAccountBinding +import io.github.wulkanowy.utils.getThemeAttrColor +import javax.inject.Inject + +class AccountAdapter @Inject constructor() : RecyclerView.Adapter() { + + var items = emptyList() + + var onClickListener: (Student) -> Unit = {} + + override fun getItemCount() = items.size + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder( + ItemAccountBinding.inflate(LayoutInflater.from(parent.context), parent, false) + ) + + @SuppressLint("SetTextI18n") + override fun onBindViewHolder(holder: ItemViewHolder, position: Int) { + val student = items[position] + + with(holder.binding) { + accountItemName.text = "${student.studentName} ${student.className}" + accountItemSchool.text = student.schoolName + + with(accountItemImage) { + val colorImage = if (student.isCurrent) context.getThemeAttrColor(R.attr.colorPrimary) + else context.getThemeAttrColor(R.attr.colorOnSurface, 153) + + setColorFilter(colorImage, PorterDuff.Mode.SRC_IN) + } + + root.setOnClickListener { onClickListener(student) } + } + } + + class ItemViewHolder(val binding: ItemAccountBinding) : RecyclerView.ViewHolder(binding.root) +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountDialog.kt index cfff31c98..dc8cce928 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountDialog.kt @@ -7,13 +7,11 @@ import android.view.ViewGroup import android.widget.Toast import android.widget.Toast.LENGTH_LONG import androidx.appcompat.app.AlertDialog -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem +import androidx.recyclerview.widget.LinearLayoutManager import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.ui.base.BaseDialogFragment import io.github.wulkanowy.ui.modules.login.LoginActivity -import io.github.wulkanowy.utils.setOnItemClickListener import kotlinx.android.synthetic.main.dialog_account.* import javax.inject.Inject @@ -23,7 +21,7 @@ class AccountDialog : BaseDialogFragment(), AccountView { lateinit var presenter: AccountPresenter @Inject - lateinit var accountAdapter: FlexibleAdapter> + lateinit var accountAdapter: AccountAdapter companion object { fun newInstance() = AccountDialog() @@ -44,18 +42,21 @@ class AccountDialog : BaseDialogFragment(), AccountView { } override fun initView() { - accountAdapter.setOnItemClickListener { presenter.onItemSelected(it) } + accountAdapter.onClickListener = presenter::onItemSelected accountDialogAdd.setOnClickListener { presenter.onAddSelected() } accountDialogRemove.setOnClickListener { presenter.onRemoveSelected() } accountDialogRecycler.apply { - layoutManager = SmoothScrollLinearLayoutManager(context) + layoutManager = LinearLayoutManager(context) adapter = accountAdapter } } - override fun updateData(data: List) { - accountAdapter.updateDataSet(data) + override fun updateData(data: List) { + with(accountAdapter) { + items = data + notifyDataSetChanged() + } } override fun showError(text: String, error: Throwable) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountItem.kt deleted file mode 100644 index d3a3ee6a3..000000000 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountItem.kt +++ /dev/null @@ -1,59 +0,0 @@ -package io.github.wulkanowy.ui.modules.account - -import android.annotation.SuppressLint -import android.graphics.PorterDuff -import android.view.View -import androidx.core.graphics.ColorUtils -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.Student -import io.github.wulkanowy.utils.getThemeAttrColor -import kotlinx.android.extensions.LayoutContainer -import kotlinx.android.synthetic.main.item_account.* - -class AccountItem(val student: Student) : AbstractFlexibleItem() { - - override fun getLayoutRes() = R.layout.item_account - - override fun createViewHolder(view: View, adapter: FlexibleAdapter>) = ViewHolder(view, adapter) - - @SuppressLint("SetTextI18n") - override fun bindViewHolder(adapter: FlexibleAdapter>, holder: ViewHolder, position: Int, payloads: MutableList?) { - val context = holder.contentView.context - - val colorImage = if (student.isCurrent) context.getThemeAttrColor(R.attr.colorPrimary) - else ColorUtils.setAlphaComponent(context.getThemeAttrColor(R.attr.colorOnSurface), 153) - - with(holder) { - accountItemName.text = "${student.studentName} ${student.className}" - accountItemSchool.text = student.schoolName - accountItemImage.setColorFilter(colorImage, PorterDuff.Mode.SRC_IN) - } - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as AccountItem - - if (student != other.student) return false - - return true - } - - override fun hashCode(): Int { - var result = student.hashCode() - result = 31 * result + student.id.toInt() - 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/modules/account/AccountPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountPresenter.kt index e9b4b81ee..3416a043f 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountPresenter.kt @@ -1,6 +1,6 @@ package io.github.wulkanowy.ui.modules.account -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem +import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.services.sync.SyncManager import io.github.wulkanowy.ui.base.BasePresenter @@ -63,32 +63,29 @@ class AccountPresenter @Inject constructor( })) } - fun onItemSelected(item: AbstractFlexibleItem<*>) { - if (item is AccountItem) { - Timber.i("Select student item ${item.student.id}") - if (item.student.isCurrent) { - view?.dismissView() - } else { - Timber.i("Attempt to change a student") - disposable.add(studentRepository.switchStudent(item.student) - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .doFinally { view?.dismissView() } - .subscribe({ - Timber.i("Change a student result: Success") - view?.recreateMainView() - }, { - Timber.i("Change a student result: An exception occurred") - errorHandler.dispatch(it) - })) - } + fun onItemSelected(student: Student) { + Timber.i("Select student item ${student.id}") + if (student.isCurrent) { + view?.dismissView() + } else { + Timber.i("Attempt to change a student") + disposable.add(studentRepository.switchStudent(student) + .subscribeOn(schedulers.backgroundThread) + .observeOn(schedulers.mainThread) + .doFinally { view?.dismissView() } + .subscribe({ + Timber.i("Change a student result: Success") + view?.recreateMainView() + }, { + Timber.i("Change a student result: An exception occurred") + errorHandler.dispatch(it) + })) } } private fun loadData() { Timber.i("Loading account data started") disposable.add(studentRepository.getSavedStudents(false) - .map { it.map { item -> AccountItem(item) } } .subscribeOn(schedulers.backgroundThread) .observeOn(schedulers.mainThread) .subscribe({ @@ -100,4 +97,3 @@ class AccountPresenter @Inject constructor( })) } } - diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountView.kt index ede5023ba..abb9e1d27 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountView.kt @@ -1,12 +1,13 @@ package io.github.wulkanowy.ui.modules.account +import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.ui.base.BaseView interface AccountView : BaseView { fun initView() - fun updateData(data: List) + fun updateData(data: List) fun dismissView() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceAdapter.kt index 75f998404..a63d5045a 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceAdapter.kt @@ -1,12 +1,80 @@ package io.github.wulkanowy.ui.modules.attendance -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.items.IFlexible +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.view.isVisible +import androidx.recyclerview.widget.RecyclerView +import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Attendance +import io.github.wulkanowy.data.repositories.attendance.SentExcuseStatus +import io.github.wulkanowy.databinding.ItemAttendanceBinding +import javax.inject.Inject -class AttendanceAdapter> : FlexibleAdapter(null, null, true) { +class AttendanceAdapter @Inject constructor() : + RecyclerView.Adapter() { + + var items = emptyList() var excuseActionMode: Boolean = false + var onClickListener: (Attendance) -> Unit = {} + var onExcuseCheckboxSelect: (attendanceItem: Attendance, checked: Boolean) -> Unit = { _, _ -> } + + override fun getItemCount() = items.size + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder( + ItemAttendanceBinding.inflate(LayoutInflater.from(parent.context), parent, false) + ) + + override fun onBindViewHolder(holder: ItemViewHolder, position: Int) { + val item = items[position] + + with(holder.binding) { + attendanceItemNumber.text = item.number.toString() + attendanceItemSubject.text = item.subject + attendanceItemDescription.text = item.name + attendanceItemAlert.visibility = item.run { if (absence && !excused) View.VISIBLE else View.INVISIBLE } + attendanceItemNumber.visibility = View.GONE + attendanceItemExcuseInfo.visibility = View.GONE + attendanceItemExcuseCheckbox.visibility = View.GONE + attendanceItemExcuseCheckbox.isChecked = false + attendanceItemExcuseCheckbox.setOnCheckedChangeListener { _, checked -> + onExcuseCheckboxSelect(item, checked) + } + + when (if (item.excuseStatus != null) SentExcuseStatus.valueOf(item.excuseStatus) else null) { + SentExcuseStatus.WAITING -> { + attendanceItemExcuseInfo.setImageResource(R.drawable.ic_excuse_waiting) + attendanceItemExcuseInfo.visibility = View.VISIBLE + attendanceItemAlert.visibility = View.INVISIBLE + } + SentExcuseStatus.DENIED -> { + attendanceItemExcuseInfo.setImageResource(R.drawable.ic_excuse_denied) + attendanceItemExcuseInfo.visibility = View.VISIBLE + } + else -> { + if (item.excusable && excuseActionMode) { + attendanceItemNumber.visibility = View.GONE + attendanceItemExcuseCheckbox.visibility = View.VISIBLE + } else { + attendanceItemNumber.visibility = View.VISIBLE + attendanceItemExcuseCheckbox.visibility = View.GONE + } + } + } + root.setOnClickListener { + onClickListener(item) + + with(attendanceItemExcuseCheckbox) { + if (excuseActionMode && isVisible) { + isChecked = !isChecked + } + } + } + } + } + + class ItemViewHolder(val binding: ItemAttendanceBinding) : RecyclerView.ViewHolder(binding.root) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceFragment.kt index 9969b1c78..31b0a3c29 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceFragment.kt @@ -13,19 +13,17 @@ import android.view.View.VISIBLE import android.view.ViewGroup import androidx.appcompat.app.AlertDialog import androidx.appcompat.view.ActionMode +import androidx.recyclerview.widget.LinearLayoutManager import com.wdullaer.materialdatetimepicker.date.DatePickerDialog -import eu.davidea.flexibleadapter.common.FlexibleItemDecoration -import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager -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.ui.widgets.DividerItemDecoration import io.github.wulkanowy.ui.modules.attendance.summary.AttendanceSummaryFragment import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.utils.SchooldaysRangeLimiter import io.github.wulkanowy.utils.dpToPx -import io.github.wulkanowy.utils.setOnItemClickListener import kotlinx.android.synthetic.main.dialog_excuse.* import kotlinx.android.synthetic.main.fragment_attendance.* import org.threeten.bp.LocalDate @@ -38,7 +36,7 @@ class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MainChildVie lateinit var presenter: AttendancePresenter @Inject - lateinit var attendanceAdapter: AttendanceAdapter> + lateinit var attendanceAdapter: AttendanceAdapter override val excuseSuccessString: String get() = getString(R.string.attendance_excuse_success) @@ -54,7 +52,7 @@ class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MainChildVie override val titleStringId get() = R.string.attendance_title - override val isViewEmpty get() = attendanceAdapter.isEmpty + override val isViewEmpty get() = attendanceAdapter.items.isEmpty() override val currentStackSize get() = (activity as? MainActivity)?.currentStackSize @@ -102,15 +100,15 @@ class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MainChildVie } override fun initView() { - attendanceAdapter.setOnItemClickListener(presenter::onAttendanceItemSelected) - attendanceAdapter.onExcuseCheckboxSelect = presenter::onExcuseCheckboxSelect + with(attendanceAdapter) { + onClickListener = presenter::onAttendanceItemSelected + onExcuseCheckboxSelect = presenter::onExcuseCheckboxSelect + } with(attendanceRecycler) { - layoutManager = SmoothScrollLinearLayoutManager(context) + layoutManager = LinearLayoutManager(context) adapter = attendanceAdapter - addItemDecoration(FlexibleItemDecoration(context) - .withDefaultDivider() - .withDrawDividerOnLastItem(false)) + addItemDecoration(DividerItemDecoration(context)) } attendanceSwipe.setOnRefreshListener(presenter::onSwipeRefresh) @@ -135,8 +133,11 @@ class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MainChildVie else false } - override fun updateData(data: List) { - attendanceAdapter.updateDataSet(data, true) + override fun updateData(data: List) { + with(attendanceAdapter) { + items = data + notifyDataSetChanged() + } } override fun updateNavigationDay(date: String) { @@ -144,7 +145,10 @@ class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MainChildVie } override fun clearData() { - attendanceAdapter.clear() + with(attendanceAdapter) { + items = emptyList() + notifyDataSetChanged() + } } override fun resetView() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceItem.kt deleted file mode 100644 index 7355aec2e..000000000 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceItem.kt +++ /dev/null @@ -1,97 +0,0 @@ -package io.github.wulkanowy.ui.modules.attendance - -import android.view.View -import android.view.View.GONE -import android.view.View.INVISIBLE -import android.view.View.VISIBLE -import androidx.core.view.isVisible -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.Attendance -import io.github.wulkanowy.data.repositories.attendance.SentExcuseStatus -import kotlinx.android.extensions.LayoutContainer -import kotlinx.android.synthetic.main.item_attendance.* - -class AttendanceItem(val attendance: Attendance) : - AbstractFlexibleItem() { - - override fun getLayoutRes() = R.layout.item_attendance - - 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 { - attendanceItemNumber.text = attendance.number.toString() - attendanceItemSubject.text = attendance.subject - attendanceItemDescription.text = attendance.name - attendanceItemAlert.visibility = attendance.run { if (absence && !excused) VISIBLE else INVISIBLE } - attendanceItemNumber.visibility = GONE - attendanceItemExcuseInfo.visibility = GONE - attendanceItemExcuseCheckbox.visibility = GONE - attendanceItemExcuseCheckbox.isChecked = false - attendanceItemExcuseCheckbox.setOnCheckedChangeListener { _, checked -> - (adapter as AttendanceAdapter).onExcuseCheckboxSelect(attendance, checked) - } - - when (if (attendance.excuseStatus != null) SentExcuseStatus.valueOf(attendance.excuseStatus) else null) { - SentExcuseStatus.WAITING -> { - attendanceItemExcuseInfo.setImageResource(R.drawable.ic_excuse_waiting) - attendanceItemExcuseInfo.visibility = VISIBLE - attendanceItemAlert.visibility = INVISIBLE - } - SentExcuseStatus.DENIED -> { - attendanceItemExcuseInfo.setImageResource(R.drawable.ic_excuse_denied) - attendanceItemExcuseInfo.visibility = VISIBLE - } - else -> { - if (attendance.excusable && (adapter as AttendanceAdapter).excuseActionMode) { - attendanceItemNumber.visibility = GONE - attendanceItemExcuseCheckbox.visibility = VISIBLE - } else { - attendanceItemNumber.visibility = VISIBLE - attendanceItemExcuseCheckbox.visibility = GONE - } - } - } - } - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as AttendanceItem - - if (attendance != other.attendance) return false - - return true - } - - override fun hashCode(): Int { - var result = attendance.hashCode() - result = 31 * result + attendance.id.toInt() - return result - } - - class ViewHolder(view: View, val adapter: FlexibleAdapter<*>) : - FlexibleViewHolder(view, adapter), - LayoutContainer { - - override val containerView: View - get() = contentView - - override fun onClick(view: View?) { - super.onClick(view) - attendanceItemExcuseCheckbox.apply { - if ((adapter as AttendanceAdapter).excuseActionMode && isVisible) { - isChecked = !isChecked - } - } - } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceModule.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceModule.kt deleted file mode 100644 index eb35fea1b..000000000 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceModule.kt +++ /dev/null @@ -1,12 +0,0 @@ -package io.github.wulkanowy.ui.modules.attendance - -import dagger.Module -import dagger.Provides -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem - -@Module -class AttendanceModule { - - @Provides - fun provideAttendanceFlexibleAdapter() = AttendanceAdapter>() -} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt index 7fc044744..3a1fb0ceb 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt @@ -1,7 +1,6 @@ package io.github.wulkanowy.ui.modules.attendance import android.annotation.SuppressLint -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import io.github.wulkanowy.data.db.entities.Attendance import io.github.wulkanowy.data.repositories.attendance.AttendanceRepository import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository @@ -111,11 +110,11 @@ class AttendancePresenter @Inject constructor( view?.finishActionMode() } - fun onAttendanceItemSelected(item: AbstractFlexibleItem<*>?) { + fun onAttendanceItemSelected(attendance: Attendance) { view?.apply { - if (item is AttendanceItem && !excuseActionMode) { - Timber.i("Select attendance item ${item.attendance.id}") - showAttendanceDialog(item.attendance) + if (!excuseActionMode) { + Timber.i("Select attendance item ${attendance.id}") + showAttendanceDialog(attendance) } } } @@ -197,9 +196,7 @@ class AttendancePresenter @Inject constructor( if (prefRepository.isShowPresent) list else list.filter { !it.presence } } - .delay(200, MILLISECONDS) - .map { items -> items.map { AttendanceItem(it) } } - .map { items -> items.sortedBy { it.attendance.number } } + .map { items -> items.sortedBy { it.number } } .subscribeOn(schedulers.backgroundThread) .observeOn(schedulers.mainThread) .doFinally { @@ -216,7 +213,7 @@ class AttendancePresenter @Inject constructor( showEmpty(it.isEmpty()) showErrorView(false) showContent(it.isNotEmpty()) - showExcuseButton(it.any { item -> item.attendance.excusable }) + showExcuseButton(it.any { item -> item.excusable }) } analytics.logEvent("load_attendance", "items" to it.size, "force_refresh" to forceRefresh) }) { @@ -236,7 +233,6 @@ class AttendancePresenter @Inject constructor( attendanceRepository.excuseForAbsence(student, semester, toExcuseList, reason) } } - .delay(200, MILLISECONDS) .subscribeOn(schedulers.backgroundThread) .observeOn(schedulers.mainThread) .doOnSubscribe { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceView.kt index 03e95053f..484070a2e 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceView.kt @@ -18,7 +18,7 @@ interface AttendanceView : BaseView { fun initView() - fun updateData(data: List) + fun updateData(data: List) fun updateNavigationDay(date: String) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryAdapter.kt new file mode 100644 index 000000000..a6d4cf220 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryAdapter.kt @@ -0,0 +1,101 @@ +package io.github.wulkanowy.ui.modules.attendance.summary + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.AttendanceSummary +import io.github.wulkanowy.databinding.ItemAttendanceSummaryBinding +import io.github.wulkanowy.databinding.ScrollableHeaderAttendanceSummaryBinding +import io.github.wulkanowy.utils.calculatePercentage +import io.github.wulkanowy.utils.getFormattedName +import org.threeten.bp.Month +import java.util.Locale +import javax.inject.Inject + +class AttendanceSummaryAdapter @Inject constructor() : + RecyclerView.Adapter() { + + private enum class ViewType(val id: Int) { + HEADER(1), + ITEM(2) + } + + var items = emptyList() + + override fun getItemCount() = items.size + 2 + + override fun getItemViewType(position: Int) = when (position) { + 0 -> ViewType.HEADER.id + else -> ViewType.ITEM.id + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + val inflater = LayoutInflater.from(parent.context) + + return when (viewType) { + ViewType.HEADER.id -> HeaderViewHolder(ScrollableHeaderAttendanceSummaryBinding.inflate(inflater, parent, false)) + ViewType.ITEM.id -> ItemViewHolder(ItemAttendanceSummaryBinding.inflate(inflater, parent, false)) + else -> throw IllegalStateException() + } + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + when (holder) { + is HeaderViewHolder -> bindHeaderViewHolder(holder.binding) + is ItemViewHolder -> bindItemViewHolder(holder.binding, position - 2) + } + } + + private fun bindHeaderViewHolder(binding: ScrollableHeaderAttendanceSummaryBinding) { + binding.attendanceSummaryScrollableHeaderPercentage.text = formatPercentage(items.calculatePercentage()) + } + + private fun bindItemViewHolder(binding: ItemAttendanceSummaryBinding, position: Int) { + val item = if (position == -1) getTotalItem() else items[position] + + with(binding) { + attendanceSummaryMonth.text = when (position) { + -1 -> root.context.getString(R.string.attendance_summary_total) + else -> item.month.getFormattedName() + } + attendanceSummaryPercentage.text = when (position) { + -1 -> formatPercentage(items.calculatePercentage()) + else -> formatPercentage(item.calculatePercentage()) + } + + attendanceSummaryPresent.text = item.presence.toString() + attendanceSummaryAbsenceUnexcused.text = item.absence.toString() + attendanceSummaryAbsenceExcused.text = item.absenceExcused.toString() + attendanceSummaryAbsenceSchool.text = item.absenceForSchoolReasons.toString() + attendanceSummaryExemption.text = item.exemption.toString() + attendanceSummaryLatenessUnexcused.text = item.lateness.toString() + attendanceSummaryLatenessExcused.text = item.latenessExcused.toString() + } + } + + private fun getTotalItem() = AttendanceSummary( + month = Month.APRIL, + presence = items.sumBy { it.presence }, + absence = items.sumBy { it.absence }, + absenceExcused = items.sumBy { it.absenceExcused }, + absenceForSchoolReasons = items.sumBy { it.absenceForSchoolReasons }, + exemption = items.sumBy { it.exemption }, + lateness = items.sumBy { it.lateness }, + latenessExcused = items.sumBy { it.latenessExcused }, + diaryId = -1, + studentId = -1, + subjectId = -1 + ) + + private fun formatPercentage(percentage: Double): String { + return if (percentage == 0.0) "0%" + else "${String.format(Locale.FRANCE, "%.2f", percentage)}%" + } + + class HeaderViewHolder(val binding: ScrollableHeaderAttendanceSummaryBinding) : + RecyclerView.ViewHolder(binding.root) + + class ItemViewHolder(val binding: ItemAttendanceSummaryBinding) : + RecyclerView.ViewHolder(binding.root) +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryFragment.kt index 8e30ea8b7..4ec9cceda 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryFragment.kt @@ -9,10 +9,9 @@ import android.view.View.VISIBLE import android.view.ViewGroup import android.widget.ArrayAdapter import android.widget.TextView -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem +import androidx.recyclerview.widget.LinearLayoutManager import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.AttendanceSummary import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.utils.dpToPx @@ -26,7 +25,7 @@ class AttendanceSummaryFragment : BaseFragment(), AttendanceSummaryView, MainVie lateinit var presenter: AttendanceSummaryPresenter @Inject - lateinit var attendanceSummaryAdapter: FlexibleAdapter> + lateinit var attendanceSummaryAdapter: AttendanceSummaryAdapter private lateinit var subjectsAdapter: ArrayAdapter @@ -36,11 +35,9 @@ class AttendanceSummaryFragment : BaseFragment(), AttendanceSummaryView, MainVie fun newInstance() = AttendanceSummaryFragment() } - override val totalString get() = getString(R.string.attendance_summary_total) - override val titleStringId get() = R.string.attendance_title - override val isViewEmpty get() = attendanceSummaryAdapter.isEmpty + override val isViewEmpty get() = attendanceSummaryAdapter.items.isEmpty() override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { return inflater.inflate(R.layout.fragment_attendance_summary, container, false) @@ -54,7 +51,7 @@ class AttendanceSummaryFragment : BaseFragment(), AttendanceSummaryView, MainVie override fun initView() { with(attendanceSummaryRecycler) { - layoutManager = SmoothScrollLinearLayoutManager(context) + layoutManager = LinearLayoutManager(context) adapter = attendanceSummaryAdapter } @@ -81,16 +78,18 @@ class AttendanceSummaryFragment : BaseFragment(), AttendanceSummaryView, MainVie } } - override fun updateDataSet(data: List, header: AttendanceSummaryScrollableHeader) { + override fun updateDataSet(data: List) { with(attendanceSummaryAdapter) { - updateDataSet(data, true) - removeAllScrollableHeaders() - addScrollableHeader(header) + items = data + notifyDataSetChanged() } } override fun clearView() { - attendanceSummaryAdapter.clear() + with(attendanceSummaryAdapter) { + items = emptyList() + notifyDataSetChanged() + } } override fun showEmpty(show: Boolean) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryItem.kt deleted file mode 100644 index 265d6ce44..000000000 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryItem.kt +++ /dev/null @@ -1,80 +0,0 @@ -package io.github.wulkanowy.ui.modules.attendance.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.item_attendance_summary.* - -class AttendanceSummaryItem( - private val month: String, - private val percentage: String, - private val present: String, - private val absence: String, - private val excusedAbsence: String, - private val schoolAbsence: String, - private val exemption: String, - private val lateness: String, - private val excusedLateness: String -) : AbstractFlexibleItem() { - - override fun getLayoutRes() = R.layout.item_attendance_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 { - attendanceSummaryMonth.text = month - attendanceSummaryPercentage.text = percentage - attendanceSummaryPresent.text = present - attendanceSummaryAbsenceUnexcused.text = absence - attendanceSummaryAbsenceExcused.text = excusedAbsence - attendanceSummaryAbsenceSchool.text = schoolAbsence - attendanceSummaryExemption.text = exemption - attendanceSummaryLatenessUnexcused.text = lateness - attendanceSummaryLatenessExcused.text = excusedLateness - } - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as AttendanceSummaryItem - - if (month != other.month) return false - if (percentage != other.percentage) return false - if (present != other.present) return false - if (absence != other.absence) return false - if (excusedAbsence != other.excusedAbsence) return false - if (schoolAbsence != other.schoolAbsence) return false - if (exemption != other.exemption) return false - if (lateness != other.lateness) return false - if (excusedLateness != other.excusedLateness) return false - - return true - } - - override fun hashCode(): Int { - var result = month.hashCode() - result = 31 * result + percentage.hashCode() - result = 31 * result + present.hashCode() - result = 31 * result + absence.hashCode() - result = 31 * result + excusedAbsence.hashCode() - result = 31 * result + schoolAbsence.hashCode() - result = 31 * result + exemption.hashCode() - result = 31 * result + lateness.hashCode() - result = 31 * result + excusedLateness.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/modules/attendance/summary/AttendanceSummaryPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryPresenter.kt index 8fc5b6e4a..72dfc327e 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryPresenter.kt @@ -1,6 +1,5 @@ package io.github.wulkanowy.ui.modules.attendance.summary -import io.github.wulkanowy.data.db.entities.AttendanceSummary import io.github.wulkanowy.data.db.entities.Subject import io.github.wulkanowy.data.repositories.attendancesummary.AttendanceSummaryRepository import io.github.wulkanowy.data.repositories.semester.SemesterRepository @@ -10,13 +9,8 @@ import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.FirebaseAnalyticsHelper import io.github.wulkanowy.utils.SchedulersProvider -import io.github.wulkanowy.utils.calculatePercentage -import io.github.wulkanowy.utils.getFormattedName import org.threeten.bp.Month import timber.log.Timber -import java.lang.String.format -import java.util.Locale.FRANCE -import java.util.concurrent.TimeUnit.MILLISECONDS import javax.inject.Inject class AttendanceSummaryPresenter @Inject constructor( @@ -88,8 +82,7 @@ class AttendanceSummaryPresenter @Inject constructor( attendanceSummaryRepository.getAttendanceSummary(student, it, subjectId, forceRefresh) } } - .map { createAttendanceSummaryItems(it) to AttendanceSummaryScrollableHeader(formatPercentage(it.calculatePercentage())) } - .delay(200, MILLISECONDS) + .map { items -> items.sortedByDescending { if (it.month.value <= Month.JUNE.value) it.month.value + 12 else it.month.value } } .subscribeOn(schedulers.backgroundThread) .observeOn(schedulers.mainThread) .doFinally { @@ -102,11 +95,11 @@ class AttendanceSummaryPresenter @Inject constructor( .subscribe({ Timber.i("Loading attendance summary result: Success") view?.apply { - showEmpty(it.first.isEmpty()) - showContent(it.first.isNotEmpty()) - updateDataSet(it.first, it.second) + showEmpty(it.isEmpty()) + showContent(it.isNotEmpty()) + updateDataSet(it) } - analytics.logEvent("load_attendance_summary", "items" to it.first.size, "force_refresh" to forceRefresh, "item_id" to subjectId) + analytics.logEvent("load_attendance_summary", "items" to it.size, "force_refresh" to forceRefresh, "item_id" to subjectId) }) { Timber.i("Loading attendance summary result: An exception occurred") errorHandler.dispatch(it) @@ -150,42 +143,4 @@ class AttendanceSummaryPresenter @Inject constructor( }) ) } - - private fun createAttendanceSummaryTotalItem(attendanceSummary: List): AttendanceSummaryItem { - return AttendanceSummaryItem( - month = view?.totalString.orEmpty(), - percentage = formatPercentage(attendanceSummary.calculatePercentage()), - present = attendanceSummary.sumBy { it.presence }.toString(), - absence = attendanceSummary.sumBy { it.absence }.toString(), - excusedAbsence = attendanceSummary.sumBy { it.absenceExcused }.toString(), - schoolAbsence = attendanceSummary.sumBy { it.absenceForSchoolReasons }.toString(), - exemption = attendanceSummary.sumBy { it.exemption }.toString(), - lateness = attendanceSummary.sumBy { it.lateness }.toString(), - excusedLateness = attendanceSummary.sumBy { it.latenessExcused }.toString() - ) - } - - private fun createAttendanceSummaryItems(attendanceSummary: List): List { - if (attendanceSummary.isEmpty()) return emptyList() - return listOf(createAttendanceSummaryTotalItem(attendanceSummary)) + attendanceSummary.sortedByDescending { - if (it.month.value <= Month.JUNE.value) it.month.value + 12 else it.month.value - }.map { - AttendanceSummaryItem( - month = it.month.getFormattedName(), - percentage = formatPercentage(it.calculatePercentage()), - present = it.presence.toString(), - absence = it.absence.toString(), - excusedAbsence = it.absenceExcused.toString(), - schoolAbsence = it.absenceForSchoolReasons.toString(), - exemption = it.exemption.toString(), - lateness = it.lateness.toString(), - excusedLateness = it.latenessExcused.toString() - ) - } - } - - private fun formatPercentage(percentage: Double): String { - return if (percentage == 0.0) "0%" - else "${format(FRANCE, "%.2f", percentage)}%" - } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryScrollableHeader.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryScrollableHeader.kt deleted file mode 100644 index c258f71d2..000000000 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryScrollableHeader.kt +++ /dev/null @@ -1,46 +0,0 @@ -package io.github.wulkanowy.ui.modules.attendance.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_attendance_summary.* - -class AttendanceSummaryScrollableHeader(private val percentage: String) : - AbstractFlexibleItem() { - - override fun getLayoutRes() = R.layout.scrollable_header_attendance_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 { attendanceSummaryScrollableHeaderPercentage.text = percentage } - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as AttendanceSummaryScrollableHeader - - if (percentage != other.percentage) return false - - return true - } - - override fun hashCode(): Int { - return percentage.hashCode() - } - - 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/modules/attendance/summary/AttendanceSummaryView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryView.kt index 8bd5332d6..dd4053c72 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryView.kt @@ -1,11 +1,10 @@ package io.github.wulkanowy.ui.modules.attendance.summary +import io.github.wulkanowy.data.db.entities.AttendanceSummary import io.github.wulkanowy.ui.base.BaseView interface AttendanceSummaryView : BaseView { - val totalString: String - val isViewEmpty: Boolean fun initView() @@ -24,7 +23,7 @@ interface AttendanceSummaryView : BaseView { fun setErrorDetails(message: String) - fun updateDataSet(data: List, header: AttendanceSummaryScrollableHeader) + fun updateDataSet(data: List) fun updateSubjects(data: ArrayList) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamAdapter.kt new file mode 100644 index 000000000..85061997c --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamAdapter.kt @@ -0,0 +1,65 @@ +package io.github.wulkanowy.ui.modules.exam + +import android.annotation.SuppressLint +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import io.github.wulkanowy.data.db.entities.Exam +import io.github.wulkanowy.databinding.HeaderExamBinding +import io.github.wulkanowy.databinding.ItemExamBinding +import io.github.wulkanowy.utils.toFormattedString +import io.github.wulkanowy.utils.weekDayName +import org.threeten.bp.LocalDate +import javax.inject.Inject + +class ExamAdapter @Inject constructor() : RecyclerView.Adapter() { + + var items = emptyList>() + + var onClickListener: (Exam) -> Unit = {} + + override fun getItemCount() = items.size + + override fun getItemViewType(position: Int) = items[position].viewType.id + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + val inflater = LayoutInflater.from(parent.context) + + return when (viewType) { + ExamItem.ViewType.HEADER.id -> HeaderViewHolder(HeaderExamBinding.inflate(inflater, parent, false)) + ExamItem.ViewType.ITEM.id -> ItemViewHolder(ItemExamBinding.inflate(inflater, parent, false)) + else -> throw IllegalStateException() + } + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + when (holder) { + is HeaderViewHolder -> bindHeaderViewHolder(holder.binding, items[position].value as LocalDate) + is ItemViewHolder -> bindItemViewHolder(holder.binding, items[position].value as Exam) + } + } + + @SuppressLint("DefaultLocale") + private fun bindHeaderViewHolder(binding: HeaderExamBinding, date: LocalDate) { + with(binding) { + examHeaderDay.text = date.weekDayName.capitalize() + examHeaderDate.text = date.toFormattedString() + } + } + + private fun bindItemViewHolder(binding: ItemExamBinding, exam: Exam) { + with(binding) { + examItemSubject.text = exam.subject + examItemTeacher.text = exam.teacher + examItemType.text = exam.type + + root.setOnClickListener { onClickListener(exam) } + } + } + + private class HeaderViewHolder(val binding: HeaderExamBinding) : + RecyclerView.ViewHolder(binding.root) + + private class ItemViewHolder(val binding: ItemExamBinding) : + RecyclerView.ViewHolder(binding.root) +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamFragment.kt index b880f4650..cc395f626 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamFragment.kt @@ -7,17 +7,14 @@ import android.view.View.GONE import android.view.View.INVISIBLE import android.view.View.VISIBLE import android.view.ViewGroup -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.common.FlexibleItemDecoration -import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem +import androidx.recyclerview.widget.LinearLayoutManager import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Exam import io.github.wulkanowy.ui.base.BaseFragment +import io.github.wulkanowy.ui.widgets.DividerItemDecoration import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.utils.dpToPx -import io.github.wulkanowy.utils.setOnItemClickListener import kotlinx.android.synthetic.main.fragment_exam.* import javax.inject.Inject @@ -27,7 +24,7 @@ class ExamFragment : BaseFragment(), ExamView, MainView.MainChildView, MainView. lateinit var presenter: ExamPresenter @Inject - lateinit var examAdapter: FlexibleAdapter> + lateinit var examAdapter: ExamAdapter companion object { private const val SAVED_DATE_KEY = "CURRENT_DATE" @@ -37,7 +34,7 @@ class ExamFragment : BaseFragment(), ExamView, MainView.MainChildView, MainView. override val titleStringId get() = R.string.exam_title - override val isViewEmpty get() = examAdapter.isEmpty + override val isViewEmpty get() = examAdapter.items.isEmpty() override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { return inflater.inflate(R.layout.fragment_exam, container, false) @@ -50,14 +47,12 @@ class ExamFragment : BaseFragment(), ExamView, MainView.MainChildView, MainView. } override fun initView() { - examAdapter.setOnItemClickListener(presenter::onExamItemSelected) + examAdapter.onClickListener = presenter::onExamItemSelected with(examRecycler) { - layoutManager = SmoothScrollLinearLayoutManager(context) + layoutManager = LinearLayoutManager(context) adapter = examAdapter - addItemDecoration(FlexibleItemDecoration(context) - .withDefaultDivider(R.layout.item_exam) - .withDrawDividerOnLastItem(false)) + addItemDecoration(DividerItemDecoration(context)) } examSwipe.setOnRefreshListener(presenter::onSwipeRefresh) @@ -74,8 +69,11 @@ class ExamFragment : BaseFragment(), ExamView, MainView.MainChildView, MainView. examSwipe.isRefreshing = false } - override fun updateData(data: List) { - examAdapter.updateDataSet(data, true) + override fun updateData(data: List>) { + with(examAdapter) { + items = data + notifyDataSetChanged() + } } override fun updateNavigationWeek(date: String) { @@ -83,7 +81,10 @@ class ExamFragment : BaseFragment(), ExamView, MainView.MainChildView, MainView. } override fun clearData() { - examAdapter.clear() + with(examAdapter) { + items = emptyList() + notifyDataSetChanged() + } } override fun resetView() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamHeader.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamHeader.kt deleted file mode 100644 index 0a5b862c3..000000000 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamHeader.kt +++ /dev/null @@ -1,52 +0,0 @@ -package io.github.wulkanowy.ui.modules.exam - -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.ExpandableViewHolder -import io.github.wulkanowy.R -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 - -class ExamHeader(private val date: LocalDate) : AbstractHeaderItem() { - - override fun getLayoutRes() = R.layout.header_exam - - 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 { - examHeaderDay.text = date.weekDayName.capitalize() - examHeaderDate.text = date.toFormattedString() - } - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as ExamHeader - - if (date != other.date) return false - - return true - } - - override fun hashCode(): Int { - return date.hashCode() - } - - class ViewHolder(view: View?, adapter: FlexibleAdapter>?) : ExpandableViewHolder(view, adapter), - LayoutContainer { - - override val containerView: View - get() = contentView - } -} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamItem.kt index 8971b4df3..579e37203 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamItem.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamItem.kt @@ -1,50 +1,9 @@ package io.github.wulkanowy.ui.modules.exam -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 io.github.wulkanowy.data.db.entities.Exam -import kotlinx.android.extensions.LayoutContainer -import kotlinx.android.synthetic.main.item_exam.* +data class ExamItem(val value: T, val viewType: ViewType) { -class ExamItem(header: ExamHeader, val exam: Exam) : AbstractSectionableItem(header) { - - override fun getLayoutRes() = R.layout.item_exam - - 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 { - examItemSubject.text = exam.subject - examItemTeacher.text = exam.teacher - examItemType.text = exam.type - } - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as ExamItem - - if (exam != other.exam) return false - - return true - } - - override fun hashCode(): Int { - var result = exam.hashCode() - result = 31 * result + exam.id.toInt() - return result - } - - class ViewHolder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter), LayoutContainer { - override val containerView: View - get() = contentView + enum class ViewType(val id: Int) { + HEADER(1), + ITEM(2) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamPresenter.kt index aac9bc4bb..495602fc5 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamPresenter.kt @@ -1,6 +1,5 @@ package io.github.wulkanowy.ui.modules.exam -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import io.github.wulkanowy.data.db.entities.Exam import io.github.wulkanowy.data.repositories.exam.ExamRepository import io.github.wulkanowy.data.repositories.semester.SemesterRepository @@ -19,7 +18,6 @@ import org.threeten.bp.LocalDate import org.threeten.bp.LocalDate.now import org.threeten.bp.LocalDate.ofEpochDay import timber.log.Timber -import java.util.concurrent.TimeUnit.MILLISECONDS import javax.inject.Inject class ExamPresenter @Inject constructor( @@ -75,11 +73,9 @@ class ExamPresenter @Inject constructor( view?.showErrorDetailsDialog(lastError) } - fun onExamItemSelected(item: AbstractFlexibleItem<*>?) { - if (item is ExamItem) { - Timber.i("Select exam item ${item.exam.id}") - view?.showExamDialog(item.exam) - } + fun onExamItemSelected(exam: Exam) { + Timber.i("Select exam item ${exam.id}") + view?.showExamDialog(exam) } fun onViewReselected() { @@ -117,8 +113,6 @@ class ExamPresenter @Inject constructor( examRepository.getExams(student, semester, currentDate.monday, currentDate.friday, forceRefresh) } } - .delay(200, MILLISECONDS) - .map { it.groupBy { exam -> exam.date }.toSortedMap() } .map { createExamItems(it) } .subscribeOn(schedulers.backgroundThread) .observeOn(schedulers.mainThread) @@ -156,12 +150,12 @@ class ExamPresenter @Inject constructor( } } - private fun createExamItems(items: Map>): List { - return items.flatMap { - ExamHeader(it.key).let { header -> - it.value.reversed().map { item -> ExamItem(header, item) } + private fun createExamItems(items: List): List> { + return items.groupBy { it.date }.toSortedMap().map { (date, exams) -> + listOf(ExamItem(date, ExamItem.ViewType.HEADER)) + exams.reversed().map { exam -> + ExamItem(exam, ExamItem.ViewType.ITEM) } - } + }.flatten() } private fun reloadView() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamView.kt index 5f4a74306..00429bae6 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamView.kt @@ -9,7 +9,7 @@ interface ExamView : BaseView { fun initView() - fun updateData(data: List) + fun updateData(data: List>) fun updateNavigationWeek(date: String) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsAdapter.kt new file mode 100644 index 000000000..22367d02c --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsAdapter.kt @@ -0,0 +1,176 @@ +package io.github.wulkanowy.ui.modules.grade.details + +import android.annotation.SuppressLint +import android.content.res.Resources +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.RecyclerView +import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.Grade +import io.github.wulkanowy.databinding.HeaderGradeDetailsBinding +import io.github.wulkanowy.databinding.ItemGradeDetailsBinding +import io.github.wulkanowy.ui.base.BaseExpandableAdapter +import io.github.wulkanowy.utils.getBackgroundColor +import io.github.wulkanowy.utils.toFormattedString +import javax.inject.Inject + +class GradeDetailsAdapter @Inject constructor() : BaseExpandableAdapter() { + + private var headers = mutableListOf() + + private var items = mutableListOf() + + private var expandedPosition = RecyclerView.NO_POSITION + + private var isExpandable = false + + var onClickListener: (Grade, position: Int) -> Unit = { _, _ -> } + + var colorTheme = "" + + fun setDataItems(data: List, isExpanded: Boolean = isExpandable) { + headers = data.filter { it.viewType == ViewType.HEADER }.toMutableList() + items = if (isExpanded) headers else data.toMutableList() + isExpandable = isExpanded + expandedPosition = RecyclerView.NO_POSITION + } + + fun updateDetailsItem(position: Int, grade: Grade) { + items[position] = GradeDetailsItem(grade, ViewType.ITEM) + notifyItemChanged(position) + } + + fun getHeaderItem(subject: String): GradeDetailsItem { + return headers.single { (it.value as GradeDetailsHeader).subject == subject } + } + + fun updateHeaderItem(item: GradeDetailsItem) { + headers[headers.indexOf(item)] = item + items[items.indexOf(item)] = item + notifyItemChanged(items.indexOf(item)) + } + + fun collapseAll() { + if (expandedPosition != -1) { + refreshList(headers) + expandedPosition = RecyclerView.NO_POSITION + } + } + + @Synchronized + private fun refreshList(newItems: List) { + val diffCallback = GradeDetailsDiffUtil(items, newItems) + val diffResult = DiffUtil.calculateDiff(diffCallback) + items = newItems.toMutableList() + diffResult.dispatchUpdatesTo(this) + } + + override fun getItemCount() = items.size + + override fun getItemViewType(position: Int) = items[position].viewType.id + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + val inflater = LayoutInflater.from(parent.context) + + return when (viewType) { + ViewType.HEADER.id -> HeaderViewHolder(HeaderGradeDetailsBinding.inflate(inflater, parent, false)) + ViewType.ITEM.id -> ItemViewHolder(ItemGradeDetailsBinding.inflate(inflater, parent, false)) + else -> throw IllegalStateException() + } + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + when (holder) { + is HeaderViewHolder -> bindHeaderViewHolder( + binding = holder.binding, + header = items[position].value as GradeDetailsHeader, + headerPosition = headers.indexOf(items[position]), + adapterPosition = position + ) + is ItemViewHolder -> bindItemViewHolder( + binding = holder.binding, + grade = items[position].value as Grade, + position = position + ) + } + } + + private fun bindHeaderViewHolder(binding: HeaderGradeDetailsBinding, header: GradeDetailsHeader, headerPosition: Int, adapterPosition: Int) { + with(binding) { + gradeHeaderDivider.visibility = if (adapterPosition == 0) View.GONE else View.VISIBLE + gradeHeaderSubject.apply { + text = header.subject + maxLines = if (headerPosition == expandedPosition) 2 else 1 + } + gradeHeaderAverage.text = formatAverage(header.average, root.context.resources) + gradeHeaderPointsSum.text = root.context.getString(R.string.grade_points_sum, header.pointsSum) + gradeHeaderPointsSum.visibility = if (!header.pointsSum.isNullOrEmpty()) View.VISIBLE else View.GONE + gradeHeaderNumber.text = root.context.resources.getQuantityString(R.plurals.grade_number_item, header.number, header.number) + gradeHeaderNote.visibility = if (header.newGrades > 0) View.VISIBLE else View.GONE + if (header.newGrades > 0) gradeHeaderNote.text = header.newGrades.toString(10) + + gradeHeaderContainer.isEnabled = isExpandable + gradeHeaderContainer.setOnClickListener { + expandedPosition = if (expandedPosition == adapterPosition) -1 else adapterPosition + + if (expandedPosition != RecyclerView.NO_POSITION) { + refreshList(headers.toMutableList().apply { + addAll(headerPosition + 1, header.grades) + }) + scrollToHeaderWithSubItems(headerPosition, header.grades.size) + } else { + refreshList(headers) + } + } + } + } + + private fun formatAverage(average: Double?, resources: Resources): String { + return if (average == null || average == .0) resources.getString(R.string.grade_no_average) + else resources.getString(R.string.grade_average, average) + } + + @SuppressLint("SetTextI18n") + private fun bindItemViewHolder(binding: ItemGradeDetailsBinding, grade: Grade, position: Int) { + with(binding) { + gradeItemValue.run { + text = grade.entry + setBackgroundResource(grade.getBackgroundColor(colorTheme)) + } + gradeItemDescription.text = when { + grade.description.isNotBlank() -> grade.description + grade.gradeSymbol.isNotBlank() -> grade.gradeSymbol + else -> root.context.getString(R.string.all_no_description) + } + gradeItemDate.text = grade.date.toFormattedString() + gradeItemWeight.text = "${root.context.getString(R.string.grade_weight)}: ${grade.weight}" + gradeItemNote.visibility = if (!grade.isRead) View.VISIBLE else View.GONE + + root.setOnClickListener { onClickListener(grade, position) } + } + } + + private class HeaderViewHolder(val binding: HeaderGradeDetailsBinding) : + RecyclerView.ViewHolder(binding.root) + + private class ItemViewHolder(val binding: ItemGradeDetailsBinding) : + RecyclerView.ViewHolder(binding.root) + + class GradeDetailsDiffUtil(private val old: List, private val new: List) : + DiffUtil.Callback() { + + override fun getOldListSize() = old.size + + override fun getNewListSize() = new.size + + override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { + return old[oldItemPosition] == new[newItemPosition] + } + + override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { + return old[oldItemPosition] == new[newItemPosition] + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsFragment.kt index 9505d354f..e0cfca2f1 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsFragment.kt @@ -10,18 +10,13 @@ import android.view.View.GONE import android.view.View.INVISIBLE import android.view.View.VISIBLE 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 androidx.recyclerview.widget.LinearLayoutManager 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.modules.grade.GradeFragment import io.github.wulkanowy.ui.modules.grade.GradeView import io.github.wulkanowy.ui.modules.main.MainActivity -import io.github.wulkanowy.utils.setOnItemClickListener import kotlinx.android.synthetic.main.fragment_grade_details.* import javax.inject.Inject @@ -31,7 +26,7 @@ class GradeDetailsFragment : BaseFragment(), GradeDetailsView, GradeView.GradeCh lateinit var presenter: GradeDetailsPresenter @Inject - lateinit var gradeDetailsAdapter: FlexibleAdapter> + lateinit var gradeDetailsAdapter: GradeDetailsAdapter private var gradeDetailsMenu: Menu? = null @@ -39,23 +34,8 @@ class GradeDetailsFragment : BaseFragment(), GradeDetailsView, GradeView.GradeCh fun newInstance() = GradeDetailsFragment() } - override val emptyAverageString: String - get() = getString(R.string.grade_no_average) - - override val averageString: String - get() = getString(R.string.grade_average) - - override val pointsSumString: String - get() = getString(R.string.grade_points_sum) - - override val weightString: String - get() = getString(R.string.grade_weight) - - override val noDescriptionString: String - get() = getString(R.string.all_no_description) - override val isViewEmpty - get() = gradeDetailsAdapter.isEmpty + get() = gradeDetailsAdapter.itemCount == 0 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -79,18 +59,11 @@ class GradeDetailsFragment : BaseFragment(), GradeDetailsView, GradeView.GradeCh } override fun initView() { - gradeDetailsAdapter.run { - isAutoCollapseOnExpand = true - isAutoScrollOnExpand = true - setOnItemClickListener { presenter.onGradeItemSelected(it) } - } + gradeDetailsAdapter.onClickListener = presenter::onGradeItemSelected gradeDetailsRecycler.run { - layoutManager = SmoothScrollLinearLayoutManager(context) + layoutManager = LinearLayoutManager(context) adapter = gradeDetailsAdapter - addItemDecoration(GradeDetailsHeaderItemDecoration(context) - .withDefaultDivider(R.layout.header_grade_details) - ) } gradeDetailsSwipe.setOnRefreshListener { presenter.onSwipeRefresh() } gradeDetailsErrorRetry.setOnClickListener { presenter.onRetry() } @@ -102,16 +75,23 @@ class GradeDetailsFragment : BaseFragment(), GradeDetailsView, GradeView.GradeCh else false } - override fun updateData(data: List) { - gradeDetailsAdapter.updateDataSet(data, true) + override fun updateData(data: List, isGradeExpandable: Boolean, gradeColorTheme: String) { + with(gradeDetailsAdapter) { + colorTheme = gradeColorTheme + setDataItems(data, isGradeExpandable) + notifyDataSetChanged() + } } - override fun updateItem(item: AbstractFlexibleItem<*>) { - gradeDetailsAdapter.updateItem(item) + override fun updateItem(item: Grade, position: Int) { + gradeDetailsAdapter.updateDetailsItem(position, item) } override fun clearView() { - gradeDetailsAdapter.clear() + with(gradeDetailsAdapter) { + setDataItems(mutableListOf()) + notifyDataSetChanged() + } } override fun collapseAllItems() { @@ -119,15 +99,15 @@ class GradeDetailsFragment : BaseFragment(), GradeDetailsView, GradeView.GradeCh } override fun scrollToStart() { - gradeDetailsRecycler.scrollToPosition(0) + gradeDetailsRecycler.smoothScrollToPosition(0) } - override fun getHeaderOfItem(item: AbstractFlexibleItem<*>): IExpandable<*, out IFlexible<*>>? { - return gradeDetailsAdapter.getExpandableOf(item) + override fun getHeaderOfItem(subject: String): GradeDetailsItem { + return gradeDetailsAdapter.getHeaderItem(subject) } - override fun getGradeNumberString(number: Int): String { - return resources.getQuantityString(R.plurals.grade_number_item, number, number) + override fun updateHeaderItem(item: GradeDetailsItem) { + gradeDetailsAdapter.updateHeaderItem(item) } override fun showProgress(show: Boolean) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsHeader.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsHeader.kt deleted file mode 100644 index 4a34a1457..000000000 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsHeader.kt +++ /dev/null @@ -1,94 +0,0 @@ -package io.github.wulkanowy.ui.modules.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, - private val pointsSum: String, - var newGrades: Int, - private val isExpandable: Boolean -) : AbstractExpandableItem() { - - init { - isExpanded = !isExpandable - } - - 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.apply { - text = subject - maxLines = if (isExpanded) 2 else 1 - } - gradeHeaderAverage.text = average - gradeHeaderPointsSum.text = pointsSum - gradeHeaderPointsSum.visibility = if (pointsSum.isNotEmpty()) VISIBLE else GONE - gradeHeaderNumber.text = number - gradeHeaderNote.visibility = if (newGrades > 0) VISIBLE else GONE - if (newGrades > 0) gradeHeaderNote.text = newGrades.toString(10) - gradeHeaderContainer.isEnabled = isExpandable - - isViewExpandable = isExpandable - } - } - - 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 - if (isExpandable != other.isExpandable) return false - - return true - } - - override fun hashCode(): Int { - var result = subject.hashCode() - result = 31 * result + number.hashCode() - result = 31 * result + average.hashCode() - result = 31 * result + isExpandable.hashCode() - return result - } - - class ViewHolder(view: View?, adapter: FlexibleAdapter>?) : - ExpandableViewHolder(view, adapter), LayoutContainer { - - var isViewExpandable = true - - init { - contentView.setOnClickListener(this) - } - - override val containerView: View - get() = contentView - - override fun isViewCollapsibleOnClick() = isViewExpandable - - override fun isViewExpandableOnClick() = isViewExpandable - - override fun onClick(view: View?) { - super.onClick(view) - mAdapter.getItem(adapterPosition)?.let { mAdapter.updateItem(it) } - } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsHeaderItemDecoration.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsHeaderItemDecoration.kt deleted file mode 100644 index 39a911e62..000000000 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsHeaderItemDecoration.kt +++ /dev/null @@ -1,38 +0,0 @@ -package io.github.wulkanowy.ui.modules.grade.details - -import android.content.Context -import android.graphics.Canvas -import androidx.recyclerview.widget.RecyclerView -import eu.davidea.flexibleadapter.common.FlexibleItemDecoration - -class GradeDetailsHeaderItemDecoration(context: Context) : FlexibleItemDecoration(context) { - - override fun drawVertical(canvas: Canvas, parent: RecyclerView) { - canvas.save() - val left: Int - val right: Int - if (parent.clipToPadding) { - left = parent.paddingLeft - right = parent.width - parent.paddingRight - canvas.clipRect(left, parent.paddingTop, right, - parent.height - parent.paddingBottom) - } else { - left = 0 - right = parent.width - } - - val itemCount = parent.childCount - for (i in 1 until itemCount) { - val child = parent.getChildAt(i) - val viewHolder = parent.getChildViewHolder(child) - if (shouldDrawDivider(viewHolder)) { - parent.getDecoratedBoundsWithMargins(child, mBounds) - val bottom = mBounds.top + Math.round(child.translationY) - val top = bottom - mDivider.intrinsicHeight - mDivider.setBounds(left, top, right, bottom) - mDivider.draw(canvas) - } - } - canvas.restore() - } -} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsItem.kt index 1e47eca5d..f1adbdea2 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsItem.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsItem.kt @@ -1,74 +1,20 @@ package io.github.wulkanowy.ui.modules.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 valueBgColor: Int, - private val weightString: String, - private val noDescriptionString: String -) : 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(valueBgColor) - } - gradeItemDescription.text = when { - grade.description.isNotBlank() -> grade.description - grade.gradeSymbol.isNotBlank() -> grade.gradeSymbol - else -> noDescriptionString - } - gradeItemDate.text = grade.date.toFormattedString() - gradeItemWeight.text = "$weightString: ${grade.weight}" - gradeItemNote.visibility = if (!grade.isRead) 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 (grade.id != other.grade.id) return false - if (weightString != other.weightString) return false - if (valueBgColor != other.valueBgColor) return false - - return true - } - - override fun hashCode(): Int { - var result = grade.hashCode() - result = 31 * result + grade.id.toInt() - result = 31 * result + weightString.hashCode() - result = 31 * result + valueBgColor - return result - } - - class ViewHolder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter), LayoutContainer { - override val containerView: View - get() = contentView - } +enum class ViewType(val id: Int) { + HEADER(1), + ITEM(2) } + +data class GradeDetailsItem( + val value: Any, + val viewType: ViewType +) + +data class GradeDetailsHeader( + val subject: String, + val number: Int, + val average: Double?, + val pointsSum: String?, + var newGrades: Int, + val grades: List +) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt index a9e5b2b7d..83501182d 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt @@ -1,6 +1,5 @@ package io.github.wulkanowy.ui.modules.grade.details -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.repositories.grade.GradeRepository import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository @@ -11,7 +10,6 @@ import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.modules.grade.GradeAverageProvider import io.github.wulkanowy.utils.FirebaseAnalyticsHelper import io.github.wulkanowy.utils.SchedulersProvider -import io.github.wulkanowy.utils.getBackgroundColor import timber.log.Timber import javax.inject.Inject @@ -43,24 +41,20 @@ class GradeDetailsPresenter @Inject constructor( loadData(semesterId, forceRefresh) } - fun onGradeItemSelected(item: AbstractFlexibleItem<*>?) { - if (item is GradeDetailsItem) { - Timber.i("Select grade item ${item.grade.id}") - view?.apply { - showGradeDialog(item.grade, preferencesRepository.gradeColorTheme) - if (!item.grade.isRead) { - item.grade.isRead = true - updateItem(item) - getHeaderOfItem(item)?.let { header -> - if (header is GradeDetailsHeader) { - header.newGrades-- - updateItem(header) - } - } - newGradesAmount-- - updateMarkAsDoneButton() - updateGrade(item.grade) + fun onGradeItemSelected(grade: Grade, position: Int) { + Timber.i("Select grade item ${grade.id}") + view?.apply { + showGradeDialog(grade, preferencesRepository.gradeColorTheme) + if (!grade.isRead) { + grade.isRead = true + updateItem(grade, position) + getHeaderOfItem(grade.subject).let { header -> + (header.value as GradeDetailsHeader).newGrades-- + updateHeaderItem(header) } + newGradesAmount-- + updateMarkAsDoneButton() + updateGrade(grade) } } } @@ -134,13 +128,11 @@ class GradeDetailsPresenter @Inject constructor( disposable.add(studentRepository.getCurrentStudent() .flatMap { semesterRepository.getSemesters(it).map { semester -> it to semester } } .flatMap { (student, semesters) -> - averageProvider.getGradeAverage(student, semesters, semesterId, forceRefresh) - .flatMap { averages -> - gradeRepository.getGrades(student, semesters.first { it.semesterId == semesterId }, forceRefresh) - .map { it.sortedByDescending { grade -> grade.date } } - .map { it.groupBy { grade -> grade.subject }.toSortedMap() } - .map { createGradeItems(it, averages) } - } + averageProvider.getGradeAverage(student, semesters, semesterId, forceRefresh).flatMap { averages -> + gradeRepository.getGrades(student, semesters.first { it.semesterId == semesterId }, forceRefresh) + .map { it.sortedByDescending { grade -> grade.date } } + .map { createGradeItems(it, averages) } + } } .subscribeOn(schedulers.backgroundThread) .observeOn(schedulers.mainThread) @@ -152,17 +144,23 @@ class GradeDetailsPresenter @Inject constructor( notifyParentDataLoaded(semesterId) } } - .subscribe({ + .subscribe({ grades -> Timber.i("Loading grade details result: Success") - newGradesAmount = it.sumBy { gradeDetailsHeader -> gradeDetailsHeader.newGrades } + newGradesAmount = grades + .filter { it.viewType == ViewType.HEADER } + .sumBy { item -> (item.value as GradeDetailsHeader).newGrades } updateMarkAsDoneButton() view?.run { - showEmpty(it.isEmpty()) + showEmpty(grades.isEmpty()) showErrorView(false) - showContent(it.isNotEmpty()) - updateData(it) + showContent(grades.isNotEmpty()) + updateData( + data = grades, + isGradeExpandable = preferencesRepository.isGradeExpandable, + gradeColorTheme = preferencesRepository.gradeColorTheme + ) } - analytics.logEvent("load_grade_details", "items" to it.size, "force_refresh" to forceRefresh) + analytics.logEvent("load_grade_details", "items" to grades.size, "force_refresh" to forceRefresh) }) { Timber.i("Loading grade details result: An exception occurred") errorHandler.dispatch(it) @@ -180,40 +178,21 @@ class GradeDetailsPresenter @Inject constructor( } } - private fun createGradeItems(items: Map>, averages: List>): List { - val isGradeExpandable = preferencesRepository.isGradeExpandable - val gradeColorTheme = preferencesRepository.gradeColorTheme - - val noDescriptionString = view?.noDescriptionString.orEmpty() - val weightString = view?.weightString.orEmpty() - val pointsSumString = view?.pointsSumString.orEmpty() - - return items.map { subject -> - GradeDetailsHeader( - subject = subject.key, - average = formatAverage(averages.singleOrNull { subject.key == it.first }?.second), - pointsSum = averages.singleOrNull { subject.key == it.first }?.takeIf { it.third.isNotEmpty() }?.let { pointsSumString.format(it.third) }.orEmpty(), - number = view?.getGradeNumberString(subject.value.size).orEmpty(), - newGrades = subject.value.filter { grade -> !grade.isRead }.size, - isExpandable = isGradeExpandable - ).apply { - subItems = subject.value.map { item -> - GradeDetailsItem( - grade = item, - valueBgColor = item.getBackgroundColor(gradeColorTheme), - weightString = weightString, - noDescriptionString = noDescriptionString - ) - } + private fun createGradeItems(items: List, averages: List>): List { + return items.groupBy { grade -> grade.subject }.toSortedMap().map { (subject, grades) -> + val subItems = grades.map { + GradeDetailsItem(it, ViewType.ITEM) } - } - } - private fun formatAverage(average: Double?): String { - return view?.run { - if (average == null || average == .0) emptyAverageString - else averageString.format(average) - }.orEmpty() + listOf(GradeDetailsItem(GradeDetailsHeader( + subject = subject, + average = averages.singleOrNull { subject == it.first }?.second, + pointsSum = averages.singleOrNull { subject == it.first }?.third, + number = grades.size, + newGrades = grades.filter { grade -> !grade.isRead }.size, + grades = subItems + ), ViewType.HEADER)) + if (preferencesRepository.isGradeExpandable) emptyList() else subItems + }.flatten() } private fun updateGrade(grade: Grade) { @@ -221,8 +200,9 @@ class GradeDetailsPresenter @Inject constructor( disposable.add(gradeRepository.updateGrade(grade) .subscribeOn(schedulers.backgroundThread) .observeOn(schedulers.mainThread) - .subscribe({ Timber.i("Update grade result: Success") }) - { error -> + .subscribe({ + Timber.i("Update grade result: Success") + }) { error -> Timber.i("Update grade result: An exception occurred") errorHandler.dispatch(error) }) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsView.kt index e2977bcbe..e71fcc3c8 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsView.kt @@ -1,8 +1,5 @@ package io.github.wulkanowy.ui.modules.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 @@ -10,21 +7,13 @@ interface GradeDetailsView : BaseView { val isViewEmpty: Boolean - val emptyAverageString: String - - val averageString: String - - val pointsSumString: String - - val weightString: String - - val noDescriptionString: String - fun initView() - fun updateData(data: List) + fun updateData(data: List, isGradeExpandable: Boolean, gradeColorTheme: String) - fun updateItem(item: AbstractFlexibleItem<*>) + fun updateItem(item: Grade, position: Int) + + fun updateHeaderItem(item: GradeDetailsItem) fun clearView() @@ -54,7 +43,5 @@ interface GradeDetailsView : BaseView { fun enableMarkAsDoneButton(enable: Boolean) - fun getGradeNumberString(number: Int): String - - fun getHeaderOfItem(item: AbstractFlexibleItem<*>): IExpandable<*, out IFlexible<*>>? + fun getHeaderOfItem(subject: String): GradeDetailsItem } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryAdapter.kt new file mode 100644 index 000000000..30c4ccc23 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryAdapter.kt @@ -0,0 +1,85 @@ +package io.github.wulkanowy.ui.modules.grade.summary + +import android.annotation.SuppressLint +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import io.github.wulkanowy.data.db.entities.GradeSummary +import io.github.wulkanowy.databinding.ItemGradeSummaryBinding +import io.github.wulkanowy.databinding.ScrollableHeaderGradeSummaryBinding +import io.github.wulkanowy.utils.calcAverage +import java.util.Locale +import javax.inject.Inject + +class GradeSummaryAdapter @Inject constructor() : RecyclerView.Adapter() { + + private enum class ViewType(val id: Int) { + HEADER(1), + ITEM(2) + } + + var items = emptyList() + + override fun getItemCount() = items.size + 1 + + override fun getItemViewType(position: Int) = when (position) { + 0 -> ViewType.HEADER.id + else -> ViewType.ITEM.id + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + val inflater = LayoutInflater.from(parent.context) + + return when (viewType) { + ViewType.HEADER.id -> HeaderViewHolder(ScrollableHeaderGradeSummaryBinding.inflate(inflater, parent, false)) + ViewType.ITEM.id -> ItemViewHolder(ItemGradeSummaryBinding.inflate(inflater, parent, false)) + else -> throw IllegalStateException() + } + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + when (holder) { + is HeaderViewHolder -> bindHeaderViewHolder(holder.binding) + is ItemViewHolder -> bindItemViewHolder(holder.binding, items[position - 1]) + } + } + + private fun bindHeaderViewHolder(binding: ScrollableHeaderGradeSummaryBinding) { + if (items.isEmpty()) return + + with(binding) { + gradeSummaryScrollableHeaderFinal.text = formatAverage(items.calcAverage()) + gradeSummaryScrollableHeaderCalculated.text = formatAverage(items + .filter { value -> value.average != 0.0 } + .map { values -> values.average } + .reversed() // fix average precision + .average() + ) + } + } + + @SuppressLint("SetTextI18n") + private fun bindItemViewHolder(binding: ItemGradeSummaryBinding, item: GradeSummary) { + with(binding) { + gradeSummaryItemTitle.text = item.subject + gradeSummaryItemPoints.text = item.pointsSum + gradeSummaryItemAverage.text = formatAverage(item.average, "") + gradeSummaryItemPredicted.text = "${item.predictedGrade} ${item.proposedPoints}".trim() + gradeSummaryItemFinal.text = "${item.finalGrade} ${item.finalPoints}".trim() + + gradeSummaryItemPointsContainer.visibility = if (item.pointsSum.isBlank()) View.GONE else View.VISIBLE + } + } + + private fun formatAverage(average: Double, defaultValue: String = "-- --"): String { + return if (average == 0.0) defaultValue + else String.format(Locale.FRANCE, "%.2f", average) + } + + private class HeaderViewHolder(val binding: ScrollableHeaderGradeSummaryBinding) : + RecyclerView.ViewHolder(binding.root) + + private class ItemViewHolder(val binding: ItemGradeSummaryBinding) : + RecyclerView.ViewHolder(binding.root) +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryFragment.kt index 05fde5227..3addfb6ed 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryFragment.kt @@ -7,10 +7,9 @@ import android.view.View.GONE import android.view.View.INVISIBLE import android.view.View.VISIBLE import android.view.ViewGroup -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem +import androidx.recyclerview.widget.LinearLayoutManager import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.GradeSummary import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.modules.grade.GradeFragment import io.github.wulkanowy.ui.modules.grade.GradeView @@ -23,14 +22,14 @@ class GradeSummaryFragment : BaseFragment(), GradeSummaryView, GradeView.GradeCh lateinit var presenter: GradeSummaryPresenter @Inject - lateinit var gradeSummaryAdapter: FlexibleAdapter> + lateinit var gradeSummaryAdapter: GradeSummaryAdapter companion object { fun newInstance() = GradeSummaryFragment() } override val isViewEmpty - get() = gradeSummaryAdapter.isEmpty + get() = gradeSummaryAdapter.items.isEmpty() override val predictedString get() = getString(R.string.grade_summary_predicted_grade) @@ -49,10 +48,8 @@ class GradeSummaryFragment : BaseFragment(), GradeSummaryView, GradeView.GradeCh } override fun initView() { - gradeSummaryAdapter.setDisplayHeadersAtStartUp(true) - gradeSummaryRecycler.run { - layoutManager = SmoothScrollLinearLayoutManager(context) + layoutManager = LinearLayoutManager(context) adapter = gradeSummaryAdapter } gradeSummarySwipe.setOnRefreshListener { presenter.onSwipeRefresh() } @@ -60,16 +57,18 @@ class GradeSummaryFragment : BaseFragment(), GradeSummaryView, GradeView.GradeCh gradeSummaryErrorDetails.setOnClickListener { presenter.onDetailsClick() } } - override fun updateData(data: List, header: GradeSummaryScrollableHeader) { - gradeSummaryAdapter.apply { - updateDataSet(data, true) - removeAllScrollableHeaders() - addScrollableHeader(header) + override fun updateData(data: List) { + with(gradeSummaryAdapter) { + items = data + notifyDataSetChanged() } } override fun clearView() { - gradeSummaryAdapter.clear() + with(gradeSummaryAdapter) { + items = emptyList() + notifyDataSetChanged() + } } override fun resetView() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryItem.kt deleted file mode 100644 index 95c32d176..000000000 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryItem.kt +++ /dev/null @@ -1,64 +0,0 @@ -package io.github.wulkanowy.ui.modules.grade.summary - -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.GradeSummary -import kotlinx.android.extensions.LayoutContainer -import kotlinx.android.synthetic.main.item_grade_summary.* - -class GradeSummaryItem( - val summary: GradeSummary, - private val average: String -) : AbstractFlexibleItem() { - - override fun getLayoutRes() = R.layout.item_grade_summary - - 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 { - gradeSummaryItemTitle.text = summary.subject - gradeSummaryItemPoints.text = summary.pointsSum - gradeSummaryItemAverage.text = average - gradeSummaryItemPredicted.text = "${summary.predictedGrade} ${summary.proposedPoints}".trim() - gradeSummaryItemFinal.text = "${summary.finalGrade} ${summary.finalPoints}".trim() - - gradeSummaryItemPointsContainer.visibility = if (summary.pointsSum.isBlank()) GONE else VISIBLE - } - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as GradeSummaryItem - - if (average != other.average) return false - if (summary != other.summary) return false - if (summary.id != other.summary.id) return false - - return true - } - - override fun hashCode(): Int { - var result = summary.hashCode() - result = 31 * result + summary.id.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/modules/grade/summary/GradeSummaryPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt index c12f2a516..53c69db70 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt @@ -9,10 +9,7 @@ import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.modules.grade.GradeAverageProvider import io.github.wulkanowy.utils.FirebaseAnalyticsHelper import io.github.wulkanowy.utils.SchedulersProvider -import io.github.wulkanowy.utils.calcAverage import timber.log.Timber -import java.lang.String.format -import java.util.Locale.FRANCE import javax.inject.Inject class GradeSummaryPresenter @Inject constructor( @@ -42,7 +39,7 @@ class GradeSummaryPresenter @Inject constructor( .map { it.sortedBy { subject -> subject.subject } } .flatMap { gradesSummary -> averageProvider.getGradeAverage(student, semesters, semesterId, forceRefresh) - .map { averages -> createGradeSummaryItemsAndHeader(gradesSummary, averages) } + .map { averages -> createGradeSummaryItems(gradesSummary, averages) } } } .subscribeOn(schedulers.backgroundThread) @@ -54,15 +51,15 @@ class GradeSummaryPresenter @Inject constructor( enableSwipe(true) notifyParentDataLoaded(semesterId) } - }.subscribe({ (gradeSummaryItems, gradeSummaryHeader) -> + }.subscribe({ Timber.i("Loading grade summary result: Success") view?.run { - showEmpty(gradeSummaryItems.isEmpty()) - showContent(gradeSummaryItems.isNotEmpty()) + showEmpty(it.isEmpty()) + showContent(it.isNotEmpty()) showErrorView(false) - updateData(gradeSummaryItems, gradeSummaryHeader) + updateData(it) } - analytics.logEvent("load_grade_summary", "items" to gradeSummaryItems.size, "force_refresh" to forceRefresh) + analytics.logEvent("load_grade_summary", "items" to it.size, "force_refresh" to forceRefresh) }) { Timber.i("Loading grade summary result: An exception occurred") errorHandler.dispatch(it) @@ -115,20 +112,11 @@ class GradeSummaryPresenter @Inject constructor( disposable.clear() } - private fun createGradeSummaryItemsAndHeader(gradesSummary: List, averages: List>): Pair, GradeSummaryScrollableHeader> { - return averages.filter { value -> value.second != 0.0 } - .let { filteredAverages -> - gradesSummary.filter { !checkEmpty(it, filteredAverages) } - .map { gradeSummary -> - GradeSummaryItem( - summary = gradeSummary, - average = formatAverage(filteredAverages.singleOrNull { gradeSummary.subject == it.first }?.second ?: .0, "") - ) - }.let { - it to GradeSummaryScrollableHeader( - formatAverage(gradesSummary.calcAverage()), - formatAverage(filteredAverages.map { values -> values.second }.average())) - } + private fun createGradeSummaryItems(gradesSummary: List, averages: List>): List { + return gradesSummary + .filter { !checkEmpty(it, averages) } + .map { gradeSummary -> + gradeSummary.copy(average = averages.singleOrNull { gradeSummary.subject == it.first }?.second ?: .0) } } @@ -137,9 +125,4 @@ class GradeSummaryPresenter @Inject constructor( finalGrade.isBlank() && predictedGrade.isBlank() && averages.singleOrNull { it.first == subject } == null } } - - private fun formatAverage(average: Double, defaultValue: String = "-- --"): String { - return if (average == 0.0) defaultValue - else format(FRANCE, "%.2f", average) - } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryScrollableHeader.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryScrollableHeader.kt deleted file mode 100644 index f1c535c71..000000000 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryScrollableHeader.kt +++ /dev/null @@ -1,53 +0,0 @@ -package io.github.wulkanowy.ui.modules.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/modules/grade/summary/GradeSummaryView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryView.kt index cf3184873..974d91415 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryView.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.ui.modules.grade.summary +import io.github.wulkanowy.data.db.entities.GradeSummary import io.github.wulkanowy.ui.base.BaseView interface GradeSummaryView : BaseView { @@ -12,7 +13,7 @@ interface GradeSummaryView : BaseView { fun initView() - fun updateData(data: List, header: GradeSummaryScrollableHeader) + fun updateData(data: List) fun resetView() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkAdapter.kt new file mode 100644 index 000000000..a87ad18e8 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkAdapter.kt @@ -0,0 +1,67 @@ +package io.github.wulkanowy.ui.modules.homework + +import android.annotation.SuppressLint +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import io.github.wulkanowy.data.db.entities.Homework +import io.github.wulkanowy.databinding.HeaderHomeworkBinding +import io.github.wulkanowy.databinding.ItemHomeworkBinding +import io.github.wulkanowy.utils.toFormattedString +import io.github.wulkanowy.utils.weekDayName +import org.threeten.bp.LocalDate +import javax.inject.Inject + +class HomeworkAdapter @Inject constructor() : RecyclerView.Adapter() { + + var items = emptyList>() + + var onClickListener: (Homework) -> Unit = {} + + override fun getItemCount() = items.size + + override fun getItemViewType(position: Int) = items[position].viewType.id + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + val inflater = LayoutInflater.from(parent.context) + + return when (viewType) { + HomeworkItem.ViewType.HEADER.id -> HeaderViewHolder(HeaderHomeworkBinding.inflate(inflater, parent, false)) + HomeworkItem.ViewType.ITEM.id -> ItemViewHolder(ItemHomeworkBinding.inflate(inflater, parent, false)) + else -> throw IllegalStateException() + } + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + when (holder) { + is HeaderViewHolder -> bindHeaderViewHolder(holder.binding, items[position].value as LocalDate) + is ItemViewHolder -> bindItemViewHolder(holder.binding, items[position].value as Homework) + } + } + + @SuppressLint("DefaultLocale") + private fun bindHeaderViewHolder(binding: HeaderHomeworkBinding, date: LocalDate) { + with(binding) { + homeworkHeaderDay.text = date.weekDayName.capitalize() + homeworkHeaderDate.text = date.toFormattedString() + } + } + + private fun bindItemViewHolder(binding: ItemHomeworkBinding, homework: Homework) { + with(binding) { + homeworkItemSubject.text = homework.subject + homeworkItemTeacher.text = homework.teacher + homeworkItemContent.text = homework.content + homeworkItemCheckImage.visibility = if (homework.isDone) View.VISIBLE else View.GONE + homeworkItemAttachmentImage.visibility = if (!homework.isDone && homework.attachments.isNotEmpty()) View.VISIBLE else View.GONE + + root.setOnClickListener { onClickListener(homework) } + } + } + + class HeaderViewHolder(val binding: HeaderHomeworkBinding) : + RecyclerView.ViewHolder(binding.root) + + class ItemViewHolder(val binding: ItemHomeworkBinding) : RecyclerView.ViewHolder(binding.root) +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkFragment.kt index a011a015c..ba0bf1bef 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkFragment.kt @@ -6,18 +6,15 @@ import android.view.View import android.view.View.GONE import android.view.View.VISIBLE import android.view.ViewGroup -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.common.FlexibleItemDecoration -import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem +import androidx.recyclerview.widget.LinearLayoutManager import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Homework import io.github.wulkanowy.ui.base.BaseFragment +import io.github.wulkanowy.ui.widgets.DividerItemDecoration import io.github.wulkanowy.ui.modules.homework.details.HomeworkDetailsDialog import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.utils.dpToPx -import io.github.wulkanowy.utils.setOnItemClickListener import kotlinx.android.synthetic.main.fragment_homework.* import javax.inject.Inject @@ -27,7 +24,7 @@ class HomeworkFragment : BaseFragment(), HomeworkView, MainView.TitledView { lateinit var presenter: HomeworkPresenter @Inject - lateinit var homeworkAdapter: FlexibleAdapter> + lateinit var homeworkAdapter: HomeworkAdapter companion object { private const val SAVED_DATE_KEY = "CURRENT_DATE" @@ -37,7 +34,7 @@ class HomeworkFragment : BaseFragment(), HomeworkView, MainView.TitledView { override val titleStringId get() = R.string.homework_title - override val isViewEmpty get() = homeworkAdapter.isEmpty + override val isViewEmpty get() = homeworkAdapter.items.isEmpty() override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { return inflater.inflate(R.layout.fragment_homework, container, false) @@ -50,14 +47,12 @@ class HomeworkFragment : BaseFragment(), HomeworkView, MainView.TitledView { } override fun initView() { - homeworkAdapter.setOnItemClickListener(presenter::onHomeworkItemSelected) + homeworkAdapter.onClickListener = presenter::onHomeworkItemSelected with(homeworkRecycler) { - layoutManager = SmoothScrollLinearLayoutManager(context) + layoutManager = LinearLayoutManager(context) adapter = homeworkAdapter - addItemDecoration(FlexibleItemDecoration(context) - .withDefaultDivider() - .withDrawDividerOnLastItem(false)) + addItemDecoration(DividerItemDecoration(context)) } homeworkSwipe.setOnRefreshListener(presenter::onSwipeRefresh) @@ -70,8 +65,11 @@ class HomeworkFragment : BaseFragment(), HomeworkView, MainView.TitledView { homeworkNavContainer.setElevationCompat(requireContext().dpToPx(8f)) } - override fun updateData(data: List) { - homeworkAdapter.updateDataSet(data, true) + override fun updateData(data: List>) { + with(homeworkAdapter) { + items = data + notifyDataSetChanged() + } } fun onReloadList() { @@ -79,7 +77,10 @@ class HomeworkFragment : BaseFragment(), HomeworkView, MainView.TitledView { } override fun clearData() { - homeworkAdapter.clear() + with(homeworkAdapter) { + items = emptyList() + notifyDataSetChanged() + } } override fun updateNavigationWeek(date: String) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkHeader.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkHeader.kt deleted file mode 100644 index 490237883..000000000 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkHeader.kt +++ /dev/null @@ -1,54 +0,0 @@ -package io.github.wulkanowy.ui.modules.homework - -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.ExpandableViewHolder -import io.github.wulkanowy.R -import io.github.wulkanowy.utils.toFormattedString -import io.github.wulkanowy.utils.weekDayName -import kotlinx.android.extensions.LayoutContainer -import kotlinx.android.synthetic.main.header_homework.* -import org.threeten.bp.LocalDate - -class HomeworkHeader(private val date: LocalDate) : AbstractHeaderItem() { - - override fun getLayoutRes() = R.layout.header_homework - - override fun createViewHolder(view: View?, adapter: FlexibleAdapter>?): ViewHolder { - return ViewHolder(view, adapter) - } - - override fun bindViewHolder( - adapter: FlexibleAdapter>?, holder: HomeworkHeader.ViewHolder, - position: Int, payloads: MutableList? - ) { - holder.run { - homeworkHeaderDay.text = date.weekDayName.capitalize() - homeworkHeaderDate.text = date.toFormattedString() - } - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as HomeworkHeader - - if (date != other.date) return false - - return true - } - - override fun hashCode(): Int { - return date.hashCode() - } - - class ViewHolder(view: View?, adapter: FlexibleAdapter>?) : ExpandableViewHolder(view, adapter), - LayoutContainer { - - override val containerView: View - get() = contentView - } -} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkItem.kt index 3c2dd7baf..7e0039583 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkItem.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkItem.kt @@ -1,53 +1,9 @@ package io.github.wulkanowy.ui.modules.homework -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 io.github.wulkanowy.data.db.entities.Homework -import kotlinx.android.extensions.LayoutContainer -import kotlinx.android.synthetic.main.item_homework.* +data class HomeworkItem(val value: T, val viewType: ViewType) { -class HomeworkItem(header: HomeworkHeader, val homework: Homework) : - AbstractSectionableItem(header) { - - override fun getLayoutRes() = R.layout.item_homework - - 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 { - homeworkItemSubject.text = homework.subject - homeworkItemTeacher.text = homework.teacher - homeworkItemContent.text = homework.content - homeworkItemCheckImage.visibility = if (homework.isDone) View.VISIBLE else View.GONE - homeworkItemAttachmentImage.visibility = if (!homework.isDone && homework.attachments.isNotEmpty()) View.VISIBLE else View.GONE - } - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as HomeworkItem - - if (homework != other.homework) return false - return true - } - - override fun hashCode(): Int { - var result = homework.hashCode() - result = 31 * result + homework.id.toInt() - return result - } - - class ViewHolder(val view: View, adapter: FlexibleAdapter<*>) : - FlexibleViewHolder(view, adapter), LayoutContainer { - override val containerView: View - get() = contentView + enum class ViewType(val id: Int) { + HEADER(1), + ITEM(2) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkPresenter.kt index aae18df32..d39efde14 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkPresenter.kt @@ -1,6 +1,5 @@ package io.github.wulkanowy.ui.modules.homework -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import io.github.wulkanowy.data.db.entities.Homework import io.github.wulkanowy.data.repositories.homework.HomeworkRepository import io.github.wulkanowy.data.repositories.semester.SemesterRepository @@ -18,7 +17,6 @@ import io.github.wulkanowy.utils.toFormattedString import org.threeten.bp.LocalDate import org.threeten.bp.LocalDate.ofEpochDay import timber.log.Timber -import java.util.concurrent.TimeUnit import javax.inject.Inject class HomeworkPresenter @Inject constructor( @@ -74,11 +72,9 @@ class HomeworkPresenter @Inject constructor( view?.showErrorDetailsDialog(lastError) } - fun onHomeworkItemSelected(item: AbstractFlexibleItem<*>?) { - if (item is HomeworkItem) { - Timber.i("Select homework item ${item.homework.id}") - view?.showTimetableDialog(item.homework) - } + fun onHomeworkItemSelected(homework: Homework) { + Timber.i("Select homework item ${homework.id}") + view?.showTimetableDialog(homework) } private fun setBaseDateOnHolidays() { @@ -110,8 +106,6 @@ class HomeworkPresenter @Inject constructor( homeworkRepository.getHomework(student, semester, currentDate, currentDate, forceRefresh) } } - .delay(200, TimeUnit.MILLISECONDS) - .map { it.groupBy { homework -> homework.date }.toSortedMap() } .map { createHomeworkItem(it) } .subscribeOn(schedulers.backgroundThread) .observeOn(schedulers.mainThread) @@ -150,12 +144,12 @@ class HomeworkPresenter @Inject constructor( } } - private fun createHomeworkItem(items: Map>): List { - return items.flatMap { - HomeworkHeader(it.key).let { header -> - it.value.reversed().map { item -> HomeworkItem(header, item) } + private fun createHomeworkItem(items: List): List> { + return items.groupBy { it.date }.toSortedMap().map { (date, exams) -> + listOf(HomeworkItem(date, HomeworkItem.ViewType.HEADER)) + exams.reversed().map { exam -> + HomeworkItem(exam, HomeworkItem.ViewType.ITEM) } - } + }.flatten() } private fun reloadView() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkView.kt index 1d241df46..2a678cd4c 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkView.kt @@ -9,7 +9,7 @@ interface HomeworkView : BaseView { fun initView() - fun updateData(data: List) + fun updateData(data: List>) fun clearData() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectAdapter.kt new file mode 100644 index 000000000..7383a5ecb --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectAdapter.kt @@ -0,0 +1,51 @@ +package io.github.wulkanowy.ui.modules.login.studentselect + +import android.annotation.SuppressLint +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.databinding.ItemLoginStudentSelectBinding +import javax.inject.Inject + +class LoginStudentSelectAdapter @Inject constructor() : + RecyclerView.Adapter() { + + var items = emptyList>() + + var onClickListener: (Student, alreadySaved: Boolean) -> Unit = { _, _ -> } + + override fun getItemCount() = items.size + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder( + ItemLoginStudentSelectBinding.inflate(LayoutInflater.from(parent.context), parent, false) + ) + + @SuppressLint("SetTextI18n") + override fun onBindViewHolder(holder: ItemViewHolder, position: Int) { + val (student, alreadySaved) = items[position] + + with(holder.binding) { + loginItemName.text = "${student.studentName} ${student.className}" + loginItemSchool.text = student.schoolName + loginItemName.isEnabled = !alreadySaved + loginItemSchool.isEnabled = !alreadySaved + loginItemCheck.isEnabled = !alreadySaved + loginItemSignedIn.visibility = if (alreadySaved) View.VISIBLE else View.GONE + + root.setOnClickListener { + onClickListener(student, alreadySaved) + + with(loginItemCheck) { + if (isEnabled) { + isChecked = !isChecked + } + } + } + } + } + + class ItemViewHolder(val binding: ItemLoginStudentSelectBinding) : + RecyclerView.ViewHolder(binding.root) +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectFragment.kt index 9860af22f..48a3cc614 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectFragment.kt @@ -6,9 +6,7 @@ import android.view.View import android.view.View.GONE import android.view.View.VISIBLE import android.view.ViewGroup -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem +import androidx.recyclerview.widget.LinearLayoutManager import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.ui.base.BaseFragment @@ -16,7 +14,6 @@ import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.openEmailClient import io.github.wulkanowy.utils.openInternetBrowser -import io.github.wulkanowy.utils.setOnItemClickListener import kotlinx.android.synthetic.main.fragment_login_student_select.* import java.io.Serializable import javax.inject.Inject @@ -27,7 +24,7 @@ class LoginStudentSelectFragment : BaseFragment(), LoginStudentSelectView { lateinit var presenter: LoginStudentSelectPresenter @Inject - lateinit var loginAdapter: FlexibleAdapter> + lateinit var loginAdapter: LoginStudentSelectAdapter @Inject lateinit var appInfo: AppInfo @@ -48,19 +45,22 @@ class LoginStudentSelectFragment : BaseFragment(), LoginStudentSelectView { } override fun initView() { + loginAdapter.onClickListener = presenter::onItemSelected loginStudentSelectSignIn.setOnClickListener { presenter.onSignIn() } - loginAdapter.apply { setOnItemClickListener { presenter.onItemSelected(it) } } loginStudentSelectContactDiscord.setOnClickListener { presenter.onDiscordClick() } loginStudentSelectContactEmail.setOnClickListener { presenter.onEmailClick() } loginStudentSelectRecycler.apply { + layoutManager = LinearLayoutManager(context) adapter = loginAdapter - layoutManager = SmoothScrollLinearLayoutManager(context) } } - override fun updateData(data: List) { - loginAdapter.updateDataSet(data) + override fun updateData(data: List>) { + with(loginAdapter) { + items = data + notifyDataSetChanged() + } } override fun openMainView() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectItem.kt deleted file mode 100644 index 06be61387..000000000 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectItem.kt +++ /dev/null @@ -1,71 +0,0 @@ -package io.github.wulkanowy.ui.modules.login.studentselect - -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.Student -import kotlinx.android.extensions.LayoutContainer -import kotlinx.android.synthetic.main.item_login_student_select.* - -class LoginStudentSelectItem(val student: Student, val alreadySaved: Boolean) : - AbstractFlexibleItem() { - - override fun getLayoutRes() = R.layout.item_login_student_select - - override fun createViewHolder(view: View, adapter: FlexibleAdapter>): ItemViewHolder { - return ItemViewHolder(view, adapter) - } - - @SuppressLint("SetTextI18n") - override fun bindViewHolder(adapter: FlexibleAdapter>, holder: ItemViewHolder, position: Int, payloads: MutableList) { - holder.apply { - loginItemName.text = "${student.studentName} ${student.className}" - loginItemSchool.text = student.schoolName - loginItemName.isEnabled = !alreadySaved - loginItemSchool.isEnabled = !alreadySaved - loginItemCheck.isEnabled = !alreadySaved - loginItemSignedIn.visibility = if (alreadySaved) VISIBLE else GONE - } - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as LoginStudentSelectItem - - if (student != other.student) return false - if (alreadySaved != other.alreadySaved) return false - - return true - } - - override fun hashCode(): Int { - return student.hashCode() - } - - class ItemViewHolder(view: View, val adapter: FlexibleAdapter<*>) : - FlexibleViewHolder(view, adapter), LayoutContainer { - - override val containerView: View - get() = itemView - - init { - loginItemCheck.keyListener = null - } - - override fun onClick(view: View?) { - super.onClick(view) - - if (loginItemCheck.isEnabled) { - loginItemCheck.apply { isChecked = !isChecked } - } - } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenter.kt index 82de5a887..b2d0aed91 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenter.kt @@ -1,6 +1,5 @@ package io.github.wulkanowy.ui.modules.login.studentselect -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter @@ -51,13 +50,14 @@ class LoginStudentSelectPresenter @Inject constructor( if (students.size == 1) registerStudents(students) } - fun onItemSelected(item: AbstractFlexibleItem<*>?) { - if (item is LoginStudentSelectItem && !item.alreadySaved) { - selectedStudents.removeAll { it == item.student } - .let { if (!it) selectedStudents.add(item.student) } + fun onItemSelected(student: Student, alreadySaved: Boolean) { + if (alreadySaved) return - view?.enableSignIn(selectedStudents.isNotEmpty()) - } + selectedStudents + .removeAll { it == student } + .let { if (!it) selectedStudents.add(student) } + + view?.enableSignIn(selectedStudents.isNotEmpty()) } private fun compareStudents(a: Student, b: Student): Boolean { @@ -73,19 +73,17 @@ class LoginStudentSelectPresenter @Inject constructor( disposable.add(studentRepository.getSavedStudents() .map { savedStudents -> students.map { student -> - Pair(student, savedStudents.any { compareStudents(student, it) }) + student to savedStudents.any { compareStudents(student, it) } } } .subscribeOn(schedulers.backgroundThread) .observeOn(schedulers.mainThread) .subscribe({ - view?.updateData(it.map { studentPair -> - LoginStudentSelectItem(studentPair.first, studentPair.second) - }) + view?.updateData(it) }, { errorHandler.dispatch(it) lastError = it - view?.updateData(students.map { student -> LoginStudentSelectItem(student, false) }) + view?.updateData(students.map { student -> student to false }) }) ) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectView.kt index d80b059db..89431dfc6 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectView.kt @@ -1,12 +1,13 @@ package io.github.wulkanowy.ui.modules.login.studentselect +import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.ui.base.BaseView interface LoginStudentSelectView : BaseView { fun initView() - fun updateData(data: List) + fun updateData(data: List>) fun openMainView() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigureActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigureActivity.kt index e8ce3bcfb..84f4e06e1 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigureActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigureActivity.kt @@ -7,13 +7,12 @@ import android.content.Intent import android.os.Bundle import android.widget.Toast import androidx.appcompat.app.AlertDialog -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem +import androidx.recyclerview.widget.LinearLayoutManager import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.ui.base.BaseActivity import io.github.wulkanowy.ui.modules.login.LoginActivity -import io.github.wulkanowy.utils.setOnItemClickListener +import io.github.wulkanowy.ui.base.WidgetConfigureAdapter import kotlinx.android.synthetic.main.activity_widget_configure.* import javax.inject.Inject @@ -21,7 +20,7 @@ class LuckyNumberWidgetConfigureActivity : BaseActivity> + lateinit var configureAdapter: WidgetConfigureAdapter @Inject override lateinit var presenter: LuckyNumberWidgetConfigurePresenter @@ -41,10 +40,10 @@ class LuckyNumberWidgetConfigureActivity : BaseActivity presenter.onThemeSelect(which) } .show() } - override fun updateData(data: List) { - configureAdapter.updateDataSet(data) + override fun updateData(data: List>) { + with(configureAdapter) { + items = data + notifyDataSetChanged() + } } override fun updateLuckyNumberWidget(widgetId: Int) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigureItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigureItem.kt deleted file mode 100644 index e260b7fcb..000000000 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigureItem.kt +++ /dev/null @@ -1,60 +0,0 @@ -package io.github.wulkanowy.ui.modules.luckynumberwidget - -import android.annotation.SuppressLint -import android.view.View -import androidx.core.graphics.ColorUtils -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.Student -import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetConfigureItem -import io.github.wulkanowy.utils.getThemeAttrColor -import kotlinx.android.extensions.LayoutContainer -import kotlinx.android.synthetic.main.item_account.* - -class LuckyNumberWidgetConfigureItem(var student: Student, val isCurrent: Boolean) : - AbstractFlexibleItem() { - - override fun getLayoutRes() = R.layout.item_account - - override fun createViewHolder(view: View, adapter: FlexibleAdapter>) = ViewHolder(view, adapter) - - @SuppressLint("SetTextI18n") - override fun bindViewHolder(adapter: FlexibleAdapter>, holder: ViewHolder, position: Int, payloads: MutableList) { - val context = holder.contentView.context - - val colorImage = if (isCurrent) context.getThemeAttrColor(R.attr.colorPrimary) - else ColorUtils.setAlphaComponent(context.getThemeAttrColor(R.attr.colorOnSurface), 153) - - with(holder) { - accountItemName.text = "${student.studentName} ${student.className}" - accountItemSchool.text = student.schoolName - accountItemImage.setColorFilter(colorImage) - } - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as TimetableWidgetConfigureItem - - if (student != other.student) return false - - return true - } - - override fun hashCode(): Int { - var result = student.hashCode() - result = 31 * result + student.id.toInt() - 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/modules/luckynumberwidget/LuckyNumberWidgetConfigurePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigurePresenter.kt index 6e4716bfa..1bb7447a2 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigurePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigurePresenter.kt @@ -1,6 +1,5 @@ package io.github.wulkanowy.ui.modules.luckynumberwidget -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import io.github.wulkanowy.data.db.SharedPrefProvider import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.repositories.student.StudentRepository @@ -29,11 +28,9 @@ class LuckyNumberWidgetConfigurePresenter @Inject constructor( loadData() } - fun onItemSelect(item: AbstractFlexibleItem<*>) { - if (item is LuckyNumberWidgetConfigureItem) { - selectedStudent = item.student - view?.showThemeDialog() - } + fun onItemSelect(student: Student) { + selectedStudent = student + view?.showThemeDialog() } fun onThemeSelect(index: Int) { @@ -51,7 +48,7 @@ class LuckyNumberWidgetConfigurePresenter @Inject constructor( disposable.add(studentRepository.getSavedStudents(false) .map { it to appWidgetId?.let { id -> sharedPref.getLong(getStudentWidgetKey(id), 0) } } .map { (students, currentStudentId) -> - students.map { student -> LuckyNumberWidgetConfigureItem(student, student.id == currentStudentId) } + students.map { student -> student to (student.id == currentStudentId) } } .subscribeOn(schedulers.backgroundThread) .observeOn(schedulers.mainThread) @@ -59,7 +56,7 @@ class LuckyNumberWidgetConfigurePresenter @Inject constructor( when { it.isEmpty() -> view?.openLoginView() it.size == 1 -> { - selectedStudent = it.single().student + selectedStudent = it.single().first view?.showThemeDialog() } else -> view?.updateData(it) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigureView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigureView.kt index fa4c0cc61..c8c348ed3 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigureView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigureView.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.ui.modules.luckynumberwidget +import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.ui.base.BaseView interface LuckyNumberWidgetConfigureView : BaseView { @@ -8,7 +9,7 @@ interface LuckyNumberWidgetConfigureView : BaseView { fun showThemeDialog() - fun updateData(data: List) + fun updateData(data: List>) fun updateLuckyNumberWidget(widgetId: Int) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt index 864ad4239..e44b47a7b 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt @@ -9,7 +9,6 @@ import android.os.Build.VERSION_CODES.LOLLIPOP import android.os.Bundle import android.view.Menu import android.view.MenuItem -import androidx.core.graphics.ColorUtils import androidx.core.view.ViewCompat import androidx.fragment.app.DialogFragment import androidx.fragment.app.Fragment @@ -115,7 +114,7 @@ class MainActivity : BaseActivity(), MainView { AHBottomNavigationItem(R.string.more_title, R.drawable.ic_main_more, 0) )) accentColor = getThemeAttrColor(R.attr.colorPrimary) - inactiveColor = ColorUtils.setAlphaComponent(getThemeAttrColor(R.attr.colorOnSurface), 153) + inactiveColor = getThemeAttrColor(R.attr.colorOnSurface, 153) defaultBackgroundColor = overlayProvider.get().compositeOverlayWithThemeSurfaceColorIfNeeded(dpToPx(8f)) titleState = ALWAYS_SHOW currentItem = startMenuIndex diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainModule.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainModule.kt index 1610d0298..03f6ac382 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainModule.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainModule.kt @@ -14,7 +14,6 @@ import io.github.wulkanowy.ui.modules.about.license.LicenseModule import io.github.wulkanowy.ui.modules.about.logviewer.LogViewerFragment import io.github.wulkanowy.ui.modules.account.AccountDialog import io.github.wulkanowy.ui.modules.attendance.AttendanceFragment -import io.github.wulkanowy.ui.modules.attendance.AttendanceModule import io.github.wulkanowy.ui.modules.attendance.summary.AttendanceSummaryFragment import io.github.wulkanowy.ui.modules.exam.ExamFragment import io.github.wulkanowy.ui.modules.grade.GradeFragment @@ -26,7 +25,6 @@ import io.github.wulkanowy.ui.modules.message.MessageFragment import io.github.wulkanowy.ui.modules.message.MessageModule import io.github.wulkanowy.ui.modules.message.preview.MessagePreviewFragment import io.github.wulkanowy.ui.modules.mobiledevice.MobileDeviceFragment -import io.github.wulkanowy.ui.modules.mobiledevice.MobileDeviceModule import io.github.wulkanowy.ui.modules.mobiledevice.token.MobileDeviceTokenDialog import io.github.wulkanowy.ui.modules.more.MoreFragment import io.github.wulkanowy.ui.modules.note.NoteFragment @@ -52,7 +50,7 @@ abstract class MainModule { } @PerFragment - @ContributesAndroidInjector(modules = [AttendanceModule::class]) + @ContributesAndroidInjector abstract fun bindAttendanceFragment(): AttendanceFragment @PerFragment @@ -116,7 +114,7 @@ abstract class MainModule { abstract fun bindAccountDialog(): AccountDialog @PerFragment - @ContributesAndroidInjector(modules = [MobileDeviceModule::class]) + @ContributesAndroidInjector abstract fun bindMobileDevices(): MobileDeviceFragment @PerFragment diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessageItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessageItem.kt deleted file mode 100644 index 7e52233c9..000000000 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessageItem.kt +++ /dev/null @@ -1,67 +0,0 @@ -package io.github.wulkanowy.ui.modules.message - -import android.graphics.Typeface.BOLD -import android.graphics.Typeface.NORMAL -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 io.github.wulkanowy.data.db.entities.Message -import io.github.wulkanowy.data.repositories.message.MessageFolder -import io.github.wulkanowy.utils.toFormattedString -import kotlinx.android.extensions.LayoutContainer -import kotlinx.android.synthetic.main.item_message.* - -class MessageItem(val message: Message, private val noSubjectString: String) : - AbstractFlexibleItem() { - - override fun createViewHolder(view: View, adapter: FlexibleAdapter>): ViewHolder { - return ViewHolder(view, adapter) - } - - override fun getLayoutRes() = R.layout.item_message - - override fun bindViewHolder(adapter: FlexibleAdapter>, holder: ViewHolder, position: Int, payloads: MutableList?) { - holder.apply { - val style = if (message.unread) BOLD else NORMAL - - messageItemAuthor.run { - text = if (message.folderId == MessageFolder.SENT.id) message.recipient else message.sender - setTypeface(null, style) - } - messageItemSubject.run { - text = if (message.subject.isNotBlank()) message.subject else noSubjectString - setTypeface(null, style) - } - messageItemDate.run { - text = message.date.toFormattedString() - setTypeface(null, style) - } - with(messageItemAttachmentIcon) { - visibility = if (message.hasAttachments) View.VISIBLE else View.GONE - } - } - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as MessageItem - - if (message != other.message) return false - return true - } - - override fun hashCode(): Int { - return message.hashCode() - } - - class ViewHolder(val 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/modules/message/tab/MessageTabAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabAdapter.kt new file mode 100644 index 000000000..93097fd20 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabAdapter.kt @@ -0,0 +1,53 @@ +package io.github.wulkanowy.ui.modules.message.tab + +import android.graphics.Typeface +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.Message +import io.github.wulkanowy.data.repositories.message.MessageFolder +import io.github.wulkanowy.databinding.ItemMessageBinding +import io.github.wulkanowy.utils.toFormattedString +import javax.inject.Inject + +class MessageTabAdapter @Inject constructor() : + RecyclerView.Adapter() { + + var items = mutableListOf() + + var onClickListener: (Message, position: Int) -> Unit = { _, _ -> } + + override fun getItemCount() = items.size + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder( + ItemMessageBinding.inflate(LayoutInflater.from(parent.context), parent, false) + ) + + override fun onBindViewHolder(holder: ItemViewHolder, position: Int) { + val item = items[position] + + with(holder.binding) { + val style = if (item.unread) Typeface.BOLD else Typeface.NORMAL + + messageItemAuthor.run { + text = if (item.folderId == MessageFolder.SENT.id) item.recipient else item.sender + setTypeface(null, style) + } + messageItemSubject.run { + text = if (item.subject.isNotBlank()) item.subject else context.getString(R.string.message_no_subject) + setTypeface(null, style) + } + messageItemDate.run { + text = item.date.toFormattedString() + setTypeface(null, style) + } + messageItemAttachmentIcon.visibility = if (item.hasAttachments) View.VISIBLE else View.GONE + + root.setOnClickListener { onClickListener(item, position) } + } + } + + class ItemViewHolder(val binding: ItemMessageBinding) : RecyclerView.ViewHolder(binding.root) +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabFragment.kt index ce0fc7805..c39aa3e28 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabFragment.kt @@ -7,19 +7,15 @@ import android.view.View.GONE import android.view.View.INVISIBLE import android.view.View.VISIBLE import android.view.ViewGroup -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.common.FlexibleItemDecoration -import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem +import androidx.recyclerview.widget.LinearLayoutManager import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.repositories.message.MessageFolder import io.github.wulkanowy.ui.base.BaseFragment +import io.github.wulkanowy.ui.widgets.DividerItemDecoration import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.message.MessageFragment -import io.github.wulkanowy.ui.modules.message.MessageItem import io.github.wulkanowy.ui.modules.message.preview.MessagePreviewFragment -import io.github.wulkanowy.utils.setOnItemClickListener import kotlinx.android.synthetic.main.fragment_message_tab.* import javax.inject.Inject @@ -29,7 +25,7 @@ class MessageTabFragment : BaseFragment(), MessageTabView { lateinit var presenter: MessageTabPresenter @Inject - lateinit var tabAdapter: FlexibleAdapter> + lateinit var tabAdapter: MessageTabAdapter companion object { const val MESSAGE_TAB_FOLDER_ID = "message_tab_folder_id" @@ -43,11 +39,8 @@ class MessageTabFragment : BaseFragment(), MessageTabView { } } - override val noSubjectString: String - get() = getString(R.string.message_no_subject) - override val isViewEmpty - get() = tabAdapter.isEmpty + get() = tabAdapter.items.isEmpty() override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { return inflater.inflate(R.layout.fragment_message_tab, container, false) @@ -62,31 +55,30 @@ class MessageTabFragment : BaseFragment(), MessageTabView { } override fun initView() { - tabAdapter.setOnItemClickListener { presenter.onMessageItemSelected(it) } + tabAdapter.onClickListener = presenter::onMessageItemSelected messageTabRecycler.run { - layoutManager = SmoothScrollLinearLayoutManager(context) + layoutManager = LinearLayoutManager(context) adapter = tabAdapter - addItemDecoration(FlexibleItemDecoration(context) - .withDefaultDivider() - .withDrawDividerOnLastItem(false) - ) + addItemDecoration(DividerItemDecoration(context)) } messageTabSwipe.setOnRefreshListener { presenter.onSwipeRefresh() } messageTabErrorRetry.setOnClickListener { presenter.onRetry() } messageTabErrorDetails.setOnClickListener { presenter.onDetailsClick() } } - override fun updateData(data: List) { - tabAdapter.updateDataSet(data) + override fun updateData(data: List) { + with(tabAdapter) { + items = data.toMutableList() + notifyDataSetChanged() + } } - override fun updateItem(item: AbstractFlexibleItem<*>) { - tabAdapter.updateItem(item) - } - - override fun clearView() { - tabAdapter.clear() + override fun updateItem(item: Message, position: Int) { + with(tabAdapter) { + items[position] = item + notifyItemChanged(position) + } } override fun showProgress(show: Boolean) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt index cbdb89b2e..37b45d03d 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt @@ -1,13 +1,12 @@ package io.github.wulkanowy.ui.modules.message.tab -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem +import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.repositories.message.MessageFolder import io.github.wulkanowy.data.repositories.message.MessageRepository import io.github.wulkanowy.data.repositories.semester.SemesterRepository import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler -import io.github.wulkanowy.ui.modules.message.MessageItem import io.github.wulkanowy.utils.FirebaseAnalyticsHelper import io.github.wulkanowy.utils.SchedulersProvider import timber.log.Timber @@ -58,15 +57,13 @@ class MessageTabPresenter @Inject constructor( loadData(forceRefresh) } - fun onMessageItemSelected(item: AbstractFlexibleItem<*>) { - if (item is MessageItem) { - Timber.i("Select message ${item.message.id} item") - view?.run { - openMessage(item.message) - if (item.message.unread) { - item.message.unread = false - updateItem(item) - } + fun onMessageItemSelected(message: Message, position: Int) { + Timber.i("Select message ${message.id} item") + view?.run { + openMessage(message) + if (message.unread) { + message.unread = false + updateItem(message, position) } } } @@ -79,7 +76,6 @@ class MessageTabPresenter @Inject constructor( .flatMap { student -> semesterRepository.getCurrentSemester(student) .flatMap { messageRepository.getMessages(student, it, folder, forceRefresh) } - .map { items -> items.map { MessageItem(it, view?.noSubjectString.orEmpty()) } } } .subscribeOn(schedulers.backgroundThread) .observeOn(schedulers.mainThread) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabView.kt index 92115ed33..94ece8ec2 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabView.kt @@ -1,23 +1,17 @@ package io.github.wulkanowy.ui.modules.message.tab -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.ui.base.BaseView -import io.github.wulkanowy.ui.modules.message.MessageItem interface MessageTabView : BaseView { - val noSubjectString: String - val isViewEmpty: Boolean fun initView() - fun updateData(data: List) + fun updateData(data: List) - fun updateItem(item: AbstractFlexibleItem<*>) - - fun clearView() + fun updateItem(item: Message, position: Int) fun showProgress(show: Boolean) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDeviceAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDeviceAdapter.kt index 27c72ce69..4bc3097d6 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDeviceAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDeviceAdapter.kt @@ -1,10 +1,38 @@ package io.github.wulkanowy.ui.modules.mobiledevice -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.items.IFlexible +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView import io.github.wulkanowy.data.db.entities.MobileDevice +import io.github.wulkanowy.databinding.ItemMobileDeviceBinding +import io.github.wulkanowy.utils.toFormattedString +import javax.inject.Inject -class MobileDeviceAdapter> : FlexibleAdapter(null, null, true) { +class MobileDeviceAdapter @Inject constructor() : + RecyclerView.Adapter() { + + var items = mutableListOf() var onDeviceUnregisterListener: (device: MobileDevice, position: Int) -> Unit = { _, _ -> } + + override fun getItemCount() = items.size + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder( + ItemMobileDeviceBinding.inflate(LayoutInflater.from(parent.context), parent, false) + ) + + override fun onBindViewHolder(holder: ItemViewHolder, position: Int) { + val device = items[position] + + with(holder.binding) { + mobileDeviceItemDate.text = device.date.toFormattedString("dd.MM.yyyy HH:mm:ss") + mobileDeviceItemName.text = device.name + mobileDeviceItemUnregister.setOnClickListener { + onDeviceUnregisterListener(device, position) + } + } + } + + class ItemViewHolder(val binding: ItemMobileDeviceBinding) : + RecyclerView.ViewHolder(binding.root) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDeviceFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDeviceFragment.kt index d47574f60..08ec26755 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDeviceFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDeviceFragment.kt @@ -6,13 +6,13 @@ import android.view.View import android.view.View.GONE import android.view.View.VISIBLE import android.view.ViewGroup -import eu.davidea.flexibleadapter.common.FlexibleItemDecoration -import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager -import eu.davidea.flexibleadapter.helpers.UndoHelper -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem +import androidx.core.view.postDelayed +import androidx.recyclerview.widget.LinearLayoutManager +import com.google.android.material.snackbar.Snackbar import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.MobileDevice import io.github.wulkanowy.ui.base.BaseFragment +import io.github.wulkanowy.ui.widgets.DividerItemDecoration import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.ui.modules.mobiledevice.token.MobileDeviceTokenDialog @@ -25,7 +25,7 @@ class MobileDeviceFragment : BaseFragment(), MobileDeviceView, MainView.TitledVi lateinit var presenter: MobileDevicePresenter @Inject - lateinit var devicesAdapter: MobileDeviceAdapter> + lateinit var devicesAdapter: MobileDeviceAdapter companion object { fun newInstance() = MobileDeviceFragment() @@ -35,7 +35,7 @@ class MobileDeviceFragment : BaseFragment(), MobileDeviceView, MainView.TitledVi get() = R.string.mobile_devices_title override val isViewEmpty: Boolean - get() = devicesAdapter.isEmpty + get() = devicesAdapter.items.isEmpty() override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { return inflater.inflate(R.layout.fragment_mobile_device, container, false) @@ -48,51 +48,55 @@ class MobileDeviceFragment : BaseFragment(), MobileDeviceView, MainView.TitledVi } override fun initView() { + devicesAdapter.onDeviceUnregisterListener = presenter::onUnregisterDevice + with(mobileDevicesRecycler) { - layoutManager = SmoothScrollLinearLayoutManager(context) + layoutManager = LinearLayoutManager(context) adapter = devicesAdapter - addItemDecoration(FlexibleItemDecoration(context) - .withDefaultDivider() - .withDrawDividerOnLastItem(false) - ) - } - with(devicesAdapter) { - isPermanentDelete = false - onDeviceUnregisterListener = presenter::onUnregisterDevice + addItemDecoration(DividerItemDecoration(context)) } + mobileDevicesSwipe.setOnRefreshListener { presenter.onSwipeRefresh() } mobileDevicesErrorRetry.setOnClickListener { presenter.onRetry() } mobileDevicesErrorDetails.setOnClickListener { presenter.onDetailsClick() } mobileDeviceAddButton.setOnClickListener { presenter.onRegisterDevice() } } - override fun updateData(data: List) { - devicesAdapter.updateDataSet(data) - } - - override fun restoreDeleteItem() { - devicesAdapter.restoreDeletedItems() - } - - override fun clearData() { - devicesAdapter.clear() - } - - override fun showUndo(position: Int, device: MobileDevice) { - val onActionListener = object : UndoHelper.OnActionListener { - override fun onActionConfirmed(action: Int, event: Int) { - presenter.onUnregisterConfirmed(device) - } - - override fun onActionCanceled(action: Int, positions: MutableList?) { - presenter.onUnregisterCancelled() - } + override fun updateData(data: List) { + with(devicesAdapter) { + items = data.toMutableList() + notifyDataSetChanged() } + } - UndoHelper(devicesAdapter, onActionListener) - .withConsecutive(false) - .withAction(UndoHelper.Action.REMOVE) - .start(listOf(position), mobileDevicesRecycler, R.string.mobile_device_removed, R.string.all_undo, 3000) + override fun deleteItem(device: MobileDevice, position: Int) { + with(devicesAdapter) { + items.removeAt(position) + notifyItemRemoved(position) + notifyItemRangeChanged(position, itemCount) + } + } + + override fun restoreDeleteItem(device: MobileDevice, position: Int) { + with(devicesAdapter) { + items.add(position, device) + notifyItemInserted(position) + notifyItemRangeChanged(position, itemCount) + } + } + + override fun showUndo(device: MobileDevice, position: Int) { + var confirmed = true + + Snackbar.make(mobileDevicesRecycler, getString(R.string.mobile_device_removed), 3000) + .setAction(R.string.all_undo) { + confirmed = false + presenter.onUnregisterCancelled(device, position) + }.show() + + view?.postDelayed(3000) { + if (confirmed) presenter.onUnregisterConfirmed(device) + } } override fun hideRefresh() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDeviceItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDeviceItem.kt deleted file mode 100644 index 436c2d0e2..000000000 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDeviceItem.kt +++ /dev/null @@ -1,53 +0,0 @@ -package io.github.wulkanowy.ui.modules.mobiledevice - -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 io.github.wulkanowy.data.db.entities.MobileDevice -import io.github.wulkanowy.utils.toFormattedString -import kotlinx.android.extensions.LayoutContainer -import kotlinx.android.synthetic.main.item_mobile_device.* - -class MobileDeviceItem(val device: MobileDevice) : AbstractFlexibleItem() { - - override fun getLayoutRes() = R.layout.item_mobile_device - - 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 { - mobileDeviceItemDate.text = device.date.toFormattedString("dd.MM.yyyy HH:mm:ss") - mobileDeviceItemName.text = device.name - mobileDeviceItemUnregister.setOnClickListener { - (adapter as MobileDeviceAdapter).onDeviceUnregisterListener(device, holder.flexibleAdapterPosition) - } - } - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as MobileDeviceItem - - if (device.id != other.device.id) return false - return true - } - - override fun hashCode(): Int { - var result = device.hashCode() - result = 31 * result + device.id.toInt() - return result - } - - class ViewHolder(val 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/modules/mobiledevice/MobileDeviceModule.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDeviceModule.kt deleted file mode 100644 index 59bbaa9f8..000000000 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDeviceModule.kt +++ /dev/null @@ -1,12 +0,0 @@ -package io.github.wulkanowy.ui.modules.mobiledevice - -import dagger.Module -import dagger.Provides -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem - -@Module -class MobileDeviceModule { - - @Provides - fun provideMobileDeviceFlexibleAdapter() = MobileDeviceAdapter>() -} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDevicePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDevicePresenter.kt index a6a83f8a9..d8c99b221 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDevicePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDevicePresenter.kt @@ -54,7 +54,6 @@ class MobileDevicePresenter @Inject constructor( mobileDeviceRepository.getDevices(student, semester, forceRefresh) } } - .map { items -> items.map { MobileDeviceItem(it) } } .subscribeOn(schedulers.backgroundThread) .observeOn(schedulers.mainThread) .doFinally { @@ -95,14 +94,15 @@ class MobileDevicePresenter @Inject constructor( fun onUnregisterDevice(device: MobileDevice, position: Int) { view?.run { - showUndo(position, device) + deleteItem(device, position) + showUndo(device, position) showEmpty(isViewEmpty) } } - fun onUnregisterCancelled() { + fun onUnregisterCancelled(device: MobileDevice, position: Int) { view?.run { - restoreDeleteItem() + restoreDeleteItem(device, position) showEmpty(isViewEmpty) } } @@ -116,7 +116,6 @@ class MobileDevicePresenter @Inject constructor( .flatMap { mobileDeviceRepository.getDevices(student, semester, it) } } } - .map { items -> items.map { MobileDeviceItem(it) } } .subscribeOn(schedulers.backgroundThread) .observeOn(schedulers.mainThread) .doFinally { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDeviceView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDeviceView.kt index 869b59bb1..ec2d3f87f 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDeviceView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDeviceView.kt @@ -9,14 +9,16 @@ interface MobileDeviceView : BaseView { fun initView() - fun updateData(data: List) + fun updateData(data: List) - fun restoreDeleteItem() + fun deleteItem(device: MobileDevice, position: Int) + + fun restoreDeleteItem(device: MobileDevice, position: Int) + + fun showUndo(device: MobileDevice, position: Int) fun hideRefresh() - fun clearData() - fun showProgress(show: Boolean) fun enableSwipe(enable: Boolean) @@ -29,7 +31,5 @@ interface MobileDeviceView : BaseView { fun setErrorDetails(message: String) - fun showUndo(position: Int, device: MobileDevice) - fun showTokenDialog() } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreAdapter.kt new file mode 100644 index 000000000..70587b0cf --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreAdapter.kt @@ -0,0 +1,34 @@ +package io.github.wulkanowy.ui.modules.more + +import android.graphics.drawable.Drawable +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import io.github.wulkanowy.databinding.ItemMoreBinding +import javax.inject.Inject + +class MoreAdapter @Inject constructor() : RecyclerView.Adapter() { + + var items = emptyList>() + + var onClickListener: (name: String) -> Unit = {} + + override fun getItemCount() = items.size + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder( + ItemMoreBinding.inflate(LayoutInflater.from(parent.context), parent, false) + ) + + override fun onBindViewHolder(holder: ItemViewHolder, position: Int) { + val (title, drawable) = items[position] + + with(holder.binding) { + moreItemTitle.text = title + moreItemImage.setImageDrawable(drawable) + + root.setOnClickListener { onClickListener(title) } + } + } + + class ItemViewHolder(val binding: ItemMoreBinding) : RecyclerView.ViewHolder(binding.root) +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreFragment.kt index ef9c36fab..25cda2b2a 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreFragment.kt @@ -5,9 +5,7 @@ import android.os.Bundle import android.view.LayoutInflater 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 androidx.recyclerview.widget.LinearLayoutManager import io.github.wulkanowy.R import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.modules.about.AboutFragment @@ -21,7 +19,6 @@ import io.github.wulkanowy.ui.modules.note.NoteFragment import io.github.wulkanowy.ui.modules.schoolandteachers.SchoolAndTeachersFragment import io.github.wulkanowy.ui.modules.settings.SettingsFragment import io.github.wulkanowy.utils.getCompatDrawable -import io.github.wulkanowy.utils.setOnItemClickListener import kotlinx.android.synthetic.main.fragment_more.* import javax.inject.Inject @@ -31,7 +28,7 @@ class MoreFragment : BaseFragment(), MoreView, MainView.TitledView, MainView.Mai lateinit var presenter: MorePresenter @Inject - lateinit var moreAdapter: FlexibleAdapter> + lateinit var moreAdapter: MoreAdapter companion object { fun newInstance() = MoreFragment() @@ -74,10 +71,10 @@ class MoreFragment : BaseFragment(), MoreView, MainView.TitledView, MainView.Mai } override fun initView() { - moreAdapter.setOnItemClickListener { presenter.onItemSelected(it) } + moreAdapter.onClickListener = presenter::onItemSelected moreRecycler.apply { - layoutManager = SmoothScrollLinearLayoutManager(context) + layoutManager = LinearLayoutManager(context) adapter = moreAdapter } } @@ -86,8 +83,11 @@ class MoreFragment : BaseFragment(), MoreView, MainView.TitledView, MainView.Mai if (::presenter.isInitialized) presenter.onViewReselected() } - override fun updateData(data: List) { - moreAdapter.updateDataSet(data) + override fun updateData(data: List>) { + with(moreAdapter) { + items = data + notifyDataSetChanged() + } } override fun openMessagesView() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreItem.kt deleted file mode 100644 index 85b604e77..000000000 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreItem.kt +++ /dev/null @@ -1,47 +0,0 @@ -package io.github.wulkanowy.ui.modules.more - -import android.graphics.drawable.Drawable -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.item_more.* - -class MoreItem(val title: String, private val drawable: Drawable?) : AbstractFlexibleItem() { - - override fun getLayoutRes() = R.layout.item_more - - 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 { - moreItemTitle.text = title - moreItemImage.setImageDrawable(drawable) - } - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as MoreItem - - if (title != other.title) return false - - return true - } - - override fun hashCode(): Int { - return title.hashCode() - } - - 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/modules/more/MorePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/more/MorePresenter.kt index 096f89e9b..593645c19 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/more/MorePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/more/MorePresenter.kt @@ -1,6 +1,5 @@ package io.github.wulkanowy.ui.modules.more -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler @@ -21,11 +20,10 @@ class MorePresenter @Inject constructor( loadData() } - fun onItemSelected(item: AbstractFlexibleItem<*>?) { - if (item !is MoreItem) return - Timber.i("Select more item \"${item.title}\"") + fun onItemSelected(title: String) { + Timber.i("Select more item \"${title}\"") view?.run { - when (item.title) { + when (title) { messagesRes?.first -> openMessagesView() homeworkRes?.first -> openHomeworkView() noteRes?.first -> openNoteView() @@ -47,15 +45,15 @@ class MorePresenter @Inject constructor( Timber.i("Load items for more view") view?.run { updateData(listOfNotNull( - messagesRes?.let { MoreItem(it.first, it.second) }, - homeworkRes?.let { MoreItem(it.first, it.second) }, - noteRes?.let { MoreItem(it.first, it.second) }, - luckyNumberRes?.let { MoreItem(it.first, it.second) }, - mobileDevicesRes?.let { MoreItem(it.first, it.second) }, - schoolAndTeachersRes?.let { MoreItem(it.first, it.second) }, - settingsRes?.let { MoreItem(it.first, it.second) }, - aboutRes?.let { MoreItem(it.first, it.second) }) - ) + messagesRes, + homeworkRes, + noteRes, + luckyNumberRes, + mobileDevicesRes, + schoolAndTeachersRes, + settingsRes, + aboutRes + )) } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreView.kt index 41008176d..922afdfd5 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreView.kt @@ -23,7 +23,7 @@ interface MoreView : BaseView { fun initView() - fun updateData(data: List) + fun updateData(data: List>) fun openSettingsView() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteAdapter.kt new file mode 100644 index 000000000..2ffcad949 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteAdapter.kt @@ -0,0 +1,60 @@ +package io.github.wulkanowy.ui.modules.note + +import android.annotation.SuppressLint +import android.graphics.Typeface +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.content.ContextCompat +import io.github.wulkanowy.sdk.scrapper.notes.Note.CategoryType +import androidx.recyclerview.widget.RecyclerView +import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.Note +import io.github.wulkanowy.databinding.ItemNoteBinding +import io.github.wulkanowy.utils.getThemeAttrColor +import io.github.wulkanowy.utils.toFormattedString +import javax.inject.Inject + +class NoteAdapter @Inject constructor() : RecyclerView.Adapter() { + + var items = mutableListOf() + + var onClickListener: (Note, position: Int) -> Unit = { _, _ -> } + + override fun getItemCount() = items.size + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder( + ItemNoteBinding.inflate(LayoutInflater.from(parent.context), parent, false) + ) + + @SuppressLint("SetTextI18n") + override fun onBindViewHolder(holder: ItemViewHolder, position: Int) { + val item = items[position] + + with(holder.binding) { + with(noteItemDate) { + text = item.date.toFormattedString() + setTypeface(null, if (item.isRead) Typeface.NORMAL else Typeface.BOLD) + } + with(noteItemType) { + text = item.category + setTypeface(null, if (item.isRead) Typeface.NORMAL else Typeface.BOLD) + } + with(noteItemPoints) { + text = "${if (item.points > 0) "+" else ""}${item.points}" + visibility = if (item.isPointsShow) View.VISIBLE else View.GONE + setTextColor(when (CategoryType.getByValue(item.categoryType)) { + CategoryType.POSITIVE -> ContextCompat.getColor(context, R.color.note_positive) + CategoryType.NEGATIVE -> ContextCompat.getColor(context, R.color.note_negative) + else -> context.getThemeAttrColor(android.R.attr.textColorPrimary) + }) + } + noteItemTeacher.text = item.teacher + noteItemContent.text = item.content + + root.setOnClickListener { onClickListener(item, position) } + } + } + + class ItemViewHolder(val binding: ItemNoteBinding) : RecyclerView.ViewHolder(binding.root) +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteFragment.kt index e5335e459..b01dc4939 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteFragment.kt @@ -6,16 +6,13 @@ import android.view.View import android.view.View.GONE import android.view.View.VISIBLE import android.view.ViewGroup -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.common.FlexibleItemDecoration -import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem +import androidx.recyclerview.widget.LinearLayoutManager import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Note import io.github.wulkanowy.ui.base.BaseFragment +import io.github.wulkanowy.ui.widgets.DividerItemDecoration import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainView -import io.github.wulkanowy.utils.setOnItemClickListener import kotlinx.android.synthetic.main.fragment_note.* import javax.inject.Inject @@ -25,7 +22,7 @@ class NoteFragment : BaseFragment(), NoteView, MainView.TitledView { lateinit var presenter: NotePresenter @Inject - lateinit var noteAdapter: FlexibleAdapter> + lateinit var noteAdapter: NoteAdapter companion object { fun newInstance() = NoteFragment() @@ -35,7 +32,7 @@ class NoteFragment : BaseFragment(), NoteView, MainView.TitledView { get() = R.string.note_title override val isViewEmpty: Boolean - get() = noteAdapter.isEmpty + get() = noteAdapter.items.isEmpty() override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { return inflater.inflate(R.layout.fragment_note, container, false) @@ -47,17 +44,12 @@ class NoteFragment : BaseFragment(), NoteView, MainView.TitledView { } override fun initView() { - noteAdapter.run { - setOnItemClickListener { presenter.onNoteItemSelected(it) } - } + noteAdapter.onClickListener = presenter::onNoteItemSelected noteRecycler.run { - layoutManager = SmoothScrollLinearLayoutManager(context) + layoutManager = LinearLayoutManager(context) adapter = noteAdapter - addItemDecoration(FlexibleItemDecoration(context) - .withDefaultDivider() - .withDrawDividerOnLastItem(false) - ) + addItemDecoration(DividerItemDecoration(context)) } noteSwipe.setOnRefreshListener { presenter.onSwipeRefresh() } noteErrorRetry.setOnClickListener { presenter.onRetry() } @@ -68,16 +60,25 @@ class NoteFragment : BaseFragment(), NoteView, MainView.TitledView { (activity as? MainActivity)?.showDialogFragment(NoteDialog.newInstance(note)) } - override fun updateData(data: List) { - noteAdapter.updateDataSet(data, true) + override fun updateData(data: List) { + with(noteAdapter) { + items = data.toMutableList() + notifyDataSetChanged() + } } - override fun updateItem(item: AbstractFlexibleItem<*>) { - noteAdapter.updateItem(item) + override fun updateItem(item: Note, position: Int) { + with(noteAdapter) { + items[position] = item + notifyItemChanged(position) + } } override fun clearData() { - noteAdapter.clear() + with(noteAdapter) { + items = mutableListOf() + notifyDataSetChanged() + } } override fun showEmpty(show: Boolean) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteItem.kt deleted file mode 100644 index 53fe6fa91..000000000 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteItem.kt +++ /dev/null @@ -1,77 +0,0 @@ -package io.github.wulkanowy.ui.modules.note - -import android.annotation.SuppressLint -import android.graphics.Typeface.BOLD -import android.graphics.Typeface.NORMAL -import android.view.View -import android.view.View.GONE -import android.view.View.VISIBLE -import androidx.core.content.ContextCompat -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.Note -import io.github.wulkanowy.sdk.scrapper.notes.Note.CategoryType -import io.github.wulkanowy.utils.getThemeAttrColor -import io.github.wulkanowy.utils.toFormattedString -import kotlinx.android.extensions.LayoutContainer -import kotlinx.android.synthetic.main.item_note.* - -class NoteItem(val note: Note) : AbstractFlexibleItem() { - - override fun getLayoutRes() = R.layout.item_note - - 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.apply { - with(noteItemDate) { - text = note.date.toFormattedString() - setTypeface(null, if (note.isRead) NORMAL else BOLD) - } - with(noteItemType) { - text = note.category - setTypeface(null, if (note.isRead) NORMAL else BOLD) - } - with(noteItemPoints) { - text = "${if (note.points > 0) "+" else ""}${note.points}" - visibility = if (note.isPointsShow) VISIBLE else GONE - setTextColor(when(CategoryType.getByValue(note.categoryType)) { - CategoryType.POSITIVE -> ContextCompat.getColor(context, R.color.note_positive) - CategoryType.NEGATIVE -> ContextCompat.getColor(context, R.color.note_negative) - else -> context.getThemeAttrColor(android.R.attr.textColorPrimary) - }) - } - noteItemTeacher.text = note.teacher - noteItemContent.text = note.content - } - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as NoteItem - - if (note != other.note) return false - if (note.id != other.note.id) return false - return true - } - - override fun hashCode(): Int { - var result = note.hashCode() - result = 31 * result + note.id.toInt() - return result - } - - class ViewHolder(val 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/modules/note/NotePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/note/NotePresenter.kt index 7acf37a4c..00df71b96 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/note/NotePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/note/NotePresenter.kt @@ -1,6 +1,5 @@ package io.github.wulkanowy.ui.modules.note -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import io.github.wulkanowy.data.db.entities.Note import io.github.wulkanowy.data.repositories.note.NoteRepository import io.github.wulkanowy.data.repositories.semester.SemesterRepository @@ -53,8 +52,7 @@ class NotePresenter @Inject constructor( disposable.add(studentRepository.getCurrentStudent() .flatMap { semesterRepository.getCurrentSemester(it).map { semester -> semester to it } } .flatMap { noteRepository.getNotes(it.second, it.first, forceRefresh) } - .map { items -> items.map { NoteItem(it) } } - .map { items -> items.sortedByDescending { it.note.date } } + .map { items -> items.sortedByDescending { it.date } } .subscribeOn(schedulers.backgroundThread) .observeOn(schedulers.mainThread) .doFinally { @@ -90,16 +88,14 @@ class NotePresenter @Inject constructor( } } - fun onNoteItemSelected(item: AbstractFlexibleItem<*>?) { - if (item is NoteItem) { - Timber.i("Select note item ${item.note.id}") - view?.run { - showNoteDialog(item.note) - if (!item.note.isRead) { - item.note.isRead = true - updateItem(item) - updateNote(item.note) - } + fun onNoteItemSelected(note: Note, position: Int) { + Timber.i("Select note item ${note.id}") + view?.run { + showNoteDialog(note) + if (!note.isRead) { + note.isRead = true + updateItem(note, position) + updateNote(note) } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteView.kt index a9c9f4f2f..a7cbab8f4 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteView.kt @@ -1,6 +1,5 @@ package io.github.wulkanowy.ui.modules.note -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import io.github.wulkanowy.data.db.entities.Note import io.github.wulkanowy.ui.base.BaseView @@ -10,9 +9,9 @@ interface NoteView : BaseView { fun initView() - fun updateData(data: List) + fun updateData(data: List) - fun updateItem(item: AbstractFlexibleItem<*>) + fun updateItem(item: Note, position: Int) fun clearData() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherAdapter.kt new file mode 100644 index 000000000..8deeae05b --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherAdapter.kt @@ -0,0 +1,40 @@ +package io.github.wulkanowy.ui.modules.schoolandteachers.teacher + +import android.annotation.SuppressLint +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.Teacher +import io.github.wulkanowy.databinding.ItemTeacherBinding +import javax.inject.Inject + +class TeacherAdapter @Inject constructor() : RecyclerView.Adapter() { + + var items = emptyList() + + override fun getItemCount() = items.size + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder( + ItemTeacherBinding.inflate(LayoutInflater.from(parent.context), parent, false) + ) + + @SuppressLint("SetTextI18n") + override fun onBindViewHolder(holder: ItemViewHolder, position: Int) { + val teacher = items[position] + + with(holder.binding) { + teacherItemName.text = teacher.name + teacherItemSubject.text = if (teacher.subject.isNotBlank()) teacher.subject else root.context.getString(R.string.teacher_no_subject) + if (teacher.shortName.isNotBlank()) { + teacherItemShortName.visibility = View.VISIBLE + teacherItemShortName.text = "[${teacher.shortName}]" + } else { + teacherItemShortName.visibility = View.GONE + } + } + } + + class ItemViewHolder(val binding: ItemTeacherBinding) : RecyclerView.ViewHolder(binding.root) +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherFragment.kt index b6bb9c101..7d1003263 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherFragment.kt @@ -6,12 +6,11 @@ import android.view.View import android.view.View.GONE import android.view.View.VISIBLE import android.view.ViewGroup -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.common.FlexibleItemDecoration -import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem +import androidx.recyclerview.widget.LinearLayoutManager import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.Teacher import io.github.wulkanowy.ui.base.BaseFragment +import io.github.wulkanowy.ui.widgets.DividerItemDecoration import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.ui.modules.schoolandteachers.SchoolAndTeachersChildView import io.github.wulkanowy.ui.modules.schoolandteachers.SchoolAndTeachersFragment @@ -25,7 +24,7 @@ class TeacherFragment : BaseFragment(), TeacherView, MainView.TitledView, lateinit var presenter: TeacherPresenter @Inject - lateinit var teacherAdapter: FlexibleAdapter> + lateinit var teacherAdapter: TeacherAdapter companion object { fun newInstance() = TeacherFragment() @@ -37,7 +36,7 @@ class TeacherFragment : BaseFragment(), TeacherView, MainView.TitledView, override val noSubjectString get() = getString(R.string.teacher_no_subject) override val isViewEmpty: Boolean - get() = teacherAdapter.isEmpty + get() = teacherAdapter.items.isEmpty() override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { return inflater.inflate(R.layout.fragment_teacher, container, false) @@ -50,28 +49,20 @@ class TeacherFragment : BaseFragment(), TeacherView, MainView.TitledView, override fun initView() { teacherRecycler.run { - layoutManager = SmoothScrollLinearLayoutManager(context) + layoutManager = LinearLayoutManager(context) adapter = teacherAdapter - addItemDecoration(FlexibleItemDecoration(context) - .withDefaultDivider() - .withDrawDividerOnLastItem(false) - ) + addItemDecoration(DividerItemDecoration(context)) } teacherSwipe.setOnRefreshListener { presenter.onSwipeRefresh() } teacherErrorRetry.setOnClickListener { presenter.onRetry() } teacherErrorDetails.setOnClickListener { presenter.onDetailsClick() } } - override fun updateData(data: List) { - teacherAdapter.updateDataSet(data, true) - } - - override fun updateItem(item: AbstractFlexibleItem<*>) { - teacherAdapter.updateItem(item) - } - - override fun clearData() { - teacherAdapter.clear() + override fun updateData(data: List) { + with(teacherAdapter) { + items = data + notifyDataSetChanged() + } } override fun showEmpty(show: Boolean) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherItem.kt deleted file mode 100644 index 368317744..000000000 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherItem.kt +++ /dev/null @@ -1,59 +0,0 @@ -package io.github.wulkanowy.ui.modules.schoolandteachers.teacher - -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.Teacher -import kotlinx.android.extensions.LayoutContainer -import kotlinx.android.synthetic.main.item_teacher.* - -class TeacherItem(val teacher: Teacher, private val noSubjectText: String) : AbstractFlexibleItem() { - - override fun getLayoutRes() = R.layout.item_teacher - - override fun createViewHolder(view: View, adapter: FlexibleAdapter>): TeacherItem.ViewHolder { - return TeacherItem.ViewHolder(view, adapter) - } - - @SuppressLint("SetTextI18n") - override fun bindViewHolder(adapter: FlexibleAdapter>, holder: TeacherItem.ViewHolder, position: Int, payloads: MutableList?) { - holder.apply { - teacherItemName.text = teacher.name - teacherItemSubject.text = if (teacher.subject.isNotBlank()) teacher.subject else noSubjectText - if (teacher.shortName.isNotBlank()) { - teacherItemShortName.visibility = VISIBLE - teacherItemShortName.text = "[${teacher.shortName}]" - } else { - teacherItemShortName.visibility = GONE - } - } - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as TeacherItem - - if (teacher != other.teacher) return false - if (teacher.id != other.teacher.id) return false - return true - } - - override fun hashCode(): Int { - var result = teacher.hashCode() - result = 31 * result + teacher.id.toInt() - return result - } - - class ViewHolder(val 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/modules/schoolandteachers/teacher/TeacherPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherPresenter.kt index c5b6cfd69..e888308fd 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherPresenter.kt @@ -58,7 +58,6 @@ class TeacherPresenter @Inject constructor( } } .map { it.filter { teacher -> teacher.name.isNotBlank() } } - .map { items -> items.map { TeacherItem(it, view?.noSubjectString.orEmpty()) } } .subscribeOn(schedulers.backgroundThread) .observeOn(schedulers.mainThread) .doFinally { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherView.kt index b16be8ff9..c655bfad8 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherView.kt @@ -1,6 +1,6 @@ package io.github.wulkanowy.ui.modules.schoolandteachers.teacher -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem +import io.github.wulkanowy.data.db.entities.Teacher import io.github.wulkanowy.ui.base.BaseView import io.github.wulkanowy.ui.modules.schoolandteachers.SchoolAndTeachersChildView @@ -12,14 +12,10 @@ interface TeacherView : BaseView, SchoolAndTeachersChildView { fun initView() - fun updateData(data: List) - - fun updateItem(item: AbstractFlexibleItem<*>) + fun updateData(data: List) fun hideRefresh() - fun clearData() - fun showProgress(show: Boolean) fun enableSwipe(enable: Boolean) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableAdapter.kt new file mode 100644 index 000000000..b4b2671e0 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableAdapter.kt @@ -0,0 +1,199 @@ +package io.github.wulkanowy.ui.modules.timetable + +import android.graphics.Paint +import android.view.LayoutInflater +import android.view.ViewGroup +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.Timetable +import io.github.wulkanowy.databinding.ItemTimetableBinding +import io.github.wulkanowy.databinding.ItemTimetableSmallBinding +import io.github.wulkanowy.utils.getThemeAttrColor +import io.github.wulkanowy.utils.toFormattedString +import javax.inject.Inject + +class TimetableAdapter @Inject constructor() : RecyclerView.Adapter() { + + private enum class ViewType(val id: Int) { + ITEM_NORMAL(1), + ITEM_SMALL(2) + } + + var items = emptyList() + + var onClickListener: (Timetable) -> Unit = {} + + var showWholeClassPlan: String = "no" + + override fun getItemCount() = items.size + + override fun getItemViewType(position: Int) = when { + !items[position].isStudentPlan && showWholeClassPlan == "small" -> ViewType.ITEM_SMALL.id + else -> ViewType.ITEM_NORMAL.id + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + val inflater = LayoutInflater.from(parent.context) + + return when (viewType) { + ViewType.ITEM_NORMAL.id -> ItemViewHolder(ItemTimetableBinding.inflate(inflater, parent, false)) + ViewType.ITEM_SMALL.id -> SmallItemViewHolder(ItemTimetableSmallBinding.inflate(inflater, parent, false)) + else -> throw IllegalStateException() + } + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + val lesson = items[position] + + when (holder) { + is ItemViewHolder -> bindNormalView(holder.binding, lesson) + is SmallItemViewHolder -> bindSmallView(holder.binding, lesson) + } + } + + private fun bindSmallView(binding: ItemTimetableSmallBinding, lesson: Timetable) { + with(binding) { + timetableSmallItemNumber.text = lesson.number.toString() + timetableSmallItemSubject.text = lesson.subject + timetableSmallItemTimeStart.text = lesson.start.toFormattedString("HH:mm") + timetableSmallItemRoom.text = lesson.room + timetableSmallItemTeacher.text = lesson.teacher + + bindSubjectStyle(timetableSmallItemSubject, lesson) + bindSmallDescription(binding, lesson) + bindSmallColors(binding, lesson) + + root.setOnClickListener { onClickListener(lesson) } + } + } + + private fun bindNormalView(binding: ItemTimetableBinding, lesson: Timetable) { + with(binding) { + timetableItemNumber.text = lesson.number.toString() + timetableItemSubject.text = lesson.subject + timetableItemRoom.text = lesson.room + timetableItemTeacher.text = lesson.teacher + timetableItemTimeStart.text = lesson.start.toFormattedString("HH:mm") + timetableItemTimeFinish.text = lesson.end.toFormattedString("HH:mm") + + bindSubjectStyle(timetableItemSubject, lesson) + bindNormalDescription(binding, lesson) + bindNormalColors(binding, lesson) + + root.setOnClickListener { onClickListener(lesson) } + } + } + + private fun bindSubjectStyle(subjectView: TextView, lesson: Timetable) { + subjectView.paintFlags = if (lesson.canceled) subjectView.paintFlags or Paint.STRIKE_THRU_TEXT_FLAG + else subjectView.paintFlags and Paint.STRIKE_THRU_TEXT_FLAG.inv() + } + + private fun bindSmallDescription(binding: ItemTimetableSmallBinding, lesson: Timetable) { + with(binding) { + if (lesson.info.isNotBlank() && !lesson.changes) { + timetableSmallItemDescription.visibility = android.view.View.VISIBLE + timetableSmallItemDescription.text = lesson.info + + timetableSmallItemRoom.visibility = android.view.View.GONE + timetableSmallItemTeacher.visibility = android.view.View.GONE + + timetableSmallItemDescription.setTextColor(root.context.getThemeAttrColor( + if (lesson.canceled) R.attr.colorPrimary + else R.attr.colorTimetableChange + )) + } else { + timetableSmallItemDescription.visibility = android.view.View.GONE + timetableSmallItemRoom.visibility = android.view.View.VISIBLE + timetableSmallItemTeacher.visibility = android.view.View.VISIBLE + } + } + } + + private fun bindNormalDescription(binding: ItemTimetableBinding, lesson: Timetable) { + with(binding) { + if (lesson.info.isNotBlank() && !lesson.changes) { + timetableItemDescription.visibility = android.view.View.VISIBLE + timetableItemDescription.text = lesson.info + + timetableItemRoom.visibility = android.view.View.GONE + timetableItemTeacher.visibility = android.view.View.GONE + + timetableItemDescription.setTextColor(root.context.getThemeAttrColor( + if (lesson.canceled) R.attr.colorPrimary + else R.attr.colorTimetableChange + )) + } else { + timetableItemDescription.visibility = android.view.View.GONE + timetableItemRoom.visibility = android.view.View.VISIBLE + timetableItemTeacher.visibility = android.view.View.VISIBLE + } + } + } + + private fun bindSmallColors(binding: ItemTimetableSmallBinding, lesson: Timetable) { + with(binding) { + if (lesson.canceled) { + updateNumberAndSubjectCanceledColor(timetableSmallItemNumber, timetableSmallItemSubject) + } else { + updateNumberColor(timetableSmallItemNumber, lesson) + updateSubjectColor(timetableSmallItemSubject, lesson) + updateRoomColor(timetableSmallItemRoom, lesson) + updateTeacherColor(timetableSmallItemTeacher, lesson) + } + } + } + + private fun bindNormalColors(binding: ItemTimetableBinding, lesson: Timetable) { + with(binding) { + if (lesson.canceled) { + updateNumberAndSubjectCanceledColor(timetableItemNumber, timetableItemSubject) + } else { + updateNumberColor(timetableItemNumber, lesson) + updateSubjectColor(timetableItemSubject, lesson) + updateRoomColor(timetableItemRoom, lesson) + updateTeacherColor(timetableItemTeacher, lesson) + } + } + } + + private fun updateNumberAndSubjectCanceledColor(numberView: TextView, subjectView: TextView) { + numberView.setTextColor(numberView.context.getThemeAttrColor(R.attr.colorPrimary)) + subjectView.setTextColor(subjectView.context.getThemeAttrColor(R.attr.colorPrimary)) + } + + private fun updateNumberColor(numberView: TextView, lesson: Timetable) { + numberView.setTextColor(numberView.context.getThemeAttrColor( + if (lesson.changes || lesson.info.isNotBlank()) R.attr.colorTimetableChange + else android.R.attr.textColorPrimary + )) + } + + private fun updateSubjectColor(subjectView: TextView, lesson: Timetable) { + subjectView.setTextColor(subjectView.context.getThemeAttrColor( + if (lesson.subjectOld.isNotBlank() && lesson.subjectOld != lesson.subject) R.attr.colorTimetableChange + else android.R.attr.textColorPrimary + )) + } + + private fun updateRoomColor(roomView: TextView, lesson: Timetable) { + roomView.setTextColor(roomView.context.getThemeAttrColor( + if (lesson.roomOld.isNotBlank() && lesson.roomOld != lesson.room) R.attr.colorTimetableChange + else android.R.attr.textColorSecondary + )) + } + + private fun updateTeacherColor(teacherTextView: TextView, lesson: Timetable) { + teacherTextView.setTextColor(teacherTextView.context.getThemeAttrColor( + if (lesson.teacherOld.isNotBlank() && lesson.teacherOld != lesson.teacher) R.attr.colorTimetableChange + else android.R.attr.textColorSecondary + )) + } + + private class ItemViewHolder(val binding: ItemTimetableBinding) : + RecyclerView.ViewHolder(binding.root) + + private class SmallItemViewHolder(val binding: ItemTimetableSmallBinding) : + RecyclerView.ViewHolder(binding.root) +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt index ef6057dfa..2c15b9b3f 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt @@ -9,20 +9,17 @@ import android.view.View import android.view.View.GONE import android.view.View.VISIBLE import android.view.ViewGroup +import androidx.recyclerview.widget.LinearLayoutManager import com.wdullaer.materialdatetimepicker.date.DatePickerDialog -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.common.FlexibleItemDecoration -import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Timetable import io.github.wulkanowy.ui.base.BaseFragment +import io.github.wulkanowy.ui.widgets.DividerItemDecoration import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.ui.modules.timetable.completed.CompletedLessonsFragment import io.github.wulkanowy.utils.SchooldaysRangeLimiter import io.github.wulkanowy.utils.dpToPx -import io.github.wulkanowy.utils.setOnItemClickListener import kotlinx.android.synthetic.main.fragment_timetable.* import org.threeten.bp.LocalDate import javax.inject.Inject @@ -34,7 +31,7 @@ class TimetableFragment : BaseFragment(), TimetableView, MainView.MainChildView, lateinit var presenter: TimetablePresenter @Inject - lateinit var timetableAdapter: FlexibleAdapter> + lateinit var timetableAdapter: TimetableAdapter companion object { private const val SAVED_DATE_KEY = "CURRENT_DATE" @@ -44,7 +41,7 @@ class TimetableFragment : BaseFragment(), TimetableView, MainView.MainChildView, override val titleStringId get() = R.string.timetable_title - override val isViewEmpty get() = timetableAdapter.isEmpty + override val isViewEmpty get() = timetableAdapter.items.isEmpty() override val currentStackSize get() = (activity as? MainActivity)?.currentStackSize @@ -64,15 +61,12 @@ class TimetableFragment : BaseFragment(), TimetableView, MainView.MainChildView, } override fun initView() { - timetableAdapter.setOnItemClickListener(presenter::onTimetableItemSelected) + timetableAdapter.onClickListener = presenter::onTimetableItemSelected with(timetableRecycler) { - layoutManager = SmoothScrollLinearLayoutManager(context) + layoutManager = LinearLayoutManager(context) adapter = timetableAdapter - addItemDecoration(FlexibleItemDecoration(context) - .withDefaultDivider() - .withDrawDividerOnLastItem(false) - ) + addItemDecoration(DividerItemDecoration(context)) } timetableSwipe.setOnRefreshListener(presenter::onSwipeRefresh) @@ -95,12 +89,19 @@ class TimetableFragment : BaseFragment(), TimetableView, MainView.MainChildView, else false } - override fun updateData(data: List) { - timetableAdapter.updateDataSet(data, true) + override fun updateData(data: List, showWholeClassPlanType: String) { + with(timetableAdapter) { + items = data + showWholeClassPlan = showWholeClassPlanType + notifyDataSetChanged() + } } override fun clearData() { - timetableAdapter.clear() + with(timetableAdapter) { + items = emptyList() + notifyDataSetChanged() + } } override fun updateNavigationDay(date: String) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableItem.kt deleted file mode 100644 index 5b35b85d1..000000000 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableItem.kt +++ /dev/null @@ -1,197 +0,0 @@ -package io.github.wulkanowy.ui.modules.timetable - -import android.annotation.SuppressLint -import android.graphics.Paint -import android.view.View -import android.view.View.GONE -import android.view.View.VISIBLE -import android.widget.TextView -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.Timetable -import io.github.wulkanowy.utils.getThemeAttrColor -import io.github.wulkanowy.utils.toFormattedString -import kotlinx.android.extensions.LayoutContainer -import kotlinx.android.synthetic.main.item_timetable.* -import kotlinx.android.synthetic.main.item_timetable_small.* - -class TimetableItem(val lesson: Timetable, private val showWholeClassPlan: String) : - AbstractFlexibleItem() { - - override fun getLayoutRes() = when { - showWholeClassPlan == "small" && !lesson.isStudentPlan -> R.layout.item_timetable_small - else -> R.layout.item_timetable - } - - 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?) { - when (itemViewType) { - R.layout.item_timetable_small -> bindSmallView(holder) - R.layout.item_timetable -> bindNormalView(holder) - } - } - - private fun bindSmallView(holder: ViewHolder) { - with(holder) { - timetableSmallItemNumber.text = lesson.number.toString() - timetableSmallItemSubject.text = lesson.subject - timetableSmallItemTimeStart.text = lesson.start.toFormattedString("HH:mm") - timetableSmallItemRoom.text = lesson.room - timetableSmallItemTeacher.text = lesson.teacher - - updateSubjectStyle(timetableSmallItemSubject) - updateSmallDescription(this) - updateSmallColors(this) - } - } - - private fun bindNormalView(holder: ViewHolder) { - with(holder) { - timetableItemNumber.text = lesson.number.toString() - timetableItemSubject.text = lesson.subject - timetableItemRoom.text = lesson.room - timetableItemTeacher.text = lesson.teacher - timetableItemTimeStart.text = lesson.start.toFormattedString("HH:mm") - timetableItemTimeFinish.text = lesson.end.toFormattedString("HH:mm") - - updateSubjectStyle(timetableItemSubject) - updateNormalDescription(this) - updateNormalColors(this) - } - } - - private fun updateSubjectStyle(subjectView: TextView) { - subjectView.paintFlags = if (lesson.canceled) subjectView.paintFlags or Paint.STRIKE_THRU_TEXT_FLAG - else subjectView.paintFlags and Paint.STRIKE_THRU_TEXT_FLAG.inv() - } - - private fun updateSmallDescription(holder: ViewHolder) { - with(holder) { - if (lesson.info.isNotBlank() && !lesson.changes) { - timetableSmallItemDescription.visibility = VISIBLE - timetableSmallItemDescription.text = lesson.info - - timetableSmallItemRoom.visibility = GONE - timetableSmallItemTeacher.visibility = GONE - - timetableSmallItemDescription.setTextColor(holder.view.context.getThemeAttrColor( - if (lesson.canceled) R.attr.colorPrimary - else R.attr.colorTimetableChange - )) - } else { - timetableSmallItemDescription.visibility = GONE - timetableSmallItemRoom.visibility = VISIBLE - timetableSmallItemTeacher.visibility = VISIBLE - } - } - } - - private fun updateNormalDescription(holder: ViewHolder) { - with(holder) { - if (lesson.info.isNotBlank() && !lesson.changes) { - timetableItemDescription.visibility = VISIBLE - timetableItemDescription.text = lesson.info - - timetableItemRoom.visibility = GONE - timetableItemTeacher.visibility = GONE - - timetableItemDescription.setTextColor(holder.view.context.getThemeAttrColor( - if (lesson.canceled) R.attr.colorPrimary - else R.attr.colorTimetableChange - )) - } else { - timetableItemDescription.visibility = GONE - timetableItemRoom.visibility = VISIBLE - timetableItemTeacher.visibility = VISIBLE - } - } - } - - private fun updateSmallColors(holder: ViewHolder) { - with(holder) { - if (lesson.canceled) { - updateNumberAndSubjectCanceledColor(timetableSmallItemNumber, timetableSmallItemSubject) - } else { - updateNumberColor(timetableSmallItemNumber) - updateSubjectColor(timetableSmallItemSubject) - updateRoomColor(timetableSmallItemRoom) - updateTeacherColor(timetableSmallItemTeacher) - } - } - } - - private fun updateNormalColors(holder: ViewHolder) { - with(holder) { - if (lesson.canceled) { - updateNumberAndSubjectCanceledColor(timetableItemNumber, timetableItemSubject) - } else { - updateNumberColor(timetableItemNumber) - updateSubjectColor(timetableItemSubject) - updateRoomColor(timetableItemRoom) - updateTeacherColor(timetableItemTeacher) - } - } - } - - private fun updateNumberAndSubjectCanceledColor(numberView: TextView, subjectView: TextView) { - numberView.setTextColor(numberView.context.getThemeAttrColor(R.attr.colorPrimary)) - subjectView.setTextColor(subjectView.context.getThemeAttrColor(R.attr.colorPrimary)) - } - - private fun updateNumberColor(numberView: TextView) { - numberView.setTextColor(numberView.context.getThemeAttrColor( - if (lesson.changes || lesson.info.isNotBlank()) R.attr.colorTimetableChange - else android.R.attr.textColorPrimary - )) - } - - private fun updateSubjectColor(subjectView: TextView) { - subjectView.setTextColor(subjectView.context.getThemeAttrColor( - if (lesson.subjectOld.isNotBlank() && lesson.subjectOld != lesson.subject) R.attr.colorTimetableChange - else android.R.attr.textColorPrimary - )) - } - - private fun updateRoomColor(roomView: TextView) { - roomView.setTextColor(roomView.context.getThemeAttrColor( - if (lesson.roomOld.isNotBlank() && lesson.roomOld != lesson.room) R.attr.colorTimetableChange - else android.R.attr.textColorSecondary - )) - } - - private fun updateTeacherColor(teacherTextView: TextView) { - teacherTextView.setTextColor(teacherTextView.context.getThemeAttrColor( - if (lesson.teacherOld.isNotBlank() && lesson.teacherOld != lesson.teacher) R.attr.colorTimetableChange - else android.R.attr.textColorSecondary - )) - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as TimetableItem - - if (lesson != other.lesson) return false - return true - } - - override fun hashCode(): Int { - var result = lesson.hashCode() - result = 31 * result + lesson.id.toInt() - return result - } - - class ViewHolder(val 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/modules/timetable/TimetablePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt index 2e9d0a0b3..03d79081d 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt @@ -1,7 +1,6 @@ package io.github.wulkanowy.ui.modules.timetable import android.annotation.SuppressLint -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import io.github.wulkanowy.data.db.entities.Timetable import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository import io.github.wulkanowy.data.repositories.semester.SemesterRepository @@ -22,7 +21,6 @@ import org.threeten.bp.LocalDate.now import org.threeten.bp.LocalDate.of import org.threeten.bp.LocalDate.ofEpochDay import timber.log.Timber -import java.util.concurrent.TimeUnit.MILLISECONDS import javax.inject.Inject class TimetablePresenter @Inject constructor( @@ -102,11 +100,9 @@ class TimetablePresenter @Inject constructor( } } - fun onTimetableItemSelected(item: AbstractFlexibleItem<*>?) { - if (item is TimetableItem) { - Timber.i("Select timetable item ${item.lesson.id}") - view?.showTimetableDialog(item.lesson) - } + fun onTimetableItemSelected(lesson: Timetable) { + Timber.i("Select timetable item ${lesson.id}") + view?.showTimetableDialog(lesson) } fun onCompletedLessonsSwitchSelected(): Boolean { @@ -139,9 +135,8 @@ class TimetablePresenter @Inject constructor( timetableRepository.getTimetable(student, semester, currentDate, currentDate, forceRefresh) } } - .delay(200, MILLISECONDS) - .map { createTimetableItems(it) } - .map { items -> items.sortedWith(compareBy({ it.lesson.number }, { !it.lesson.isStudentPlan })) } + .map { items -> items.filter { if (prefRepository.showWholeClassPlan == "no") it.isStudentPlan else true } } + .map { items -> items.sortedWith(compareBy({ it.number }, { !it.isStudentPlan })) } .subscribeOn(schedulers.backgroundThread) .observeOn(schedulers.mainThread) .doFinally { @@ -154,7 +149,7 @@ class TimetablePresenter @Inject constructor( .subscribe({ Timber.i("Loading timetable result: Success") view?.apply { - updateData(it) + updateData(it, prefRepository.showWholeClassPlan) showEmpty(it.isEmpty()) showErrorView(false) showContent(it.isNotEmpty()) @@ -178,12 +173,6 @@ class TimetablePresenter @Inject constructor( } } - private fun createTimetableItems(items: List): List { - return items - .filter { if (prefRepository.showWholeClassPlan == "no") it.isStudentPlan else true } - .map { TimetableItem(it, prefRepository.showWholeClassPlan) } - } - private fun reloadView() { Timber.i("Reload timetable view with the date ${currentDate.toFormattedString()}") view?.apply { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableView.kt index f730a2712..8399498c6 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableView.kt @@ -12,7 +12,7 @@ interface TimetableView : BaseView { fun initView() - fun updateData(data: List) + fun updateData(data: List, showWholeClassPlanType: String) fun updateNavigationDay(date: String) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonItem.kt deleted file mode 100644 index fd6dc8a66..000000000 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonItem.kt +++ /dev/null @@ -1,58 +0,0 @@ -package io.github.wulkanowy.ui.modules.timetable.completed - -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.CompletedLesson -import io.github.wulkanowy.utils.getThemeAttrColor -import kotlinx.android.extensions.LayoutContainer -import kotlinx.android.synthetic.main.item_completed_lesson.* - -class CompletedLessonItem(val completedLesson: CompletedLesson) : AbstractFlexibleItem() { - - override fun getLayoutRes() = R.layout.item_completed_lesson - - override fun createViewHolder(view: View?, adapter: FlexibleAdapter>?): CompletedLessonItem.ViewHolder { - return CompletedLessonItem.ViewHolder(view, adapter) - } - - override fun bindViewHolder(adapter: FlexibleAdapter>?, holder: CompletedLessonItem.ViewHolder?, position: Int, payloads: MutableList?) { - holder?.apply { - completedLessonItemNumber.text = completedLesson.number.toString() - completedLessonItemNumber.setTextColor(holder.contentView.context.getThemeAttrColor( - if (completedLesson.substitution.isNotEmpty()) R.attr.colorTimetableChange - else android.R.attr.textColorPrimary - )) - completedLessonItemSubject.text = completedLesson.subject - completedLessonItemTopic.text = completedLesson.topic - completedLessonItemAlert.visibility = if (completedLesson.substitution.isNotEmpty()) VISIBLE else GONE - } - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as CompletedLessonItem - - if (completedLesson != other.completedLesson) return false - - return true - } - - override fun hashCode(): Int { - return completedLesson.hashCode() - } - - 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/modules/timetable/completed/CompletedLessonsAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsAdapter.kt new file mode 100644 index 000000000..3399a8a23 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsAdapter.kt @@ -0,0 +1,45 @@ +package io.github.wulkanowy.ui.modules.timetable.completed + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.CompletedLesson +import io.github.wulkanowy.databinding.ItemCompletedLessonBinding +import io.github.wulkanowy.utils.getThemeAttrColor +import javax.inject.Inject + +class CompletedLessonsAdapter @Inject constructor() : + RecyclerView.Adapter() { + + var items = emptyList() + + var onClickListener: (CompletedLesson) -> Unit = {} + + override fun getItemCount() = items.size + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder( + ItemCompletedLessonBinding.inflate(LayoutInflater.from(parent.context), parent, false) + ) + + override fun onBindViewHolder(holder: ItemViewHolder, position: Int) { + val item = items[position] + + with(holder.binding) { + completedLessonItemNumber.text = item.number.toString() + completedLessonItemNumber.setTextColor(root.context.getThemeAttrColor( + if (item.substitution.isNotEmpty()) R.attr.colorTimetableChange + else android.R.attr.textColorPrimary + )) + completedLessonItemSubject.text = item.subject + completedLessonItemTopic.text = item.topic + completedLessonItemAlert.visibility = if (item.substitution.isNotEmpty()) View.VISIBLE else View.GONE + + root.setOnClickListener { onClickListener(item) } + } + } + + class ItemViewHolder(val binding: ItemCompletedLessonBinding) : + RecyclerView.ViewHolder(binding.root) +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsFragment.kt index 60c16b2dc..544343a42 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsFragment.kt @@ -7,19 +7,17 @@ import android.view.View.GONE import android.view.View.INVISIBLE import android.view.View.VISIBLE import android.view.ViewGroup +import androidx.recyclerview.widget.LinearLayoutManager import com.wdullaer.materialdatetimepicker.date.DatePickerDialog -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.data.db.entities.CompletedLesson import io.github.wulkanowy.ui.base.BaseFragment +import io.github.wulkanowy.ui.widgets.DividerItemDecoration import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.utils.SchooldaysRangeLimiter import io.github.wulkanowy.utils.dpToPx import io.github.wulkanowy.utils.getCompatDrawable -import io.github.wulkanowy.utils.setOnItemClickListener import kotlinx.android.synthetic.main.fragment_timetable_completed.* import org.threeten.bp.LocalDate import javax.inject.Inject @@ -30,7 +28,7 @@ class CompletedLessonsFragment : BaseFragment(), CompletedLessonsView, MainView. lateinit var presenter: CompletedLessonsPresenter @Inject - lateinit var completedLessonsAdapter: FlexibleAdapter> + lateinit var completedLessonsAdapter: CompletedLessonsAdapter companion object { private const val SAVED_DATE_KEY = "CURRENT_DATE" @@ -40,7 +38,7 @@ class CompletedLessonsFragment : BaseFragment(), CompletedLessonsView, MainView. override val titleStringId get() = R.string.completed_lessons_title - override val isViewEmpty get() = completedLessonsAdapter.isEmpty + override val isViewEmpty get() = completedLessonsAdapter.items.isEmpty() override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { return inflater.inflate(R.layout.fragment_timetable_completed, container, false) @@ -53,11 +51,12 @@ class CompletedLessonsFragment : BaseFragment(), CompletedLessonsView, MainView. } override fun initView() { - completedLessonsAdapter.setOnItemClickListener(presenter::onCompletedLessonsItemSelected) + completedLessonsAdapter.onClickListener = presenter::onCompletedLessonsItemSelected with(completedLessonsRecycler) { - layoutManager = SmoothScrollLinearLayoutManager(context) + layoutManager = LinearLayoutManager(context) adapter = completedLessonsAdapter + addItemDecoration(DividerItemDecoration(context)) } completedLessonsSwipe.setOnRefreshListener(presenter::onSwipeRefresh) @@ -71,12 +70,18 @@ class CompletedLessonsFragment : BaseFragment(), CompletedLessonsView, MainView. completedLessonsNavContainer.setElevationCompat(requireContext().dpToPx(8f)) } - override fun updateData(data: List) { - completedLessonsAdapter.updateDataSet(data, true) + override fun updateData(data: List) { + with(completedLessonsAdapter) { + items = data + notifyDataSetChanged() + } } override fun clearData() { - completedLessonsAdapter.clear() + with(completedLessonsAdapter) { + items = emptyList() + notifyDataSetChanged() + } } override fun updateNavigationDay(date: String) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsPresenter.kt index 001fed97b..355411eb5 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsPresenter.kt @@ -1,7 +1,7 @@ package io.github.wulkanowy.ui.modules.timetable.completed import android.annotation.SuppressLint -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem +import io.github.wulkanowy.data.db.entities.CompletedLesson import io.github.wulkanowy.data.repositories.completedlessons.CompletedLessonsRepository import io.github.wulkanowy.data.repositories.semester.SemesterRepository import io.github.wulkanowy.data.repositories.student.StudentRepository @@ -18,7 +18,6 @@ import org.threeten.bp.LocalDate import org.threeten.bp.LocalDate.now import org.threeten.bp.LocalDate.ofEpochDay import timber.log.Timber -import java.util.concurrent.TimeUnit import javax.inject.Inject class CompletedLessonsPresenter @Inject constructor( @@ -87,11 +86,9 @@ class CompletedLessonsPresenter @Inject constructor( view?.showErrorDetailsDialog(lastError) } - fun onCompletedLessonsItemSelected(item: AbstractFlexibleItem<*>?) { - if (item is CompletedLessonItem) { - Timber.i("Select completed lessons item ${item.completedLesson.id}") - view?.showCompletedLessonDialog(item.completedLesson) - } + fun onCompletedLessonsItemSelected(completedLesson: CompletedLesson) { + Timber.i("Select completed lessons item ${completedLesson.id}") + view?.showCompletedLessonDialog(completedLesson) } private fun setBaseDateOnHolidays() { @@ -119,9 +116,7 @@ class CompletedLessonsPresenter @Inject constructor( completedLessonsRepository.getCompletedLessons(student, semester, currentDate, currentDate, forceRefresh) } } - .delay(200, TimeUnit.MILLISECONDS) - .map { items -> items.map { CompletedLessonItem(it) } } - .map { items -> items.sortedBy { it.completedLesson.number } } + .map { items -> items.sortedBy { it.number } } .subscribeOn(schedulers.backgroundThread) .observeOn(schedulers.mainThread) .doFinally { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsView.kt index a6a327e2d..170e19694 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsView.kt @@ -10,7 +10,7 @@ interface CompletedLessonsView : BaseView { fun initView() - fun updateData(data: List) + fun updateData(data: List) fun clearData() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigureActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigureActivity.kt index 7636637f4..9208cd9e6 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigureActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigureActivity.kt @@ -8,14 +8,13 @@ import android.os.Bundle import android.widget.Toast import android.widget.Toast.LENGTH_LONG import androidx.appcompat.app.AlertDialog -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem +import androidx.recyclerview.widget.LinearLayoutManager import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.ui.base.BaseActivity +import io.github.wulkanowy.ui.base.WidgetConfigureAdapter import io.github.wulkanowy.ui.modules.login.LoginActivity import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.EXTRA_FROM_PROVIDER -import io.github.wulkanowy.utils.setOnItemClickListener import kotlinx.android.synthetic.main.activity_widget_configure.* import javax.inject.Inject @@ -23,7 +22,7 @@ class TimetableWidgetConfigureActivity : BaseActivity> + lateinit var configureAdapter: WidgetConfigureAdapter @Inject override lateinit var presenter: TimetableWidgetConfigurePresenter @@ -43,10 +42,10 @@ class TimetableWidgetConfigureActivity : BaseActivity) { - configureAdapter.updateDataSet(data) + override fun updateData(data: List>) { + with(configureAdapter) { + items = data + notifyDataSetChanged() + } } override fun updateTimetableWidget(widgetId: Int) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigureItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigureItem.kt deleted file mode 100644 index a3338c684..000000000 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigureItem.kt +++ /dev/null @@ -1,59 +0,0 @@ -package io.github.wulkanowy.ui.modules.timetablewidget - -import android.annotation.SuppressLint -import android.view.View -import androidx.core.graphics.ColorUtils -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.Student -import io.github.wulkanowy.utils.getThemeAttrColor -import kotlinx.android.extensions.LayoutContainer -import kotlinx.android.synthetic.main.item_account.* - -class TimetableWidgetConfigureItem(val student: Student, private val isCurrent: Boolean) : - AbstractFlexibleItem() { - - override fun getLayoutRes() = R.layout.item_account - - override fun createViewHolder(view: View, adapter: FlexibleAdapter>) = ViewHolder(view, adapter) - - @SuppressLint("SetTextI18n") - override fun bindViewHolder(adapter: FlexibleAdapter>, holder: ViewHolder, position: Int, payloads: MutableList) { - val context = holder.contentView.context - - val colorImage = if (isCurrent) context.getThemeAttrColor(R.attr.colorPrimary) - else ColorUtils.setAlphaComponent(context.getThemeAttrColor(R.attr.colorOnSurface), 153) - - with(holder) { - accountItemName.text = "${student.studentName} ${student.className}" - accountItemSchool.text = student.schoolName - accountItemImage.setColorFilter(colorImage) - } - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as TimetableWidgetConfigureItem - - if (student != other.student) return false - - return true - } - - override fun hashCode(): Int { - var result = student.hashCode() - result = 31 * result + student.id.toInt() - 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/modules/timetablewidget/TimetableWidgetConfigurePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigurePresenter.kt index e16851083..57dde824c 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigurePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigurePresenter.kt @@ -1,6 +1,5 @@ package io.github.wulkanowy.ui.modules.timetablewidget -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import io.github.wulkanowy.data.db.SharedPrefProvider import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.repositories.student.StudentRepository @@ -32,13 +31,11 @@ class TimetableWidgetConfigurePresenter @Inject constructor( loadData() } - fun onItemSelect(item: AbstractFlexibleItem<*>) { - if (item is TimetableWidgetConfigureItem) { - selectedStudent = item.student + fun onItemSelect(student: Student) { + selectedStudent = student - if (isFromProvider) registerStudent(selectedStudent) - else view?.showThemeDialog() - } + if (isFromProvider) registerStudent(selectedStudent) + else view?.showThemeDialog() } fun onThemeSelect(index: Int) { @@ -48,7 +45,7 @@ class TimetableWidgetConfigurePresenter @Inject constructor( registerStudent(selectedStudent) } - fun onDismissThemeView(){ + fun onDismissThemeView() { view?.finishView() } @@ -56,7 +53,7 @@ class TimetableWidgetConfigurePresenter @Inject constructor( disposable.add(studentRepository.getSavedStudents(false) .map { it to appWidgetId?.let { id -> sharedPref.getLong(getStudentWidgetKey(id), 0) } } .map { (students, currentStudentId) -> - students.map { student -> TimetableWidgetConfigureItem(student, student.id == currentStudentId) } + students.map { student -> student to (student.id == currentStudentId) } } .subscribeOn(schedulers.backgroundThread) .observeOn(schedulers.mainThread) @@ -64,7 +61,7 @@ class TimetableWidgetConfigurePresenter @Inject constructor( when { it.isEmpty() -> view?.openLoginView() it.size == 1 && !isFromProvider -> { - selectedStudent = it.single().student + selectedStudent = it.single().first view?.showThemeDialog() } else -> view?.updateData(it) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigureView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigureView.kt index 7cac892d9..056225ab5 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigureView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigureView.kt @@ -1,12 +1,13 @@ package io.github.wulkanowy.ui.modules.timetablewidget +import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.ui.base.BaseView interface TimetableWidgetConfigureView : BaseView { fun initView() - fun updateData(data: List) + fun updateData(data: List>) fun updateTimetableWidget(widgetId: Int) diff --git a/app/src/main/java/io/github/wulkanowy/ui/widgets/DividerItemDecoration.kt b/app/src/main/java/io/github/wulkanowy/ui/widgets/DividerItemDecoration.kt new file mode 100644 index 000000000..b0b6999eb --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/widgets/DividerItemDecoration.kt @@ -0,0 +1,27 @@ +package io.github.wulkanowy.ui.widgets + +import android.content.Context +import android.graphics.Canvas +import android.view.View +import androidx.recyclerview.widget.DividerItemDecoration +import androidx.recyclerview.widget.RecyclerView + +class DividerItemDecoration(context: Context) : DividerItemDecoration(context, VERTICAL) { + + override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) { + canvas.save() + val dividerLeft = parent.paddingLeft + val dividerRight = parent.width - parent.paddingRight + val childCount = parent.childCount + + for (i in 0..childCount - 2) { + val child: View = parent.getChildAt(i) + val params = child.layoutParams as RecyclerView.LayoutParams + val dividerTop: Int = child.bottom + params.bottomMargin + val dividerBottom = dividerTop + drawable!!.intrinsicHeight + drawable?.setBounds(dividerLeft, dividerTop, dividerRight, dividerBottom) + drawable?.draw(canvas) + } + canvas.restore() + } +} diff --git a/app/src/main/java/io/github/wulkanowy/utils/ContextExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/ContextExtension.kt index 582b1c835..489e7e6fb 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/ContextExtension.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/ContextExtension.kt @@ -9,6 +9,8 @@ import androidx.annotation.ColorInt import androidx.annotation.ColorRes import androidx.annotation.DrawableRes import androidx.core.content.ContextCompat +import androidx.core.graphics.ColorUtils +import io.github.wulkanowy.R @ColorInt fun Context.getThemeAttrColor(@AttrRes colorAttr: Int): Int { @@ -20,6 +22,11 @@ fun Context.getThemeAttrColor(@AttrRes colorAttr: Int): Int { } } +@ColorInt +fun Context.getThemeAttrColor(@AttrRes colorAttr: Int, alpha: Int): Int { + return ColorUtils.setAlphaComponent(getThemeAttrColor(colorAttr), alpha) +} + @ColorInt fun Context.getCompatColor(@ColorRes colorRes: Int) = ContextCompat.getColor(this, colorRes) diff --git a/app/src/main/java/io/github/wulkanowy/utils/FlexibleAdapterExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/FlexibleAdapterExtension.kt deleted file mode 100644 index bd6867a38..000000000 --- a/app/src/main/java/io/github/wulkanowy/utils/FlexibleAdapterExtension.kt +++ /dev/null @@ -1,11 +0,0 @@ -package io.github.wulkanowy.utils - -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem - -inline fun FlexibleAdapter<*>.setOnItemClickListener(crossinline listener: (item: AbstractFlexibleItem<*>) -> Unit) { - addListener(FlexibleAdapter.OnItemClickListener { _, position -> - listener(getItem(position) as AbstractFlexibleItem<*>) - true - }) -} \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_account.xml b/app/src/main/res/layout/dialog_account.xml index f2ee0bca7..6e975c80d 100644 --- a/app/src/main/res/layout/dialog_account.xml +++ b/app/src/main/res/layout/dialog_account.xml @@ -2,7 +2,7 @@ diff --git a/app/src/main/res/layout/header_exam.xml b/app/src/main/res/layout/header_exam.xml index 3a2e38c59..43f089f73 100644 --- a/app/src/main/res/layout/header_exam.xml +++ b/app/src/main/res/layout/header_exam.xml @@ -10,7 +10,7 @@ android:paddingTop="10dp" android:paddingRight="20dp" android:paddingBottom="10dp" - tools:context=".ui.modules.exam.ExamHeader"> + tools:context=".ui.modules.exam.ExamAdapter"> diff --git a/app/src/main/res/layout/header_grade_details.xml b/app/src/main/res/layout/header_grade_details.xml index 75e01745f..f2ba9a8c9 100644 --- a/app/src/main/res/layout/header_grade_details.xml +++ b/app/src/main/res/layout/header_grade_details.xml @@ -1,81 +1,93 @@ - + android:background="?android:windowBackground"> - + android:background="?selectableItemBackground" + tools:context=".ui.modules.grade.details.GradeDetailsAdapter"> - + - + - + - - + + + + + + + diff --git a/app/src/main/res/layout/header_homework.xml b/app/src/main/res/layout/header_homework.xml index 207fcdb40..e1c6608f4 100644 --- a/app/src/main/res/layout/header_homework.xml +++ b/app/src/main/res/layout/header_homework.xml @@ -10,7 +10,7 @@ android:paddingTop="10dp" android:paddingRight="20dp" android:paddingBottom="10dp" - tools:context=".ui.modules.homework.HomeworkHeader"> + tools:context=".ui.modules.homework.HomeworkAdapter"> diff --git a/app/src/main/res/layout/item_about.xml b/app/src/main/res/layout/item_about.xml index b5dc3758c..f988c47ba 100644 --- a/app/src/main/res/layout/item_about.xml +++ b/app/src/main/res/layout/item_about.xml @@ -4,17 +4,16 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="72dp" - android:background="?selectableItemBackground"> + android:background="?selectableItemBackground" + tools:context=".ui.modules.about.AboutAdapter"> @@ -24,9 +23,7 @@ android:layout_width="wrap_content" android:layout_height="32dp" android:layout_marginEnd="16dp" - android:layout_marginRight="16dp" android:layout_toEndOf="@id/aboutItemImage" - android:layout_toRightOf="@id/aboutItemImage" android:ellipsize="end" android:gravity="bottom" android:maxLines="1" @@ -39,13 +36,11 @@ android:layout_height="20dp" android:layout_below="@id/aboutItemTitle" android:layout_marginEnd="16dp" - android:layout_marginRight="16dp" android:layout_toEndOf="@id/aboutItemImage" - android:layout_toRightOf="@id/aboutItemImage" android:ellipsize="end" android:gravity="bottom" android:maxLines="1" android:textColor="?android:textColorSecondary" android:textSize="14sp" tools:text="@tools:sample/lorem" /> - \ No newline at end of file + diff --git a/app/src/main/res/layout/item_account.xml b/app/src/main/res/layout/item_account.xml index 309be8121..614e4d2df 100644 --- a/app/src/main/res/layout/item_account.xml +++ b/app/src/main/res/layout/item_account.xml @@ -1,5 +1,5 @@ - + tools:context=".ui.modules.account.AccountAdapter"> - + diff --git a/app/src/main/res/layout/item_attendance.xml b/app/src/main/res/layout/item_attendance.xml index 56bcab440..6917c6ce0 100644 --- a/app/src/main/res/layout/item_attendance.xml +++ b/app/src/main/res/layout/item_attendance.xml @@ -11,7 +11,7 @@ android:paddingEnd="12dp" android:paddingRight="12dp" android:paddingBottom="7dp" - tools:context=".ui.modules.attendance.AttendanceItem"> + tools:context=".ui.modules.attendance.AttendanceAdapter"> + tools:text="5" + tools:visibility="visible" /> + android:orientation="vertical" + tools:context=".ui.modules.attendance.summary.AttendanceSummaryAdapter"> @@ -46,7 +46,6 @@ android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginStart="20dp" - android:layout_marginLeft="20dp" android:layout_weight="1" android:text="@string/attendance_present" android:textSize="14sp" /> @@ -57,9 +56,7 @@ android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginStart="10dp" - android:layout_marginLeft="10dp" android:layout_marginEnd="25dp" - android:layout_marginRight="25dp" android:gravity="end" android:textSize="12sp" tools:text="50" /> @@ -77,7 +74,6 @@ android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginStart="20dp" - android:layout_marginLeft="20dp" android:layout_weight="1" android:text="@string/attendance_absence_unexcused" android:textSize="14sp" /> @@ -88,9 +84,7 @@ android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginStart="10dp" - android:layout_marginLeft="10dp" android:layout_marginEnd="25dp" - android:layout_marginRight="25dp" android:gravity="end" android:textSize="12sp" tools:text="0" /> @@ -108,7 +102,6 @@ android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginStart="20dp" - android:layout_marginLeft="20dp" android:layout_weight="1" android:text="@string/attendance_absence_excused" android:textSize="14sp" /> @@ -119,9 +112,7 @@ android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginStart="10dp" - android:layout_marginLeft="10dp" android:layout_marginEnd="25dp" - android:layout_marginRight="25dp" android:gravity="end" android:textSize="12sp" tools:text="25" /> @@ -139,7 +130,6 @@ android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginStart="20dp" - android:layout_marginLeft="20dp" android:layout_weight="1" android:text="@string/attendance_absence_school" android:textSize="14sp" /> @@ -150,9 +140,7 @@ android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginStart="10dp" - android:layout_marginLeft="10dp" android:layout_marginEnd="25dp" - android:layout_marginRight="25dp" android:gravity="end" android:textSize="12sp" tools:text="0" /> @@ -170,7 +158,6 @@ android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginStart="20dp" - android:layout_marginLeft="20dp" android:layout_weight="1" android:text="@string/attendance_exemption" android:textSize="14sp" /> @@ -181,9 +168,7 @@ android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginStart="10dp" - android:layout_marginLeft="10dp" android:layout_marginEnd="25dp" - android:layout_marginRight="25dp" android:gravity="end" android:textSize="12sp" tools:text="6" /> @@ -201,7 +186,6 @@ android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginStart="20dp" - android:layout_marginLeft="20dp" android:layout_weight="1" android:text="@string/attendance_unexcused_lateness" android:textSize="14sp" /> @@ -212,9 +196,7 @@ android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginStart="10dp" - android:layout_marginLeft="10dp" android:layout_marginEnd="25dp" - android:layout_marginRight="25dp" android:gravity="end" android:textSize="12sp" tools:text="0" /> @@ -232,7 +214,6 @@ android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginStart="20dp" - android:layout_marginLeft="20dp" android:layout_weight="1" android:text="@string/attendance_excused_lateness" android:textSize="14sp" /> @@ -243,9 +224,7 @@ android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginStart="10dp" - android:layout_marginLeft="10dp" android:layout_marginEnd="25dp" - android:layout_marginRight="25dp" android:gravity="end" android:textSize="12sp" tools:text="0" /> diff --git a/app/src/main/res/layout/item_completed_lesson.xml b/app/src/main/res/layout/item_completed_lesson.xml index 8b49ba765..b9beec804 100644 --- a/app/src/main/res/layout/item_completed_lesson.xml +++ b/app/src/main/res/layout/item_completed_lesson.xml @@ -3,16 +3,12 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" - android:background="@drawable/ic_all_divider" - android:foreground="?selectableItemBackground" + android:background="?selectableItemBackground" android:paddingStart="8dp" - android:paddingLeft="8dp" android:paddingTop="6dp" android:paddingEnd="12dp" - android:paddingRight="12dp" android:paddingBottom="6dp" - tools:context=".ui.modules.timetable.completed.CompletedLessonItem" - tools:ignore="UnusedAttribute"> + tools:context=".ui.modules.timetable.completed.CompletedLessonsAdapter"> + android:minHeight="56dp" + android:orientation="vertical" + tools:context=".ui.modules.about.contributor.ContributorAdapter"> + android:layout_marginBottom="8dp" + android:contentDescription="@string/contributor_avatar_description" /> + tools:text="@tools:sample/lorem" /> diff --git a/app/src/main/res/layout/item_exam.xml b/app/src/main/res/layout/item_exam.xml index 459a7ecb3..bd1d47312 100644 --- a/app/src/main/res/layout/item_exam.xml +++ b/app/src/main/res/layout/item_exam.xml @@ -4,14 +4,13 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:background="?selectableItemBackground" - tools:context=".ui.modules.exam.ExamItem"> + tools:context=".ui.modules.exam.ExamAdapter"> @@ -22,7 +21,6 @@ android:layout_height="wrap_content" android:layout_below="@id/examItemSubject" android:layout_alignStart="@id/examItemSubject" - android:layout_alignLeft="@id/examItemSubject" android:layout_marginTop="5dp" android:layout_marginBottom="5dp" android:textSize="13sp" @@ -34,15 +32,11 @@ android:layout_height="wrap_content" android:layout_below="@id/examItemSubject" android:layout_alignParentEnd="true" - android:layout_alignParentRight="true" android:layout_marginStart="10dp" - android:layout_marginLeft="10dp" android:layout_marginTop="5dp" android:layout_marginEnd="20dp" - android:layout_marginRight="20dp" android:layout_marginBottom="10dp" android:layout_toEndOf="@id/examItemType" - android:layout_toRightOf="@id/examItemType" android:gravity="end" android:textSize="13sp" tools:text="@tools:sample/lorem" /> diff --git a/app/src/main/res/layout/item_grade_details.xml b/app/src/main/res/layout/item_grade_details.xml index d6d99d9b8..ccc968c08 100644 --- a/app/src/main/res/layout/item_grade_details.xml +++ b/app/src/main/res/layout/item_grade_details.xml @@ -1,82 +1,82 @@ - + android:background="?android:windowBackground"> - - - + android:background="?selectableItemBackground" + android:paddingStart="12dp" + android:paddingTop="7dp" + android:paddingEnd="12dp" + android:paddingBottom="7dp" + tools:context=".ui.modules.grade.details.GradeDetailsAdapter"> - + - + - - + + + + + + + + diff --git a/app/src/main/res/layout/item_grade_summary.xml b/app/src/main/res/layout/item_grade_summary.xml index a3a49ab62..85a78571e 100644 --- a/app/src/main/res/layout/item_grade_summary.xml +++ b/app/src/main/res/layout/item_grade_summary.xml @@ -4,7 +4,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" - tools:context=".ui.modules.grade.summary.GradeSummaryItem"> + tools:context=".ui.modules.grade.summary.GradeSummaryAdapter"> @@ -48,7 +47,6 @@ android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginStart="20dp" - android:layout_marginLeft="20dp" android:layout_weight="1" android:text="@string/grade_summary_points" android:textSize="14sp" /> @@ -59,9 +57,7 @@ android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginStart="10dp" - android:layout_marginLeft="10dp" android:layout_marginEnd="25dp" - android:layout_marginRight="25dp" android:gravity="end" android:textSize="12sp" tools:text="123/150" /> @@ -79,7 +75,6 @@ android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginStart="20dp" - android:layout_marginLeft="20dp" android:layout_weight="1" android:text="@string/grade_summary_predicted_grade" android:textSize="14sp" /> @@ -90,9 +85,7 @@ android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginStart="10dp" - android:layout_marginLeft="10dp" android:layout_marginEnd="25dp" - android:layout_marginRight="25dp" android:gravity="end" android:textSize="12sp" tools:text="5" /> @@ -110,7 +103,6 @@ android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginStart="20dp" - android:layout_marginLeft="20dp" android:layout_weight="1" android:text="@string/grade_summary_final_grade" android:textSize="14sp" /> @@ -121,9 +113,7 @@ android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginStart="10dp" - android:layout_marginLeft="10dp" android:layout_marginEnd="25dp" - android:layout_marginRight="25dp" android:gravity="end" android:textSize="12sp" tools:text="5" /> diff --git a/app/src/main/res/layout/item_homework.xml b/app/src/main/res/layout/item_homework.xml index 62566e9c2..89c3c6885 100644 --- a/app/src/main/res/layout/item_homework.xml +++ b/app/src/main/res/layout/item_homework.xml @@ -5,7 +5,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:background="?selectableItemBackground" - tools:context=".ui.modules.homework.HomeworkItem"> + tools:context=".ui.modules.homework.HomeworkAdapter"> + android:paddingRight="16dp" + tools:context=".ui.modules.about.license.LicenseAdapter"> + tools:context=".ui.modules.login.studentselect.LoginStudentSelectAdapter"> diff --git a/app/src/main/res/layout/item_message.xml b/app/src/main/res/layout/item_message.xml index 4379c2fc2..111de88c9 100644 --- a/app/src/main/res/layout/item_message.xml +++ b/app/src/main/res/layout/item_message.xml @@ -9,14 +9,13 @@ android:paddingTop="10dp" android:paddingRight="16dp" android:paddingBottom="10dp" - tools:context=".ui.modules.message.MessageItem"> + tools:context=".ui.modules.message.tab.MessageTabAdapter"> + tools:context=".ui.modules.mobiledevice.MobileDeviceAdapter"> + tools:context=".ui.modules.more.MoreAdapter"> diff --git a/app/src/main/res/layout/item_note.xml b/app/src/main/res/layout/item_note.xml index 3a56f9de7..660f32e00 100644 --- a/app/src/main/res/layout/item_note.xml +++ b/app/src/main/res/layout/item_note.xml @@ -5,7 +5,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:background="?selectableItemBackground" - tools:context=".ui.modules.note.NoteItem"> + tools:context=".ui.modules.note.NoteAdapter"> + tools:context=".ui.modules.schoolandteachers.teacher.TeacherAdapter"> + tools:context=".ui.modules.timetable.TimetableAdapter"> @@ -86,35 +80,30 @@ android:id="@+id/timetableItemTeacher" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_alignBottom="@+id/timetableItemNumber" android:layout_marginStart="10dp" - android:layout_marginLeft="10dp" android:layout_marginEnd="16dp" - android:layout_marginRight="16dp" - android:layout_toEndOf="@id/timetableItemRoom" - android:layout_toRightOf="@id/timetableItemRoom" - android:maxLines="1" android:ellipsize="end" + android:maxLines="1" android:textColor="?android:textColorSecondary" android:textSize="13sp" + app:layout_constraintBottom_toBottomOf="@+id/timetableItemNumber" + app:layout_constraintStart_toEndOf="@id/timetableItemRoom" tools:text="Agata Kowalska - Błaszczyk" tools:visibility="gone" /> - + diff --git a/app/src/main/res/layout/item_timetable_small.xml b/app/src/main/res/layout/item_timetable_small.xml index 7a5ab033f..98a213ec3 100644 --- a/app/src/main/res/layout/item_timetable_small.xml +++ b/app/src/main/res/layout/item_timetable_small.xml @@ -3,14 +3,13 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:background="?selectableItemBackground" - tools:context=".ui.modules.timetable.TimetableItem"> + tools:context=".ui.modules.timetable.TimetableAdapter"> @@ -37,9 +34,7 @@ android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_marginStart="10dp" - android:layout_marginLeft="10dp" android:layout_toEndOf="@+id/timetableSmallItemTimeStart" - android:layout_toRightOf="@+id/timetableSmallItemTimeStart" android:ellipsize="end" android:maxLines="1" android:textColor="?android:textColorPrimary" @@ -51,10 +46,8 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="10dp" - android:layout_marginLeft="10dp" android:layout_marginTop="1dp" android:layout_toEndOf="@+id/timetableSmallItemSubject" - android:layout_toRightOf="@+id/timetableSmallItemSubject" android:maxLines="1" android:textColor="?android:textColorSecondary" android:textSize="13sp" @@ -65,10 +58,8 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="10dp" - android:layout_marginLeft="10dp" android:layout_marginTop="1dp" android:layout_toEndOf="@id/timetableSmallItemRoom" - android:layout_toRightOf="@id/timetableSmallItemRoom" android:ellipsize="end" android:maxLines="1" android:textColor="?android:textColorSecondary" @@ -80,12 +71,9 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="10dp" - android:layout_marginLeft="10dp" android:layout_marginTop="1dp" android:layout_marginEnd="16dp" - android:layout_marginRight="16dp" android:layout_toEndOf="@id/timetableSmallItemTeacher" - android:layout_toRightOf="@id/timetableSmallItemTeacher" android:ellipsize="end" android:singleLine="true" android:textColor="?android:textColorSecondary" diff --git a/app/src/main/res/layout/scrollable_header_about.xml b/app/src/main/res/layout/scrollable_header_about.xml index 9b7bb7324..72186bf79 100644 --- a/app/src/main/res/layout/scrollable_header_about.xml +++ b/app/src/main/res/layout/scrollable_header_about.xml @@ -4,14 +4,14 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="104dp" - android:orientation="vertical"> + android:orientation="vertical" + tools:context=".ui.modules.about.AboutAdapter"> + android:orientation="vertical" + tools:context=".ui.modules.attendance.summary.AttendanceSummaryAdapter"> + android:orientation="horizontal" + tools:context=".ui.modules.grade.summary.GradeSummaryAdapter"> - \ No newline at end of file + diff --git a/app/src/test/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenterTest.kt b/app/src/test/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenterTest.kt index 258dc54fe..121391dee 100644 --- a/app/src/test/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenterTest.kt +++ b/app/src/test/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenterTest.kt @@ -53,7 +53,7 @@ class LoginStudentSelectPresenterTest { fun onSelectedStudentTest() { doReturn(Single.just(listOf(1L))).`when`(studentRepository).saveStudents(listOf(testStudent)) doReturn(Completable.complete()).`when`(studentRepository).switchStudent(testStudent) - presenter.onItemSelected(LoginStudentSelectItem(testStudent, false)) + presenter.onItemSelected(testStudent, false) presenter.onSignIn() verify(loginStudentSelectView).showContent(false) verify(loginStudentSelectView).showProgress(true) @@ -64,7 +64,7 @@ class LoginStudentSelectPresenterTest { fun onSelectedStudentErrorTest() { doReturn(Single.error(testException)).`when`(studentRepository).saveStudents(listOf(testStudent)) doReturn(Completable.complete()).`when`(studentRepository).logoutStudent(testStudent) - presenter.onItemSelected(LoginStudentSelectItem(testStudent, false)) + presenter.onItemSelected(testStudent, false) presenter.onSignIn() verify(loginStudentSelectView).showContent(false) verify(loginStudentSelectView).showProgress(true) From 98f2f0e74f718bad524cca01170ca3b11528d49c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Fri, 1 May 2020 19:00:42 +0200 Subject: [PATCH 09/85] Migrate from fabric to firebase crashlytics (#789) --- .travis.yml | 9 +--- app/build.gradle | 14 ++--- app/src/main/AndroidManifest.xml | 27 ++++++++-- .../java/io/github/wulkanowy/WulkanowyApp.kt | 2 - .../java/io/github/wulkanowy/utils/AppInfo.kt | 3 -- .../wulkanowy/utils/CrashlyticsUtils.kt | 54 +++++++++++-------- build.gradle | 6 +-- gradle/wrapper/gradle-wrapper.properties | 2 +- 8 files changed, 66 insertions(+), 51 deletions(-) diff --git a/.travis.yml b/.travis.yml index 514e37c0b..88b711a6c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -48,20 +48,15 @@ before_script: script: - ./gradlew dependencies --stacktrace --daemon - fossa --no-ansi || true - #- ./gradlew lintPlayRelease -x fabricGenerateResourcesPlayRelease --stacktrace --daemon - - ./gradlew -Pcoverage testPlayDebugUnitTest -x fabricGenerateResourcesPlay --stacktrace --daemon + - ./gradlew -Pcoverage testPlayDebugUnitTest --stacktrace --daemon - ./gradlew -Pcoverage createFdroidDebugCoverageReport --stacktrace --daemon - ./gradlew -Pcoverage jacocoTestReport --stacktrace --daemon - - if [ -z ${SONAR_HOST+x} ]; then echo "sonar scan skipped"; else - git fetch --unshallow; - ./gradlew sonarqube -x test -x lint -x fabricGenerateResourcesPlayRelease -x fabricGenerateResourcesFdroidRelease -Dsonar.host.url=$SONAR_HOST -Dsonar.organization=$SONAR_ORG -Dsonar.login=$SONAR_KEY -Dsonar.branch.name=${TRAVIS_PULL_REQUEST_BRANCH:-$TRAVIS_BRANCH} --stacktrace --daemon; - fi - | if [ $TRAVIS_TAG ]; then gpg --yes --batch --passphrase=$SERVICES_ENCRYPT_KEY ./app/src/release/google-services.json.gpg; gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/key.p12.gpg; gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/upload-key.jks.gpg; - ./gradlew publishPlayRelease -PenableCrashlytics --stacktrace; + ./gradlew publishPlayRelease -PenableFirebase --stacktrace; fi after_success: diff --git a/app/build.gradle b/app/build.gradle index 7c311dfc2..3d8fd082d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -2,7 +2,7 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-kapt' apply plugin: 'kotlin-android-extensions' -apply plugin: 'io.fabric' +apply plugin: 'com.google.firebase.crashlytics' apply plugin: 'com.github.triplet.play' apply plugin: 'com.mikepenz.aboutlibraries.plugin' apply from: 'jacoco.gradle' @@ -24,8 +24,7 @@ android { testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary = true manifestPlaceholders = [ - fabric_api_key : System.getenv("FABRIC_API_KEY") ?: "null", - crashlytics_enabled: project.hasProperty("enableCrashlytics") + firebase_enabled: project.hasProperty("enableFirebase") ] javaCompileOptions { annotationProcessorOptions { @@ -52,18 +51,16 @@ android { buildTypes { release { - buildConfigField "boolean", "CRASHLYTICS_ENABLED", "true" minifyEnabled true shrinkResources true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' signingConfig signingConfigs.release } debug { - buildConfigField "boolean", "CRASHLYTICS_ENABLED", project.hasProperty("enableCrashlytics") ? "true" : "false" applicationIdSuffix ".dev" versionNameSuffix "-dev" testCoverageEnabled = project.hasProperty('coverage') - ext.enableCrashlytics = project.hasProperty("enableCrashlytics") + ext.enableCrashlytics = project.hasProperty("enableFirebase") } } @@ -75,7 +72,6 @@ android { } fdroid { - buildConfigField "boolean", "CRASHLYTICS_ENABLED", "false" dimension "platform" } } @@ -190,8 +186,8 @@ dependencies { playImplementation 'com.google.firebase:firebase-analytics:17.4.0' playImplementation 'com.google.firebase:firebase-inappmessaging-display-ktx:19.0.6' playImplementation "com.google.firebase:firebase-inappmessaging-ktx:19.0.6" - playImplementation "com.google.firebase:firebase-messaging:20.1.0" - playImplementation "com.crashlytics.sdk.android:crashlytics:2.10.1" + playImplementation 'com.google.firebase:firebase-messaging:20.1.6' + playImplementation 'com.google.firebase:firebase-crashlytics:17.0.0' playImplementation 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava' releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker" diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 42c754756..4478f4087 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -107,12 +107,33 @@ android:resource="@xml/provider_paths" /> + + + + + android:name="firebase_analytics_collection_enabled" + android:value="${firebase_enabled}" /> + + + + android:value="${firebase_enabled}" /> + + + + Date: Sun, 3 May 2020 15:06:49 +0200 Subject: [PATCH 10/85] Add nav bar color in night style (#790) --- app/src/main/res/values-night/styles.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/res/values-night/styles.xml b/app/src/main/res/values-night/styles.xml index 492645381..a06a46f00 100644 --- a/app/src/main/res/values-night/styles.xml +++ b/app/src/main/res/values-night/styles.xml @@ -8,6 +8,8 @@ @color/colorDividerInverse ?colorSurface ?android:textColorPrimary + @android:color/black + @android:color/black false true From 70fc51a0b5687dad97af278699bf75cf2d1bf06b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Wed, 6 May 2020 23:07:13 +0200 Subject: [PATCH 11/85] Update Crowdin configuration file --- crowdin.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 crowdin.yml diff --git a/crowdin.yml b/crowdin.yml new file mode 100644 index 000000000..ed131b89d --- /dev/null +++ b/crowdin.yml @@ -0,0 +1,14 @@ +files: + - source: /app/src/main/res/values/*.xml + ignore: + - /app/src/main/res/values/styles.xml + - /app/src/main/res/values/api_hosts.xml + - /app/src/main/res/values/api_symbols.xml + - /app/src/main/res/values/attrs.xml + - /app/src/main/res/values/colors.xml + - /app/src/main/res/values/dimens.xml + - /app/src/main/res/values/preferences_keys.xml + - /app/src/main/res/values/preferences_defaults.xml + translation: /app/src/main/res/values-%android_code%/%original_file_name%.xml + translate_attributes: 0 + content_segmentation: 0 From ec80f939f10245d17697dfa3ed05be7014170414 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Wed, 6 May 2020 23:10:19 +0200 Subject: [PATCH 12/85] Update Crowdin configuration file --- crowdin.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crowdin.yml b/crowdin.yml index ed131b89d..2399a56b5 100644 --- a/crowdin.yml +++ b/crowdin.yml @@ -9,6 +9,6 @@ files: - /app/src/main/res/values/dimens.xml - /app/src/main/res/values/preferences_keys.xml - /app/src/main/res/values/preferences_defaults.xml - translation: /app/src/main/res/values-%android_code%/%original_file_name%.xml + translation: /app/src/main/res/values-%android_code%/%original_file_name% translate_attributes: 0 content_segmentation: 0 From 8eb0c0351bb961146bb846aa0dda05aaab8e95f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sun, 10 May 2020 10:39:10 +0200 Subject: [PATCH 13/85] Use view binding instead of kotlin synthetics (#791) --- .bettercodehub.yml | 2 +- app/build.gradle | 5 - .../github/wulkanowy/ui/base/BaseActivity.kt | 8 +- .../wulkanowy/ui/base/BaseDialogFragment.kt | 10 +- .../github/wulkanowy/ui/base/BaseFragment.kt | 16 +- .../github/wulkanowy/ui/base/ErrorDialog.kt | 48 ++--- .../ui/modules/about/AboutFragment.kt | 14 +- .../about/contributor/ContributorFragment.kt | 24 +-- .../modules/about/license/LicenseFragment.kt | 20 +- .../about/logviewer/LogViewerFragment.kt | 24 +-- .../ui/modules/account/AccountDialog.kt | 18 +- .../ui/modules/attendance/AttendanceDialog.kt | 20 +- .../modules/attendance/AttendanceFragment.kt | 67 ++++--- .../summary/AttendanceSummaryAdapter.kt | 2 +- .../summary/AttendanceSummaryFragment.kt | 51 +++-- .../wulkanowy/ui/modules/exam/ExamDialog.kt | 22 ++- .../wulkanowy/ui/modules/exam/ExamFragment.kt | 58 +++--- .../ui/modules/grade/GradeFragment.kt | 45 +++-- .../grade/details/GradeDetailsDialog.kt | 75 +++---- .../grade/details/GradeDetailsFragment.kt | 51 +++-- .../statistics/GradeStatisticsAdapter.kt | 47 +++-- .../statistics/GradeStatisticsFragment.kt | 61 +++--- .../ui/modules/grade/statistics/ViewType.kt | 8 +- .../grade/summary/GradeSummaryFragment.kt | 47 +++-- .../ui/modules/homework/HomeworkFragment.kt | 56 +++--- .../details/HomeworkDetailsAdapter.kt | 28 +-- .../homework/details/HomeworkDetailsDialog.kt | 18 +- .../ui/modules/login/LoginActivity.kt | 19 +- .../login/advanced/LoginAdvancedFragment.kt | 183 +++++++++--------- .../modules/login/form/LoginFormFragment.kt | 84 ++++---- .../login/recover/LoginRecoverFragment.kt | 75 ++++--- .../LoginStudentSelectFragment.kt | 40 ++-- .../login/symbol/LoginSymbolFragment.kt | 54 +++--- .../luckynumber/LuckyNumberFragment.kt | 47 +++-- .../LuckyNumberWidgetConfigureActivity.kt | 11 +- .../wulkanowy/ui/modules/main/MainActivity.kt | 16 +- .../ui/modules/message/MessageFragment.kt | 36 ++-- .../message/preview/MessagePreviewAdapter.kt | 44 +++-- .../message/preview/MessagePreviewFragment.kt | 33 ++-- .../message/send/SendMessageActivity.kt | 60 +++--- .../modules/message/tab/MessageTabFragment.kt | 44 ++--- .../mobiledevice/MobileDeviceFragment.kt | 49 +++-- .../token/MobileDeviceTokenDialog.kt | 20 +- .../wulkanowy/ui/modules/more/MoreFragment.kt | 18 +- .../wulkanowy/ui/modules/note/NoteDialog.kt | 23 ++- .../wulkanowy/ui/modules/note/NoteFragment.kt | 42 ++-- .../SchoolAndTeachersFragment.kt | 35 ++-- .../school/SchoolFragment.kt | 60 +++--- .../teacher/TeacherFragment.kt | 43 ++-- .../ui/modules/settings/SettingsFragment.kt | 8 +- .../ui/modules/splash/SplashActivity.kt | 3 +- .../ui/modules/timetable/TimetableDialog.kt | 138 +++++++------ .../ui/modules/timetable/TimetableFragment.kt | 61 +++--- .../completed/CompletedLessonDialog.kt | 50 +++-- .../completed/CompletedLessonsFragment.kt | 65 ++++--- .../TimetableWidgetConfigureActivity.kt | 9 +- .../wulkanowy/utils/LifecycleAwareVariable.kt | 55 ++++++ app/src/main/res/layout/fragment_about.xml | 4 +- ...t_creator.xml => fragment_contributor.xml} | 0 59 files changed, 1178 insertions(+), 1096 deletions(-) create mode 100644 app/src/main/java/io/github/wulkanowy/utils/LifecycleAwareVariable.kt rename app/src/main/res/layout/{fragment_creator.xml => fragment_contributor.xml} (100%) diff --git a/.bettercodehub.yml b/.bettercodehub.yml index 349f7675a..d7be51695 100644 --- a/.bettercodehub.yml +++ b/.bettercodehub.yml @@ -1,3 +1,3 @@ -component_depth: 8 +component_depth: 10 languages: - kotlin diff --git a/app/build.gradle b/app/build.gradle index 3d8fd082d..412dd1bd5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,7 +1,6 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-kapt' -apply plugin: 'kotlin-android-extensions' apply plugin: 'com.google.firebase.crashlytics' apply plugin: 'com.github.triplet.play' apply plugin: 'com.mikepenz.aboutlibraries.plugin' @@ -103,10 +102,6 @@ android { } } -androidExtensions { - experimental = true -} - play { serviceAccountEmail = System.getenv("PLAY_SERVICE_ACCOUNT_EMAIL") ?: "jan@fakelog.cf" serviceAccountCredentials = file('key.p12') 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 ee74832fd..f20fb22f3 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 @@ -11,6 +11,7 @@ import android.widget.Toast import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatDelegate +import androidx.viewbinding.ViewBinding import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar.LENGTH_LONG import dagger.android.AndroidInjection @@ -20,10 +21,13 @@ import io.github.wulkanowy.R import io.github.wulkanowy.ui.modules.login.LoginActivity import io.github.wulkanowy.utils.FragmentLifecycleLogger import io.github.wulkanowy.utils.getThemeAttrColor +import io.github.wulkanowy.utils.lifecycleAwareVariable import javax.inject.Inject -abstract class BaseActivity> : AppCompatActivity(), BaseView, - HasAndroidInjector { +abstract class BaseActivity, VB : ViewBinding> : + AppCompatActivity(), BaseView, HasAndroidInjector { + + protected var binding: VB by lifecycleAwareVariable() @Inject lateinit var androidInjector: DispatchingAndroidInjector diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BaseDialogFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/base/BaseDialogFragment.kt index fdc463714..0279dccf4 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/BaseDialogFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/BaseDialogFragment.kt @@ -1,9 +1,13 @@ package io.github.wulkanowy.ui.base import android.widget.Toast +import androidx.viewbinding.ViewBinding import dagger.android.support.DaggerAppCompatDialogFragment +import io.github.wulkanowy.utils.lifecycleAwareVariable -abstract class BaseDialogFragment : DaggerAppCompatDialogFragment(), BaseView { +abstract class BaseDialogFragment : DaggerAppCompatDialogFragment(), BaseView { + + protected var binding: VB by lifecycleAwareVariable() override fun showError(text: String, error: Throwable) { showMessage(text) @@ -14,11 +18,11 @@ abstract class BaseDialogFragment : DaggerAppCompatDialogFragment(), BaseView { } override fun showExpiredDialog() { - (activity as? BaseActivity<*>)?.showExpiredDialog() + (activity as? BaseActivity<*, *>)?.showExpiredDialog() } override fun openClearLoginView() { - (activity as? BaseActivity<*>)?.openClearLoginView() + (activity as? BaseActivity<*, *>)?.openClearLoginView() } override fun showErrorDetailsDialog(error: Throwable) { 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 2f5878d0d..83f787654 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,12 +1,18 @@ package io.github.wulkanowy.ui.base import android.view.View +import androidx.annotation.LayoutRes +import androidx.viewbinding.ViewBinding import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar.LENGTH_LONG import dagger.android.support.DaggerFragment import io.github.wulkanowy.R +import io.github.wulkanowy.utils.lifecycleAwareVariable -abstract class BaseFragment : DaggerFragment(), BaseView { +abstract class BaseFragment(@LayoutRes layoutId: Int) : DaggerFragment(layoutId), + BaseView { + + protected var binding: VB by lifecycleAwareVariable() protected var messageContainer: View? = null @@ -16,7 +22,7 @@ abstract class BaseFragment : DaggerFragment(), BaseView { .setAction(R.string.all_details) { if (isAdded) showErrorDetailsDialog(error) } .show() } else { - (activity as? BaseActivity<*>)?.showError(text, error) + (activity as? BaseActivity<*, *>)?.showError(text, error) } } @@ -28,15 +34,15 @@ abstract class BaseFragment : DaggerFragment(), BaseView { if (messageContainer != null) { Snackbar.make(messageContainer!!, text, LENGTH_LONG).show() } else { - (activity as? BaseActivity<*>)?.showMessage(text) + (activity as? BaseActivity<*, *>)?.showMessage(text) } } override fun showExpiredDialog() { - (activity as? BaseActivity<*>)?.showExpiredDialog() + (activity as? BaseActivity<*, *>)?.showExpiredDialog() } override fun openClearLoginView() { - (activity as? BaseActivity<*>)?.openClearLoginView() + (activity as? BaseActivity<*, *>)?.openClearLoginView() } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/ErrorDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/base/ErrorDialog.kt index ae3604bfd..5fd6a86a5 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/ErrorDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/ErrorDialog.kt @@ -11,6 +11,7 @@ import android.widget.Toast import android.widget.Toast.LENGTH_LONG import androidx.core.content.getSystemService import io.github.wulkanowy.R +import io.github.wulkanowy.databinding.DialogErrorBinding import io.github.wulkanowy.sdk.exception.FeatureDisabledException import io.github.wulkanowy.sdk.exception.FeatureNotAvailableException import io.github.wulkanowy.sdk.exception.ServiceUnavailableException @@ -18,7 +19,6 @@ import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.getString import io.github.wulkanowy.utils.openEmailClient import io.github.wulkanowy.utils.openInternetBrowser -import kotlinx.android.synthetic.main.dialog_error.* import java.io.InterruptedIOException import java.io.PrintWriter import java.io.StringWriter @@ -26,7 +26,7 @@ import java.net.SocketTimeoutException import java.net.UnknownHostException import javax.inject.Inject -class ErrorDialog : BaseDialogFragment() { +class ErrorDialog : BaseDialogFragment() { private lateinit var error: Throwable @@ -52,7 +52,7 @@ class ErrorDialog : BaseDialogFragment() { } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.dialog_error, container, false) + return DialogErrorBinding.inflate(inflater).apply { binding = this }.root } override fun onActivityCreated(savedInstanceState: Bundle?) { @@ -62,27 +62,29 @@ class ErrorDialog : BaseDialogFragment() { error.printStackTrace(PrintWriter(this)) } - errorDialogContent.text = stringWriter.toString() - with(errorDialogHorizontalScroll) { - post { fullScroll(HorizontalScrollView.FOCUS_LEFT) } - } - errorDialogCopy.setOnClickListener { - val clip = ClipData.newPlainText("wulkanowy", stringWriter.toString()) - activity?.getSystemService()?.setPrimaryClip(clip) + with(binding) { + errorDialogContent.text = stringWriter.toString() + with(errorDialogHorizontalScroll) { + post { fullScroll(HorizontalScrollView.FOCUS_LEFT) } + } + errorDialogCopy.setOnClickListener { + val clip = ClipData.newPlainText("wulkanowy", stringWriter.toString()) + activity?.getSystemService()?.setPrimaryClip(clip) - Toast.makeText(context, R.string.all_copied, LENGTH_LONG).show() - } - errorDialogCancel.setOnClickListener { dismiss() } - errorDialogReport.setOnClickListener { openEmailClient(stringWriter.toString()) } - errorDialogMessage.text = resources.getString(error) - errorDialogReport.isEnabled = when (error) { - is UnknownHostException, - is InterruptedIOException, - is SocketTimeoutException, - is ServiceUnavailableException, - is FeatureDisabledException, - is FeatureNotAvailableException -> false - else -> true + Toast.makeText(context, R.string.all_copied, LENGTH_LONG).show() + } + errorDialogCancel.setOnClickListener { dismiss() } + errorDialogReport.setOnClickListener { openEmailClient(stringWriter.toString()) } + errorDialogMessage.text = resources.getString(error) + errorDialogReport.isEnabled = when (error) { + is UnknownHostException, + is InterruptedIOException, + is SocketTimeoutException, + is ServiceUnavailableException, + is FeatureDisabledException, + is FeatureNotAvailableException -> false + else -> true + } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutFragment.kt index b2893c1e1..3828a2bc4 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutFragment.kt @@ -2,11 +2,10 @@ package io.github.wulkanowy.ui.modules.about import android.graphics.drawable.Drawable import android.os.Bundle -import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup import androidx.recyclerview.widget.LinearLayoutManager import io.github.wulkanowy.R +import io.github.wulkanowy.databinding.FragmentAboutBinding import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.modules.about.contributor.ContributorFragment import io.github.wulkanowy.ui.modules.about.license.LicenseFragment @@ -17,10 +16,10 @@ import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.getCompatDrawable import io.github.wulkanowy.utils.openEmailClient import io.github.wulkanowy.utils.openInternetBrowser -import kotlinx.android.synthetic.main.fragment_about.* import javax.inject.Inject -class AboutFragment : BaseFragment(), AboutView, MainView.TitledView { +class AboutFragment : BaseFragment(R.layout.fragment_about), AboutView, + MainView.TitledView { @Inject lateinit var presenter: AboutPresenter @@ -77,19 +76,16 @@ class AboutFragment : BaseFragment(), AboutView, MainView.TitledView { fun newInstance() = AboutFragment() } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_about, container, false) - } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + binding = FragmentAboutBinding.bind(view) presenter.onAttachView(this) } override fun initView() { aboutAdapter.onClickListener = presenter::onItemSelected - with(aboutRecycler) { + with(binding.aboutRecycler) { layoutManager = LinearLayoutManager(context) adapter = aboutAdapter } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/about/contributor/ContributorFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/about/contributor/ContributorFragment.kt index 2544836cb..42eebd347 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/about/contributor/ContributorFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/about/contributor/ContributorFragment.kt @@ -1,22 +1,21 @@ package io.github.wulkanowy.ui.modules.about.contributor import android.os.Bundle -import android.view.LayoutInflater import android.view.View import android.view.View.GONE import android.view.View.VISIBLE -import android.view.ViewGroup import androidx.recyclerview.widget.LinearLayoutManager import io.github.wulkanowy.R import io.github.wulkanowy.data.pojos.Contributor +import io.github.wulkanowy.databinding.FragmentContributorBinding import io.github.wulkanowy.ui.base.BaseFragment -import io.github.wulkanowy.ui.widgets.DividerItemDecoration import io.github.wulkanowy.ui.modules.main.MainView +import io.github.wulkanowy.ui.widgets.DividerItemDecoration import io.github.wulkanowy.utils.openInternetBrowser -import kotlinx.android.synthetic.main.fragment_creator.* import javax.inject.Inject -class ContributorFragment : BaseFragment(), ContributorView, MainView.TitledView { +class ContributorFragment : BaseFragment(R.layout.fragment_contributor), + ContributorView, MainView.TitledView { @Inject lateinit var presenter: ContributorPresenter @@ -30,23 +29,20 @@ class ContributorFragment : BaseFragment(), ContributorView, MainView.TitledView fun newInstance() = ContributorFragment() } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_creator, container, false) - } - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentContributorBinding.bind(view) presenter.onAttachView(this) } override fun initView() { - with(creatorRecycler) { + with(binding.creatorRecycler) { layoutManager = LinearLayoutManager(context) adapter = creatorsAdapter addItemDecoration(DividerItemDecoration(context)) } creatorsAdapter.onClickListener = presenter::onItemSelected - creatorSeeMore.setOnClickListener { presenter.onSeeMoreClick() } + binding.creatorSeeMore.setOnClickListener { presenter.onSeeMoreClick() } } override fun updateData(data: List) { @@ -65,7 +61,7 @@ class ContributorFragment : BaseFragment(), ContributorView, MainView.TitledView } override fun showProgress(show: Boolean) { - creatorProgress.visibility = if (show) VISIBLE else GONE + binding.creatorProgress.visibility = if (show) VISIBLE else GONE } override fun onDestroyView() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/about/license/LicenseFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/about/license/LicenseFragment.kt index d64c6225c..f6c3b5698 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/about/license/LicenseFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/about/license/LicenseFragment.kt @@ -1,11 +1,9 @@ package io.github.wulkanowy.ui.modules.about.license import android.os.Bundle -import android.view.LayoutInflater import android.view.View import android.view.View.GONE import android.view.View.VISIBLE -import android.view.ViewGroup import androidx.appcompat.app.AlertDialog import androidx.core.text.parseAsHtml import androidx.recyclerview.widget.LinearLayoutManager @@ -13,12 +11,13 @@ import com.mikepenz.aboutlibraries.Libs import com.mikepenz.aboutlibraries.entity.Library import dagger.Lazy import io.github.wulkanowy.R +import io.github.wulkanowy.databinding.FragmentLicenseBinding import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.modules.main.MainView -import kotlinx.android.synthetic.main.fragment_license.* import javax.inject.Inject -class LicenseFragment : BaseFragment(), LicenseView, MainView.TitledView { +class LicenseFragment : BaseFragment(R.layout.fragment_license), + LicenseView, MainView.TitledView { @Inject lateinit var presenter: LicensePresenter @@ -40,19 +39,16 @@ class LicenseFragment : BaseFragment(), LicenseView, MainView.TitledView { fun newInstance() = LicenseFragment() } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_license, container, false) - } - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentLicenseBinding.bind(view) presenter.onAttachView(this) } override fun initView() { licenseAdapter.onClickListener = presenter::onItemSelected - with(licenseRecycler) { + with(binding.licenseRecycler) { layoutManager = LinearLayoutManager(context) adapter = licenseAdapter } @@ -77,7 +73,7 @@ class LicenseFragment : BaseFragment(), LicenseView, MainView.TitledView { } override fun showProgress(show: Boolean) { - licenseProgress.visibility = if (show) VISIBLE else GONE + binding.licenseProgress.visibility = if (show) VISIBLE else GONE } override fun onDestroyView() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/about/logviewer/LogViewerFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/about/logviewer/LogViewerFragment.kt index 0b7b05c78..08f91aff1 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/about/logviewer/LogViewerFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/about/logviewer/LogViewerFragment.kt @@ -8,23 +8,22 @@ import android.net.Uri import android.os.Build.VERSION.SDK_INT import android.os.Build.VERSION_CODES.LOLLIPOP import android.os.Bundle -import android.view.LayoutInflater import android.view.Menu import android.view.MenuInflater import android.view.MenuItem import android.view.View -import android.view.ViewGroup import androidx.core.content.FileProvider import androidx.recyclerview.widget.LinearLayoutManager import io.github.wulkanowy.BuildConfig.APPLICATION_ID import io.github.wulkanowy.R +import io.github.wulkanowy.databinding.FragmentLogviewerBinding import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.modules.main.MainView -import kotlinx.android.synthetic.main.fragment_logviewer.* import java.io.File import javax.inject.Inject -class LogViewerFragment : BaseFragment(), LogViewerView, MainView.TitledView { +class LogViewerFragment : BaseFragment(R.layout.fragment_logviewer), + LogViewerView, MainView.TitledView { @Inject lateinit var presenter: LogViewerPresenter @@ -43,13 +42,10 @@ class LogViewerFragment : BaseFragment(), LogViewerView, MainView.TitledView { setHasOptionsMenu(true) } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_logviewer, container, false) - } - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) - messageContainer = logViewerRecycler + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentLogviewerBinding.bind(view) + messageContainer = binding.logViewerRecycler presenter.onAttachView(this) } @@ -63,18 +59,18 @@ class LogViewerFragment : BaseFragment(), LogViewerView, MainView.TitledView { } override fun initView() { - with(logViewerRecycler) { + with(binding.logViewerRecycler) { layoutManager = LinearLayoutManager(context) adapter = logAdapter } - logViewRefreshButton.setOnClickListener { presenter.onRefreshClick() } + binding.logViewRefreshButton.setOnClickListener { presenter.onRefreshClick() } } override fun setLines(lines: List) { logAdapter.lines = lines logAdapter.notifyDataSetChanged() - logViewerRecycler.scrollToPosition(lines.size - 1) + binding.logViewerRecycler.scrollToPosition(lines.size - 1) } override fun shareLogs(files: List) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountDialog.kt index dc8cce928..ce811e0b0 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountDialog.kt @@ -10,12 +10,12 @@ import androidx.appcompat.app.AlertDialog import androidx.recyclerview.widget.LinearLayoutManager import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.databinding.DialogAccountBinding import io.github.wulkanowy.ui.base.BaseDialogFragment import io.github.wulkanowy.ui.modules.login.LoginActivity -import kotlinx.android.synthetic.main.dialog_account.* import javax.inject.Inject -class AccountDialog : BaseDialogFragment(), AccountView { +class AccountDialog : BaseDialogFragment(), AccountView { @Inject lateinit var presenter: AccountPresenter @@ -33,7 +33,7 @@ class AccountDialog : BaseDialogFragment(), AccountView { } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.dialog_account, container, false) + return DialogAccountBinding.inflate(inflater).apply { binding = this }.root } override fun onActivityCreated(savedInstanceState: Bundle?) { @@ -44,11 +44,13 @@ class AccountDialog : BaseDialogFragment(), AccountView { override fun initView() { accountAdapter.onClickListener = presenter::onItemSelected - accountDialogAdd.setOnClickListener { presenter.onAddSelected() } - accountDialogRemove.setOnClickListener { presenter.onRemoveSelected() } - accountDialogRecycler.apply { - layoutManager = LinearLayoutManager(context) - adapter = accountAdapter + with(binding) { + accountDialogAdd.setOnClickListener { presenter.onAddSelected() } + accountDialogRemove.setOnClickListener { presenter.onRemoveSelected() } + accountDialogRecycler.apply { + layoutManager = LinearLayoutManager(context) + adapter = accountAdapter + } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceDialog.kt index 611dd999e..97b76e812 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceDialog.kt @@ -5,13 +5,15 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.DialogFragment -import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Attendance +import io.github.wulkanowy.databinding.DialogAttendanceBinding +import io.github.wulkanowy.utils.lifecycleAwareVariable import io.github.wulkanowy.utils.toFormattedString -import kotlinx.android.synthetic.main.dialog_attendance.* class AttendanceDialog : DialogFragment() { + private var binding: DialogAttendanceBinding by lifecycleAwareVariable() + private lateinit var attendance: Attendance companion object { @@ -33,16 +35,18 @@ class AttendanceDialog : DialogFragment() { } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.dialog_attendance, container, false) + return DialogAttendanceBinding.inflate(inflater).apply { binding = this }.root } override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) - attendanceDialogSubject.text = attendance.subject - attendanceDialogDescription.text = attendance.name - attendanceDialogDate.text = attendance.date.toFormattedString() - attendanceDialogNumber.text = attendance.number.toString() - attendanceDialogClose.setOnClickListener { dismiss() } + with(binding) { + attendanceDialogSubject.text = attendance.subject + attendanceDialogDescription.text = attendance.name + attendanceDialogDate.text = attendance.date.toFormattedString() + attendanceDialogNumber.text = attendance.number.toString() + attendanceDialogClose.setOnClickListener { dismiss() } + } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceFragment.kt index 31b0a3c29..6599243dd 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceFragment.kt @@ -10,13 +10,14 @@ import android.view.View import android.view.View.GONE import android.view.View.INVISIBLE import android.view.View.VISIBLE -import android.view.ViewGroup import androidx.appcompat.app.AlertDialog import androidx.appcompat.view.ActionMode import androidx.recyclerview.widget.LinearLayoutManager import com.wdullaer.materialdatetimepicker.date.DatePickerDialog import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Attendance +import io.github.wulkanowy.databinding.DialogExcuseBinding +import io.github.wulkanowy.databinding.FragmentAttendanceBinding import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.widgets.DividerItemDecoration import io.github.wulkanowy.ui.modules.attendance.summary.AttendanceSummaryFragment @@ -24,12 +25,10 @@ import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.utils.SchooldaysRangeLimiter import io.github.wulkanowy.utils.dpToPx -import kotlinx.android.synthetic.main.dialog_excuse.* -import kotlinx.android.synthetic.main.fragment_attendance.* import org.threeten.bp.LocalDate import javax.inject.Inject -class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MainChildView, +class AttendanceFragment : BaseFragment(R.layout.fragment_attendance), AttendanceView, MainView.MainChildView, MainView.TitledView { @Inject @@ -89,13 +88,10 @@ class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MainChildVie setHasOptionsMenu(true) } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_attendance, container, false) - } - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) - messageContainer = attendanceRecycler + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentAttendanceBinding.bind(view) + messageContainer = binding.attendanceRecycler presenter.onAttachView(this, savedInstanceState?.getLong(SAVED_DATE_KEY)) } @@ -105,23 +101,25 @@ class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MainChildVie onExcuseCheckboxSelect = presenter::onExcuseCheckboxSelect } - with(attendanceRecycler) { + with(binding.attendanceRecycler) { layoutManager = LinearLayoutManager(context) adapter = attendanceAdapter addItemDecoration(DividerItemDecoration(context)) } - attendanceSwipe.setOnRefreshListener(presenter::onSwipeRefresh) - attendanceErrorRetry.setOnClickListener { presenter.onRetry() } - attendanceErrorDetails.setOnClickListener { presenter.onDetailsClick() } + with(binding) { + attendanceSwipe.setOnRefreshListener(presenter::onSwipeRefresh) + attendanceErrorRetry.setOnClickListener { presenter.onRetry() } + attendanceErrorDetails.setOnClickListener { presenter.onDetailsClick() } - attendancePreviousButton.setOnClickListener { presenter.onPreviousDay() } - attendanceNavDate.setOnClickListener { presenter.onPickDate() } - attendanceNextButton.setOnClickListener { presenter.onNextDay() } + attendancePreviousButton.setOnClickListener { presenter.onPreviousDay() } + attendanceNavDate.setOnClickListener { presenter.onPickDate() } + attendanceNextButton.setOnClickListener { presenter.onNextDay() } - attendanceExcuseButton.setOnClickListener { presenter.onExcuseButtonClick() } + attendanceExcuseButton.setOnClickListener { presenter.onExcuseButtonClick() } - attendanceNavContainer.setElevationCompat(requireContext().dpToPx(8f)) + attendanceNavContainer.setElevationCompat(requireContext().dpToPx(8f)) + } } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { @@ -141,7 +139,7 @@ class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MainChildVie } override fun updateNavigationDay(date: String) { - attendanceNavDate.text = date + binding.attendanceNavDate.text = date } override fun clearData() { @@ -152,7 +150,7 @@ class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MainChildVie } override fun resetView() { - attendanceRecycler.smoothScrollToPosition(0) + binding.attendanceRecycler.smoothScrollToPosition(0) } override fun onFragmentReselected() { @@ -168,43 +166,43 @@ class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MainChildVie } override fun showEmpty(show: Boolean) { - attendanceEmpty.visibility = if (show) VISIBLE else GONE + binding.attendanceEmpty.visibility = if (show) VISIBLE else GONE } override fun showErrorView(show: Boolean) { - attendanceError.visibility = if (show) VISIBLE else GONE + binding.attendanceError.visibility = if (show) VISIBLE else GONE } override fun setErrorDetails(message: String) { - attendanceErrorMessage.text = message + binding.attendanceErrorMessage.text = message } override fun showProgress(show: Boolean) { - attendanceProgress.visibility = if (show) VISIBLE else GONE + binding.attendanceProgress.visibility = if (show) VISIBLE else GONE } override fun enableSwipe(enable: Boolean) { - attendanceSwipe.isEnabled = enable + binding.attendanceSwipe.isEnabled = enable } override fun showContent(show: Boolean) { - attendanceRecycler.visibility = if (show) VISIBLE else GONE + binding. attendanceRecycler.visibility = if (show) VISIBLE else GONE } override fun hideRefresh() { - attendanceSwipe.isRefreshing = false + binding.attendanceSwipe.isRefreshing = false } override fun showPreButton(show: Boolean) { - attendancePreviousButton.visibility = if (show) VISIBLE else INVISIBLE + binding.attendancePreviousButton.visibility = if (show) VISIBLE else INVISIBLE } override fun showNextButton(show: Boolean) { - attendanceNextButton.visibility = if (show) VISIBLE else INVISIBLE + binding. attendanceNextButton.visibility = if (show) VISIBLE else INVISIBLE } override fun showExcuseButton(show: Boolean) { - attendanceExcuseButton.visibility = if (show) VISIBLE else GONE + binding.attendanceExcuseButton.visibility = if (show) VISIBLE else GONE } override fun showAttendanceDialog(lesson: Attendance) { @@ -227,14 +225,15 @@ class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MainChildVie } override fun showExcuseDialog() { + val dialogBinding = DialogExcuseBinding.inflate(LayoutInflater.from(context)) AlertDialog.Builder(requireContext()) .setTitle(R.string.attendance_excuse_title) - .setView(R.layout.dialog_excuse) + .setView(dialogBinding.root) .setNegativeButton(android.R.string.cancel) { _, _ -> } .create() .apply { setButton(BUTTON_POSITIVE, getString(R.string.attendance_excuse_dialog_submit)) { _, _ -> - presenter.onExcuseDialogSubmit(excuseReason.text?.toString().orEmpty()) + presenter.onExcuseDialogSubmit(dialogBinding.excuseReason.text?.toString().orEmpty()) } }.show() } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryAdapter.kt index a6d4cf220..236c3da16 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryAdapter.kt @@ -23,7 +23,7 @@ class AttendanceSummaryAdapter @Inject constructor() : var items = emptyList() - override fun getItemCount() = items.size + 2 + override fun getItemCount() = if (items.isNotEmpty()) items.size + 2 else 0 override fun getItemViewType(position: Int) = when (position) { 0 -> ViewType.HEADER.id diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryFragment.kt index 4ec9cceda..1b9fe08ef 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryFragment.kt @@ -1,25 +1,25 @@ package io.github.wulkanowy.ui.modules.attendance.summary import android.os.Bundle -import android.view.LayoutInflater import android.view.View import android.view.View.GONE import android.view.View.INVISIBLE import android.view.View.VISIBLE -import android.view.ViewGroup import android.widget.ArrayAdapter import android.widget.TextView import androidx.recyclerview.widget.LinearLayoutManager import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.AttendanceSummary +import io.github.wulkanowy.databinding.FragmentAttendanceSummaryBinding import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.utils.dpToPx import io.github.wulkanowy.utils.setOnItemSelectedListener -import kotlinx.android.synthetic.main.fragment_attendance_summary.* import javax.inject.Inject -class AttendanceSummaryFragment : BaseFragment(), AttendanceSummaryView, MainView.TitledView { +class AttendanceSummaryFragment : + BaseFragment(R.layout.fragment_attendance_summary), + AttendanceSummaryView, MainView.TitledView { @Inject lateinit var presenter: AttendanceSummaryPresenter @@ -39,35 +39,34 @@ class AttendanceSummaryFragment : BaseFragment(), AttendanceSummaryView, MainVie override val isViewEmpty get() = attendanceSummaryAdapter.items.isEmpty() - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_attendance_summary, container, false) - } - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) - messageContainer = attendanceSummaryRecycler + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentAttendanceSummaryBinding.bind(view) + messageContainer = binding.attendanceSummaryRecycler presenter.onAttachView(this, savedInstanceState?.getInt(SAVED_SUBJECT_KEY)) } override fun initView() { - with(attendanceSummaryRecycler) { + with(binding.attendanceSummaryRecycler) { layoutManager = LinearLayoutManager(context) adapter = attendanceSummaryAdapter } - attendanceSummarySwipe.setOnRefreshListener(presenter::onSwipeRefresh) - attendanceSummaryErrorRetry.setOnClickListener { presenter.onRetry() } - attendanceSummaryErrorDetails.setOnClickListener { presenter.onDetailsClick() } + with(binding) { + attendanceSummarySwipe.setOnRefreshListener(presenter::onSwipeRefresh) + attendanceSummaryErrorRetry.setOnClickListener { presenter.onRetry() } + attendanceSummaryErrorDetails.setOnClickListener { presenter.onDetailsClick() } + } subjectsAdapter = ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item, mutableListOf()) subjectsAdapter.setDropDownViewResource(R.layout.item_attendance_summary_subject) - with(attendanceSummarySubjects) { + with(binding.attendanceSummarySubjects) { adapter = subjectsAdapter setOnItemSelectedListener { presenter.onSubjectSelected(it?.text?.toString()) } } - attendanceSummarySubjectsContainer.setElevationCompat(requireContext().dpToPx(1f)) + binding.attendanceSummarySubjectsContainer.setElevationCompat(requireContext().dpToPx(1f)) } override fun updateSubjects(data: ArrayList) { @@ -93,35 +92,35 @@ class AttendanceSummaryFragment : BaseFragment(), AttendanceSummaryView, MainVie } override fun showEmpty(show: Boolean) { - attendanceSummaryEmpty.visibility = if (show) VISIBLE else GONE + binding.attendanceSummaryEmpty.visibility = if (show) VISIBLE else GONE } override fun showErrorView(show: Boolean) { - attendanceSummaryError.visibility = if (show) VISIBLE else GONE + binding.attendanceSummaryError.visibility = if (show) VISIBLE else GONE } override fun setErrorDetails(message: String) { - attendanceSummaryErrorMessage.text = message + binding.attendanceSummaryErrorMessage.text = message } override fun showProgress(show: Boolean) { - attendanceSummaryProgress.visibility = if (show) VISIBLE else GONE + binding.attendanceSummaryProgress.visibility = if (show) VISIBLE else GONE } override fun enableSwipe(enable: Boolean) { - attendanceSummarySwipe.isEnabled = enable + binding.attendanceSummarySwipe.isEnabled = enable } override fun showContent(show: Boolean) { - attendanceSummaryRecycler.visibility = if (show) VISIBLE else GONE + binding.attendanceSummaryRecycler.visibility = if (show) VISIBLE else GONE } override fun showSubjects(show: Boolean) { - attendanceSummarySubjectsContainer.visibility = if (show) VISIBLE else INVISIBLE + binding.attendanceSummarySubjectsContainer.visibility = if (show) VISIBLE else INVISIBLE } override fun hideRefresh() { - attendanceSummarySwipe.isRefreshing = false + binding.attendanceSummarySwipe.isRefreshing = false } override fun onSaveInstanceState(outState: Bundle) { @@ -130,7 +129,7 @@ class AttendanceSummaryFragment : BaseFragment(), AttendanceSummaryView, MainVie } override fun onDestroyView() { - super.onDestroyView() presenter.onDetachView() + super.onDestroyView() } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamDialog.kt index ed5092c96..c1a6f1f0d 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamDialog.kt @@ -5,13 +5,15 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.DialogFragment -import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Exam +import io.github.wulkanowy.databinding.DialogExamBinding +import io.github.wulkanowy.utils.lifecycleAwareVariable import io.github.wulkanowy.utils.toFormattedString -import kotlinx.android.synthetic.main.dialog_exam.* class ExamDialog : DialogFragment() { + private var binding: DialogExamBinding by lifecycleAwareVariable() + private lateinit var exam: Exam companion object { @@ -33,18 +35,20 @@ class ExamDialog : DialogFragment() { } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.dialog_exam, container, false) + return DialogExamBinding.inflate(inflater).apply { binding = this }.root } override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) - examDialogSubjectValue.text = exam.subject - examDialogTypeValue.text = exam.type - examDialogTeacherValue.text = exam.teacher - examDialogDateValue.text = exam.entryDate.toFormattedString() - examDialogDescriptionValue.text = exam.description + with(binding) { + examDialogSubjectValue.text = exam.subject + examDialogTypeValue.text = exam.type + examDialogTeacherValue.text = exam.teacher + examDialogDateValue.text = exam.entryDate.toFormattedString() + examDialogDescriptionValue.text = exam.description - examDialogClose.setOnClickListener { dismiss() } + examDialogClose.setOnClickListener { dismiss() } + } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamFragment.kt index cc395f626..eeab30c15 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamFragment.kt @@ -1,24 +1,23 @@ package io.github.wulkanowy.ui.modules.exam import android.os.Bundle -import android.view.LayoutInflater import android.view.View import android.view.View.GONE import android.view.View.INVISIBLE import android.view.View.VISIBLE -import android.view.ViewGroup import androidx.recyclerview.widget.LinearLayoutManager import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Exam +import io.github.wulkanowy.databinding.FragmentExamBinding import io.github.wulkanowy.ui.base.BaseFragment -import io.github.wulkanowy.ui.widgets.DividerItemDecoration import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainView +import io.github.wulkanowy.ui.widgets.DividerItemDecoration import io.github.wulkanowy.utils.dpToPx -import kotlinx.android.synthetic.main.fragment_exam.* import javax.inject.Inject -class ExamFragment : BaseFragment(), ExamView, MainView.MainChildView, MainView.TitledView { +class ExamFragment : BaseFragment(R.layout.fragment_exam), ExamView, + MainView.MainChildView, MainView.TitledView { @Inject lateinit var presenter: ExamPresenter @@ -36,37 +35,36 @@ class ExamFragment : BaseFragment(), ExamView, MainView.MainChildView, MainView. override val isViewEmpty get() = examAdapter.items.isEmpty() - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_exam, container, false) - } - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) - messageContainer = examRecycler + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentExamBinding.bind(view) + messageContainer = binding.examRecycler presenter.onAttachView(this, savedInstanceState?.getLong(SAVED_DATE_KEY)) } override fun initView() { examAdapter.onClickListener = presenter::onExamItemSelected - with(examRecycler) { + with(binding.examRecycler) { layoutManager = LinearLayoutManager(context) adapter = examAdapter addItemDecoration(DividerItemDecoration(context)) } - examSwipe.setOnRefreshListener(presenter::onSwipeRefresh) - examErrorRetry.setOnClickListener { presenter.onRetry() } - examErrorDetails.setOnClickListener { presenter.onDetailsClick() } + with(binding) { + examSwipe.setOnRefreshListener(presenter::onSwipeRefresh) + examErrorRetry.setOnClickListener { presenter.onRetry() } + examErrorDetails.setOnClickListener { presenter.onDetailsClick() } - examPreviousButton.setOnClickListener { presenter.onPreviousWeek() } - examNextButton.setOnClickListener { presenter.onNextWeek() } + examPreviousButton.setOnClickListener { presenter.onPreviousWeek() } + examNextButton.setOnClickListener { presenter.onNextWeek() } - examNavContainer.setElevationCompat(requireContext().dpToPx(8f)) + examNavContainer.setElevationCompat(requireContext().dpToPx(8f)) + } } override fun hideRefresh() { - examSwipe.isRefreshing = false + binding.examSwipe.isRefreshing = false } override fun updateData(data: List>) { @@ -77,7 +75,7 @@ class ExamFragment : BaseFragment(), ExamView, MainView.MainChildView, MainView. } override fun updateNavigationWeek(date: String) { - examNavDate.text = date + binding.examNavDate.text = date } override fun clearData() { @@ -88,7 +86,7 @@ class ExamFragment : BaseFragment(), ExamView, MainView.MainChildView, MainView. } override fun resetView() { - examRecycler.scrollToPosition(0) + binding.examRecycler.scrollToPosition(0) } override fun onFragmentReselected() { @@ -96,35 +94,35 @@ class ExamFragment : BaseFragment(), ExamView, MainView.MainChildView, MainView. } override fun showEmpty(show: Boolean) { - examEmpty.visibility = if (show) VISIBLE else GONE + binding.examEmpty.visibility = if (show) VISIBLE else GONE } override fun showErrorView(show: Boolean) { - examError.visibility = if (show) VISIBLE else GONE + binding.examError.visibility = if (show) VISIBLE else GONE } override fun setErrorDetails(message: String) { - examErrorMessage.text = message + binding.examErrorMessage.text = message } override fun showProgress(show: Boolean) { - examProgress.visibility = if (show) VISIBLE else GONE + binding.examProgress.visibility = if (show) VISIBLE else GONE } override fun enableSwipe(enable: Boolean) { - examSwipe.isEnabled = enable + binding.examSwipe.isEnabled = enable } override fun showContent(show: Boolean) { - examRecycler.visibility = if (show) VISIBLE else GONE + binding.examRecycler.visibility = if (show) VISIBLE else GONE } override fun showPreButton(show: Boolean) { - examPreviousButton.visibility = if (show) VISIBLE else INVISIBLE + binding.examPreviousButton.visibility = if (show) VISIBLE else INVISIBLE } override fun showNextButton(show: Boolean) { - examNextButton.visibility = if (show) VISIBLE else INVISIBLE + binding.examNextButton.visibility = if (show) VISIBLE else INVISIBLE } override fun showExamDialog(exam: Exam) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeFragment.kt index b1faf18f1..59b83c4b0 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeFragment.kt @@ -1,16 +1,15 @@ package io.github.wulkanowy.ui.modules.grade import android.os.Bundle -import android.view.LayoutInflater import android.view.Menu import android.view.MenuInflater import android.view.MenuItem import android.view.View import android.view.View.INVISIBLE import android.view.View.VISIBLE -import android.view.ViewGroup import androidx.appcompat.app.AlertDialog import io.github.wulkanowy.R +import io.github.wulkanowy.databinding.FragmentGradeBinding import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.base.BaseFragmentPagerAdapter import io.github.wulkanowy.ui.modules.grade.details.GradeDetailsFragment @@ -19,10 +18,9 @@ import io.github.wulkanowy.ui.modules.grade.summary.GradeSummaryFragment import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.utils.dpToPx import io.github.wulkanowy.utils.setOnSelectPageListener -import kotlinx.android.synthetic.main.fragment_grade.* import javax.inject.Inject -class GradeFragment : BaseFragment(), GradeView, MainView.MainChildView, MainView.TitledView { +class GradeFragment : BaseFragment(R.layout.fragment_grade), GradeView, MainView.MainChildView, MainView.TitledView { @Inject lateinit var presenter: GradePresenter @@ -42,19 +40,16 @@ class GradeFragment : BaseFragment(), GradeView, MainView.MainChildView, MainVie override var subtitleString = "" - override val currentPageIndex get() = gradeViewPager.currentItem + override val currentPageIndex get() = binding.gradeViewPager.currentItem 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) + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentGradeBinding.bind(view) presenter.onAttachView(this, savedInstanceState?.getInt(SAVED_SEMESTER_KEY)) } @@ -66,7 +61,7 @@ class GradeFragment : BaseFragment(), GradeView, MainView.MainChildView, MainVie override fun initView() { with(pagerAdapter) { - containerId = gradeViewPager.id + containerId = binding.gradeViewPager.id addFragmentsWithTitle(mapOf( GradeDetailsFragment.newInstance() to getString(R.string.all_details), GradeSummaryFragment.newInstance() to getString(R.string.grade_menu_summary), @@ -74,19 +69,21 @@ class GradeFragment : BaseFragment(), GradeView, MainView.MainChildView, MainVie )) } - with(gradeViewPager) { + with(binding.gradeViewPager) { adapter = pagerAdapter offscreenPageLimit = 3 setOnSelectPageListener(presenter::onPageSelected) } - with(gradeTabLayout) { - setupWithViewPager(gradeViewPager) + with(binding.gradeTabLayout) { + setupWithViewPager(binding.gradeViewPager) setElevationCompat(context.dpToPx(4f)) } - gradeErrorRetry.setOnClickListener { presenter.onRetry() } - gradeErrorDetails.setOnClickListener { presenter.onDetailsClick() } + with(binding) { + gradeErrorRetry.setOnClickListener { presenter.onRetry() } + gradeErrorDetails.setOnClickListener { presenter.onDetailsClick() } + } } override fun onOptionsItemSelected(item: MenuItem): Boolean { @@ -99,20 +96,22 @@ class GradeFragment : BaseFragment(), GradeView, MainView.MainChildView, MainVie } override fun showContent(show: Boolean) { - gradeViewPager.visibility = if (show) VISIBLE else INVISIBLE - gradeTabLayout.visibility = if (show) VISIBLE else INVISIBLE + with(binding) { + 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 + binding.gradeProgress.visibility = if (show) VISIBLE else INVISIBLE } override fun showErrorView(show: Boolean) { - gradeError.visibility = if (show) VISIBLE else INVISIBLE + binding.gradeError.visibility = if (show) VISIBLE else INVISIBLE } override fun setErrorDetails(message: String) { - gradeErrorMessage.text = message + binding.gradeErrorMessage.text = message } override fun showSemesterSwitch(show: Boolean) { @@ -166,7 +165,7 @@ class GradeFragment : BaseFragment(), GradeView, MainView.MainChildView, MainVie } override fun onDestroyView() { - super.onDestroyView() presenter.onDetachView() + super.onDestroyView() } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsDialog.kt index a8f8a8653..698aff3ea 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsDialog.kt @@ -8,14 +8,17 @@ import android.view.ViewGroup import androidx.fragment.app.DialogFragment import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Grade +import io.github.wulkanowy.databinding.DialogGradeBinding import io.github.wulkanowy.utils.colorStringId import io.github.wulkanowy.utils.getBackgroundColor import io.github.wulkanowy.utils.getGradeColor +import io.github.wulkanowy.utils.lifecycleAwareVariable import io.github.wulkanowy.utils.toFormattedString -import kotlinx.android.synthetic.main.dialog_grade.* class GradeDetailsDialog : DialogFragment() { + private var binding: DialogGradeBinding by lifecycleAwareVariable() + private lateinit var grade: Grade private lateinit var colorScheme: String @@ -44,47 +47,49 @@ class GradeDetailsDialog : DialogFragment() { } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.dialog_grade, container, false) + return DialogGradeBinding.inflate(inflater).apply { binding = this }.root } override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) - gradeDialogSubject.text = grade.subject + with(binding) { + gradeDialogSubject.text = grade.subject - gradeDialogColorAndWeightValue.run { - text = context.getString(R.string.grade_weight_value, grade.weight) - setBackgroundResource(grade.getGradeColor()) - } - - gradeDialogDateValue.text = grade.date.toFormattedString() - gradeDialogColorValue.text = getString(grade.colorStringId) - - gradeDialogCommentValue.apply { - if (grade.comment.isBlank()) { - visibility = GONE - gradeDialogComment.visibility = GONE - } else text = grade.comment - } - - gradeDialogValue.run { - text = grade.entry - setBackgroundResource(grade.getBackgroundColor(colorScheme)) - } - - gradeDialogTeacherValue.text = if (grade.teacher.isBlank()) { - getString(R.string.all_no_data) - } else grade.teacher - - gradeDialogDescriptionValue.text = grade.run { - when { - description.isBlank() && gradeSymbol.isNotBlank() -> gradeSymbol - description.isBlank() && gradeSymbol.isBlank() -> getString(R.string.all_no_description) - gradeSymbol.isNotBlank() && description.isNotBlank() -> "$gradeSymbol - $description" - else -> description + gradeDialogColorAndWeightValue.run { + text = context.getString(R.string.grade_weight_value, grade.weight) + setBackgroundResource(grade.getGradeColor()) } - } - gradeDialogClose.setOnClickListener { dismiss() } + gradeDialogDateValue.text = grade.date.toFormattedString() + gradeDialogColorValue.text = getString(grade.colorStringId) + + gradeDialogCommentValue.apply { + if (grade.comment.isBlank()) { + visibility = GONE + gradeDialogComment.visibility = GONE + } else text = grade.comment + } + + gradeDialogValue.run { + text = grade.entry + setBackgroundResource(grade.getBackgroundColor(colorScheme)) + } + + gradeDialogTeacherValue.text = if (grade.teacher.isBlank()) { + getString(R.string.all_no_data) + } else grade.teacher + + gradeDialogDescriptionValue.text = grade.run { + when { + description.isBlank() && gradeSymbol.isNotBlank() -> gradeSymbol + description.isBlank() && gradeSymbol.isBlank() -> getString(R.string.all_no_description) + gradeSymbol.isNotBlank() && description.isNotBlank() -> "$gradeSymbol - $description" + else -> description + } + } + + gradeDialogClose.setOnClickListener { dismiss() } + } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsFragment.kt index e0cfca2f1..ac50b9f53 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsFragment.kt @@ -1,7 +1,6 @@ package io.github.wulkanowy.ui.modules.grade.details import android.os.Bundle -import android.view.LayoutInflater import android.view.Menu import android.view.MenuInflater import android.view.MenuItem @@ -9,18 +8,19 @@ import android.view.View import android.view.View.GONE import android.view.View.INVISIBLE import android.view.View.VISIBLE -import android.view.ViewGroup import androidx.recyclerview.widget.LinearLayoutManager import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Grade +import io.github.wulkanowy.databinding.FragmentGradeDetailsBinding import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.modules.grade.GradeFragment import io.github.wulkanowy.ui.modules.grade.GradeView import io.github.wulkanowy.ui.modules.main.MainActivity -import kotlinx.android.synthetic.main.fragment_grade_details.* import javax.inject.Inject -class GradeDetailsFragment : BaseFragment(), GradeDetailsView, GradeView.GradeChildView { +class GradeDetailsFragment : + BaseFragment(R.layout.fragment_grade_details), GradeDetailsView, + GradeView.GradeChildView { @Inject lateinit var presenter: GradeDetailsPresenter @@ -42,13 +42,10 @@ class GradeDetailsFragment : BaseFragment(), GradeDetailsView, GradeView.GradeCh setHasOptionsMenu(true) } - 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) - messageContainer = gradeDetailsRecycler + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentGradeDetailsBinding.bind(view) + messageContainer = binding.gradeDetailsRecycler presenter.onAttachView(this) } @@ -61,13 +58,15 @@ class GradeDetailsFragment : BaseFragment(), GradeDetailsView, GradeView.GradeCh override fun initView() { gradeDetailsAdapter.onClickListener = presenter::onGradeItemSelected - gradeDetailsRecycler.run { - layoutManager = LinearLayoutManager(context) - adapter = gradeDetailsAdapter + with(binding) { + with(gradeDetailsRecycler) { + layoutManager = LinearLayoutManager(context) + adapter = gradeDetailsAdapter + } + gradeDetailsSwipe.setOnRefreshListener { presenter.onSwipeRefresh() } + gradeDetailsErrorRetry.setOnClickListener { presenter.onRetry() } + gradeDetailsErrorDetails.setOnClickListener { presenter.onDetailsClick() } } - gradeDetailsSwipe.setOnRefreshListener { presenter.onSwipeRefresh() } - gradeDetailsErrorRetry.setOnClickListener { presenter.onRetry() } - gradeDetailsErrorDetails.setOnClickListener { presenter.onDetailsClick() } } override fun onOptionsItemSelected(item: MenuItem): Boolean { @@ -99,7 +98,7 @@ class GradeDetailsFragment : BaseFragment(), GradeDetailsView, GradeView.GradeCh } override fun scrollToStart() { - gradeDetailsRecycler.smoothScrollToPosition(0) + binding.gradeDetailsRecycler.smoothScrollToPosition(0) } override fun getHeaderOfItem(subject: String): GradeDetailsItem { @@ -111,31 +110,31 @@ class GradeDetailsFragment : BaseFragment(), GradeDetailsView, GradeView.GradeCh } override fun showProgress(show: Boolean) { - gradeDetailsProgress.visibility = if (show) VISIBLE else GONE + binding.gradeDetailsProgress.visibility = if (show) VISIBLE else GONE } override fun enableSwipe(enable: Boolean) { - gradeDetailsSwipe.isEnabled = enable + binding.gradeDetailsSwipe.isEnabled = enable } override fun showContent(show: Boolean) { - gradeDetailsRecycler.visibility = if (show) VISIBLE else INVISIBLE + binding.gradeDetailsRecycler.visibility = if (show) VISIBLE else INVISIBLE } override fun showEmpty(show: Boolean) { - gradeDetailsEmpty.visibility = if (show) VISIBLE else INVISIBLE + binding.gradeDetailsEmpty.visibility = if (show) VISIBLE else INVISIBLE } override fun showErrorView(show: Boolean) { - gradeDetailsError.visibility = if (show) VISIBLE else GONE + binding.gradeDetailsError.visibility = if (show) VISIBLE else GONE } override fun setErrorDetails(message: String) { - gradeDetailsErrorMessage.text = message + binding.gradeDetailsErrorMessage.text = message } override fun showRefresh(show: Boolean) { - gradeDetailsSwipe.isRefreshing = show + binding.gradeDetailsSwipe.isRefreshing = show } override fun showGradeDialog(grade: Grade, colorScheme: String) { @@ -167,7 +166,7 @@ class GradeDetailsFragment : BaseFragment(), GradeDetailsView, GradeView.GradeCh } override fun onDestroyView() { - super.onDestroyView() presenter.onDetachView() + super.onDestroyView() } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsAdapter.kt index a7b7b6534..8c1f0b0d6 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsAdapter.kt @@ -2,7 +2,6 @@ package io.github.wulkanowy.ui.modules.grade.statistics import android.graphics.Color import android.view.LayoutInflater -import android.view.View import android.view.View.GONE import android.view.View.VISIBLE import android.view.ViewGroup @@ -21,9 +20,9 @@ import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.GradePointsStatistics import io.github.wulkanowy.data.db.entities.GradeStatistics import io.github.wulkanowy.data.pojos.GradeStatisticsItem +import io.github.wulkanowy.databinding.ItemGradeStatisticsBarBinding +import io.github.wulkanowy.databinding.ItemGradeStatisticsPieBinding import io.github.wulkanowy.utils.getThemeAttrColor -import kotlinx.android.synthetic.main.item_grade_statistics_bar.view.* -import kotlinx.android.synthetic.main.item_grade_statistics_pie.view.* import javax.inject.Inject class GradeStatisticsAdapter @Inject constructor() : @@ -62,30 +61,26 @@ class GradeStatisticsAdapter @Inject constructor() : override fun getItemCount() = items.size - override fun getItemViewType(position: Int): Int { - return when (items[position].type) { - ViewType.SEMESTER, ViewType.PARTIAL -> R.layout.item_grade_statistics_pie - ViewType.POINTS -> R.layout.item_grade_statistics_bar - } - } + override fun getItemViewType(position: Int) = items[position].type.id override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { - val viewHolder = LayoutInflater.from(parent.context).inflate(viewType, parent, false) + val inflater = LayoutInflater.from(parent.context) return when (viewType) { - R.layout.item_grade_statistics_bar -> GradeStatisticsBar(viewHolder) - else -> GradeStatisticsPie(viewHolder) + ViewType.PARTIAL.id, ViewType.SEMESTER.id -> PieViewHolder(ItemGradeStatisticsPieBinding.inflate(inflater, parent, false)) + ViewType.POINTS.id -> BarViewHolder(ItemGradeStatisticsBarBinding.inflate(inflater, parent, false)) + else -> throw IllegalStateException() } } override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { when (holder) { - is GradeStatisticsPie -> bindPieChart(holder, items[position].partial) - is GradeStatisticsBar -> bindBarChart(holder, items[position].points!!) + is PieViewHolder -> bindPieChart(holder, items[position].partial) + is BarViewHolder -> bindBarChart(holder, items[position].points!!) } } - private fun bindPieChart(holder: GradeStatisticsPie, partials: List) { - with(holder.view.gradeStatisticsPieTitle) { + private fun bindPieChart(holder: PieViewHolder, partials: List) { + with(holder.binding.gradeStatisticsPieTitle) { text = partials.firstOrNull()?.subject visibility = if (items.size == 1) GONE else VISIBLE } @@ -105,10 +100,10 @@ class GradeStatisticsAdapter @Inject constructor() : valueTextColor = Color.WHITE setColors(partials.map { gradeColors.single { color -> color.first == it.grade }.second - }.toIntArray(), holder.view.context) + }.toIntArray(), holder.binding.root.context) } - with(holder.view.gradeStatisticsPie) { + with(holder.binding.gradeStatisticsPie) { setTouchEnabled(false) if (partials.size == 1) animateXY(1000, 1000) data = PieData(dataset).apply { @@ -140,8 +135,8 @@ class GradeStatisticsAdapter @Inject constructor() : } } - private fun bindBarChart(holder: GradeStatisticsBar, points: GradePointsStatistics) { - with(holder.view.gradeStatisticsBarTitle) { + private fun bindBarChart(holder: BarViewHolder, points: GradePointsStatistics) { + with(holder.binding.gradeStatisticsBarTitle) { text = points.subject visibility = if (items.size == 1) GONE else VISIBLE } @@ -153,14 +148,14 @@ class GradeStatisticsAdapter @Inject constructor() : with(dataset) { valueTextSize = 12f - valueTextColor = holder.view.context.getThemeAttrColor(android.R.attr.textColorPrimary) + valueTextColor = holder.binding.root.context.getThemeAttrColor(android.R.attr.textColorPrimary) valueFormatter = object : ValueFormatter() { override fun getBarLabel(barEntry: BarEntry) = "${barEntry.y}%" } colors = gradePointsColors } - with(holder.view.gradeStatisticsBar) { + with(holder.binding.gradeStatisticsBar) { setTouchEnabled(false) if (items.size == 1) animateXY(1000, 1000) data = BarData(dataset).apply { @@ -183,7 +178,7 @@ class GradeStatisticsAdapter @Inject constructor() : description.isEnabled = false - holder.view.context.getThemeAttrColor(android.R.attr.textColorPrimary).let { + holder.binding.root.context.getThemeAttrColor(android.R.attr.textColorPrimary).let { axisLeft.textColor = it axisRight.textColor = it } @@ -203,7 +198,9 @@ class GradeStatisticsAdapter @Inject constructor() : } } - class GradeStatisticsPie(val view: View) : RecyclerView.ViewHolder(view) + private class PieViewHolder(val binding: ItemGradeStatisticsPieBinding) : + RecyclerView.ViewHolder(binding.root) - class GradeStatisticsBar(val view: View) : RecyclerView.ViewHolder(view) + private class BarViewHolder(val binding: ItemGradeStatisticsBarBinding) : + RecyclerView.ViewHolder(binding.root) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsFragment.kt index c05cf3800..c6786dbcc 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsFragment.kt @@ -1,23 +1,23 @@ package io.github.wulkanowy.ui.modules.grade.statistics import android.os.Bundle -import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup import android.widget.ArrayAdapter import android.widget.TextView import androidx.recyclerview.widget.LinearLayoutManager import io.github.wulkanowy.R import io.github.wulkanowy.data.pojos.GradeStatisticsItem +import io.github.wulkanowy.databinding.FragmentGradeStatisticsBinding import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.modules.grade.GradeFragment import io.github.wulkanowy.ui.modules.grade.GradeView import io.github.wulkanowy.utils.dpToPx import io.github.wulkanowy.utils.setOnItemSelectedListener -import kotlinx.android.synthetic.main.fragment_grade_statistics.* import javax.inject.Inject -class GradeStatisticsFragment : BaseFragment(), GradeStatisticsView, GradeView.GradeChildView { +class GradeStatisticsFragment : + BaseFragment(R.layout.fragment_grade_statistics), + GradeStatisticsView, GradeView.GradeChildView { @Inject lateinit var presenter: GradeStatisticsPresenter @@ -36,24 +36,21 @@ class GradeStatisticsFragment : BaseFragment(), GradeStatisticsView, GradeView.G override val isViewEmpty get() = statisticsAdapter.items.isEmpty() override val currentType - get() = when (gradeStatisticsTypeSwitch.checkedRadioButtonId) { + get() = when (binding.gradeStatisticsTypeSwitch.checkedRadioButtonId) { R.id.gradeStatisticsTypeSemester -> ViewType.SEMESTER R.id.gradeStatisticsTypePartial -> ViewType.PARTIAL else -> ViewType.POINTS } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_grade_statistics, container, false) - } - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) - messageContainer = gradeStatisticsSwipe + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentGradeStatisticsBinding.bind(view) + messageContainer = binding.gradeStatisticsSwipe presenter.onAttachView(this, savedInstanceState?.getSerializable(SAVED_CHART_TYPE) as? ViewType) } override fun initView() { - with(gradeStatisticsRecycler) { + with(binding.gradeStatisticsRecycler) { layoutManager = LinearLayoutManager(requireContext()) adapter = statisticsAdapter } @@ -61,16 +58,18 @@ class GradeStatisticsFragment : BaseFragment(), GradeStatisticsView, GradeView.G subjectsAdapter = ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item, mutableListOf()) subjectsAdapter.setDropDownViewResource(R.layout.item_attendance_summary_subject) - with(gradeStatisticsSubjects) { + with(binding.gradeStatisticsSubjects) { adapter = subjectsAdapter setOnItemSelectedListener { presenter.onSubjectSelected(it?.text?.toString()) } } - gradeStatisticsSubjectsContainer.setElevationCompat(requireContext().dpToPx(1f)) + with(binding) { + gradeStatisticsSubjectsContainer.setElevationCompat(requireContext().dpToPx(1f)) - gradeStatisticsSwipe.setOnRefreshListener(presenter::onSwipeRefresh) - gradeStatisticsErrorRetry.setOnClickListener { presenter.onRetry() } - gradeStatisticsErrorDetails.setOnClickListener { presenter.onDetailsClick() } + gradeStatisticsSwipe.setOnRefreshListener(presenter::onSwipeRefresh) + gradeStatisticsErrorRetry.setOnClickListener { presenter.onRetry() } + gradeStatisticsErrorDetails.setOnClickListener { presenter.onDetailsClick() } + } } override fun updateSubjects(data: ArrayList) { @@ -88,8 +87,10 @@ class GradeStatisticsFragment : BaseFragment(), GradeStatisticsView, GradeView.G } override fun showSubjects(show: Boolean) { - gradeStatisticsSubjectsContainer.visibility = if (show) View.VISIBLE else View.INVISIBLE - gradeStatisticsTypeSwitch.visibility = if (show) View.VISIBLE else View.INVISIBLE + with(binding) { + gradeStatisticsSubjectsContainer.visibility = if (show) View.VISIBLE else View.INVISIBLE + gradeStatisticsTypeSwitch.visibility = if (show) View.VISIBLE else View.INVISIBLE + } } override fun clearView() { @@ -97,35 +98,35 @@ class GradeStatisticsFragment : BaseFragment(), GradeStatisticsView, GradeView.G } override fun resetView() { - gradeStatisticsScroll.scrollTo(0, 0) + binding.gradeStatisticsScroll.scrollTo(0, 0) } override fun showContent(show: Boolean) { - gradeStatisticsRecycler.visibility = if (show) View.VISIBLE else View.GONE + binding.gradeStatisticsRecycler.visibility = if (show) View.VISIBLE else View.GONE } override fun showEmpty(show: Boolean) { - gradeStatisticsEmpty.visibility = if (show) View.VISIBLE else View.INVISIBLE + binding.gradeStatisticsEmpty.visibility = if (show) View.VISIBLE else View.INVISIBLE } override fun showErrorView(show: Boolean) { - gradeStatisticsError.visibility = if (show) View.VISIBLE else View.GONE + binding.gradeStatisticsError.visibility = if (show) View.VISIBLE else View.GONE } override fun setErrorDetails(message: String) { - gradeStatisticsErrorMessage.text = message + binding.gradeStatisticsErrorMessage.text = message } override fun showProgress(show: Boolean) { - gradeStatisticsProgress.visibility = if (show) View.VISIBLE else View.GONE + binding.gradeStatisticsProgress.visibility = if (show) View.VISIBLE else View.GONE } override fun enableSwipe(enable: Boolean) { - gradeStatisticsSwipe.isEnabled = enable + binding.gradeStatisticsSwipe.isEnabled = enable } override fun showRefresh(show: Boolean) { - gradeStatisticsSwipe.isRefreshing = show + binding.gradeStatisticsSwipe.isRefreshing = show } override fun onParentLoadData(semesterId: Int, forceRefresh: Boolean) { @@ -150,7 +151,7 @@ class GradeStatisticsFragment : BaseFragment(), GradeStatisticsView, GradeView.G override fun onResume() { super.onResume() - gradeStatisticsTypeSwitch.setOnCheckedChangeListener { _, _ -> presenter.onTypeChange() } + binding.gradeStatisticsTypeSwitch.setOnCheckedChangeListener { _, _ -> presenter.onTypeChange() } } override fun onSaveInstanceState(outState: Bundle) { @@ -159,7 +160,7 @@ class GradeStatisticsFragment : BaseFragment(), GradeStatisticsView, GradeView.G } override fun onDestroyView() { - super.onDestroyView() presenter.onDetachView() + super.onDestroyView() } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/ViewType.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/ViewType.kt index 08c37d587..02e95b0e5 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/ViewType.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/ViewType.kt @@ -1,7 +1,7 @@ package io.github.wulkanowy.ui.modules.grade.statistics -enum class ViewType { - SEMESTER, - PARTIAL, - POINTS +enum class ViewType(val id: Int) { + SEMESTER(1), + PARTIAL(2), + POINTS(3) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryFragment.kt index 3addfb6ed..d169f7c62 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryFragment.kt @@ -1,22 +1,22 @@ package io.github.wulkanowy.ui.modules.grade.summary import android.os.Bundle -import android.view.LayoutInflater import android.view.View import android.view.View.GONE import android.view.View.INVISIBLE import android.view.View.VISIBLE -import android.view.ViewGroup import androidx.recyclerview.widget.LinearLayoutManager import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.GradeSummary +import io.github.wulkanowy.databinding.FragmentGradeSummaryBinding import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.modules.grade.GradeFragment import io.github.wulkanowy.ui.modules.grade.GradeView -import kotlinx.android.synthetic.main.fragment_grade_summary.* import javax.inject.Inject -class GradeSummaryFragment : BaseFragment(), GradeSummaryView, GradeView.GradeChildView { +class GradeSummaryFragment : + BaseFragment(R.layout.fragment_grade_summary), GradeSummaryView, + GradeView.GradeChildView { @Inject lateinit var presenter: GradeSummaryPresenter @@ -37,24 +37,23 @@ class GradeSummaryFragment : BaseFragment(), GradeSummaryView, GradeView.GradeCh override val finalString get() = getString(R.string.grade_summary_final_grade) - 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) - messageContainer = gradeSummaryRecycler + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentGradeSummaryBinding.bind(view) + messageContainer = binding.gradeSummaryRecycler presenter.onAttachView(this) } override fun initView() { - gradeSummaryRecycler.run { + with(binding.gradeSummaryRecycler) { layoutManager = LinearLayoutManager(context) adapter = gradeSummaryAdapter } - gradeSummarySwipe.setOnRefreshListener { presenter.onSwipeRefresh() } - gradeSummaryErrorRetry.setOnClickListener { presenter.onRetry() } - gradeSummaryErrorDetails.setOnClickListener { presenter.onDetailsClick() } + with(binding) { + gradeSummarySwipe.setOnRefreshListener { presenter.onSwipeRefresh() } + gradeSummaryErrorRetry.setOnClickListener { presenter.onRetry() } + gradeSummaryErrorDetails.setOnClickListener { presenter.onDetailsClick() } + } } override fun updateData(data: List) { @@ -72,35 +71,35 @@ class GradeSummaryFragment : BaseFragment(), GradeSummaryView, GradeView.GradeCh } override fun resetView() { - gradeSummaryRecycler.scrollToPosition(0) + binding.gradeSummaryRecycler.scrollToPosition(0) } override fun showContent(show: Boolean) { - gradeSummaryRecycler.visibility = if (show) VISIBLE else INVISIBLE + binding.gradeSummaryRecycler.visibility = if (show) VISIBLE else INVISIBLE } override fun showEmpty(show: Boolean) { - gradeSummaryEmpty.visibility = if (show) VISIBLE else INVISIBLE + binding.gradeSummaryEmpty.visibility = if (show) VISIBLE else INVISIBLE } override fun showErrorView(show: Boolean) { - gradeSummaryError.visibility = if (show) VISIBLE else INVISIBLE + binding.gradeSummaryError.visibility = if (show) VISIBLE else INVISIBLE } override fun setErrorDetails(message: String) { - gradeSummaryErrorMessage.text = message + binding.gradeSummaryErrorMessage.text = message } override fun showProgress(show: Boolean) { - gradeSummaryProgress.visibility = if (show) VISIBLE else GONE + binding.gradeSummaryProgress.visibility = if (show) VISIBLE else GONE } override fun enableSwipe(enable: Boolean) { - gradeSummarySwipe.isEnabled = enable + binding.gradeSummarySwipe.isEnabled = enable } override fun showRefresh(show: Boolean) { - gradeSummarySwipe.isRefreshing = show + binding.gradeSummarySwipe.isRefreshing = show } override fun onParentLoadData(semesterId: Int, forceRefresh: Boolean) { @@ -124,7 +123,7 @@ class GradeSummaryFragment : BaseFragment(), GradeSummaryView, GradeView.GradeCh } override fun onDestroyView() { - super.onDestroyView() presenter.onDetachView() + super.onDestroyView() } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkFragment.kt index ba0bf1bef..f30529575 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkFragment.kt @@ -1,24 +1,23 @@ package io.github.wulkanowy.ui.modules.homework import android.os.Bundle -import android.view.LayoutInflater import android.view.View import android.view.View.GONE import android.view.View.VISIBLE -import android.view.ViewGroup import androidx.recyclerview.widget.LinearLayoutManager import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Homework +import io.github.wulkanowy.databinding.FragmentHomeworkBinding import io.github.wulkanowy.ui.base.BaseFragment -import io.github.wulkanowy.ui.widgets.DividerItemDecoration import io.github.wulkanowy.ui.modules.homework.details.HomeworkDetailsDialog import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainView +import io.github.wulkanowy.ui.widgets.DividerItemDecoration import io.github.wulkanowy.utils.dpToPx -import kotlinx.android.synthetic.main.fragment_homework.* import javax.inject.Inject -class HomeworkFragment : BaseFragment(), HomeworkView, MainView.TitledView { +class HomeworkFragment : BaseFragment(R.layout.fragment_homework), + HomeworkView, MainView.TitledView { @Inject lateinit var presenter: HomeworkPresenter @@ -36,33 +35,32 @@ class HomeworkFragment : BaseFragment(), HomeworkView, MainView.TitledView { override val isViewEmpty get() = homeworkAdapter.items.isEmpty() - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_homework, container, false) - } - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) - messageContainer = homeworkRecycler + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentHomeworkBinding.bind(view) + messageContainer = binding.homeworkRecycler presenter.onAttachView(this, savedInstanceState?.getLong(SAVED_DATE_KEY)) } override fun initView() { homeworkAdapter.onClickListener = presenter::onHomeworkItemSelected - with(homeworkRecycler) { + with(binding.homeworkRecycler) { layoutManager = LinearLayoutManager(context) adapter = homeworkAdapter addItemDecoration(DividerItemDecoration(context)) } - homeworkSwipe.setOnRefreshListener(presenter::onSwipeRefresh) - homeworkErrorRetry.setOnClickListener { presenter.onRetry() } - homeworkErrorDetails.setOnClickListener { presenter.onDetailsClick() } + with(binding) { + homeworkSwipe.setOnRefreshListener(presenter::onSwipeRefresh) + homeworkErrorRetry.setOnClickListener { presenter.onRetry() } + homeworkErrorDetails.setOnClickListener { presenter.onDetailsClick() } - homeworkPreviousButton.setOnClickListener { presenter.onPreviousDay() } - homeworkNextButton.setOnClickListener { presenter.onNextDay() } + homeworkPreviousButton.setOnClickListener { presenter.onPreviousDay() } + homeworkNextButton.setOnClickListener { presenter.onNextDay() } - homeworkNavContainer.setElevationCompat(requireContext().dpToPx(8f)) + homeworkNavContainer.setElevationCompat(requireContext().dpToPx(8f)) + } } override fun updateData(data: List>) { @@ -84,43 +82,43 @@ class HomeworkFragment : BaseFragment(), HomeworkView, MainView.TitledView { } override fun updateNavigationWeek(date: String) { - homeworkNavDate.text = date + binding.homeworkNavDate.text = date } override fun hideRefresh() { - homeworkSwipe.isRefreshing = false + binding.homeworkSwipe.isRefreshing = false } override fun showEmpty(show: Boolean) { - homeworkEmpty.visibility = if (show) VISIBLE else GONE + binding.homeworkEmpty.visibility = if (show) VISIBLE else GONE } override fun showErrorView(show: Boolean) { - homeworkError.visibility = if (show) VISIBLE else GONE + binding.homeworkError.visibility = if (show) VISIBLE else GONE } override fun setErrorDetails(message: String) { - homeworkErrorMessage.text = message + binding.homeworkErrorMessage.text = message } override fun showProgress(show: Boolean) { - homeworkProgress.visibility = if (show) VISIBLE else GONE + binding.homeworkProgress.visibility = if (show) VISIBLE else GONE } override fun enableSwipe(enable: Boolean) { - homeworkSwipe.isEnabled = enable + binding.homeworkSwipe.isEnabled = enable } override fun showContent(show: Boolean) { - homeworkRecycler.visibility = if (show) VISIBLE else GONE + binding.homeworkRecycler.visibility = if (show) VISIBLE else GONE } override fun showPreButton(show: Boolean) { - homeworkPreviousButton.visibility = if (show) VISIBLE else View.INVISIBLE + binding.homeworkPreviousButton.visibility = if (show) VISIBLE else View.INVISIBLE } override fun showNextButton(show: Boolean) { - homeworkNextButton.visibility = if (show) VISIBLE else View.INVISIBLE + binding.homeworkNextButton.visibility = if (show) VISIBLE else View.INVISIBLE } override fun showTimetableDialog(homework: Homework) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsAdapter.kt index 3706b56e1..923ab953a 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsAdapter.kt @@ -1,14 +1,13 @@ package io.github.wulkanowy.ui.modules.homework.details import android.view.LayoutInflater -import android.view.View import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView -import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Homework +import io.github.wulkanowy.databinding.ItemHomeworkDialogAttachmentBinding +import io.github.wulkanowy.databinding.ItemHomeworkDialogAttachmentsHeaderBinding +import io.github.wulkanowy.databinding.ItemHomeworkDialogDetailsBinding import io.github.wulkanowy.utils.toFormattedString -import kotlinx.android.synthetic.main.item_homework_dialog_attachment.view.* -import kotlinx.android.synthetic.main.item_homework_dialog_details.view.* import javax.inject.Inject class HomeworkDetailsAdapter @Inject constructor() : @@ -42,9 +41,9 @@ class HomeworkDetailsAdapter @Inject constructor() : val inflater = LayoutInflater.from(parent.context) return when (viewType) { - ViewType.ATTACHMENTS_HEADER.id -> AttachmentsHeaderViewHolder(inflater.inflate(R.layout.item_homework_dialog_attachments_header, parent, false)) - ViewType.ATTACHMENT.id -> AttachmentViewHolder(inflater.inflate(R.layout.item_homework_dialog_attachment, parent, false)) - else -> DetailsViewHolder(inflater.inflate(R.layout.item_homework_dialog_details, parent, false)) + ViewType.ATTACHMENTS_HEADER.id -> AttachmentsHeaderViewHolder(ItemHomeworkDialogAttachmentsHeaderBinding.inflate(inflater, parent, false)) + ViewType.ATTACHMENT.id -> AttachmentViewHolder(ItemHomeworkDialogAttachmentBinding.inflate(inflater, parent, false)) + else -> DetailsViewHolder(ItemHomeworkDialogDetailsBinding.inflate(inflater, parent, false)) } } @@ -56,7 +55,7 @@ class HomeworkDetailsAdapter @Inject constructor() : } private fun bindDetailsViewHolder(holder: DetailsViewHolder) { - with(holder.view) { + with(holder.binding) { homeworkDialogDate.text = homework?.date?.toFormattedString() homeworkDialogEntryDate.text = homework?.entryDate?.toFormattedString() homeworkDialogSubject.text = homework?.subject @@ -68,17 +67,20 @@ class HomeworkDetailsAdapter @Inject constructor() : private fun bindAttachmentViewHolder(holder: AttachmentViewHolder, position: Int) { val item = attachments[position] - with(holder.view) { + with(holder.binding) { homeworkDialogAttachment.text = item.second - setOnClickListener { + root.setOnClickListener { onAttachmentClickListener(item.first) } } } - class DetailsViewHolder(val view: View) : RecyclerView.ViewHolder(view) + class DetailsViewHolder(val binding: ItemHomeworkDialogDetailsBinding) : + RecyclerView.ViewHolder(binding.root) - class AttachmentsHeaderViewHolder(val view: View) : RecyclerView.ViewHolder(view) + class AttachmentsHeaderViewHolder(val binding: ItemHomeworkDialogAttachmentsHeaderBinding) : + RecyclerView.ViewHolder(binding.root) - class AttachmentViewHolder(val view: View) : RecyclerView.ViewHolder(view) + class AttachmentViewHolder(val binding: ItemHomeworkDialogAttachmentBinding) : + RecyclerView.ViewHolder(binding.root) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsDialog.kt index 54cb5c68c..07f21ef85 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsDialog.kt @@ -8,13 +8,13 @@ import android.view.ViewGroup import androidx.recyclerview.widget.LinearLayoutManager import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Homework +import io.github.wulkanowy.databinding.DialogHomeworkBinding import io.github.wulkanowy.ui.base.BaseDialogFragment import io.github.wulkanowy.ui.modules.homework.HomeworkFragment import io.github.wulkanowy.utils.openInternetBrowser -import kotlinx.android.synthetic.main.dialog_homework.* import javax.inject.Inject -class HomeworkDetailsDialog : BaseDialogFragment(), HomeworkDetailsView { +class HomeworkDetailsDialog : BaseDialogFragment(), HomeworkDetailsView { @Inject lateinit var presenter: HomeworkDetailsPresenter @@ -43,7 +43,7 @@ class HomeworkDetailsDialog : BaseDialogFragment(), HomeworkDetailsView { } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.dialog_homework, container, false) + return DialogHomeworkBinding.inflate(inflater).apply { binding = this }.root } override fun onActivityCreated(savedInstanceState: Bundle?) { @@ -53,11 +53,13 @@ class HomeworkDetailsDialog : BaseDialogFragment(), HomeworkDetailsView { @SuppressLint("SetTextI18n") override fun initView() { - homeworkDialogRead.text = view?.context?.getString(if (homework.isDone) R.string.homework_mark_as_undone else R.string.homework_mark_as_done) - homeworkDialogRead.setOnClickListener { presenter.toggleDone(homework) } - homeworkDialogClose.setOnClickListener { dismiss() } + with(binding) { + homeworkDialogRead.text = view?.context?.getString(if (homework.isDone) R.string.homework_mark_as_undone else R.string.homework_mark_as_done) + homeworkDialogRead.setOnClickListener { presenter.toggleDone(homework) } + homeworkDialogClose.setOnClickListener { dismiss() } + } - with(homeworkDialogRecycler) { + with(binding.homeworkDialogRecycler) { layoutManager = LinearLayoutManager(context) adapter = detailsAdapter.apply { onAttachmentClickListener = { context.openInternetBrowser(it, ::showMessage) } @@ -68,7 +70,7 @@ class HomeworkDetailsDialog : BaseDialogFragment(), HomeworkDetailsView { override fun updateMarkAsDoneLabel(isDone: Boolean) { (parentFragment as? HomeworkFragment)?.onReloadList() - homeworkDialogRead.text = view?.context?.getString(if (isDone) R.string.homework_mark_as_undone else R.string.homework_mark_as_done) + binding.homeworkDialogRead.text = view?.context?.getString(if (isDone) R.string.homework_mark_as_undone else R.string.homework_mark_as_done) } override fun onDestroyView() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginActivity.kt index 19f23593e..ffb36fe6e 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginActivity.kt @@ -4,8 +4,8 @@ import android.content.Context import android.content.Intent import android.os.Bundle import android.view.MenuItem -import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.databinding.ActivityLoginBinding import io.github.wulkanowy.ui.base.BaseActivity import io.github.wulkanowy.ui.base.BaseFragmentPagerAdapter import io.github.wulkanowy.ui.modules.login.advanced.LoginAdvancedFragment @@ -14,10 +14,9 @@ import io.github.wulkanowy.ui.modules.login.recover.LoginRecoverFragment import io.github.wulkanowy.ui.modules.login.studentselect.LoginStudentSelectFragment import io.github.wulkanowy.ui.modules.login.symbol.LoginSymbolFragment import io.github.wulkanowy.utils.setOnSelectPageListener -import kotlinx.android.synthetic.main.activity_login.* import javax.inject.Inject -class LoginActivity : BaseActivity(), LoginView { +class LoginActivity : BaseActivity(), LoginView { @Inject override lateinit var presenter: LoginPresenter @@ -30,13 +29,13 @@ class LoginActivity : BaseActivity(), LoginView { fun getStartIntent(context: Context) = Intent(context, LoginActivity::class.java) } - override val currentViewIndex get() = loginViewpager.currentItem + override val currentViewIndex get() = binding.loginViewpager.currentItem override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(R.layout.activity_login) - setSupportActionBar(loginToolbar) - messageContainer = loginContainer + setContentView(ActivityLoginBinding.inflate(layoutInflater).apply { binding = this }.root) + setSupportActionBar(binding.loginToolbar) + messageContainer = binding.loginContainer presenter.onAttachView(this) } @@ -48,7 +47,7 @@ class LoginActivity : BaseActivity(), LoginView { } with(loginAdapter) { - containerId = loginViewpager.id + containerId = binding.loginViewpager.id addFragments(listOf( LoginFormFragment.newInstance(), LoginSymbolFragment.newInstance(), @@ -58,7 +57,7 @@ class LoginActivity : BaseActivity(), LoginView { )) } - with(loginViewpager) { + with(binding.loginViewpager) { offscreenPageLimit = 2 adapter = loginAdapter setOnSelectPageListener(presenter::onViewSelected) @@ -71,7 +70,7 @@ class LoginActivity : BaseActivity(), LoginView { } override fun switchView(index: Int) { - loginViewpager.setCurrentItem(index, false) + binding.loginViewpager.setCurrentItem(index, false) } override fun showActionBar(show: Boolean) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/advanced/LoginAdvancedFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/advanced/LoginAdvancedFragment.kt index aaea31eca..3b6a985be 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/advanced/LoginAdvancedFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/advanced/LoginAdvancedFragment.kt @@ -1,15 +1,14 @@ package io.github.wulkanowy.ui.modules.login.advanced import android.os.Bundle -import android.view.LayoutInflater import android.view.View import android.view.View.GONE import android.view.View.VISIBLE -import android.view.ViewGroup import android.widget.ArrayAdapter import androidx.core.widget.doOnTextChanged import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.databinding.FragmentLoginAdvancedBinding import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.modules.login.LoginActivity @@ -17,10 +16,11 @@ import io.github.wulkanowy.ui.modules.login.form.LoginSymbolAdapter import io.github.wulkanowy.utils.hideSoftInput import io.github.wulkanowy.utils.setOnEditorDoneSignIn import io.github.wulkanowy.utils.showSoftInput -import kotlinx.android.synthetic.main.fragment_login_advanced.* import javax.inject.Inject -class LoginAdvancedFragment : BaseFragment(), LoginAdvancedView { +class LoginAdvancedFragment : + BaseFragment(R.layout.fragment_login_advanced), + LoginAdvancedView { @Inject lateinit var presenter: LoginAdvancedPresenter @@ -30,17 +30,17 @@ class LoginAdvancedFragment : BaseFragment(), LoginAdvancedView { } override val formLoginType: String - get() = when (loginTypeSwitch.checkedRadioButtonId) { + get() = when (binding.loginTypeSwitch.checkedRadioButtonId) { R.id.loginTypeApi -> "API" R.id.loginTypeScrapper -> "SCRAPPER" else -> "HYBRID" } override val formUsernameValue: String - get() = loginFormUsername.text.toString().trim() + get() = binding.loginFormUsername.text.toString().trim() override val formPassValue: String - get() = loginFormPass.text.toString().trim() + get() = binding.loginFormPass.text.toString().trim() private lateinit var hostKeys: Array @@ -49,19 +49,19 @@ class LoginAdvancedFragment : BaseFragment(), LoginAdvancedView { private lateinit var hostSymbols: Array override val formHostValue: String - get() = hostValues.getOrNull(hostKeys.indexOf(loginFormHost.text.toString())).orEmpty() + get() = hostValues.getOrNull(hostKeys.indexOf(binding.loginFormHost.text.toString())).orEmpty() override val formHostSymbol: String - get() = hostSymbols.getOrNull(hostKeys.indexOf(loginFormHost.text.toString())).orEmpty() + get() = hostSymbols.getOrNull(hostKeys.indexOf(binding.loginFormHost.text.toString())).orEmpty() override val formPinValue: String - get() = loginFormPin.text.toString().trim() + get() = binding.loginFormPin.text.toString().trim() override val formSymbolValue: String - get() = loginFormSymbol.text.toString().trim() + get() = binding.loginFormSymbol.text.toString().trim() override val formTokenValue: String - get() = loginFormToken.text.toString().trim() + get() = binding.loginFormToken.text.toString().trim() override val nicknameLabel: String get() = getString(R.string.login_nickname_hint) @@ -69,12 +69,9 @@ class LoginAdvancedFragment : BaseFragment(), LoginAdvancedView { override val emailLabel: String get() = getString(R.string.login_email_hint) - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_login_advanced, container, false) - } - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentLoginAdvancedBinding.bind(view) presenter.onAttachView(this) } @@ -83,191 +80,201 @@ class LoginAdvancedFragment : BaseFragment(), LoginAdvancedView { hostValues = resources.getStringArray(R.array.hosts_values) hostSymbols = resources.getStringArray(R.array.hosts_symbols) - loginFormUsername.doOnTextChanged { _, _, _, _ -> presenter.onUsernameTextChanged() } - loginFormPass.doOnTextChanged { _, _, _, _ -> presenter.onPassTextChanged() } - loginFormPin.doOnTextChanged { _, _, _, _ -> presenter.onPinTextChanged() } - loginFormSymbol.doOnTextChanged { _, _, _, _ -> presenter.onSymbolTextChanged() } - loginFormToken.doOnTextChanged { _, _, _, _ -> presenter.onTokenTextChanged() } - loginFormHost.setOnItemClickListener { _, _, _, _ -> presenter.onHostSelected() } - loginFormSignIn.setOnClickListener { presenter.onSignInClick() } + with(binding) { + loginFormUsername.doOnTextChanged { _, _, _, _ -> presenter.onUsernameTextChanged() } + loginFormPass.doOnTextChanged { _, _, _, _ -> presenter.onPassTextChanged() } + loginFormPin.doOnTextChanged { _, _, _, _ -> presenter.onPinTextChanged() } + loginFormSymbol.doOnTextChanged { _, _, _, _ -> presenter.onSymbolTextChanged() } + loginFormToken.doOnTextChanged { _, _, _, _ -> presenter.onTokenTextChanged() } + loginFormHost.setOnItemClickListener { _, _, _, _ -> presenter.onHostSelected() } + loginFormSignIn.setOnClickListener { presenter.onSignInClick() } - loginTypeSwitch.setOnCheckedChangeListener { _, checkedId -> - presenter.onLoginModeSelected(when (checkedId) { - R.id.loginTypeApi -> Sdk.Mode.API - R.id.loginTypeScrapper -> Sdk.Mode.SCRAPPER - else -> Sdk.Mode.HYBRID - }) + loginTypeSwitch.setOnCheckedChangeListener { _, checkedId -> + presenter.onLoginModeSelected(when (checkedId) { + R.id.loginTypeApi -> Sdk.Mode.API + R.id.loginTypeScrapper -> Sdk.Mode.SCRAPPER + else -> Sdk.Mode.HYBRID + }) + } + + loginFormPin.setOnEditorDoneSignIn { loginFormSignIn.callOnClick() } + loginFormPass.setOnEditorDoneSignIn { loginFormSignIn.callOnClick() } + + loginFormSymbol.setAdapter(ArrayAdapter(requireContext(), android.R.layout.simple_list_item_1, resources.getStringArray(R.array.symbols_values))) } - loginFormPin.setOnEditorDoneSignIn { loginFormSignIn.callOnClick() } - loginFormPass.setOnEditorDoneSignIn { loginFormSignIn.callOnClick() } - - loginFormSymbol.setAdapter(ArrayAdapter(requireContext(), android.R.layout.simple_list_item_1, resources.getStringArray(R.array.symbols_values))) - - with(loginFormHost) { + with(binding.loginFormHost) { setText(hostKeys.getOrNull(0).orEmpty()) setAdapter(LoginSymbolAdapter(context, R.layout.support_simple_spinner_dropdown_item, hostKeys)) - setOnClickListener { if (loginFormContainer.visibility == GONE) dismissDropDown() } + setOnClickListener { if (binding.loginFormContainer.visibility == GONE) dismissDropDown() } } } override fun showMobileApiWarningMessage() { - loginFormAdvancedWarningInfo.text = getString(R.string.login_advanced_warning_mobile_api) + binding.loginFormAdvancedWarningInfo.text = getString(R.string.login_advanced_warning_mobile_api) } override fun showScraperWarningMessage() { - loginFormAdvancedWarningInfo.text = getString(R.string.login_advanced_warning_scraper) + binding.loginFormAdvancedWarningInfo.text = getString(R.string.login_advanced_warning_scraper) } override fun showHybridWarningMessage() { - loginFormAdvancedWarningInfo.text = getString(R.string.login_advanced_warning_hybrid) + binding.loginFormAdvancedWarningInfo.text = getString(R.string.login_advanced_warning_hybrid) } override fun setDefaultCredentials(username: String, pass: String, symbol: String, token: String, pin: String) { - loginFormUsername.setText(username) - loginFormPass.setText(pass) - loginFormToken.setText(token) - loginFormSymbol.setText(symbol) - loginFormPin.setText(pin) + with(binding) { + loginFormUsername.setText(username) + loginFormPass.setText(pass) + loginFormToken.setText(token) + loginFormSymbol.setText(symbol) + loginFormPin.setText(pin) + } } override fun setUsernameLabel(label: String) { - loginFormUsernameLayout.hint = label + binding.loginFormUsernameLayout.hint = label } override fun setSymbol(symbol: String) { - loginFormSymbol.setText(symbol) + binding.loginFormSymbol.setText(symbol) } override fun setErrorUsernameRequired() { - with(loginFormUsernameLayout) { + with(binding.loginFormUsernameLayout) { requestFocus() error = getString(R.string.login_field_required) } } override fun setErrorLoginRequired() { - with(loginFormUsernameLayout) { + with(binding.loginFormUsernameLayout) { requestFocus() error = getString(R.string.login_invalid_login) } } override fun setErrorEmailRequired() { - with(loginFormUsernameLayout) { + with(binding.loginFormUsernameLayout) { requestFocus() error = getString(R.string.login_invalid_email) } } override fun setErrorPassRequired(focus: Boolean) { - with(loginFormPassLayout) { + with(binding.loginFormPassLayout) { if (focus) requestFocus() error = getString(R.string.login_field_required) } } override fun setErrorPassInvalid(focus: Boolean) { - with(loginFormPassLayout) { + with(binding.loginFormPassLayout) { if (focus) requestFocus() error = getString(R.string.login_invalid_password) } } override fun setErrorPassIncorrect() { - with(loginFormPassLayout) { + with(binding.loginFormPassLayout) { requestFocus() error = getString(R.string.login_incorrect_password) } } override fun setErrorPinRequired() { - with(loginFormPinLayout) { + with(binding.loginFormPinLayout) { requestFocus() error = getString(R.string.login_field_required) } } override fun setErrorPinInvalid(message: String) { - with(loginFormPinLayout) { + with(binding.loginFormPinLayout) { requestFocus() error = message } } override fun setErrorSymbolRequired() { - with(loginFormSymbolLayout) { + with(binding.loginFormSymbolLayout) { requestFocus() error = getString(R.string.login_field_required) } } override fun setErrorSymbolInvalid(message: String) { - with(loginFormSymbolLayout) { + with(binding.loginFormSymbolLayout) { requestFocus() error = message } } override fun setErrorTokenRequired() { - with(loginFormTokenLayout) { + with(binding.loginFormTokenLayout) { requestFocus() error = getString(R.string.login_field_required) } } override fun setErrorTokenInvalid(message: String) { - with(loginFormTokenLayout) { + with(binding.loginFormTokenLayout) { requestFocus() error = message } } override fun clearUsernameError() { - loginFormUsernameLayout.error = null + binding.loginFormUsernameLayout.error = null } override fun clearPassError() { - loginFormPassLayout.error = null + binding.loginFormPassLayout.error = null } override fun clearPinKeyError() { - loginFormPinLayout.error = null + binding.loginFormPinLayout.error = null } override fun clearSymbolError() { - loginFormSymbolLayout.error = null + binding.loginFormSymbolLayout.error = null } override fun clearTokenError() { - loginFormTokenLayout.error = null + binding.loginFormTokenLayout.error = null } override fun showOnlyHybridModeInputs() { - loginFormUsernameLayout.visibility = VISIBLE - loginFormPassLayout.visibility = VISIBLE - loginFormHostLayout.visibility = VISIBLE - loginFormPinLayout.visibility = GONE - loginFormSymbolLayout.visibility = VISIBLE - loginFormTokenLayout.visibility = GONE + with(binding) { + loginFormUsernameLayout.visibility = VISIBLE + loginFormPassLayout.visibility = VISIBLE + loginFormHostLayout.visibility = VISIBLE + loginFormPinLayout.visibility = GONE + loginFormSymbolLayout.visibility = VISIBLE + loginFormTokenLayout.visibility = GONE + } } override fun showOnlyScrapperModeInputs() { - loginFormUsernameLayout.visibility = VISIBLE - loginFormPassLayout.visibility = VISIBLE - loginFormHostLayout.visibility = VISIBLE - loginFormPinLayout.visibility = GONE - loginFormSymbolLayout.visibility = VISIBLE - loginFormTokenLayout.visibility = GONE + with(binding) { + loginFormUsernameLayout.visibility = VISIBLE + loginFormPassLayout.visibility = VISIBLE + loginFormHostLayout.visibility = VISIBLE + loginFormPinLayout.visibility = GONE + loginFormSymbolLayout.visibility = VISIBLE + loginFormTokenLayout.visibility = GONE + } } override fun showOnlyMobileApiModeInputs() { - loginFormUsernameLayout.visibility = GONE - loginFormPassLayout.visibility = GONE - loginFormHostLayout.visibility = GONE - loginFormPinLayout.visibility = VISIBLE - loginFormSymbolLayout.visibility = VISIBLE - loginFormTokenLayout.visibility = VISIBLE + with(binding) { + loginFormUsernameLayout.visibility = GONE + loginFormPassLayout.visibility = GONE + loginFormHostLayout.visibility = GONE + loginFormPinLayout.visibility = VISIBLE + loginFormSymbolLayout.visibility = VISIBLE + loginFormTokenLayout.visibility = VISIBLE + } } override fun showSoftKeyboard() { @@ -279,17 +286,17 @@ class LoginAdvancedFragment : BaseFragment(), LoginAdvancedView { } override fun showProgress(show: Boolean) { - loginFormProgress.visibility = if (show) VISIBLE else GONE + binding.loginFormProgress.visibility = if (show) VISIBLE else GONE } override fun showContent(show: Boolean) { - loginFormContainer.visibility = if (show) VISIBLE else GONE + binding.loginFormContainer.visibility = if (show) VISIBLE else GONE } override fun notifyParentAccountLogged(students: List) { (activity as? LoginActivity)?.onFormFragmentAccountLogged(students, Triple( - loginFormUsername.text.toString(), - loginFormPass.text.toString(), + binding.loginFormUsername.text.toString(), + binding.loginFormPass.text.toString(), resources.getStringArray(R.array.hosts_values)[1] )) } @@ -300,7 +307,7 @@ class LoginAdvancedFragment : BaseFragment(), LoginAdvancedView { } override fun onDestroyView() { - super.onDestroyView() presenter.onDetachView() + super.onDestroyView() } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormFragment.kt index 058e702e6..a2a083c00 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormFragment.kt @@ -2,14 +2,13 @@ package io.github.wulkanowy.ui.modules.login.form import android.annotation.SuppressLint import android.os.Bundle -import android.view.LayoutInflater import android.view.View import android.view.View.GONE import android.view.View.VISIBLE -import android.view.ViewGroup import androidx.core.widget.doOnTextChanged import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.databinding.FragmentLoginFormBinding import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.modules.login.LoginActivity import io.github.wulkanowy.utils.AppInfo @@ -18,10 +17,10 @@ import io.github.wulkanowy.utils.openEmailClient import io.github.wulkanowy.utils.openInternetBrowser import io.github.wulkanowy.utils.setOnEditorDoneSignIn import io.github.wulkanowy.utils.showSoftInput -import kotlinx.android.synthetic.main.fragment_login_form.* import javax.inject.Inject -class LoginFormFragment : BaseFragment(), LoginFormView { +class LoginFormFragment : BaseFragment(R.layout.fragment_login_form), + LoginFormView { @Inject lateinit var presenter: LoginFormPresenter @@ -34,16 +33,16 @@ class LoginFormFragment : BaseFragment(), LoginFormView { } override val formUsernameValue: String - get() = loginFormUsername.text.toString() + get() = binding.loginFormUsername.text.toString() override val formPassValue: String - get() = loginFormPass.text.toString() + get() = binding.loginFormPass.text.toString() override val formHostValue: String - get() = hostValues.getOrNull(hostKeys.indexOf(loginFormHost.text.toString())).orEmpty() + get() = hostValues.getOrNull(hostKeys.indexOf(binding.loginFormHost.text.toString())).orEmpty() override val formHostSymbol: String - get() = hostSymbols.getOrNull(hostKeys.indexOf(loginFormHost.text.toString())).orEmpty() + get() = hostSymbols.getOrNull(hostKeys.indexOf(binding.loginFormHost.text.toString())).orEmpty() override val nicknameLabel: String get() = getString(R.string.login_nickname_hint) @@ -57,12 +56,9 @@ class LoginFormFragment : BaseFragment(), LoginFormView { private lateinit var hostSymbols: Array - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_login_form, container, false) - } - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentLoginFormBinding.bind(view) presenter.onAttachView(this) } @@ -71,81 +67,85 @@ class LoginFormFragment : BaseFragment(), LoginFormView { hostValues = resources.getStringArray(R.array.hosts_values) hostSymbols = resources.getStringArray(R.array.hosts_symbols) - loginFormUsername.doOnTextChanged { _, _, _, _ -> presenter.onUsernameTextChanged() } - loginFormPass.doOnTextChanged { _, _, _, _ -> presenter.onPassTextChanged() } - loginFormHost.setOnItemClickListener { _, _, _, _ -> presenter.onHostSelected() } - loginFormSignIn.setOnClickListener { presenter.onSignInClick() } - loginFormAdvancedButton.setOnClickListener { presenter.onAdvancedLoginClick() } - loginFormPrivacyLink.setOnClickListener { presenter.onPrivacyLinkClick() } - loginFormFaq.setOnClickListener { presenter.onFaqClick() } - loginFormContactEmail.setOnClickListener { presenter.onEmailClick() } - loginFormRecoverLink.setOnClickListener { presenter.onRecoverClick() } - loginFormPass.setOnEditorDoneSignIn { loginFormSignIn.callOnClick() } + with(binding) { + loginFormUsername.doOnTextChanged { _, _, _, _ -> presenter.onUsernameTextChanged() } + loginFormPass.doOnTextChanged { _, _, _, _ -> presenter.onPassTextChanged() } + loginFormHost.setOnItemClickListener { _, _, _, _ -> presenter.onHostSelected() } + loginFormSignIn.setOnClickListener { presenter.onSignInClick() } + loginFormAdvancedButton.setOnClickListener { presenter.onAdvancedLoginClick() } + loginFormPrivacyLink.setOnClickListener { presenter.onPrivacyLinkClick() } + loginFormFaq.setOnClickListener { presenter.onFaqClick() } + loginFormContactEmail.setOnClickListener { presenter.onEmailClick() } + loginFormRecoverLink.setOnClickListener { presenter.onRecoverClick() } + loginFormPass.setOnEditorDoneSignIn { loginFormSignIn.callOnClick() } + } - with(loginFormHost) { + with(binding.loginFormHost) { setText(hostKeys.getOrNull(0).orEmpty()) setAdapter(LoginSymbolAdapter(context, R.layout.support_simple_spinner_dropdown_item, hostKeys)) - setOnClickListener { if (loginFormContainer.visibility == GONE) dismissDropDown() } + setOnClickListener { if (binding.loginFormContainer.visibility == GONE) dismissDropDown() } } } override fun setCredentials(username: String, pass: String) { - loginFormUsername.setText(username) - loginFormPass.setText(pass) + with(binding) { + loginFormUsername.setText(username) + loginFormPass.setText(pass) + } } override fun setUsernameLabel(label: String) { - loginFormUsernameLayout.hint = label + binding.loginFormUsernameLayout.hint = label } override fun setErrorUsernameRequired() { - with(loginFormUsernameLayout) { + with(binding.loginFormUsernameLayout) { requestFocus() error = getString(R.string.login_field_required) } } override fun setErrorLoginRequired() { - with(loginFormUsernameLayout) { + with(binding.loginFormUsernameLayout) { requestFocus() error = getString(R.string.login_invalid_login) } } override fun setErrorEmailRequired() { - with(loginFormUsernameLayout) { + with(binding.loginFormUsernameLayout) { requestFocus() error = getString(R.string.login_invalid_email) } } override fun setErrorPassRequired(focus: Boolean) { - with(loginFormPassLayout) { + with(binding.loginFormPassLayout) { if (focus) requestFocus() error = getString(R.string.login_field_required) } } override fun setErrorPassInvalid(focus: Boolean) { - with(loginFormPassLayout) { + with(binding.loginFormPassLayout) { if (focus) requestFocus() error = getString(R.string.login_invalid_password) } } override fun setErrorPassIncorrect() { - with(loginFormPassLayout) { + with(binding.loginFormPassLayout) { requestFocus() error = getString(R.string.login_incorrect_password) } } override fun clearUsernameError() { - loginFormUsernameLayout.error = null + binding.loginFormUsernameLayout.error = null } override fun clearPassError() { - loginFormPassLayout.error = null + binding.loginFormPassLayout.error = null } override fun showSoftKeyboard() { @@ -157,16 +157,16 @@ class LoginFormFragment : BaseFragment(), LoginFormView { } override fun showProgress(show: Boolean) { - loginFormProgress.visibility = if (show) VISIBLE else GONE + binding.loginFormProgress.visibility = if (show) VISIBLE else GONE } override fun showContent(show: Boolean) { - loginFormContainer.visibility = if (show) VISIBLE else GONE + binding.loginFormContainer.visibility = if (show) VISIBLE else GONE } @SuppressLint("SetTextI18n") override fun showVersion() { - loginFormVersion.text = "v${appInfo.versionName}" + binding.loginFormVersion.text = "v${appInfo.versionName}" } override fun notifyParentAccountLogged(students: List, loginData: Triple) { @@ -178,7 +178,7 @@ class LoginFormFragment : BaseFragment(), LoginFormView { } override fun showContact(show: Boolean) { - loginFormContact.visibility = if (show) VISIBLE else GONE + binding.loginFormContact.visibility = if (show) VISIBLE else GONE } override fun openAdvancedLogin() { @@ -190,8 +190,8 @@ class LoginFormFragment : BaseFragment(), LoginFormView { } override fun onDestroyView() { - super.onDestroyView() presenter.onDetachView() + super.onDestroyView() } override fun openFaqPage() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverFragment.kt index 1bff49262..2accf1fe6 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverFragment.kt @@ -3,25 +3,24 @@ package io.github.wulkanowy.ui.modules.login.recover import android.annotation.SuppressLint import android.graphics.Color import android.os.Bundle -import android.view.LayoutInflater import android.view.View import android.view.View.GONE import android.view.View.VISIBLE -import android.view.ViewGroup import android.webkit.JavascriptInterface import android.webkit.WebView import android.webkit.WebViewClient import androidx.core.widget.doOnTextChanged import io.github.wulkanowy.R +import io.github.wulkanowy.databinding.FragmentLoginRecoverBinding import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.modules.login.LoginActivity import io.github.wulkanowy.ui.modules.login.form.LoginSymbolAdapter import io.github.wulkanowy.utils.hideSoftInput import io.github.wulkanowy.utils.showSoftInput -import kotlinx.android.synthetic.main.fragment_login_recover.* import javax.inject.Inject -class LoginRecoverFragment : BaseFragment(), LoginRecoverView { +class LoginRecoverFragment : + BaseFragment(R.layout.fragment_login_recover), LoginRecoverView { @Inject lateinit var presenter: LoginRecoverPresenter @@ -37,13 +36,13 @@ class LoginRecoverFragment : BaseFragment(), LoginRecoverView { private lateinit var hostSymbols: Array override val recoverHostValue: String - get() = hostValues.getOrNull(hostKeys.indexOf(loginRecoverHost.text.toString())).orEmpty() + get() = hostValues.getOrNull(hostKeys.indexOf(binding.loginRecoverHost.text.toString())).orEmpty() override val formHostSymbol: String - get() = hostSymbols.getOrNull(hostKeys.indexOf(loginRecoverHost.text.toString())).orEmpty() + get() = hostSymbols.getOrNull(hostKeys.indexOf(binding.loginRecoverHost.text.toString())).orEmpty() override val recoverNameValue: String - get() = loginRecoverName.text.toString().trim() + get() = binding.loginRecoverName.text.toString().trim() override val emailHintString: String get() = getString(R.string.login_email_hint) @@ -54,91 +53,90 @@ class LoginRecoverFragment : BaseFragment(), LoginRecoverView { override val invalidEmailString: String get() = getString(R.string.login_invalid_email) - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_login_recover, container, false) - } - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentLoginRecoverBinding.bind(view) presenter.onAttachView(this) } override fun initView() { - loginRecoverWebView.setBackgroundColor(Color.TRANSPARENT) hostKeys = resources.getStringArray(R.array.hosts_keys) hostValues = resources.getStringArray(R.array.hosts_values) hostSymbols = resources.getStringArray(R.array.hosts_symbols) - loginRecoverName.doOnTextChanged { _, _, _, _ -> presenter.onNameTextChanged() } - loginRecoverHost.setOnItemClickListener { _, _, _, _ -> presenter.onHostSelected() } - loginRecoverButton.setOnClickListener { presenter.onRecoverClick() } - loginRecoverErrorRetry.setOnClickListener { presenter.onRecoverClick() } - loginRecoverErrorDetails.setOnClickListener { presenter.onDetailsClick() } - loginRecoverLogin.setOnClickListener { (activity as LoginActivity).switchView(0) } + with(binding) { + loginRecoverWebView.setBackgroundColor(Color.TRANSPARENT) + loginRecoverName.doOnTextChanged { _, _, _, _ -> presenter.onNameTextChanged() } + loginRecoverHost.setOnItemClickListener { _, _, _, _ -> presenter.onHostSelected() } + loginRecoverButton.setOnClickListener { presenter.onRecoverClick() } + loginRecoverErrorRetry.setOnClickListener { presenter.onRecoverClick() } + loginRecoverErrorDetails.setOnClickListener { presenter.onDetailsClick() } + loginRecoverLogin.setOnClickListener { (activity as LoginActivity).switchView(0) } + } - with(loginRecoverHost) { + with(binding.loginRecoverHost) { setText(hostKeys.getOrNull(0).orEmpty()) setAdapter(LoginSymbolAdapter(context, R.layout.support_simple_spinner_dropdown_item, hostKeys)) - setOnClickListener { if (loginRecoverFormContainer.visibility == GONE) dismissDropDown() } + setOnClickListener { if (binding.loginRecoverFormContainer.visibility == GONE) dismissDropDown() } } } override fun setDefaultCredentials(username: String) { - loginRecoverName.setText(username) + binding.loginRecoverName.setText(username) } override fun setErrorNameRequired() { - with(loginRecoverNameLayout) { + with(binding.loginRecoverNameLayout) { requestFocus() error = getString(R.string.login_field_required) } } override fun setUsernameHint(hint: String) { - loginRecoverNameLayout.hint = hint + binding.loginRecoverNameLayout.hint = hint } override fun setUsernameError(message: String) { - with(loginRecoverNameLayout) { + with(binding.loginRecoverNameLayout) { requestFocus() error = message } } override fun clearUsernameError() { - loginRecoverNameLayout.error = null + binding.loginRecoverNameLayout.error = null } override fun showProgress(show: Boolean) { - loginRecoverProgress.visibility = if (show) VISIBLE else GONE + binding.loginRecoverProgress.visibility = if (show) VISIBLE else GONE } override fun showRecoverForm(show: Boolean) { - loginRecoverFormContainer.visibility = if (show) VISIBLE else GONE + binding.loginRecoverFormContainer.visibility = if (show) VISIBLE else GONE } override fun showCaptcha(show: Boolean) { - loginRecoverCaptchaContainer.visibility = if (show) VISIBLE else GONE + binding.loginRecoverCaptchaContainer.visibility = if (show) VISIBLE else GONE } override fun showErrorView(show: Boolean) { - loginRecoverError.visibility = if (show) VISIBLE else GONE + binding.loginRecoverError.visibility = if (show) VISIBLE else GONE } override fun setErrorDetails(message: String) { - loginRecoverErrorMessage.text = message + binding.loginRecoverErrorMessage.text = message } override fun showSuccessView(show: Boolean) { - loginRecoverSuccess.visibility = if (show) VISIBLE else GONE + binding.loginRecoverSuccess.visibility = if (show) VISIBLE else GONE } override fun setSuccessTitle(title: String) { - loginRecoverSuccessTitle.text = title + binding.loginRecoverSuccessTitle.text = title } override fun setSuccessMessage(message: String) { - loginRecoverSuccessMessage.text = message + binding.loginRecoverSuccessMessage.text = message } override fun showSoftKeyboard() { @@ -159,7 +157,7 @@ class LoginRecoverFragment : BaseFragment(), LoginRecoverView { callback:e =>Android.captchaCallback(e)}) """.trimIndent() - with(loginRecoverWebView) { + with(binding.loginRecoverWebView) { settings.javaScriptEnabled = true webViewClient = object : WebViewClient() { private var recoverWebViewSuccess: Boolean = true @@ -197,8 +195,9 @@ class LoginRecoverFragment : BaseFragment(), LoginRecoverView { } override fun onDestroyView() { - super.onDestroyView() - loginRecoverWebView.destroy() + binding.loginRecoverWebView.destroy() presenter.onDetachView() + + super.onDestroyView() } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectFragment.kt index 48a3cc614..b0df51991 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectFragment.kt @@ -1,24 +1,24 @@ package io.github.wulkanowy.ui.modules.login.studentselect import android.os.Bundle -import android.view.LayoutInflater import android.view.View import android.view.View.GONE import android.view.View.VISIBLE -import android.view.ViewGroup import androidx.recyclerview.widget.LinearLayoutManager import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.databinding.FragmentLoginStudentSelectBinding import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.openEmailClient import io.github.wulkanowy.utils.openInternetBrowser -import kotlinx.android.synthetic.main.fragment_login_student_select.* import java.io.Serializable import javax.inject.Inject -class LoginStudentSelectFragment : BaseFragment(), LoginStudentSelectView { +class LoginStudentSelectFragment : + BaseFragment(R.layout.fragment_login_student_select), + LoginStudentSelectView { @Inject lateinit var presenter: LoginStudentSelectPresenter @@ -35,24 +35,24 @@ class LoginStudentSelectFragment : BaseFragment(), LoginStudentSelectView { fun newInstance() = LoginStudentSelectFragment() } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_login_student_select, container, false) - } - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentLoginStudentSelectBinding.bind(view) presenter.onAttachView(this, savedInstanceState?.getSerializable(SAVED_STUDENTS)) } override fun initView() { loginAdapter.onClickListener = presenter::onItemSelected - loginStudentSelectSignIn.setOnClickListener { presenter.onSignIn() } - loginStudentSelectContactDiscord.setOnClickListener { presenter.onDiscordClick() } - loginStudentSelectContactEmail.setOnClickListener { presenter.onEmailClick() } - loginStudentSelectRecycler.apply { - layoutManager = LinearLayoutManager(context) - adapter = loginAdapter + with(binding) { + loginStudentSelectSignIn.setOnClickListener { presenter.onSignIn() } + loginStudentSelectContactDiscord.setOnClickListener { presenter.onDiscordClick() } + loginStudentSelectContactEmail.setOnClickListener { presenter.onEmailClick() } + + with(loginStudentSelectRecycler) { + layoutManager = LinearLayoutManager(context) + adapter = loginAdapter + } } } @@ -68,15 +68,15 @@ class LoginStudentSelectFragment : BaseFragment(), LoginStudentSelectView { } override fun showProgress(show: Boolean) { - loginStudentSelectProgress.visibility = if (show) VISIBLE else GONE + binding.loginStudentSelectProgress.visibility = if (show) VISIBLE else GONE } override fun showContent(show: Boolean) { - loginStudentSelectContent.visibility = if (show) VISIBLE else GONE + binding.loginStudentSelectContent.visibility = if (show) VISIBLE else GONE } override fun enableSignIn(enable: Boolean) { - loginStudentSelectSignIn.isEnabled = enable + binding.loginStudentSelectSignIn.isEnabled = enable } fun onParentInitStudentSelectFragment(students: List) { @@ -89,7 +89,7 @@ class LoginStudentSelectFragment : BaseFragment(), LoginStudentSelectView { } override fun showContact(show: Boolean) { - loginStudentSelectContact.visibility = if (show) VISIBLE else GONE + binding.loginStudentSelectContact.visibility = if (show) VISIBLE else GONE } override fun onDestroyView() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolFragment.kt index 5602b6248..befbffd50 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolFragment.kt @@ -1,17 +1,16 @@ package io.github.wulkanowy.ui.modules.login.symbol import android.os.Bundle -import android.view.LayoutInflater import android.view.View import android.view.View.GONE import android.view.View.VISIBLE -import android.view.ViewGroup import android.view.inputmethod.EditorInfo.IME_ACTION_DONE import android.view.inputmethod.EditorInfo.IME_NULL import android.widget.ArrayAdapter import androidx.core.widget.doOnTextChanged import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.databinding.FragmentLoginSymbolBinding import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.modules.login.LoginActivity import io.github.wulkanowy.utils.AppInfo @@ -19,10 +18,10 @@ import io.github.wulkanowy.utils.hideSoftInput import io.github.wulkanowy.utils.openEmailClient import io.github.wulkanowy.utils.openInternetBrowser import io.github.wulkanowy.utils.showSoftInput -import kotlinx.android.synthetic.main.fragment_login_symbol.* import javax.inject.Inject -class LoginSymbolFragment : BaseFragment(), LoginSymbolView { +class LoginSymbolFragment : + BaseFragment(R.layout.fragment_login_symbol), LoginSymbolView { @Inject lateinit var presenter: LoginSymbolPresenter @@ -37,29 +36,28 @@ class LoginSymbolFragment : BaseFragment(), LoginSymbolView { } override val symbolNameError: CharSequence? - get() = loginSymbolNameLayout.error + get() = binding.loginSymbolNameLayout.error - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_login_symbol, container, false) - } - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentLoginSymbolBinding.bind(view) presenter.onAttachView(this, savedInstanceState?.getSerializable(SAVED_LOGIN_DATA)) } override fun initView() { - loginSymbolSignIn.setOnClickListener { presenter.attemptLogin(loginSymbolName.text.toString()) } - loginSymbolFaq.setOnClickListener { presenter.onFaqClick() } - loginSymbolContactEmail.setOnClickListener { presenter.onEmailClick() } + with(binding) { + loginSymbolSignIn.setOnClickListener { presenter.attemptLogin(loginSymbolName.text.toString()) } + loginSymbolFaq.setOnClickListener { presenter.onFaqClick() } + loginSymbolContactEmail.setOnClickListener { presenter.onEmailClick() } - loginSymbolName.doOnTextChanged { _, _, _, _ -> presenter.onSymbolTextChanged() } + loginSymbolName.doOnTextChanged { _, _, _, _ -> presenter.onSymbolTextChanged() } - loginSymbolName.apply { - setOnEditorActionListener { _, id, _ -> - if (id == IME_ACTION_DONE || id == IME_NULL) loginSymbolSignIn.callOnClick() else false + loginSymbolName.apply { + setOnEditorActionListener { _, id, _ -> + if (id == IME_ACTION_DONE || id == IME_NULL) loginSymbolSignIn.callOnClick() else false + } + setAdapter(ArrayAdapter(context, android.R.layout.simple_list_item_1, resources.getStringArray(R.array.symbols_values))) } - setAdapter(ArrayAdapter(context, android.R.layout.simple_list_item_1, resources.getStringArray(R.array.symbols_values))) } } @@ -68,25 +66,25 @@ class LoginSymbolFragment : BaseFragment(), LoginSymbolView { } override fun setErrorSymbolIncorrect() { - loginSymbolNameLayout.apply { + binding.loginSymbolNameLayout.apply { requestFocus() error = getString(R.string.login_incorrect_symbol) } } override fun setErrorSymbolRequire() { - loginSymbolNameLayout.apply { + binding.loginSymbolNameLayout.apply { requestFocus() error = getString(R.string.login_field_required) } } override fun clearSymbolError() { - loginSymbolNameLayout.error = null + binding.loginSymbolNameLayout.error = null } override fun clearAndFocusSymbol() { - loginSymbolNameLayout.apply { + binding.loginSymbolNameLayout.apply { editText?.text = null requestFocus() } @@ -101,11 +99,11 @@ class LoginSymbolFragment : BaseFragment(), LoginSymbolView { } override fun showProgress(show: Boolean) { - loginSymbolProgress.visibility = if (show) VISIBLE else GONE + binding.loginSymbolProgress.visibility = if (show) VISIBLE else GONE } override fun showContent(show: Boolean) { - loginSymbolContainer.visibility = if (show) VISIBLE else GONE + binding.loginSymbolContainer.visibility = if (show) VISIBLE else GONE } override fun notifyParentAccountLogged(students: List) { @@ -118,12 +116,12 @@ class LoginSymbolFragment : BaseFragment(), LoginSymbolView { } override fun showContact(show: Boolean) { - loginSymbolContact.visibility = if (show) VISIBLE else GONE + binding.loginSymbolContact.visibility = if (show) VISIBLE else GONE } override fun onDestroyView() { - super.onDestroyView() presenter.onDetachView() + super.onDestroyView() } override fun openFaqPage() { @@ -139,7 +137,7 @@ class LoginSymbolFragment : BaseFragment(), LoginSymbolView { "${appInfo.systemManufacturer} ${appInfo.systemModel}", appInfo.systemVersion.toString(), appInfo.versionName, - "$host/${loginSymbolName.text}", + "$host/${binding.loginSymbolName.text}", lastError ) ) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberFragment.kt index 12bf1a132..0775ce189 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberFragment.kt @@ -1,19 +1,19 @@ package io.github.wulkanowy.ui.modules.luckynumber import android.os.Bundle -import android.view.LayoutInflater import android.view.View import android.view.View.GONE import android.view.View.VISIBLE -import android.view.ViewGroup import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.LuckyNumber +import io.github.wulkanowy.databinding.FragmentLuckyNumberBinding import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.modules.main.MainView -import kotlinx.android.synthetic.main.fragment_lucky_number.* import javax.inject.Inject -class LuckyNumberFragment : BaseFragment(), LuckyNumberView, MainView.TitledView { +class LuckyNumberFragment : + BaseFragment(R.layout.fragment_lucky_number), LuckyNumberView, + MainView.TitledView { @Inject lateinit var presenter: LuckyNumberPresenter @@ -25,58 +25,57 @@ class LuckyNumberFragment : BaseFragment(), LuckyNumberView, MainView.TitledView override val titleStringId: Int get() = R.string.lucky_number_title - override val isViewEmpty get() = luckyNumberText.text.isBlank() + override val isViewEmpty get() = binding.luckyNumberText.text.isBlank() - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_lucky_number, container, false) - } - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) - messageContainer = luckyNumberSwipe + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentLuckyNumberBinding.bind(view) + messageContainer = binding.luckyNumberSwipe presenter.onAttachView(this) } override fun initView() { - luckyNumberSwipe.setOnRefreshListener { presenter.onSwipeRefresh() } - luckyNumberErrorRetry.setOnClickListener { presenter.onRetry() } - luckyNumberErrorDetails.setOnClickListener { presenter.onDetailsClick() } + with(binding) { + luckyNumberSwipe.setOnRefreshListener { presenter.onSwipeRefresh() } + luckyNumberErrorRetry.setOnClickListener { presenter.onRetry() } + luckyNumberErrorDetails.setOnClickListener { presenter.onDetailsClick() } + } } override fun updateData(data: LuckyNumber) { - luckyNumberText.text = data.luckyNumber.toString() + binding.luckyNumberText.text = data.luckyNumber.toString() } override fun hideRefresh() { - luckyNumberSwipe.isRefreshing = false + binding.luckyNumberSwipe.isRefreshing = false } override fun showEmpty(show: Boolean) { - luckyNumberEmpty.visibility = if (show) VISIBLE else GONE + binding.luckyNumberEmpty.visibility = if (show) VISIBLE else GONE } override fun showErrorView(show: Boolean) { - luckyNumberError.visibility = if (show) VISIBLE else GONE + binding.luckyNumberError.visibility = if (show) VISIBLE else GONE } override fun setErrorDetails(message: String) { - luckyNumberErrorMessage.text = message + binding.luckyNumberErrorMessage.text = message } override fun showProgress(show: Boolean) { - luckyNumberProgress.visibility = if (show) VISIBLE else GONE + binding.luckyNumberProgress.visibility = if (show) VISIBLE else GONE } override fun enableSwipe(enable: Boolean) { - luckyNumberSwipe.isEnabled = enable + binding.luckyNumberSwipe.isEnabled = enable } override fun showContent(show: Boolean) { - luckyNumberContent.visibility = if (show) VISIBLE else GONE + binding.luckyNumberContent.visibility = if (show) VISIBLE else GONE } override fun onDestroyView() { - super.onDestroyView() presenter.onDetachView() + super.onDestroyView() } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigureActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigureActivity.kt index 84f4e06e1..d7d5c4ff1 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigureActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigureActivity.kt @@ -10,13 +10,14 @@ import androidx.appcompat.app.AlertDialog import androidx.recyclerview.widget.LinearLayoutManager import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.databinding.ActivityWidgetConfigureBinding import io.github.wulkanowy.ui.base.BaseActivity -import io.github.wulkanowy.ui.modules.login.LoginActivity import io.github.wulkanowy.ui.base.WidgetConfigureAdapter -import kotlinx.android.synthetic.main.activity_widget_configure.* +import io.github.wulkanowy.ui.modules.login.LoginActivity import javax.inject.Inject -class LuckyNumberWidgetConfigureActivity : BaseActivity(), +class LuckyNumberWidgetConfigureActivity : + BaseActivity(), LuckyNumberWidgetConfigureView { @Inject @@ -30,7 +31,7 @@ class LuckyNumberWidgetConfigureActivity : BaseActivity(), MainView { +class MainActivity : BaseActivity(), MainView { @Inject override lateinit var presenter: MainPresenter @@ -82,9 +82,9 @@ class MainActivity : BaseActivity(), MainView { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(R.layout.activity_main) - setSupportActionBar(mainToolbar) - messageContainer = mainFragmentContainer + setContentView(ActivityMainBinding.inflate(layoutInflater).apply { binding = this }.root) + setSupportActionBar(binding.mainToolbar) + messageContainer = binding.mainFragmentContainer presenter.onAttachView(this, intent.getSerializableExtra(EXTRA_START_MENU) as? MainView.Section) @@ -100,12 +100,12 @@ class MainActivity : BaseActivity(), MainView { } override fun initView() { - with(mainToolbar) { + with(binding.mainToolbar) { if (SDK_INT >= LOLLIPOP) stateListAnimator = null setBackgroundColor(overlayProvider.get().compositeOverlayWithThemeSurfaceColorIfNeeded(dpToPx(4f))) } - with(mainBottomNav) { + with(binding.mainBottomNav) { addItems(listOf( AHBottomNavigationItem(R.string.grade_title, R.drawable.ic_main_grade, 0), AHBottomNavigationItem(R.string.attendance_title, R.drawable.ic_main_attendance, 0), @@ -166,7 +166,7 @@ class MainActivity : BaseActivity(), MainView { } override fun showActionBarElevation(show: Boolean) { - ViewCompat.setElevation(mainToolbar, if (show) dpToPx(4f) else 0f) + ViewCompat.setElevation(binding.mainToolbar, if (show) dpToPx(4f) else 0f) } override fun notifyMenuViewReselected() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessageFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessageFragment.kt index 7a3e135dd..4a9d217b0 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessageFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessageFragment.kt @@ -1,16 +1,15 @@ package io.github.wulkanowy.ui.modules.message import android.os.Bundle -import android.view.LayoutInflater import android.view.View import android.view.View.INVISIBLE import android.view.View.VISIBLE -import android.view.ViewGroup import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.repositories.message.MessageFolder.RECEIVED import io.github.wulkanowy.data.repositories.message.MessageFolder.SENT import io.github.wulkanowy.data.repositories.message.MessageFolder.TRASHED +import io.github.wulkanowy.databinding.FragmentMessageBinding import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.base.BaseFragmentPagerAdapter import io.github.wulkanowy.ui.modules.main.MainView @@ -18,10 +17,10 @@ import io.github.wulkanowy.ui.modules.message.send.SendMessageActivity import io.github.wulkanowy.ui.modules.message.tab.MessageTabFragment import io.github.wulkanowy.utils.dpToPx import io.github.wulkanowy.utils.setOnSelectPageListener -import kotlinx.android.synthetic.main.fragment_message.* import javax.inject.Inject -class MessageFragment : BaseFragment(), MessageView, MainView.TitledView { +class MessageFragment : BaseFragment(R.layout.fragment_message), + MessageView, MainView.TitledView { @Inject lateinit var presenter: MessagePresenter @@ -35,20 +34,17 @@ class MessageFragment : BaseFragment(), MessageView, MainView.TitledView { override val titleStringId get() = R.string.message_title - override val currentPageIndex get() = messageViewPager.currentItem + override val currentPageIndex get() = binding.messageViewPager.currentItem - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_message, container, false) - } - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentMessageBinding.bind(view) presenter.onAttachView(this) } override fun initView() { with(pagerAdapter) { - containerId = messageViewPager.id + containerId = binding.messageViewPager.id addFragmentsWithTitle(mapOf( MessageTabFragment.newInstance(RECEIVED) to getString(R.string.message_inbox), MessageTabFragment.newInstance(SENT) to getString(R.string.message_sent), @@ -56,27 +52,29 @@ class MessageFragment : BaseFragment(), MessageView, MainView.TitledView { )) } - with(messageViewPager) { + with(binding.messageViewPager) { adapter = pagerAdapter offscreenPageLimit = 2 setOnSelectPageListener(presenter::onPageSelected) } - with(messageTabLayout) { - setupWithViewPager(messageViewPager) + with(binding.messageTabLayout) { + setupWithViewPager(binding.messageViewPager) setElevationCompat(context.dpToPx(4f)) } - openSendMessageButton.setOnClickListener { presenter.onSendMessageButtonClicked() } + binding.openSendMessageButton.setOnClickListener { presenter.onSendMessageButtonClicked() } } override fun showContent(show: Boolean) { - messageViewPager.visibility = if (show) VISIBLE else INVISIBLE - messageTabLayout.visibility = if (show) VISIBLE else INVISIBLE + with(binding) { + messageViewPager.visibility = if (show) VISIBLE else INVISIBLE + messageTabLayout.visibility = if (show) VISIBLE else INVISIBLE + } } override fun showProgress(show: Boolean) { - messageProgress.visibility = if (show) VISIBLE else INVISIBLE + binding.messageProgress.visibility = if (show) VISIBLE else INVISIBLE } fun onDeleteMessage(message: Message) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewAdapter.kt index a0ac6ec70..436dee53f 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewAdapter.kt @@ -10,10 +10,11 @@ import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.MessageAttachment import io.github.wulkanowy.data.db.entities.MessageWithAttachment import io.github.wulkanowy.data.repositories.message.MessageFolder +import io.github.wulkanowy.databinding.ItemMessageAttachmentBinding +import io.github.wulkanowy.databinding.ItemMessageDividerBinding +import io.github.wulkanowy.databinding.ItemMessagePreviewBinding import io.github.wulkanowy.utils.openInternetBrowser import io.github.wulkanowy.utils.toFormattedString -import kotlinx.android.synthetic.main.item_message_attachment.view.* -import kotlinx.android.synthetic.main.item_message_preview.view.* import javax.inject.Inject class MessagePreviewAdapter @Inject constructor() : @@ -45,44 +46,47 @@ class MessagePreviewAdapter @Inject constructor() : val inflater = LayoutInflater.from(parent.context) return when (viewType) { - ViewType.MESSAGE.id -> MessageViewHolder(inflater.inflate(R.layout.item_message_preview, parent, false)) - ViewType.DIVIDER.id -> DividerViewHolder(inflater.inflate(R.layout.item_message_divider, parent, false)) - ViewType.ATTACHMENT.id -> AttachmentViewHolder(inflater.inflate(R.layout.item_message_attachment, parent, false)) + ViewType.MESSAGE.id -> MessageViewHolder(ItemMessagePreviewBinding.inflate(inflater, parent, false)) + ViewType.DIVIDER.id -> DividerViewHolder(ItemMessageDividerBinding.inflate(inflater, parent, false)) + ViewType.ATTACHMENT.id -> AttachmentViewHolder(ItemMessageAttachmentBinding.inflate(inflater, parent, false)) else -> throw IllegalStateException() } } override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { when (holder) { - is MessageViewHolder -> bindMessage(holder.view, requireNotNull(messageWithAttachment).message) - is AttachmentViewHolder -> bindAttachment(holder.view, requireNotNull(messageWithAttachment).attachments[position - 2]) + is MessageViewHolder -> bindMessage(holder, requireNotNull(messageWithAttachment).message) + is AttachmentViewHolder -> bindAttachment(holder, requireNotNull(messageWithAttachment).attachments[position - 2]) } } @SuppressLint("SetTextI18n") - private fun bindMessage(view: View, message: Message) { - with(view) { - messagePreviewSubject.text = if (message.subject.isNotBlank()) message.subject else context.getString(R.string.message_no_subject) - messagePreviewDate.text = context.getString(R.string.message_date, message.date.toFormattedString("yyyy-MM-dd HH:mm:ss")) + private fun bindMessage(holder: MessageViewHolder, message: Message) { + with(holder.binding) { + messagePreviewSubject.text = if (message.subject.isNotBlank()) message.subject else root.context.getString(R.string.message_no_subject) + messagePreviewDate.text = root.context.getString(R.string.message_date, message.date.toFormattedString("yyyy-MM-dd HH:mm:ss")) messagePreviewContent.text = message.content - messagePreviewAuthor.text = if (message.folderId == MessageFolder.SENT.id) "${context.getString(R.string.message_to)} ${message.recipient}" - else "${context.getString(R.string.message_from)} ${message.sender}" + messagePreviewAuthor.text = if (message.folderId == MessageFolder.SENT.id) "${root.context.getString(R.string.message_to)} ${message.recipient}" + else "${root.context.getString(R.string.message_from)} ${message.sender}" } } - private fun bindAttachment(view: View, attachment: MessageAttachment) { - with(view) { + private fun bindAttachment(holder: AttachmentViewHolder, attachment: MessageAttachment) { + with(holder.binding) { messagePreviewAttachment.visibility = View.VISIBLE messagePreviewAttachment.text = attachment.filename - setOnClickListener { - context.openInternetBrowser(attachment.url) { } + root.setOnClickListener { + root.context.openInternetBrowser(attachment.url) { } } } } - class MessageViewHolder(val view: View) : RecyclerView.ViewHolder(view) + class MessageViewHolder(val binding: ItemMessagePreviewBinding) : + RecyclerView.ViewHolder(binding.root) - class DividerViewHolder(val view: View) : RecyclerView.ViewHolder(view) + class DividerViewHolder(val binding: ItemMessageDividerBinding) : + RecyclerView.ViewHolder(binding.root) - class AttachmentViewHolder(val view: View) : RecyclerView.ViewHolder(view) + class AttachmentViewHolder(val binding: ItemMessageAttachmentBinding) : + RecyclerView.ViewHolder(binding.root) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewFragment.kt index 953238c08..99eede15a 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewFragment.kt @@ -1,27 +1,27 @@ package io.github.wulkanowy.ui.modules.message.preview import android.os.Bundle -import android.view.LayoutInflater import android.view.Menu import android.view.MenuInflater import android.view.MenuItem import android.view.View import android.view.View.GONE import android.view.View.VISIBLE -import android.view.ViewGroup import androidx.recyclerview.widget.LinearLayoutManager import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.MessageWithAttachment +import io.github.wulkanowy.databinding.FragmentMessagePreviewBinding import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.ui.modules.message.MessageFragment import io.github.wulkanowy.ui.modules.message.send.SendMessageActivity -import kotlinx.android.synthetic.main.fragment_message_preview.* import javax.inject.Inject -class MessagePreviewFragment : BaseFragment(), MessagePreviewView, MainView.TitledView { +class MessagePreviewFragment : + BaseFragment(R.layout.fragment_message_preview), + MessagePreviewView, MainView.TitledView { @Inject lateinit var presenter: MessagePreviewPresenter @@ -56,20 +56,17 @@ class MessagePreviewFragment : BaseFragment(), MessagePreviewView, MainView.Titl setHasOptionsMenu(true) } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_message_preview, container, false) - } - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) - messageContainer = messagePreviewContainer + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentMessagePreviewBinding.bind(view) + messageContainer = binding.messagePreviewContainer presenter.onAttachView(this, (savedInstanceState ?: arguments)?.getSerializable(MESSAGE_ID_KEY) as? Message) } override fun initView() { - messagePreviewErrorDetails.setOnClickListener { presenter.onDetailsClick() } + binding.messagePreviewErrorDetails.setOnClickListener { presenter.onDetailsClick() } - with(messagePreviewRecycler) { + with(binding.messagePreviewRecycler) { layoutManager = LinearLayoutManager(context) adapter = previewAdapter } @@ -100,11 +97,11 @@ class MessagePreviewFragment : BaseFragment(), MessagePreviewView, MainView.Titl } override fun showProgress(show: Boolean) { - messagePreviewProgress.visibility = if (show) VISIBLE else GONE + binding.messagePreviewProgress.visibility = if (show) VISIBLE else GONE } override fun showContent(show: Boolean) { - messagePreviewRecycler.visibility = if (show) VISIBLE else GONE + binding.messagePreviewRecycler.visibility = if (show) VISIBLE else GONE } override fun showOptions(show: Boolean) { @@ -122,15 +119,15 @@ class MessagePreviewFragment : BaseFragment(), MessagePreviewView, MainView.Titl } override fun showErrorView(show: Boolean) { - messagePreviewError.visibility = if (show) VISIBLE else GONE + binding.messagePreviewError.visibility = if (show) VISIBLE else GONE } override fun setErrorDetails(message: String) { - messagePreviewErrorMessage.text = message + binding.messagePreviewErrorMessage.text = message } override fun setErrorRetryCallback(callback: () -> Unit) { - messagePreviewErrorRetry.setOnClickListener { callback() } + binding.messagePreviewErrorRetry.setOnClickListener { callback() } } override fun openMessageReply(message: Message?) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessageActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessageActivity.kt index 232b05d79..7b7503433 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessageActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessageActivity.kt @@ -14,14 +14,14 @@ import android.widget.Toast.LENGTH_LONG import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.ReportingUnit +import io.github.wulkanowy.databinding.ActivitySendMessageBinding import io.github.wulkanowy.ui.base.BaseActivity import io.github.wulkanowy.utils.dpToPx import io.github.wulkanowy.utils.hideSoftInput import io.github.wulkanowy.utils.showSoftInput -import kotlinx.android.synthetic.main.activity_send_message.* import javax.inject.Inject -class SendMessageActivity : BaseActivity(), SendMessageView { +class SendMessageActivity : BaseActivity(), SendMessageView { @Inject override lateinit var presenter: SendMessagePresenter @@ -41,17 +41,17 @@ class SendMessageActivity : BaseActivity(), SendMessageVie } override val isDropdownListVisible: Boolean - get() = sendMessageTo.isDropdownListVisible + get() = binding.sendMessageTo.isDropdownListVisible @Suppress("UNCHECKED_CAST") override val formRecipientsData: List - get() = sendMessageTo.addedChipItems as List + get() = binding.sendMessageTo.addedChipItems as List override val formSubjectValue: String - get() = sendMessageSubject.text.toString() + get() = binding.sendMessageSubject.text.toString() override val formContentValue: String - get() = sendMessageMessageContent.text.toString() + get() = binding.sendMessageMessageContent.text.toString() override val messageRequiredRecipients: String get() = getString(R.string.message_required_recipients) @@ -64,18 +64,20 @@ class SendMessageActivity : BaseActivity(), SendMessageVie override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(R.layout.activity_send_message) - setSupportActionBar(sendMessageToolbar) + setContentView(ActivitySendMessageBinding.inflate(layoutInflater).apply { binding = this }.root) + setSupportActionBar(binding.sendMessageToolbar) supportActionBar?.setDisplayHomeAsUpEnabled(true) - messageContainer = sendMessageContainer + messageContainer = binding.sendMessageContainer presenter.onAttachView(this, intent.getSerializableExtra(EXTRA_MESSAGE) as? Message, intent.getSerializableExtra(EXTRA_REPLY) as? Boolean) } override fun initView() { setUpExtendedHitArea() - sendMessageScroll.setOnTouchListener { _, _ -> presenter.onTouchScroll() } - sendMessageTo.onTextChangeListener = presenter::onRecipientsTextChange + with(binding) { + sendMessageScroll.setOnTouchListener { _, _ -> presenter.onTouchScroll() } + sendMessageTo.onTextChangeListener = presenter::onRecipientsTextChange + } } override fun onCreateOptionsMenu(menu: Menu?): Boolean { @@ -93,27 +95,27 @@ class SendMessageActivity : BaseActivity(), SendMessageVie } override fun setReportingUnit(unit: ReportingUnit) { - sendMessageFrom.text = unit.senderName + binding.sendMessageFrom.text = unit.senderName } override fun setRecipients(recipients: List) { - sendMessageTo.filterableChipItems = recipients + binding.sendMessageTo.filterableChipItems = recipients } override fun setSelectedRecipients(recipients: List) { - sendMessageTo.addChips(recipients) + binding.sendMessageTo.addChips(recipients) } override fun showProgress(show: Boolean) { - sendMessageProgress.visibility = if (show) VISIBLE else GONE + binding.sendMessageProgress.visibility = if (show) VISIBLE else GONE } override fun showContent(show: Boolean) { - sendMessageContent.visibility = if (show) VISIBLE else GONE + binding.sendMessageContent.visibility = if (show) VISIBLE else GONE } override fun showEmpty(show: Boolean) { - sendMessageEmpty.visibility = if (show) VISIBLE else GONE + binding.sendMessageEmpty.visibility = if (show) VISIBLE else GONE } override fun showActionBar(show: Boolean) { @@ -121,11 +123,11 @@ class SendMessageActivity : BaseActivity(), SendMessageVie } override fun setSubject(subject: String) { - sendMessageSubject.setText(subject) + binding.sendMessageSubject.setText(subject) } override fun setContent(content: String) { - sendMessageMessageContent.setText(content) + binding.sendMessageMessageContent.setText(content) } override fun showMessage(text: String) { @@ -137,12 +139,14 @@ class SendMessageActivity : BaseActivity(), SendMessageVie } override fun hideDropdownList() { - sendMessageTo.hideDropdownList() + binding.sendMessageTo.hideDropdownList() } override fun scrollToRecipients() { - sendMessageScroll.post { - sendMessageScroll.scrollTo(0, sendMessageTo.bottom - dpToPx(53f).toInt()) + with(binding.sendMessageScroll) { + post { + scrollTo(0, binding.sendMessageTo.bottom - dpToPx(53f).toInt()) + } } } @@ -153,24 +157,24 @@ class SendMessageActivity : BaseActivity(), SendMessageVie private fun setUpExtendedHitArea() { fun extendHitArea() { val containerHitRect = Rect().apply { - sendMessageContent.getHitRect(this) + binding.sendMessageContent.getHitRect(this) } val contentHitRect = Rect().apply { - sendMessageMessageContent.getHitRect(this) + binding.sendMessageMessageContent.getHitRect(this) } contentHitRect.top = contentHitRect.bottom contentHitRect.bottom = containerHitRect.bottom - sendMessageContent.touchDelegate = TouchDelegate(contentHitRect, sendMessageMessageContent) + binding.sendMessageContent.touchDelegate = TouchDelegate(contentHitRect, binding.sendMessageMessageContent) } - sendMessageMessageContent.post { - sendMessageMessageContent.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ -> + with(binding.sendMessageMessageContent) { + post { + addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ -> extendHitArea() } extendHitArea() } - extendHitArea() } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabFragment.kt index c39aa3e28..3fdf16845 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabFragment.kt @@ -1,25 +1,24 @@ package io.github.wulkanowy.ui.modules.message.tab import android.os.Bundle -import android.view.LayoutInflater import android.view.View import android.view.View.GONE import android.view.View.INVISIBLE import android.view.View.VISIBLE -import android.view.ViewGroup import androidx.recyclerview.widget.LinearLayoutManager import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.repositories.message.MessageFolder +import io.github.wulkanowy.databinding.FragmentMessageTabBinding import io.github.wulkanowy.ui.base.BaseFragment -import io.github.wulkanowy.ui.widgets.DividerItemDecoration import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.message.MessageFragment import io.github.wulkanowy.ui.modules.message.preview.MessagePreviewFragment -import kotlinx.android.synthetic.main.fragment_message_tab.* +import io.github.wulkanowy.ui.widgets.DividerItemDecoration import javax.inject.Inject -class MessageTabFragment : BaseFragment(), MessageTabView { +class MessageTabFragment : BaseFragment(R.layout.fragment_message_tab), + MessageTabView { @Inject lateinit var presenter: MessageTabPresenter @@ -42,13 +41,10 @@ class MessageTabFragment : BaseFragment(), MessageTabView { override val isViewEmpty get() = tabAdapter.items.isEmpty() - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_message_tab, container, false) - } - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) - messageContainer = messageTabRecycler + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentMessageTabBinding.bind(view) + messageContainer = binding.messageTabRecycler presenter.onAttachView(this, MessageFolder.valueOf( (savedInstanceState ?: arguments)?.getString(MESSAGE_TAB_FOLDER_ID).orEmpty() )) @@ -57,14 +53,16 @@ class MessageTabFragment : BaseFragment(), MessageTabView { override fun initView() { tabAdapter.onClickListener = presenter::onMessageItemSelected - messageTabRecycler.run { + with(binding.messageTabRecycler) { layoutManager = LinearLayoutManager(context) adapter = tabAdapter addItemDecoration(DividerItemDecoration(context)) } - messageTabSwipe.setOnRefreshListener { presenter.onSwipeRefresh() } - messageTabErrorRetry.setOnClickListener { presenter.onRetry() } - messageTabErrorDetails.setOnClickListener { presenter.onDetailsClick() } + with(binding) { + messageTabSwipe.setOnRefreshListener { presenter.onSwipeRefresh() } + messageTabErrorRetry.setOnClickListener { presenter.onRetry() } + messageTabErrorDetails.setOnClickListener { presenter.onDetailsClick() } + } } override fun updateData(data: List) { @@ -82,31 +80,31 @@ class MessageTabFragment : BaseFragment(), MessageTabView { } override fun showProgress(show: Boolean) { - messageTabProgress.visibility = if (show) VISIBLE else GONE + binding.messageTabProgress.visibility = if (show) VISIBLE else GONE } override fun enableSwipe(enable: Boolean) { - messageTabSwipe.isEnabled = enable + binding.messageTabSwipe.isEnabled = enable } override fun showContent(show: Boolean) { - messageTabRecycler.visibility = if (show) VISIBLE else INVISIBLE + binding.messageTabRecycler.visibility = if (show) VISIBLE else INVISIBLE } override fun showEmpty(show: Boolean) { - messageTabEmpty.visibility = if (show) VISIBLE else INVISIBLE + binding.messageTabEmpty.visibility = if (show) VISIBLE else INVISIBLE } override fun showErrorView(show: Boolean) { - messageTabError.visibility = if (show) VISIBLE else GONE + binding.messageTabError.visibility = if (show) VISIBLE else GONE } override fun setErrorDetails(message: String) { - messageTabErrorMessage.text = message + binding.messageTabErrorMessage.text = message } override fun showRefresh(show: Boolean) { - messageTabSwipe.isRefreshing = show + binding.messageTabSwipe.isRefreshing = show } override fun openMessage(message: Message) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDeviceFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDeviceFragment.kt index 08ec26755..6a3e5a441 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDeviceFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDeviceFragment.kt @@ -1,25 +1,25 @@ package io.github.wulkanowy.ui.modules.mobiledevice import android.os.Bundle -import android.view.LayoutInflater import android.view.View import android.view.View.GONE import android.view.View.VISIBLE -import android.view.ViewGroup import androidx.core.view.postDelayed import androidx.recyclerview.widget.LinearLayoutManager import com.google.android.material.snackbar.Snackbar import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.MobileDevice +import io.github.wulkanowy.databinding.FragmentMobileDeviceBinding import io.github.wulkanowy.ui.base.BaseFragment -import io.github.wulkanowy.ui.widgets.DividerItemDecoration import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.ui.modules.mobiledevice.token.MobileDeviceTokenDialog -import kotlinx.android.synthetic.main.fragment_mobile_device.* +import io.github.wulkanowy.ui.widgets.DividerItemDecoration import javax.inject.Inject -class MobileDeviceFragment : BaseFragment(), MobileDeviceView, MainView.TitledView { +class MobileDeviceFragment : + BaseFragment(R.layout.fragment_mobile_device), MobileDeviceView, + MainView.TitledView { @Inject lateinit var presenter: MobileDevicePresenter @@ -37,29 +37,28 @@ class MobileDeviceFragment : BaseFragment(), MobileDeviceView, MainView.TitledVi override val isViewEmpty: Boolean get() = devicesAdapter.items.isEmpty() - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_mobile_device, container, false) - } - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) - messageContainer = mobileDevicesRecycler + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentMobileDeviceBinding.bind(view) + messageContainer = binding.mobileDevicesRecycler presenter.onAttachView(this) } override fun initView() { devicesAdapter.onDeviceUnregisterListener = presenter::onUnregisterDevice - with(mobileDevicesRecycler) { + with(binding.mobileDevicesRecycler) { layoutManager = LinearLayoutManager(context) adapter = devicesAdapter addItemDecoration(DividerItemDecoration(context)) } - mobileDevicesSwipe.setOnRefreshListener { presenter.onSwipeRefresh() } - mobileDevicesErrorRetry.setOnClickListener { presenter.onRetry() } - mobileDevicesErrorDetails.setOnClickListener { presenter.onDetailsClick() } - mobileDeviceAddButton.setOnClickListener { presenter.onRegisterDevice() } + with(binding) { + mobileDevicesSwipe.setOnRefreshListener { presenter.onSwipeRefresh() } + mobileDevicesErrorRetry.setOnClickListener { presenter.onRetry() } + mobileDevicesErrorDetails.setOnClickListener { presenter.onDetailsClick() } + mobileDeviceAddButton.setOnClickListener { presenter.onRegisterDevice() } + } } override fun updateData(data: List) { @@ -88,7 +87,7 @@ class MobileDeviceFragment : BaseFragment(), MobileDeviceView, MainView.TitledVi override fun showUndo(device: MobileDevice, position: Int) { var confirmed = true - Snackbar.make(mobileDevicesRecycler, getString(R.string.mobile_device_removed), 3000) + Snackbar.make(binding.mobileDevicesRecycler, getString(R.string.mobile_device_removed), 3000) .setAction(R.string.all_undo) { confirmed = false presenter.onUnregisterCancelled(device, position) @@ -100,31 +99,31 @@ class MobileDeviceFragment : BaseFragment(), MobileDeviceView, MainView.TitledVi } override fun hideRefresh() { - mobileDevicesSwipe.isRefreshing = false + binding.mobileDevicesSwipe.isRefreshing = false } override fun showProgress(show: Boolean) { - mobileDevicesProgress.visibility = if (show) VISIBLE else GONE + binding.mobileDevicesProgress.visibility = if (show) VISIBLE else GONE } override fun showEmpty(show: Boolean) { - mobileDevicesEmpty.visibility = if (show) VISIBLE else GONE + binding.mobileDevicesEmpty.visibility = if (show) VISIBLE else GONE } override fun showErrorView(show: Boolean) { - mobileDevicesError.visibility = if (show) VISIBLE else GONE + binding.mobileDevicesError.visibility = if (show) VISIBLE else GONE } override fun setErrorDetails(message: String) { - mobileDevicesErrorMessage.text = message + binding.mobileDevicesErrorMessage.text = message } override fun enableSwipe(enable: Boolean) { - mobileDevicesSwipe.isEnabled = enable + binding.mobileDevicesSwipe.isEnabled = enable } override fun showContent(show: Boolean) { - mobileDevicesRecycler.visibility = if (show) VISIBLE else GONE + binding.mobileDevicesRecycler.visibility = if (show) VISIBLE else GONE } override fun showTokenDialog() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/token/MobileDeviceTokenDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/token/MobileDeviceTokenDialog.kt index 8b81156b1..9ac6049e9 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/token/MobileDeviceTokenDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/token/MobileDeviceTokenDialog.kt @@ -14,11 +14,11 @@ import android.widget.Toast import androidx.core.content.getSystemService import io.github.wulkanowy.R import io.github.wulkanowy.data.pojos.MobileDeviceToken +import io.github.wulkanowy.databinding.DialogMobileDeviceBinding import io.github.wulkanowy.ui.base.BaseDialogFragment -import kotlinx.android.synthetic.main.dialog_mobile_device.* import javax.inject.Inject -class MobileDeviceTokenDialog : BaseDialogFragment(), MobileDeviceTokenVIew { +class MobileDeviceTokenDialog : BaseDialogFragment(), MobileDeviceTokenVIew { @Inject lateinit var presenter: MobileDeviceTokenPresenter @@ -33,7 +33,7 @@ class MobileDeviceTokenDialog : BaseDialogFragment(), MobileDeviceTokenVIew { } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.dialog_mobile_device, container, false) + return DialogMobileDeviceBinding.inflate(inflater).apply { binding = this }.root } override fun onActivityCreated(savedInstanceState: Bundle?) { @@ -42,24 +42,24 @@ class MobileDeviceTokenDialog : BaseDialogFragment(), MobileDeviceTokenVIew { } override fun initView() { - mobileDeviceDialogClose.setOnClickListener { dismiss() } + binding.mobileDeviceDialogClose.setOnClickListener { dismiss() } } override fun updateData(token: MobileDeviceToken) { - with(mobileDeviceDialogToken) { + with(binding.mobileDeviceDialogToken) { text = token.token setOnClickListener { clickCopy(token.token) } } - with(mobileDeviceDialogSymbol) { + with(binding.mobileDeviceDialogSymbol) { text = token.symbol setOnClickListener { clickCopy(token.symbol) } } - with(mobileDeviceDialogPin) { + with(binding.mobileDeviceDialogPin) { text = token.pin setOnClickListener { clickCopy(token.pin) } } - mobileDeviceQr.setImageBitmap(Base64.decode(token.qr, Base64.DEFAULT).let { + binding.mobileDeviceQr.setImageBitmap(Base64.decode(token.qr, Base64.DEFAULT).let { BitmapFactory.decodeByteArray(it, 0, it.size) }) } @@ -71,11 +71,11 @@ class MobileDeviceTokenDialog : BaseDialogFragment(), MobileDeviceTokenVIew { } override fun hideLoading() { - mobileDeviceDialogProgress.visibility = GONE + binding.mobileDeviceDialogProgress.visibility = GONE } override fun showContent() { - mobileDeviceDialogContent.visibility = VISIBLE + binding.mobileDeviceDialogContent.visibility = VISIBLE } override fun closeDialog() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreFragment.kt index 25cda2b2a..a79087de2 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreFragment.kt @@ -2,11 +2,10 @@ package io.github.wulkanowy.ui.modules.more import android.graphics.drawable.Drawable import android.os.Bundle -import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup import androidx.recyclerview.widget.LinearLayoutManager import io.github.wulkanowy.R +import io.github.wulkanowy.databinding.FragmentMoreBinding import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.modules.about.AboutFragment import io.github.wulkanowy.ui.modules.homework.HomeworkFragment @@ -19,10 +18,10 @@ import io.github.wulkanowy.ui.modules.note.NoteFragment import io.github.wulkanowy.ui.modules.schoolandteachers.SchoolAndTeachersFragment import io.github.wulkanowy.ui.modules.settings.SettingsFragment import io.github.wulkanowy.utils.getCompatDrawable -import kotlinx.android.synthetic.main.fragment_more.* import javax.inject.Inject -class MoreFragment : BaseFragment(), MoreView, MainView.TitledView, MainView.MainChildView { +class MoreFragment : BaseFragment(R.layout.fragment_more), MoreView, + MainView.TitledView, MainView.MainChildView { @Inject lateinit var presenter: MorePresenter @@ -61,19 +60,16 @@ class MoreFragment : BaseFragment(), MoreView, MainView.TitledView, MainView.Mai override val aboutRes: Pair? get() = context?.run { getString(R.string.about_title) to getCompatDrawable(R.drawable.ic_all_about) } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_more, container, false) - } - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentMoreBinding.bind(view) presenter.onAttachView(this) } override fun initView() { moreAdapter.onClickListener = presenter::onItemSelected - moreRecycler.apply { + with(binding.moreRecycler) { layoutManager = LinearLayoutManager(context) adapter = moreAdapter } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteDialog.kt index 20ffea4eb..6d1b181ac 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteDialog.kt @@ -9,13 +9,16 @@ import androidx.core.content.ContextCompat import androidx.fragment.app.DialogFragment import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Note +import io.github.wulkanowy.databinding.DialogNoteBinding import io.github.wulkanowy.utils.getThemeAttrColor import io.github.wulkanowy.utils.toFormattedString -import kotlinx.android.synthetic.main.dialog_note.* import io.github.wulkanowy.sdk.scrapper.notes.Note.CategoryType +import io.github.wulkanowy.utils.lifecycleAwareVariable class NoteDialog : DialogFragment() { + private var binding: DialogNoteBinding by lifecycleAwareVariable() + private lateinit var note: Note companion object { @@ -37,19 +40,22 @@ class NoteDialog : DialogFragment() { } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.dialog_note, container, false) + return DialogNoteBinding.inflate(inflater).apply { binding = this }.root } @SuppressLint("SetTextI18n") override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) - noteDialogDate.text = note.date.toFormattedString() - noteDialogCategory.text = note.category - noteDialogTeacher.text = note.teacher - noteDialogContent.text = note.content + with(binding) { + noteDialogDate.text = note.date.toFormattedString() + noteDialogCategory.text = note.category + noteDialogTeacher.text = note.teacher + noteDialogContent.text = note.content + } + if (note.isPointsShow) { - with(noteDialogPoints) { + with(binding.noteDialogPoints) { text = "${if (note.points > 0) "+" else ""}${note.points}" setTextColor(when (CategoryType.getByValue(note.categoryType)) { CategoryType.POSITIVE -> ContextCompat.getColor(requireContext(), R.color.note_positive) @@ -58,6 +64,7 @@ class NoteDialog : DialogFragment() { }) } } - noteDialogClose.setOnClickListener { dismiss() } + + binding.noteDialogClose.setOnClickListener { dismiss() } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteFragment.kt index b01dc4939..473381659 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteFragment.kt @@ -1,22 +1,21 @@ package io.github.wulkanowy.ui.modules.note import android.os.Bundle -import android.view.LayoutInflater import android.view.View import android.view.View.GONE import android.view.View.VISIBLE -import android.view.ViewGroup import androidx.recyclerview.widget.LinearLayoutManager import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Note +import io.github.wulkanowy.databinding.FragmentNoteBinding import io.github.wulkanowy.ui.base.BaseFragment -import io.github.wulkanowy.ui.widgets.DividerItemDecoration import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainView -import kotlinx.android.synthetic.main.fragment_note.* +import io.github.wulkanowy.ui.widgets.DividerItemDecoration import javax.inject.Inject -class NoteFragment : BaseFragment(), NoteView, MainView.TitledView { +class NoteFragment : BaseFragment(R.layout.fragment_note), NoteView, + MainView.TitledView { @Inject lateinit var presenter: NotePresenter @@ -34,26 +33,25 @@ class NoteFragment : BaseFragment(), NoteView, MainView.TitledView { override val isViewEmpty: Boolean get() = noteAdapter.items.isEmpty() - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_note, container, false) - } - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentNoteBinding.bind(view) presenter.onAttachView(this) } override fun initView() { noteAdapter.onClickListener = presenter::onNoteItemSelected - noteRecycler.run { + with(binding.noteRecycler) { layoutManager = LinearLayoutManager(context) adapter = noteAdapter addItemDecoration(DividerItemDecoration(context)) } - noteSwipe.setOnRefreshListener { presenter.onSwipeRefresh() } - noteErrorRetry.setOnClickListener { presenter.onRetry() } - noteErrorDetails.setOnClickListener { presenter.onDetailsClick() } + with(binding) { + noteSwipe.setOnRefreshListener { presenter.onSwipeRefresh() } + noteErrorRetry.setOnClickListener { presenter.onRetry() } + noteErrorDetails.setOnClickListener { presenter.onDetailsClick() } + } } override fun showNoteDialog(note: Note) { @@ -82,31 +80,31 @@ class NoteFragment : BaseFragment(), NoteView, MainView.TitledView { } override fun showEmpty(show: Boolean) { - noteEmpty.visibility = if (show) VISIBLE else GONE + binding.noteEmpty.visibility = if (show) VISIBLE else GONE } override fun showErrorView(show: Boolean) { - noteError.visibility = if (show) VISIBLE else GONE + binding.noteError.visibility = if (show) VISIBLE else GONE } override fun setErrorDetails(message: String) { - noteErrorMessage.text = message + binding.noteErrorMessage.text = message } override fun showProgress(show: Boolean) { - noteProgress.visibility = if (show) VISIBLE else GONE + binding.noteProgress.visibility = if (show) VISIBLE else GONE } override fun enableSwipe(enable: Boolean) { - noteSwipe.isEnabled = enable + binding.noteSwipe.isEnabled = enable } override fun showContent(show: Boolean) { - noteRecycler.visibility = if (show) VISIBLE else GONE + binding.noteRecycler.visibility = if (show) VISIBLE else GONE } override fun hideRefresh() { - noteSwipe.isRefreshing = false + binding.noteSwipe.isRefreshing = false } override fun onDestroyView() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/SchoolAndTeachersFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/SchoolAndTeachersFragment.kt index 5f9c0b9a0..0245aa076 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/SchoolAndTeachersFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/SchoolAndTeachersFragment.kt @@ -1,12 +1,11 @@ package io.github.wulkanowy.ui.modules.schoolandteachers import android.os.Bundle -import android.view.LayoutInflater import android.view.View import android.view.View.INVISIBLE import android.view.View.VISIBLE -import android.view.ViewGroup import io.github.wulkanowy.R +import io.github.wulkanowy.databinding.FragmentSchoolandteachersBinding import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.base.BaseFragmentPagerAdapter import io.github.wulkanowy.ui.modules.main.MainView @@ -14,10 +13,11 @@ import io.github.wulkanowy.ui.modules.schoolandteachers.school.SchoolFragment import io.github.wulkanowy.ui.modules.schoolandteachers.teacher.TeacherFragment import io.github.wulkanowy.utils.dpToPx import io.github.wulkanowy.utils.setOnSelectPageListener -import kotlinx.android.synthetic.main.fragment_schoolandteachers.* import javax.inject.Inject -class SchoolAndTeachersFragment : BaseFragment(), SchoolAndTeachersView, MainView.TitledView { +class SchoolAndTeachersFragment : + BaseFragment(R.layout.fragment_schoolandteachers), + SchoolAndTeachersView, MainView.TitledView { @Inject lateinit var presenter: SchoolAndTeachersPresenter @@ -31,45 +31,44 @@ class SchoolAndTeachersFragment : BaseFragment(), SchoolAndTeachersView, MainVie override val titleStringId: Int get() = R.string.schoolandteachers_title - override val currentPageIndex get() = schoolandteachersViewPager.currentItem + override val currentPageIndex get() = binding.schoolandteachersViewPager.currentItem - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_schoolandteachers, container, false) - } - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentSchoolandteachersBinding.bind(view) presenter.onAttachView(this) } override fun initView() { with(pagerAdapter) { - containerId = schoolandteachersViewPager.id + containerId = binding.schoolandteachersViewPager.id addFragmentsWithTitle(mapOf( SchoolFragment.newInstance() to getString(R.string.school_title), TeacherFragment.newInstance() to getString(R.string.teachers_title) )) } - with(schoolandteachersViewPager) { + with(binding.schoolandteachersViewPager) { adapter = pagerAdapter offscreenPageLimit = 2 setOnSelectPageListener(presenter::onPageSelected) } - with(schoolandteachersTabLayout) { - setupWithViewPager(schoolandteachersViewPager) + with(binding.schoolandteachersTabLayout) { + setupWithViewPager(binding.schoolandteachersViewPager) setElevationCompat(context.dpToPx(4f)) } } override fun showContent(show: Boolean) { - schoolandteachersViewPager.visibility = if (show) VISIBLE else INVISIBLE - schoolandteachersTabLayout.visibility = if (show) VISIBLE else INVISIBLE + with(binding) { + schoolandteachersViewPager.visibility = if (show) VISIBLE else INVISIBLE + schoolandteachersTabLayout.visibility = if (show) VISIBLE else INVISIBLE + } } override fun showProgress(show: Boolean) { - schoolandteachersProgress.visibility = if (show) VISIBLE else INVISIBLE + binding.schoolandteachersProgress.visibility = if (show) VISIBLE else INVISIBLE } fun onChildFragmentLoaded() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/school/SchoolFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/school/SchoolFragment.kt index 5a7c4cade..722999f86 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/school/SchoolFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/school/SchoolFragment.kt @@ -1,89 +1,89 @@ package io.github.wulkanowy.ui.modules.schoolandteachers.school import android.os.Bundle -import android.view.LayoutInflater import android.view.View import android.view.View.GONE import android.view.View.VISIBLE -import android.view.ViewGroup import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.School +import io.github.wulkanowy.databinding.FragmentSchoolBinding import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.ui.modules.schoolandteachers.SchoolAndTeachersChildView import io.github.wulkanowy.ui.modules.schoolandteachers.SchoolAndTeachersFragment import io.github.wulkanowy.utils.openDialer import io.github.wulkanowy.utils.openNavigation -import kotlinx.android.synthetic.main.fragment_school.* import javax.inject.Inject -class SchoolFragment : BaseFragment(), SchoolView, MainView.TitledView, SchoolAndTeachersChildView { +class SchoolFragment : BaseFragment(R.layout.fragment_school), SchoolView, + MainView.TitledView, SchoolAndTeachersChildView { @Inject lateinit var presenter: SchoolPresenter override val titleStringId get() = R.string.school_title - override val isViewEmpty get() = schoolName.text.isBlank() + override val isViewEmpty get() = binding.schoolName.text.isBlank() companion object { fun newInstance() = SchoolFragment() } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_school, container, false) - } - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentSchoolBinding.bind(view) presenter.onAttachView(this) } override fun initView() { - schoolSwipe.setOnRefreshListener { presenter.onSwipeRefresh() } - schoolErrorRetry.setOnClickListener { presenter.onRetry() } - schoolErrorDetails.setOnClickListener { presenter.onDetailsClick() } + with(binding) { + schoolSwipe.setOnRefreshListener { presenter.onSwipeRefresh() } + schoolErrorRetry.setOnClickListener { presenter.onRetry() } + schoolErrorDetails.setOnClickListener { presenter.onDetailsClick() } - schoolAddressButton.setOnClickListener { presenter.onAddressSelected() } - schoolTelephoneButton.setOnClickListener { presenter.onTelephoneSelected() } + schoolAddressButton.setOnClickListener { presenter.onAddressSelected() } + schoolTelephoneButton.setOnClickListener { presenter.onTelephoneSelected() } + } } override fun updateData(data: School) { - schoolName.text = data.name - schoolAddress.text = data.address.ifBlank { "-" } - schoolAddressButton.visibility = if (data.address.isNotBlank()) VISIBLE else GONE - schoolTelephone.text = data.contact.ifBlank { "-" } - schoolTelephoneButton.visibility = if (data.contact.isNotBlank()) VISIBLE else GONE - schoolHeadmaster.text = data.headmaster - schoolPedagogue.text = data.pedagogue + with(binding) { + schoolName.text = data.name + schoolAddress.text = data.address.ifBlank { "-" } + schoolAddressButton.visibility = if (data.address.isNotBlank()) VISIBLE else GONE + schoolTelephone.text = data.contact.ifBlank { "-" } + schoolTelephoneButton.visibility = if (data.contact.isNotBlank()) VISIBLE else GONE + schoolHeadmaster.text = data.headmaster + schoolPedagogue.text = data.pedagogue + } } override fun showEmpty(show: Boolean) { - schoolEmpty.visibility = if (show) VISIBLE else GONE + binding.schoolEmpty.visibility = if (show) VISIBLE else GONE } override fun showErrorView(show: Boolean) { - schoolError.visibility = if (show) VISIBLE else GONE + binding.schoolError.visibility = if (show) VISIBLE else GONE } override fun setErrorDetails(message: String) { - schoolErrorMessage.text = message + binding.schoolErrorMessage.text = message } override fun showProgress(show: Boolean) { - schoolProgress.visibility = if (show) VISIBLE else GONE + binding.schoolProgress.visibility = if (show) VISIBLE else GONE } override fun enableSwipe(enable: Boolean) { - schoolSwipe.isEnabled = enable + binding.schoolSwipe.isEnabled = enable } override fun showContent(show: Boolean) { - schoolContent.visibility = if (show) VISIBLE else GONE + binding.schoolContent.visibility = if (show) VISIBLE else GONE } override fun hideRefresh() { - schoolSwipe.isRefreshing = false + binding.schoolSwipe.isRefreshing = false } override fun notifyParentDataLoaded() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherFragment.kt index 7d1003263..aa50339c1 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherFragment.kt @@ -1,24 +1,22 @@ package io.github.wulkanowy.ui.modules.schoolandteachers.teacher import android.os.Bundle -import android.view.LayoutInflater import android.view.View import android.view.View.GONE import android.view.View.VISIBLE -import android.view.ViewGroup import androidx.recyclerview.widget.LinearLayoutManager import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Teacher +import io.github.wulkanowy.databinding.FragmentTeacherBinding import io.github.wulkanowy.ui.base.BaseFragment -import io.github.wulkanowy.ui.widgets.DividerItemDecoration import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.ui.modules.schoolandteachers.SchoolAndTeachersChildView import io.github.wulkanowy.ui.modules.schoolandteachers.SchoolAndTeachersFragment -import kotlinx.android.synthetic.main.fragment_teacher.* +import io.github.wulkanowy.ui.widgets.DividerItemDecoration import javax.inject.Inject -class TeacherFragment : BaseFragment(), TeacherView, MainView.TitledView, - SchoolAndTeachersChildView { +class TeacherFragment : BaseFragment(R.layout.fragment_teacher), + TeacherView, MainView.TitledView, SchoolAndTeachersChildView { @Inject lateinit var presenter: TeacherPresenter @@ -38,24 +36,23 @@ class TeacherFragment : BaseFragment(), TeacherView, MainView.TitledView, override val isViewEmpty: Boolean get() = teacherAdapter.items.isEmpty() - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_teacher, container, false) - } - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentTeacherBinding.bind(view) presenter.onAttachView(this) } override fun initView() { - teacherRecycler.run { + with(binding.teacherRecycler) { layoutManager = LinearLayoutManager(context) adapter = teacherAdapter addItemDecoration(DividerItemDecoration(context)) } - teacherSwipe.setOnRefreshListener { presenter.onSwipeRefresh() } - teacherErrorRetry.setOnClickListener { presenter.onRetry() } - teacherErrorDetails.setOnClickListener { presenter.onDetailsClick() } + with(binding) { + teacherSwipe.setOnRefreshListener { presenter.onSwipeRefresh() } + teacherErrorRetry.setOnClickListener { presenter.onRetry() } + teacherErrorDetails.setOnClickListener { presenter.onDetailsClick() } + } } override fun updateData(data: List) { @@ -66,31 +63,31 @@ class TeacherFragment : BaseFragment(), TeacherView, MainView.TitledView, } override fun showEmpty(show: Boolean) { - teacherEmpty.visibility = if (show) VISIBLE else GONE + binding.teacherEmpty.visibility = if (show) VISIBLE else GONE } override fun showErrorView(show: Boolean) { - teacherError.visibility = if (show) VISIBLE else GONE + binding.teacherError.visibility = if (show) VISIBLE else GONE } override fun setErrorDetails(message: String) { - teacherErrorMessage.text = message + binding.teacherErrorMessage.text = message } override fun showProgress(show: Boolean) { - teacherProgress.visibility = if (show) VISIBLE else GONE + binding.teacherProgress.visibility = if (show) VISIBLE else GONE } override fun enableSwipe(enable: Boolean) { - teacherSwipe.isEnabled = enable + binding.teacherSwipe.isEnabled = enable } override fun showContent(show: Boolean) { - teacherRecycler.visibility = if (show) VISIBLE else GONE + binding.teacherRecycler.visibility = if (show) VISIBLE else GONE } override fun hideRefresh() { - teacherSwipe.isRefreshing = false + binding.teacherSwipe.isRefreshing = false } override fun notifyParentDataLoaded() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsFragment.kt index fe0a97632..aa38a87af 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsFragment.kt @@ -91,19 +91,19 @@ class SettingsFragment : PreferenceFragmentCompat(), } override fun showError(text: String, error: Throwable) { - (activity as? BaseActivity<*>)?.showError(text, error) + (activity as? BaseActivity<*, *>)?.showError(text, error) } override fun showMessage(text: String) { - (activity as? BaseActivity<*>)?.showMessage(text) + (activity as? BaseActivity<*, *>)?.showMessage(text) } override fun showExpiredDialog() { - (activity as? BaseActivity<*>)?.showExpiredDialog() + (activity as? BaseActivity<*, *>)?.showExpiredDialog() } override fun openClearLoginView() { - (activity as? BaseActivity<*>)?.openClearLoginView() + (activity as? BaseActivity<*, *>)?.openClearLoginView() } override fun showErrorDetailsDialog(error: Throwable) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/splash/SplashActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/splash/SplashActivity.kt index 3e0106d1a..23a437506 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/splash/SplashActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/splash/SplashActivity.kt @@ -3,12 +3,13 @@ package io.github.wulkanowy.ui.modules.splash import android.os.Bundle import android.widget.Toast import android.widget.Toast.LENGTH_LONG +import androidx.viewbinding.ViewBinding import io.github.wulkanowy.ui.base.BaseActivity import io.github.wulkanowy.ui.modules.login.LoginActivity import io.github.wulkanowy.ui.modules.main.MainActivity import javax.inject.Inject -class SplashActivity : BaseActivity(), SplashView { +class SplashActivity : BaseActivity(), SplashView { @Inject override lateinit var presenter: SplashPresenter diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableDialog.kt index 57ec5998d..8efecf07c 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableDialog.kt @@ -11,13 +11,16 @@ import android.view.ViewGroup import androidx.fragment.app.DialogFragment import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Timetable +import io.github.wulkanowy.databinding.DialogTimetableBinding import io.github.wulkanowy.utils.getThemeAttrColor +import io.github.wulkanowy.utils.lifecycleAwareVariable import io.github.wulkanowy.utils.toFormattedString -import kotlinx.android.synthetic.main.dialog_timetable.* import org.threeten.bp.LocalDateTime class TimetableDialog : DialogFragment() { + private var binding: DialogTimetableBinding by lifecycleAwareVariable() + private lateinit var lesson: Timetable companion object { @@ -39,13 +42,13 @@ class TimetableDialog : DialogFragment() { } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.dialog_timetable, container, false) + return DialogTimetableBinding.inflate(inflater).apply { binding = this }.root } override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) - lesson.run { + with(lesson) { setInfo(info, teacher, canceled, changes) setSubject(subject, subjectOld) setTeacher(teacher, teacherOld) @@ -54,74 +57,81 @@ class TimetableDialog : DialogFragment() { setTime(start, end) } - timetableDialogClose.setOnClickListener { dismiss() } + binding.timetableDialogClose.setOnClickListener { dismiss() } } private fun setSubject(subject: String, subjectOld: String) { - timetableDialogSubject.text = subject - if (subjectOld.isNotBlank() && subjectOld != subject) { - timetableDialogSubject.run { - paintFlags = paintFlags or STRIKE_THRU_TEXT_FLAG - text = subjectOld - } - timetableDialogSubjectNew.run { - visibility = VISIBLE - text = subject + with(binding) { + timetableDialogSubject.text = subject + if (subjectOld.isNotBlank() && subjectOld != subject) { + timetableDialogSubject.run { + paintFlags = paintFlags or STRIKE_THRU_TEXT_FLAG + text = subjectOld + } + timetableDialogSubjectNew.run { + visibility = VISIBLE + text = subject + } } } } private fun setInfo(info: String, teacher: String, canceled: Boolean, changes: Boolean) { - when { - info.isNotBlank() -> { - if (canceled) { - timetableDialogChangesTitle.setTextColor(requireContext().getThemeAttrColor(R.attr.colorPrimary)) - timetableDialogChanges.setTextColor(requireContext().getThemeAttrColor(R.attr.colorPrimary)) - } else { - timetableDialogChangesTitle.setTextColor(requireContext().getThemeAttrColor(R.attr.colorTimetableChange)) - timetableDialogChanges.setTextColor(requireContext().getThemeAttrColor(R.attr.colorTimetableChange)) - } + with(binding) { + when { + info.isNotBlank() -> { + if (canceled) { + timetableDialogChangesTitle.setTextColor(requireContext().getThemeAttrColor(R.attr.colorPrimary)) + timetableDialogChanges.setTextColor(requireContext().getThemeAttrColor(R.attr.colorPrimary)) + } else { + timetableDialogChangesTitle.setTextColor(requireContext().getThemeAttrColor(R.attr.colorTimetableChange)) + timetableDialogChanges.setTextColor(requireContext().getThemeAttrColor(R.attr.colorTimetableChange)) + } - timetableDialogChanges.text = when { - canceled && !changes -> "Lekcja odwołana: $info" - changes && teacher.isNotBlank() -> "Zastępstwo: $teacher" - changes && teacher.isBlank() -> "Zastępstwo, ${info.decapitalize()}" - else -> info.capitalize() + timetableDialogChanges.text = when { + canceled && !changes -> "Lekcja odwołana: $info" + changes && teacher.isNotBlank() -> "Zastępstwo: $teacher" + changes && teacher.isBlank() -> "Zastępstwo, ${info.decapitalize()}" + else -> info.capitalize() + } + } + else -> { + timetableDialogChangesTitle.visibility = GONE + timetableDialogChanges.visibility = GONE } - } else -> { - timetableDialogChangesTitle.visibility = GONE - timetableDialogChanges.visibility = GONE } } } private fun setTeacher(teacher: String, teacherOld: String) { - when { - teacherOld.isNotBlank() && teacherOld != teacher -> { - timetableDialogTeacher.run { - visibility = VISIBLE - paintFlags = paintFlags or STRIKE_THRU_TEXT_FLAG - text = teacherOld - } - if (teacher.isNotBlank()) { - timetableDialogTeacherNew.run { + with(binding) { + when { + teacherOld.isNotBlank() && teacherOld != teacher -> { + timetableDialogTeacher.run { visibility = VISIBLE - text = teacher + paintFlags = paintFlags or STRIKE_THRU_TEXT_FLAG + text = teacherOld + } + if (teacher.isNotBlank()) { + timetableDialogTeacherNew.run { + visibility = VISIBLE + text = teacher + } } } - } - teacher.isNotBlank() -> timetableDialogTeacher.text = teacher - else -> { - timetableDialogTeacherTitle.visibility = GONE - timetableDialogTeacher.visibility = GONE + teacher.isNotBlank() -> timetableDialogTeacher.text = teacher + else -> { + timetableDialogTeacherTitle.visibility = GONE + timetableDialogTeacher.visibility = GONE + } } } } private fun setGroup(group: String) { - group.let { + with(binding) { when { - it.isNotBlank() -> timetableDialogGroup.text = it + group.isNotBlank() -> timetableDialogGroup.text = group else -> { timetableDialogGroupTitle.visibility = GONE timetableDialogGroup.visibility = GONE @@ -131,30 +141,32 @@ class TimetableDialog : DialogFragment() { } private fun setRoom(room: String, roomOld: String) { - when { - roomOld.isNotBlank() && roomOld != room -> { - timetableDialogRoom.run { - visibility = VISIBLE - paintFlags = paintFlags or STRIKE_THRU_TEXT_FLAG - text = roomOld - } - if (room.isNotBlank()) { - timetableDialogRoomNew.run { + with(binding) { + when { + roomOld.isNotBlank() && roomOld != room -> { + timetableDialogRoom.run { visibility = VISIBLE - text = room + paintFlags = paintFlags or STRIKE_THRU_TEXT_FLAG + text = roomOld + } + if (room.isNotBlank()) { + timetableDialogRoomNew.run { + visibility = VISIBLE + text = room + } } } - } - room.isNotBlank() -> timetableDialogRoom.text = room - else -> { - timetableDialogRoomTitle.visibility = GONE - timetableDialogRoom.visibility = GONE + room.isNotBlank() -> timetableDialogRoom.text = room + else -> { + timetableDialogRoomTitle.visibility = GONE + timetableDialogRoom.visibility = GONE + } } } } @SuppressLint("SetTextI18n") private fun setTime(start: LocalDateTime, end: LocalDateTime) { - timetableDialogTime.text = "${start.toFormattedString("HH:mm")} - ${end.toFormattedString("HH:mm")}" + binding.timetableDialogTime.text = "${start.toFormattedString("HH:mm")} - ${end.toFormattedString("HH:mm")}" } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt index 2c15b9b3f..2119cebde 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt @@ -1,31 +1,29 @@ package io.github.wulkanowy.ui.modules.timetable import android.os.Bundle -import android.view.LayoutInflater import android.view.Menu import android.view.MenuInflater import android.view.MenuItem import android.view.View import android.view.View.GONE import android.view.View.VISIBLE -import android.view.ViewGroup import androidx.recyclerview.widget.LinearLayoutManager import com.wdullaer.materialdatetimepicker.date.DatePickerDialog import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Timetable +import io.github.wulkanowy.databinding.FragmentTimetableBinding import io.github.wulkanowy.ui.base.BaseFragment -import io.github.wulkanowy.ui.widgets.DividerItemDecoration import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.ui.modules.timetable.completed.CompletedLessonsFragment +import io.github.wulkanowy.ui.widgets.DividerItemDecoration import io.github.wulkanowy.utils.SchooldaysRangeLimiter import io.github.wulkanowy.utils.dpToPx -import kotlinx.android.synthetic.main.fragment_timetable.* import org.threeten.bp.LocalDate import javax.inject.Inject -class TimetableFragment : BaseFragment(), TimetableView, MainView.MainChildView, - MainView.TitledView { +class TimetableFragment : BaseFragment(R.layout.fragment_timetable), + TimetableView, MainView.MainChildView, MainView.TitledView { @Inject lateinit var presenter: TimetablePresenter @@ -50,34 +48,33 @@ class TimetableFragment : BaseFragment(), TimetableView, MainView.MainChildView, setHasOptionsMenu(true) } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_timetable, container, false) - } - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) - messageContainer = timetableRecycler + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentTimetableBinding.bind(view) + messageContainer = binding.timetableRecycler presenter.onAttachView(this, savedInstanceState?.getLong(SAVED_DATE_KEY)) } override fun initView() { timetableAdapter.onClickListener = presenter::onTimetableItemSelected - with(timetableRecycler) { + with(binding.timetableRecycler) { layoutManager = LinearLayoutManager(context) adapter = timetableAdapter addItemDecoration(DividerItemDecoration(context)) } - timetableSwipe.setOnRefreshListener(presenter::onSwipeRefresh) - timetableErrorRetry.setOnClickListener { presenter.onRetry() } - timetableErrorDetails.setOnClickListener { presenter.onDetailsClick() } + with(binding) { + timetableSwipe.setOnRefreshListener(presenter::onSwipeRefresh) + timetableErrorRetry.setOnClickListener { presenter.onRetry() } + timetableErrorDetails.setOnClickListener { presenter.onDetailsClick() } - timetablePreviousButton.setOnClickListener { presenter.onPreviousDay() } - timetableNavDate.setOnClickListener { presenter.onPickDate() } - timetableNextButton.setOnClickListener { presenter.onNextDay() } + timetablePreviousButton.setOnClickListener { presenter.onPreviousDay() } + timetableNavDate.setOnClickListener { presenter.onPickDate() } + timetableNextButton.setOnClickListener { presenter.onNextDay() } - timetableNavContainer.setElevationCompat(requireContext().dpToPx(8f)) + timetableNavContainer.setElevationCompat(requireContext().dpToPx(8f)) + } } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { @@ -105,15 +102,15 @@ class TimetableFragment : BaseFragment(), TimetableView, MainView.MainChildView, } override fun updateNavigationDay(date: String) { - timetableNavDate.text = date + binding.timetableNavDate.text = date } override fun hideRefresh() { - timetableSwipe.isRefreshing = false + binding.timetableSwipe.isRefreshing = false } override fun resetView() { - timetableRecycler.smoothScrollToPosition(0) + binding.timetableRecycler.smoothScrollToPosition(0) } override fun onFragmentReselected() { @@ -125,35 +122,35 @@ class TimetableFragment : BaseFragment(), TimetableView, MainView.MainChildView, } override fun showEmpty(show: Boolean) { - timetableEmpty.visibility = if (show) VISIBLE else GONE + binding.timetableEmpty.visibility = if (show) VISIBLE else GONE } override fun showErrorView(show: Boolean) { - timetableError.visibility = if (show) VISIBLE else GONE + binding.timetableError.visibility = if (show) VISIBLE else GONE } override fun setErrorDetails(message: String) { - timetableErrorMessage.text = message + binding.timetableErrorMessage.text = message } override fun showProgress(show: Boolean) { - timetableProgress.visibility = if (show) VISIBLE else GONE + binding.timetableProgress.visibility = if (show) VISIBLE else GONE } override fun enableSwipe(enable: Boolean) { - timetableSwipe.isEnabled = enable + binding.timetableSwipe.isEnabled = enable } override fun showContent(show: Boolean) { - timetableRecycler.visibility = if (show) VISIBLE else GONE + binding.timetableRecycler.visibility = if (show) VISIBLE else GONE } override fun showPreButton(show: Boolean) { - timetablePreviousButton.visibility = if (show) VISIBLE else View.INVISIBLE + binding.timetablePreviousButton.visibility = if (show) VISIBLE else View.INVISIBLE } override fun showNextButton(show: Boolean) { - timetableNextButton.visibility = if (show) VISIBLE else View.INVISIBLE + binding.timetableNextButton.visibility = if (show) VISIBLE else View.INVISIBLE } override fun showTimetableDialog(lesson: Timetable) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonDialog.kt index 8f7b1ec5b..56ea16cfa 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonDialog.kt @@ -6,12 +6,14 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.DialogFragment -import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.CompletedLesson -import kotlinx.android.synthetic.main.dialog_lesson_completed.* +import io.github.wulkanowy.databinding.DialogLessonCompletedBinding +import io.github.wulkanowy.utils.lifecycleAwareVariable class CompletedLessonDialog : DialogFragment() { + private var binding: DialogLessonCompletedBinding by lifecycleAwareVariable() + private lateinit var completedLesson: CompletedLesson companion object { @@ -28,46 +30,54 @@ class CompletedLessonDialog : DialogFragment() { super.onCreate(savedInstanceState) setStyle(STYLE_NO_TITLE, 0) arguments?.run { - completedLesson = getSerializable(CompletedLessonDialog.ARGUMENT_KEY) as CompletedLesson + completedLesson = getSerializable(ARGUMENT_KEY) as CompletedLesson } } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.dialog_lesson_completed, container, false) + return DialogLessonCompletedBinding.inflate(inflater).apply { binding = this }.root } @SuppressLint("SetTextI18n") override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) - completedLessonDialogSubject.text = completedLesson.subject - completedLessonDialogTopic.text = completedLesson.topic - completedLessonDialogTeacher.text = completedLesson.teacher - completedLessonDialogAbsence.text = completedLesson.absence - completedLessonDialogChanges.text = completedLesson.substitution - completedLessonDialogResources.text = completedLesson.resources + with(binding) { + completedLessonDialogSubject.text = completedLesson.subject + completedLessonDialogTopic.text = completedLesson.topic + completedLessonDialogTeacher.text = completedLesson.teacher + completedLessonDialogAbsence.text = completedLesson.absence + completedLessonDialogChanges.text = completedLesson.substitution + completedLessonDialogResources.text = completedLesson.resources + } completedLesson.substitution.let { if (it.isBlank()) { - completedLessonDialogChangesTitle.visibility = View.GONE - completedLessonDialogChanges.visibility = View.GONE - } else completedLessonDialogChanges.text = it + with(binding) { + completedLessonDialogChangesTitle.visibility = View.GONE + completedLessonDialogChanges.visibility = View.GONE + } + } else binding.completedLessonDialogChanges.text = it } completedLesson.absence.let { if (it.isBlank()) { - completedLessonDialogAbsenceTitle.visibility = View.GONE - completedLessonDialogAbsence.visibility = View.GONE - } else completedLessonDialogAbsence.text = it + with(binding) { + completedLessonDialogAbsenceTitle.visibility = View.GONE + completedLessonDialogAbsence.visibility = View.GONE + } + } else binding.completedLessonDialogAbsence.text = it } completedLesson.resources.let { if (it.isBlank()) { - completedLessonDialogResourcesTitle.visibility = View.GONE - completedLessonDialogResources.visibility = View.GONE - } else completedLessonDialogResources.text = it + with(binding) { + completedLessonDialogResourcesTitle.visibility = View.GONE + completedLessonDialogResources.visibility = View.GONE + } + } else binding.completedLessonDialogResources.text = it } - completedLessonDialogClose.setOnClickListener { dismiss() } + binding.completedLessonDialogClose.setOnClickListener { dismiss() } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsFragment.kt index 544343a42..2efd30a34 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsFragment.kt @@ -1,28 +1,28 @@ package io.github.wulkanowy.ui.modules.timetable.completed import android.os.Bundle -import android.view.LayoutInflater import android.view.View import android.view.View.GONE import android.view.View.INVISIBLE import android.view.View.VISIBLE -import android.view.ViewGroup import androidx.recyclerview.widget.LinearLayoutManager import com.wdullaer.materialdatetimepicker.date.DatePickerDialog import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.CompletedLesson +import io.github.wulkanowy.databinding.FragmentTimetableCompletedBinding import io.github.wulkanowy.ui.base.BaseFragment -import io.github.wulkanowy.ui.widgets.DividerItemDecoration import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainView +import io.github.wulkanowy.ui.widgets.DividerItemDecoration import io.github.wulkanowy.utils.SchooldaysRangeLimiter import io.github.wulkanowy.utils.dpToPx import io.github.wulkanowy.utils.getCompatDrawable -import kotlinx.android.synthetic.main.fragment_timetable_completed.* import org.threeten.bp.LocalDate import javax.inject.Inject -class CompletedLessonsFragment : BaseFragment(), CompletedLessonsView, MainView.TitledView { +class CompletedLessonsFragment : + BaseFragment(R.layout.fragment_timetable_completed), + CompletedLessonsView, MainView.TitledView { @Inject lateinit var presenter: CompletedLessonsPresenter @@ -40,34 +40,33 @@ class CompletedLessonsFragment : BaseFragment(), CompletedLessonsView, MainView. override val isViewEmpty get() = completedLessonsAdapter.items.isEmpty() - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_timetable_completed, container, false) - } - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) - messageContainer = completedLessonsRecycler + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentTimetableCompletedBinding.bind(view) + messageContainer = binding.completedLessonsRecycler presenter.onAttachView(this, savedInstanceState?.getLong(SAVED_DATE_KEY)) } override fun initView() { completedLessonsAdapter.onClickListener = presenter::onCompletedLessonsItemSelected - with(completedLessonsRecycler) { + with(binding.completedLessonsRecycler) { layoutManager = LinearLayoutManager(context) adapter = completedLessonsAdapter addItemDecoration(DividerItemDecoration(context)) } - completedLessonsSwipe.setOnRefreshListener(presenter::onSwipeRefresh) - completedLessonErrorRetry.setOnClickListener { presenter.onRetry() } - completedLessonErrorDetails.setOnClickListener { presenter.onDetailsClick() } + with(binding) { + completedLessonsSwipe.setOnRefreshListener(presenter::onSwipeRefresh) + completedLessonErrorRetry.setOnClickListener { presenter.onRetry() } + completedLessonErrorDetails.setOnClickListener { presenter.onDetailsClick() } - completedLessonsPreviousButton.setOnClickListener { presenter.onPreviousDay() } - completedLessonsNavDate.setOnClickListener { presenter.onPickDate() } - completedLessonsNextButton.setOnClickListener { presenter.onNextDay() } + completedLessonsPreviousButton.setOnClickListener { presenter.onPreviousDay() } + completedLessonsNavDate.setOnClickListener { presenter.onPickDate() } + completedLessonsNextButton.setOnClickListener { presenter.onNextDay() } - completedLessonsNavContainer.setElevationCompat(requireContext().dpToPx(8f)) + completedLessonsNavContainer.setElevationCompat(requireContext().dpToPx(8f)) + } } override fun updateData(data: List) { @@ -85,48 +84,50 @@ class CompletedLessonsFragment : BaseFragment(), CompletedLessonsView, MainView. } override fun updateNavigationDay(date: String) { - completedLessonsNavDate.text = date + binding.completedLessonsNavDate.text = date } override fun hideRefresh() { - completedLessonsSwipe.isRefreshing = false + binding.completedLessonsSwipe.isRefreshing = false } override fun showEmpty(show: Boolean) { - completedLessonsEmpty.visibility = if (show) VISIBLE else GONE + binding.completedLessonsEmpty.visibility = if (show) VISIBLE else GONE } override fun showErrorView(show: Boolean) { - completedLessonError.visibility = if (show) VISIBLE else GONE + binding.completedLessonError.visibility = if (show) VISIBLE else GONE } override fun setErrorDetails(message: String) { - completedLessonErrorMessage.text = message + binding.completedLessonErrorMessage.text = message } override fun showFeatureDisabled() { - completedLessonsInfo.text = getString(R.string.error_feature_disabled) - completedLessonsInfoImage.setImageDrawable(requireContext().getCompatDrawable(R.drawable.ic_all_close_circle)) + with(binding) { + completedLessonsInfo.text = getString(R.string.error_feature_disabled) + completedLessonsInfoImage.setImageDrawable(requireContext().getCompatDrawable(R.drawable.ic_all_close_circle)) + } } override fun showProgress(show: Boolean) { - completedLessonsProgress.visibility = if (show) VISIBLE else GONE + binding.completedLessonsProgress.visibility = if (show) VISIBLE else GONE } override fun enableSwipe(enable: Boolean) { - completedLessonsSwipe.isEnabled = enable + binding.completedLessonsSwipe.isEnabled = enable } override fun showContent(show: Boolean) { - completedLessonsRecycler.visibility = if (show) VISIBLE else GONE + binding.completedLessonsRecycler.visibility = if (show) VISIBLE else GONE } override fun showPreButton(show: Boolean) { - completedLessonsPreviousButton.visibility = if (show) VISIBLE else INVISIBLE + binding.completedLessonsPreviousButton.visibility = if (show) VISIBLE else INVISIBLE } override fun showNextButton(show: Boolean) { - completedLessonsNextButton.visibility = if (show) VISIBLE else INVISIBLE + binding.completedLessonsNextButton.visibility = if (show) VISIBLE else INVISIBLE } override fun showCompletedLessonDialog(completedLesson: CompletedLesson) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigureActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigureActivity.kt index 9208cd9e6..75548c9cc 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigureActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigureActivity.kt @@ -11,14 +11,15 @@ import androidx.appcompat.app.AlertDialog import androidx.recyclerview.widget.LinearLayoutManager import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.databinding.ActivityWidgetConfigureBinding import io.github.wulkanowy.ui.base.BaseActivity import io.github.wulkanowy.ui.base.WidgetConfigureAdapter import io.github.wulkanowy.ui.modules.login.LoginActivity import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.EXTRA_FROM_PROVIDER -import kotlinx.android.synthetic.main.activity_widget_configure.* import javax.inject.Inject -class TimetableWidgetConfigureActivity : BaseActivity(), +class TimetableWidgetConfigureActivity : + BaseActivity(), TimetableWidgetConfigureView { @Inject @@ -32,7 +33,7 @@ class TimetableWidgetConfigureActivity : BaseActivity : ReadWriteProperty, LifecycleObserver { + + private var _value: T? = null + + override fun setValue(thisRef: Fragment, property: KProperty<*>, value: T) { + thisRef.viewLifecycleOwner.lifecycle.removeObserver(this) + _value = value + thisRef.viewLifecycleOwner.lifecycle.addObserver(this) + } + + override fun getValue(thisRef: Fragment, property: KProperty<*>) = _value + ?: throw IllegalStateException("Trying to call an lifecycle-aware value outside of the view lifecycle, or the value has not been initialized") + + @Suppress("unused") + @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) + fun onDestroyView() { + _value = null + } +} + +class LifecycleAwareVariableActivity : ReadWriteProperty, LifecycleObserver { + + private var _value: T? = null + + override fun setValue(thisRef: AppCompatActivity, property: KProperty<*>, value: T) { + thisRef.lifecycle.removeObserver(this) + _value = value + thisRef.lifecycle.addObserver(this) + } + + override fun getValue(thisRef: AppCompatActivity, property: KProperty<*>) = _value + ?: throw IllegalStateException("Trying to call an lifecycle-aware value outside of the view lifecycle, or the value has not been initialized") + + @Suppress("unused") + @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) + fun onDestroyView() { + _value = null + } +} + + +@Suppress("unused") +fun Fragment.lifecycleAwareVariable() = LifecycleAwareVariable() + +fun AppCompatActivity.lifecycleAwareVariable() = LifecycleAwareVariableActivity() diff --git a/app/src/main/res/layout/fragment_about.xml b/app/src/main/res/layout/fragment_about.xml index 556bdfd09..6747b7d0c 100644 --- a/app/src/main/res/layout/fragment_about.xml +++ b/app/src/main/res/layout/fragment_about.xml @@ -1,10 +1,12 @@ + android:layout_height="match_parent" + tools:listitem="@layout/item_about" /> diff --git a/app/src/main/res/layout/fragment_creator.xml b/app/src/main/res/layout/fragment_contributor.xml similarity index 100% rename from app/src/main/res/layout/fragment_creator.xml rename to app/src/main/res/layout/fragment_contributor.xml From 6d1fa0cf056289457c3ca7616861a3dd362caea7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sun, 10 May 2020 10:51:01 +0200 Subject: [PATCH 14/85] Optimize grade average provider (#792) --- app/build.gradle | 2 +- .../data/repositories/grade/GradeLocalTest.kt | 4 +- .../repositories/grade/GradeRepositoryTest.kt | 27 +-- .../data/repositories/grade/GradeLocal.kt | 21 +- .../data/repositories/grade/GradeRemote.kt | 20 +- .../repositories/grade/GradeRepository.kt | 58 +++-- .../gradessummary/GradeSummaryLocal.kt | 24 --- .../gradessummary/GradeSummaryRemote.kt | 35 --- .../gradessummary/GradeSummaryRepository.kt | 35 --- .../wulkanowy/services/ServicesModule.kt | 5 - .../services/sync/works/GradeSummaryWork.kt | 14 -- .../services/sync/works/GradeWork.kt | 1 - .../ui/modules/grade/GradeAverageProvider.kt | 87 ++++---- .../modules/grade/GradeDetailsWithAverage.kt | 12 ++ .../grade/details/GradeDetailsAdapter.kt | 2 +- .../modules/grade/details/GradeDetailsItem.kt | 1 - .../grade/details/GradeDetailsPresenter.kt | 25 +-- .../grade/summary/GradeSummaryPresenter.kt | 26 +-- .../modules/grade/GradeAverageProviderTest.kt | 203 ++++++++++-------- 19 files changed, 278 insertions(+), 324 deletions(-) delete mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/gradessummary/GradeSummaryLocal.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/gradessummary/GradeSummaryRemote.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/gradessummary/GradeSummaryRepository.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/services/sync/works/GradeSummaryWork.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeDetailsWithAverage.kt diff --git a/app/build.gradle b/app/build.gradle index 412dd1bd5..0f35bc822 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -122,7 +122,7 @@ configurations.all { } dependencies { - implementation "io.github.wulkanowy:sdk:0.17.4" + implementation "io.github.wulkanowy:sdk:445905b" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation "androidx.core:core-ktx:1.2.0" diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/grade/GradeLocalTest.kt b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/grade/GradeLocalTest.kt index 7233c306a..eb1a55480 100644 --- a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/grade/GradeLocalTest.kt +++ b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/grade/GradeLocalTest.kt @@ -24,7 +24,7 @@ class GradeLocalTest { fun createDb() { testDb = Room.inMemoryDatabaseBuilder(ApplicationProvider.getApplicationContext(), AppDatabase::class.java) .build() - gradeLocal = GradeLocal(testDb.gradeDao) + gradeLocal = GradeLocal(testDb.gradeDao, testDb.gradeSummaryDao) } @After @@ -43,7 +43,7 @@ class GradeLocalTest { val semester = Semester(1, 2, "", 2019, 2, 1, now(), now(), 1, 1) val grades = gradeLocal - .getGrades(semester) + .getGradesDetails(semester) .blockingGet() assertEquals(2, grades.size) diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/grade/GradeRepositoryTest.kt b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/grade/GradeRepositoryTest.kt index f3c6b7a17..cdd514772 100644 --- a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/grade/GradeRepositoryTest.kt +++ b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/grade/GradeRepositoryTest.kt @@ -11,6 +11,7 @@ import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.repositories.TestInternetObservingStrategy import io.github.wulkanowy.sdk.Sdk +import io.github.wulkanowy.sdk.pojo.Grade import io.mockk.MockKAnnotations import io.mockk.every import io.mockk.impl.annotations.MockK @@ -52,7 +53,7 @@ class GradeRepositoryTest { fun initApi() { MockKAnnotations.init(this) testDb = Room.inMemoryDatabaseBuilder(getApplicationContext(), AppDatabase::class.java).build() - gradeLocal = GradeLocal(testDb.gradeDao) + gradeLocal = GradeLocal(testDb.gradeDao, testDb.gradeSummaryDao) gradeRemote = GradeRemote(mockSdk) every { studentMock.registrationDate } returns LocalDateTime.of(2019, 2, 27, 12, 0) @@ -75,10 +76,10 @@ class GradeRepositoryTest { createGradeApi(5, 4.0, of(2019, 2, 26), "przed zalogowanie w aplikacji"), createGradeApi(5, 4.0, of(2019, 2, 27), "Ocena z dnia logowania"), createGradeApi(5, 4.0, of(2019, 2, 28), "Ocena jeszcze nowsza") - )) + ) to emptyList()) val grades = GradeRepository(settings, gradeLocal, gradeRemote) - .getGrades(studentMock, semesterMock, true).blockingGet().sortedByDescending { it.date } + .getGrades(studentMock, semesterMock, true).blockingGet().first.sortedByDescending { it.date } assertFalse { grades[0].isRead } assertFalse { grades[1].isRead } @@ -99,10 +100,10 @@ class GradeRepositoryTest { createGradeApi(4, 3.0, of(2019, 2, 26), "starszą niż ostatnia lokalnie"), createGradeApi(3, 4.0, of(2019, 2, 27), "Ta jest z tego samego dnia co ostatnia lokalnie"), createGradeApi(2, 5.0, of(2019, 2, 28), "Ta jest już w ogóle nowa") - )) + ) to emptyList()) val grades = GradeRepository(settings, gradeLocal, gradeRemote) - .getGrades(studentMock, semesterMock, true).blockingGet().sortedByDescending { it.date } + .getGrades(studentMock, semesterMock, true).blockingGet().first.sortedByDescending { it.date } assertFalse { grades[0].isRead } assertFalse { grades[1].isRead } @@ -121,12 +122,12 @@ class GradeRepositoryTest { every { mockSdk.getGrades(1) } returns Single.just(listOf( createGradeApi(5, 3.0, of(2019, 2, 25), "Taka sama ocena"), createGradeApi(3, 5.0, of(2019, 2, 26), "Jakaś inna ocena") - )) + ) to emptyList()) val grades = GradeRepository(settings, gradeLocal, gradeRemote) .getGrades(studentMock, semesterMock, true).blockingGet() - assertEquals(2, grades.size) + assertEquals(2, grades.first.size) } @Test @@ -140,12 +141,12 @@ class GradeRepositoryTest { createGradeApi(5, 3.0, of(2019, 2, 25), "Taka sama ocena"), createGradeApi(5, 3.0, of(2019, 2, 25), "Taka sama ocena"), createGradeApi(3, 5.0, of(2019, 2, 26), "Jakaś inna ocena") - )) + ) to emptyList()) val grades = GradeRepository(settings, gradeLocal, gradeRemote) .getGrades(studentMock, semesterMock, true).blockingGet() - assertEquals(3, grades.size) + assertEquals(3, grades.first.size) } @Test @@ -156,12 +157,12 @@ class GradeRepositoryTest { createGradeApi(5, 3.0, of(2019, 2, 25), "Taka sama ocena"), createGradeApi(5, 3.0, of(2019, 2, 25), "Taka sama ocena"), createGradeApi(3, 5.0, of(2019, 2, 26), "Jakaś inna ocena") - )) + ) to emptyList()) val grades = GradeRepository(settings, gradeLocal, gradeRemote) .getGrades(studentMock, semesterMock, true).blockingGet() - assertEquals(3, grades.size) + assertEquals(3, grades.first.size) } @Test @@ -171,11 +172,11 @@ class GradeRepositoryTest { createGradeLocal(3, 5.0, of(2019, 2, 26), "Jakaś inna ocena") )) - every { mockSdk.getGrades(1) } returns Single.just(listOf()) + every { mockSdk.getGrades(1) } returns Single.just(emptyList() to emptyList()) val grades = GradeRepository(settings, gradeLocal, gradeRemote) .getGrades(studentMock, semesterMock, true).blockingGet() - assertEquals(0, grades.size) + assertEquals(0, grades.first.size) } } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/grade/GradeLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/grade/GradeLocal.kt index 4983a4740..944ed34ae 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/grade/GradeLocal.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/grade/GradeLocal.kt @@ -1,14 +1,19 @@ package io.github.wulkanowy.data.repositories.grade import io.github.wulkanowy.data.db.dao.GradeDao +import io.github.wulkanowy.data.db.dao.GradeSummaryDao import io.github.wulkanowy.data.db.entities.Grade +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 GradeLocal @Inject constructor(private val gradeDb: GradeDao) { +class GradeLocal @Inject constructor( + private val gradeDb: GradeDao, + private val gradeSummaryDb: GradeSummaryDao +) { fun saveGrades(grades: List) { gradeDb.insertAll(grades) @@ -22,7 +27,19 @@ class GradeLocal @Inject constructor(private val gradeDb: GradeDao) { gradeDb.updateAll(grades) } - fun getGrades(semester: Semester): Maybe> { + fun getGradesDetails(semester: Semester): Maybe> { return gradeDb.loadAll(semester.semesterId, semester.studentId).filter { it.isNotEmpty() } } + + fun saveGradesSummary(gradesSummary: List) { + gradeSummaryDb.insertAll(gradesSummary) + } + + fun deleteGradesSummary(gradesSummary: List) { + gradeSummaryDb.deleteAll(gradesSummary) + } + + fun getGradesSummary(semester: Semester): Maybe> { + return gradeSummaryDb.loadAll(semester.semesterId, semester.studentId).filter { it.isNotEmpty() } + } } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/grade/GradeRemote.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/grade/GradeRemote.kt index 6f19095ba..abb2f98c9 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/grade/GradeRemote.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/grade/GradeRemote.kt @@ -1,6 +1,7 @@ package io.github.wulkanowy.data.repositories.grade import io.github.wulkanowy.data.db.entities.Grade +import io.github.wulkanowy.data.db.entities.GradeSummary import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.sdk.Sdk @@ -12,11 +13,11 @@ import javax.inject.Singleton @Singleton class GradeRemote @Inject constructor(private val sdk: Sdk) { - fun getGrades(student: Student, semester: Semester): Single> { + fun getGrades(student: Student, semester: Semester): Single, List>> { return sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) .getGrades(semester.semesterId) - .map { grades -> - grades.map { + .map { (details, summary) -> + details.map { Grade( studentId = semester.studentId, semesterId = semester.semesterId, @@ -33,6 +34,19 @@ class GradeRemote @Inject constructor(private val sdk: Sdk) { date = it.date, teacher = it.teacher ) + } to summary.map { + GradeSummary( + semesterId = semester.semesterId, + studentId = semester.studentId, + position = 0, + subject = it.name, + predictedGrade = it.predicted, + finalGrade = it.final, + pointsSum = it.pointsSum, + proposedPoints = it.proposedPoints, + finalPoints = it.finalPoints, + average = it.average + ) } } } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/grade/GradeRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/grade/GradeRepository.kt index 351ebc392..e28350a51 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/grade/GradeRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/grade/GradeRepository.kt @@ -3,6 +3,7 @@ package io.github.wulkanowy.data.repositories.grade 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.GradeSummary import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.utils.uniqueSubtract @@ -19,34 +20,47 @@ class GradeRepository @Inject constructor( private val remote: GradeRemote ) { - fun getGrades(student: Student, semester: Semester, forceRefresh: Boolean = false, notify: Boolean = false): Single> { - return local.getGrades(semester).filter { !forceRefresh } - .switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings) - .flatMap { - if (it) remote.getGrades(student, semester) - else Single.error(UnknownHostException()) - }.flatMap { new -> - local.getGrades(semester).toSingle(emptyList()) - .doOnSuccess { old -> - val notifyBreakDate = old.maxBy { it.date }?.date ?: student.registrationDate.toLocalDate() - local.deleteGrades(old.uniqueSubtract(new)) - local.saveGrades(new.uniqueSubtract(old) - .onEach { - if (it.date >= notifyBreakDate) it.apply { - isRead = false - if (notify) isNotified = false - } - }) - } - }.flatMap { local.getGrades(semester).toSingle(emptyList()) }) + fun getGrades(student: Student, semester: Semester, forceRefresh: Boolean = false, notify: Boolean = false): Single, List>> { + return local.getGradesDetails(semester).flatMap { details -> + local.getGradesSummary(semester).map { summary -> details to summary } + }.filter { !forceRefresh } + .switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings).flatMap { + if (it) remote.getGrades(student, semester) + else Single.error(UnknownHostException()) + }.flatMap { (newDetails, newSummary) -> + local.getGradesDetails(semester).toSingle(emptyList()) + .doOnSuccess { old -> + val notifyBreakDate = old.maxBy { it.date }?.date ?: student.registrationDate.toLocalDate() + local.deleteGrades(old.uniqueSubtract(newDetails)) + local.saveGrades(newDetails.uniqueSubtract(old) + .onEach { + if (it.date >= notifyBreakDate) it.apply { + isRead = false + if (notify) isNotified = false + } + }) + }.flatMap { + local.getGradesSummary(semester).toSingle(emptyList()) + .doOnSuccess { old -> + local.deleteGradesSummary(old.uniqueSubtract(newSummary)) + local.saveGradesSummary(newSummary.uniqueSubtract(old)) + } + } + }.flatMap { + local.getGradesDetails(semester).toSingle(emptyList()).flatMap { details -> + local.getGradesSummary(semester).toSingle(emptyList()).map { summary -> + details to summary + } + } + }) } fun getUnreadGrades(semester: Semester): Single> { - return local.getGrades(semester).map { it.filter { grade -> !grade.isRead } }.toSingle(emptyList()) + return local.getGradesDetails(semester).map { it.filter { grade -> !grade.isRead } }.toSingle(emptyList()) } fun getNotNotifiedGrades(semester: Semester): Single> { - return local.getGrades(semester).map { it.filter { grade -> !grade.isNotified } }.toSingle(emptyList()) + return local.getGradesDetails(semester).map { it.filter { grade -> !grade.isNotified } }.toSingle(emptyList()) } fun updateGrade(grade: Grade): Completable { diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/gradessummary/GradeSummaryLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/gradessummary/GradeSummaryLocal.kt deleted file mode 100644 index e74641d3a..000000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/gradessummary/GradeSummaryLocal.kt +++ /dev/null @@ -1,24 +0,0 @@ -package io.github.wulkanowy.data.repositories.gradessummary - -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 saveGradesSummary(gradesSummary: List) { - gradeSummaryDb.insertAll(gradesSummary) - } - - fun deleteGradesSummary(gradesSummary: List) { - gradeSummaryDb.deleteAll(gradesSummary) - } - - fun getGradesSummary(semester: Semester): Maybe> { - return gradeSummaryDb.loadAll(semester.semesterId, semester.studentId).filter { it.isNotEmpty() } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/gradessummary/GradeSummaryRemote.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/gradessummary/GradeSummaryRemote.kt deleted file mode 100644 index 1b09974dd..000000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/gradessummary/GradeSummaryRemote.kt +++ /dev/null @@ -1,35 +0,0 @@ -package io.github.wulkanowy.data.repositories.gradessummary - -import io.github.wulkanowy.data.db.entities.GradeSummary -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.init -import io.reactivex.Single -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class GradeSummaryRemote @Inject constructor(private val sdk: Sdk) { - - fun getGradeSummary(student: Student, semester: Semester): Single> { - return sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) - .getGradesSummary(semester.semesterId) - .map { gradesSummary -> - gradesSummary.map { - GradeSummary( - semesterId = semester.semesterId, - studentId = semester.studentId, - position = 0, - subject = it.name, - predictedGrade = it.predicted, - finalGrade = it.final, - pointsSum = it.pointsSum, - proposedPoints = it.proposedPoints, - finalPoints = it.finalPoints, - average = it.average - ) - } - } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/gradessummary/GradeSummaryRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/gradessummary/GradeSummaryRepository.kt deleted file mode 100644 index f1d7a6c1c..000000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/gradessummary/GradeSummaryRepository.kt +++ /dev/null @@ -1,35 +0,0 @@ -package io.github.wulkanowy.data.repositories.gradessummary - -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.db.entities.Student -import io.github.wulkanowy.utils.uniqueSubtract -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(student: Student, semester: Semester, forceRefresh: Boolean = false): Single> { - return local.getGradesSummary(semester).filter { !forceRefresh } - .switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings) - .flatMap { - if (it) remote.getGradeSummary(student, semester) - else Single.error(UnknownHostException()) - }.flatMap { new -> - local.getGradesSummary(semester).toSingle(emptyList()) - .doOnSuccess { old -> - local.deleteGradesSummary(old.uniqueSubtract(new)) - local.saveGradesSummary(new.uniqueSubtract(old)) - } - }.flatMap { local.getGradesSummary(semester).toSingle(emptyList()) }) - } -} diff --git a/app/src/main/java/io/github/wulkanowy/services/ServicesModule.kt b/app/src/main/java/io/github/wulkanowy/services/ServicesModule.kt index 7240a50bb..c7c573e27 100644 --- a/app/src/main/java/io/github/wulkanowy/services/ServicesModule.kt +++ b/app/src/main/java/io/github/wulkanowy/services/ServicesModule.kt @@ -21,7 +21,6 @@ import io.github.wulkanowy.services.sync.works.AttendanceWork import io.github.wulkanowy.services.sync.works.CompletedLessonWork import io.github.wulkanowy.services.sync.works.ExamWork import io.github.wulkanowy.services.sync.works.GradeStatisticsWork -import io.github.wulkanowy.services.sync.works.GradeSummaryWork import io.github.wulkanowy.services.sync.works.GradeWork import io.github.wulkanowy.services.sync.works.HomeworkWork import io.github.wulkanowy.services.sync.works.LuckyNumberWork @@ -64,10 +63,6 @@ abstract class ServicesModule { @IntoSet abstract fun provideAttendanceWork(work: AttendanceWork): Work - @Binds - @IntoSet - abstract fun provideGradeSummaryWork(work: GradeSummaryWork): Work - @Binds @IntoSet abstract fun provideExamWork(work: ExamWork): Work diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeSummaryWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeSummaryWork.kt deleted file mode 100644 index 4c8e955d1..000000000 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeSummaryWork.kt +++ /dev/null @@ -1,14 +0,0 @@ -package io.github.wulkanowy.services.sync.works - -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.repositories.gradessummary.GradeSummaryRepository -import io.reactivex.Completable -import javax.inject.Inject - -class GradeSummaryWork @Inject constructor(private val gradeSummaryRepository: GradeSummaryRepository) : Work { - - override fun create(student: Student, semester: Semester): Completable { - return gradeSummaryRepository.getGradesSummary(student, semester, true).ignoreElement() - } -} diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeWork.kt index 7559d2892..6e90826ad 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeWork.kt @@ -58,4 +58,3 @@ class GradeWork @Inject constructor( ) } } - diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt index 3bb084d3a..954b57566 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt @@ -3,71 +3,76 @@ package io.github.wulkanowy.ui.modules.grade import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.repositories.grade.GradeRepository -import io.github.wulkanowy.data.repositories.gradessummary.GradeSummaryRepository import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository +import io.github.wulkanowy.data.repositories.semester.SemesterRepository import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.calcAverage import io.github.wulkanowy.utils.changeModifier -import io.reactivex.Maybe import io.reactivex.Single import javax.inject.Inject class GradeAverageProvider @Inject constructor( - private val preferencesRepository: PreferencesRepository, + private val semesterRepository: SemesterRepository, private val gradeRepository: GradeRepository, - private val gradeSummaryRepository: GradeSummaryRepository + private val preferencesRepository: PreferencesRepository ) { private val plusModifier = preferencesRepository.gradePlusModifier private val minusModifier = preferencesRepository.gradeMinusModifier - fun getGradeAverage(student: Student, semesters: List, selectedSemesterId: Int, forceRefresh: Boolean): Single>> { - return when (preferencesRepository.gradeAverageMode) { - "all_year" -> getAllYearAverage(student, semesters, selectedSemesterId, forceRefresh) - "only_one_semester" -> getOnlyOneSemesterAverage(student, semesters, selectedSemesterId, forceRefresh) - else -> throw IllegalArgumentException("Incorrect grade average mode: ${preferencesRepository.gradeAverageMode} ") + fun getGradesDetailsWithAverage(student: Student, semesterId: Int, forceRefresh: Boolean = false): Single> { + return semesterRepository.getSemesters(student).flatMap { semesters -> + when (preferencesRepository.gradeAverageMode) { + "only_one_semester" -> getSemesterDetailsWithAverage(student, semesters.single { it.semesterId == semesterId }, forceRefresh) + "all_year" -> calculateWholeYearAverage(student, semesters, semesterId, forceRefresh) + else -> throw IllegalArgumentException("Incorrect grade average mode: ${preferencesRepository.gradeAverageMode} ") + } } } - private fun getAllYearAverage(student: Student, semesters: List, semesterId: Int, forceRefresh: Boolean): Single>> { + private fun calculateWholeYearAverage(student: Student, semesters: List, semesterId: Int, forceRefresh: Boolean): Single> { val selectedSemester = semesters.single { it.semesterId == semesterId } val firstSemester = semesters.single { it.diaryId == selectedSemester.diaryId && it.semesterName == 1 } - return getAverageFromGradeSummary(student, selectedSemester, forceRefresh) - .switchIfEmpty(gradeRepository.getGrades(student, selectedSemester, forceRefresh) - .flatMap { firstGrades -> - if (selectedSemester == firstSemester) Single.just(firstGrades) - else { - gradeRepository.getGrades(student, firstSemester) - .map { secondGrades -> secondGrades + firstGrades } + return getSemesterDetailsWithAverage(student, selectedSemester, forceRefresh).flatMap { selectedDetails -> + val isAnyAverage = selectedDetails.any { it.average != .0 } + + if (selectedSemester != firstSemester) { + getSemesterDetailsWithAverage(student, firstSemester, forceRefresh).map { secondDetails -> + selectedDetails.map { selected -> + val second = secondDetails.singleOrNull { it.subject == selected.subject } + selected.copy( + average = if (!isAnyAverage || preferencesRepository.gradeAverageForceCalc) { + (selected.grades + second?.grades.orEmpty()).calcAverage() + } else (selected.average + (second?.average ?: selected.average)) / 2 + ) } - }.map { grades -> - grades.map { if (student.loginMode == Sdk.Mode.SCRAPPER.name) it.changeModifier(plusModifier, minusModifier) else it } - .groupBy { it.subject } - .map { Triple(it.key, it.value.calcAverage(), "") } - }) + } + } else Single.just(selectedDetails) + } } - private fun getOnlyOneSemesterAverage(student: Student, semesters: List, semesterId: Int, forceRefresh: Boolean): Single>> { - val selectedSemester = semesters.single { it.semesterId == semesterId } + private fun getSemesterDetailsWithAverage(student: Student, semester: Semester, forceRefresh: Boolean): Single> { + return gradeRepository.getGrades(student, semester, forceRefresh).map { (details, summaries) -> + val isAnyAverage = summaries.any { it.average != .0 } + val allGrades = details.groupBy { it.subject } - return getAverageFromGradeSummary(student, selectedSemester, forceRefresh) - .switchIfEmpty(gradeRepository.getGrades(student, selectedSemester, forceRefresh) - .map { grades -> - grades.map { if (student.loginMode == Sdk.Mode.SCRAPPER.name) it.changeModifier(plusModifier, minusModifier) else it } - .groupBy { it.subject } - .map { Triple(it.key, it.value.calcAverage(), "") } - }) - } - - private fun getAverageFromGradeSummary(student: Student, selectedSemester: Semester, forceRefresh: Boolean): Maybe>> { - return gradeSummaryRepository.getGradesSummary(student, selectedSemester, forceRefresh) - .toMaybe() - .flatMap { - if (it.any { summary -> summary.average != .0 }) { - Maybe.just(it.map { summary -> Triple(summary.subject, summary.average, summary.pointsSum) }) - } else Maybe.empty() - }.filter { !preferencesRepository.gradeAverageForceCalc } + summaries.map { summary -> + val grades = allGrades[summary.subject].orEmpty() + GradeDetailsWithAverage( + subject = summary.subject, + average = if (!isAnyAverage || preferencesRepository.gradeAverageForceCalc) { + grades.map { + if (student.loginMode == Sdk.Mode.SCRAPPER.name) it.changeModifier(plusModifier, minusModifier) + else it + }.calcAverage() + } else summary.average, + points = summary.pointsSum, + summary = summary, + grades = grades + ) + } + } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeDetailsWithAverage.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeDetailsWithAverage.kt new file mode 100644 index 000000000..3f5d706b3 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeDetailsWithAverage.kt @@ -0,0 +1,12 @@ +package io.github.wulkanowy.ui.modules.grade + +import io.github.wulkanowy.data.db.entities.Grade +import io.github.wulkanowy.data.db.entities.GradeSummary + +data class GradeDetailsWithAverage( + val subject: String, + val average: Double, + val points: String, + val summary: GradeSummary, + val grades: List +) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsAdapter.kt index 22367d02c..7adab547e 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsAdapter.kt @@ -107,7 +107,7 @@ class GradeDetailsAdapter @Inject constructor() : BaseExpandableAdapter 0) View.VISIBLE else View.GONE if (header.newGrades > 0) gradeHeaderNote.text = header.newGrades.toString(10) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsItem.kt index f1adbdea2..281974969 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsItem.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsItem.kt @@ -12,7 +12,6 @@ data class GradeDetailsItem( data class GradeDetailsHeader( val subject: String, - val number: Int, val average: Double?, val pointsSum: String?, var newGrades: Int, diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt index 83501182d..26c222643 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt @@ -8,6 +8,7 @@ import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.modules.grade.GradeAverageProvider +import io.github.wulkanowy.ui.modules.grade.GradeDetailsWithAverage import io.github.wulkanowy.utils.FirebaseAnalyticsHelper import io.github.wulkanowy.utils.SchedulersProvider import timber.log.Timber @@ -126,14 +127,7 @@ class GradeDetailsPresenter @Inject constructor( private fun loadData(semesterId: Int, forceRefresh: Boolean) { Timber.i("Loading grade details data started") disposable.add(studentRepository.getCurrentStudent() - .flatMap { semesterRepository.getSemesters(it).map { semester -> it to semester } } - .flatMap { (student, semesters) -> - averageProvider.getGradeAverage(student, semesters, semesterId, forceRefresh).flatMap { averages -> - gradeRepository.getGrades(student, semesters.first { it.semesterId == semesterId }, forceRefresh) - .map { it.sortedByDescending { grade -> grade.date } } - .map { createGradeItems(it, averages) } - } - } + .flatMap { averageProvider.getGradesDetailsWithAverage(it, semesterId, forceRefresh) } .subscribeOn(schedulers.backgroundThread) .observeOn(schedulers.mainThread) .doFinally { @@ -146,16 +140,14 @@ class GradeDetailsPresenter @Inject constructor( } .subscribe({ grades -> Timber.i("Loading grade details result: Success") - newGradesAmount = grades - .filter { it.viewType == ViewType.HEADER } - .sumBy { item -> (item.value as GradeDetailsHeader).newGrades } + newGradesAmount = grades.sumBy { it.grades.sumBy { grade -> if (!grade.isRead) 1 else 0 } } updateMarkAsDoneButton() view?.run { showEmpty(grades.isEmpty()) showErrorView(false) showContent(grades.isNotEmpty()) updateData( - data = grades, + data = createGradeItems(grades), isGradeExpandable = preferencesRepository.isGradeExpandable, gradeColorTheme = preferencesRepository.gradeColorTheme ) @@ -178,17 +170,16 @@ class GradeDetailsPresenter @Inject constructor( } } - private fun createGradeItems(items: List, averages: List>): List { - return items.groupBy { grade -> grade.subject }.toSortedMap().map { (subject, grades) -> + private fun createGradeItems(items: List): List { + return items.filter { it.grades.isNotEmpty() }.map { (subject, average, points, _, grades) -> val subItems = grades.map { GradeDetailsItem(it, ViewType.ITEM) } listOf(GradeDetailsItem(GradeDetailsHeader( subject = subject, - average = averages.singleOrNull { subject == it.first }?.second, - pointsSum = averages.singleOrNull { subject == it.first }?.third, - number = grades.size, + average = average, + pointsSum = points, newGrades = grades.filter { grade -> !grade.isRead }.size, grades = subItems ), ViewType.HEADER)) + if (preferencesRepository.isGradeExpandable) emptyList() else subItems diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt index 53c69db70..9b8372136 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt @@ -1,12 +1,11 @@ package io.github.wulkanowy.ui.modules.grade.summary import io.github.wulkanowy.data.db.entities.GradeSummary -import io.github.wulkanowy.data.repositories.gradessummary.GradeSummaryRepository -import io.github.wulkanowy.data.repositories.semester.SemesterRepository import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.modules.grade.GradeAverageProvider +import io.github.wulkanowy.ui.modules.grade.GradeDetailsWithAverage import io.github.wulkanowy.utils.FirebaseAnalyticsHelper import io.github.wulkanowy.utils.SchedulersProvider import timber.log.Timber @@ -16,8 +15,6 @@ class GradeSummaryPresenter @Inject constructor( schedulers: SchedulersProvider, errorHandler: ErrorHandler, studentRepository: StudentRepository, - private val gradeSummaryRepository: GradeSummaryRepository, - private val semesterRepository: SemesterRepository, private val averageProvider: GradeAverageProvider, private val analytics: FirebaseAnalyticsHelper ) : BasePresenter(errorHandler, studentRepository, schedulers) { @@ -33,15 +30,8 @@ class GradeSummaryPresenter @Inject constructor( fun onParentViewLoadData(semesterId: Int, forceRefresh: Boolean) { Timber.i("Loading grade summary data started") disposable.add(studentRepository.getCurrentStudent() - .flatMap { semesterRepository.getSemesters(it).map { semesters -> it to semesters } } - .flatMap { (student, semesters) -> - gradeSummaryRepository.getGradesSummary(student, semesters.first { it.semesterId == semesterId }, forceRefresh) - .map { it.sortedBy { subject -> subject.subject } } - .flatMap { gradesSummary -> - averageProvider.getGradeAverage(student, semesters, semesterId, forceRefresh) - .map { averages -> createGradeSummaryItems(gradesSummary, averages) } - } - } + .flatMap { averageProvider.getGradesDetailsWithAverage(it, semesterId, forceRefresh) } + .map { createGradeSummaryItems(it) } .subscribeOn(schedulers.backgroundThread) .observeOn(schedulers.mainThread) .doFinally { @@ -112,12 +102,10 @@ class GradeSummaryPresenter @Inject constructor( disposable.clear() } - private fun createGradeSummaryItems(gradesSummary: List, averages: List>): List { - return gradesSummary - .filter { !checkEmpty(it, averages) } - .map { gradeSummary -> - gradeSummary.copy(average = averages.singleOrNull { gradeSummary.subject == it.first }?.second ?: .0) - } + private fun createGradeSummaryItems(items: List): List { + return items.map { + it.summary.copy(average = it.average) + } } private fun checkEmpty(gradeSummary: GradeSummary, averages: List>): Boolean { diff --git a/app/src/test/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProviderTest.kt b/app/src/test/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProviderTest.kt index 009ed610e..68b3f6bbf 100644 --- a/app/src/test/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProviderTest.kt +++ b/app/src/test/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProviderTest.kt @@ -3,17 +3,17 @@ package io.github.wulkanowy.ui.modules.grade import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.db.entities.GradeSummary import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.repositories.grade.GradeRepository -import io.github.wulkanowy.data.repositories.gradessummary.GradeSummaryRepository -import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository import io.github.wulkanowy.data.repositories.createSemesterEntity +import io.github.wulkanowy.data.repositories.grade.GradeRepository +import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository +import io.github.wulkanowy.data.repositories.semester.SemesterRepository import io.github.wulkanowy.sdk.Sdk import io.reactivex.Single import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test import org.mockito.Mock -import org.mockito.Mockito.doReturn +import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations import org.threeten.bp.LocalDate.now import org.threeten.bp.LocalDate.of @@ -25,10 +25,10 @@ class GradeAverageProviderTest { lateinit var preferencesRepository: PreferencesRepository @Mock - lateinit var gradeRepository: GradeRepository + lateinit var semesterRepository: SemesterRepository @Mock - lateinit var gradeSummaryRepository: GradeSummaryRepository + lateinit var gradeRepository: GradeRepository private lateinit var gradeAverageProvider: GradeAverageProvider @@ -41,165 +41,192 @@ class GradeAverageProviderTest { ) private val firstGrades = listOf( + // avg: 3.5 getGrade(22, "Matematyka", 4.0), getGrade(22, "Matematyka", 3.0), + + // avg: 3.5 getGrade(22, "Fizyka", 6.0), getGrade(22, "Fizyka", 1.0) ) - private val secondGrade = listOf( + private val firstSummaries = listOf( + getSummary(semesterId = 22, subject = "Matematyka", average = 3.9), + getSummary(semesterId = 22, subject = "Fizyka", average = 3.1) + ) + + private val secondGrades = listOf( + // avg: 2.5 getGrade(23, "Matematyka", 2.0), getGrade(23, "Matematyka", 3.0), + + // avg: 3.0 getGrade(23, "Fizyka", 4.0), getGrade(23, "Fizyka", 2.0) ) + private val secondSummaries = listOf( + getSummary(semesterId = 23, subject = "Matematyka", average = 2.9), + getSummary(semesterId = 23, subject = "Fizyka", average = 3.4) + ) + private val secondGradeWithModifier = listOf( + // avg: 3.375 getGrade(24, "Język polski", 3.0, -0.50), getGrade(24, "Język polski", 4.0, 0.25) ) + private val secondSummariesWithModifier = listOf( + getSummary(24, "Język polski", 3.49) + ) + @Before - fun initTest() { + fun setUp() { MockitoAnnotations.initMocks(this) - gradeAverageProvider = GradeAverageProvider(preferencesRepository, gradeRepository, gradeSummaryRepository) - doReturn(.33).`when`(preferencesRepository).gradeMinusModifier - doReturn(.33).`when`(preferencesRepository).gradePlusModifier - doReturn(false).`when`(preferencesRepository).gradeAverageForceCalc + `when`(preferencesRepository.gradeMinusModifier).thenReturn(.33) + `when`(preferencesRepository.gradePlusModifier).thenReturn(.33) + `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(false) + `when`(semesterRepository.getSemesters(student)).thenReturn(Single.just(semesters)) - doReturn(Single.just(firstGrades)).`when`(gradeRepository).getGrades(student, semesters[1], true) - doReturn(Single.just(secondGrade)).`when`(gradeRepository).getGrades(student, semesters[2], true) + gradeAverageProvider = GradeAverageProvider(semesterRepository, gradeRepository, preferencesRepository) } @Test fun onlyOneSemesterTest() { - doReturn("only_one_semester").`when`(preferencesRepository).gradeAverageMode - doReturn(Single.just(emptyList())).`when`(gradeSummaryRepository).getGradesSummary(student, semesters[2], true) + `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true) + `when`(preferencesRepository.gradeAverageMode).thenReturn("only_one_semester") + `when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGrades to secondSummaries)) - val averages = gradeAverageProvider.getGradeAverage(student, semesters, semesters[2].semesterId, true) - .blockingGet() + val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet() - assertEquals(2, averages.size) - assertEquals(2.5, averages.single { it.first == "Matematyka" }.second, .0) - assertEquals(3.0, averages.single { it.first == "Fizyka" }.second, .0) + assertEquals(2, items.size) + assertEquals(2.5, items.single { it.subject == "Matematyka" }.average, .0) + assertEquals(3.0, items.single { it.subject == "Fizyka" }.average, .0) } @Test fun onlyOneSemester_gradesWithModifiers_default() { - doReturn("only_one_semester").`when`(preferencesRepository).gradeAverageMode - doReturn(Single.just(emptyList())).`when`(gradeSummaryRepository).getGradesSummary(student, semesters[2], true) - doReturn(Single.just(secondGradeWithModifier)).`when`(gradeRepository).getGrades(student, semesters[2], true) + `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true) + `when`(preferencesRepository.gradeAverageMode).thenReturn("only_one_semester") + `when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGradeWithModifier to secondSummariesWithModifier)) - val averages = gradeAverageProvider.getGradeAverage(student, semesters, semesters[2].semesterId, true) - .blockingGet() + val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet() - assertEquals(3.5, averages.single { it.first == "Język polski" }.second, .0) + assertEquals(3.5, items.single { it.subject == "Język polski" }.average, .0) } @Test fun onlyOneSemester_gradesWithModifiers_api() { - doReturn("only_one_semester").`when`(preferencesRepository).gradeAverageMode - doReturn(Single.just(emptyList())).`when`(gradeSummaryRepository).getGradesSummary(student.copy(loginMode = Sdk.Mode.API.name), semesters[2], true) - doReturn(Single.just(secondGradeWithModifier)).`when`(gradeRepository).getGrades(student.copy(loginMode = Sdk.Mode.API.name), semesters[2], true) + val student = student.copy(loginMode = Sdk.Mode.API.name) - val averages = gradeAverageProvider.getGradeAverage(student.copy(loginMode = Sdk.Mode.API.name), semesters, semesters[2].semesterId, true) - .blockingGet() + `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true) + `when`(preferencesRepository.gradeAverageMode).thenReturn("only_one_semester") + `when`(semesterRepository.getSemesters(student)).thenReturn(Single.just(semesters)) + `when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGradeWithModifier to secondSummariesWithModifier)) - assertEquals(3.375, averages.single { it.first == "Język polski" }.second, .0) + val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet() + + assertEquals(3.375, items.single { it.subject == "Język polski" }.average, .0) } @Test fun onlyOneSemester_gradesWithModifiers_scrapper() { - doReturn("only_one_semester").`when`(preferencesRepository).gradeAverageMode - doReturn(Single.just(emptyList())).`when`(gradeSummaryRepository).getGradesSummary(student, semesters[2], true) - doReturn(Single.just(secondGradeWithModifier)).`when`(gradeRepository).getGrades(student.copy(loginMode = Sdk.Mode.SCRAPPER.name), semesters[2], true) + val student = student.copy(loginMode = Sdk.Mode.SCRAPPER.name) - val averages = gradeAverageProvider.getGradeAverage(student.copy(loginMode = Sdk.Mode.SCRAPPER.name), semesters, semesters[2].semesterId, true) - .blockingGet() + `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true) + `when`(preferencesRepository.gradeAverageMode).thenReturn("only_one_semester") + `when`(semesterRepository.getSemesters(student)).thenReturn(Single.just(semesters)) + `when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGradeWithModifier to secondSummariesWithModifier)) - assertEquals(3.5, averages.single { it.first == "Język polski" }.second, .0) + val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet() + + assertEquals(3.5, items.single { it.subject == "Język polski" }.average, .0) } @Test fun onlyOneSemester_gradesWithModifiers_hybrid() { - doReturn("only_one_semester").`when`(preferencesRepository).gradeAverageMode - doReturn(Single.just(emptyList())).`when`(gradeSummaryRepository).getGradesSummary(student.copy(loginMode = Sdk.Mode.HYBRID.name), semesters[2], true) - doReturn(Single.just(secondGradeWithModifier)).`when`(gradeRepository).getGrades(student.copy(loginMode = Sdk.Mode.HYBRID.name), semesters[2], true) + val student = student.copy(loginMode = Sdk.Mode.HYBRID.name) - val averages = gradeAverageProvider.getGradeAverage(student.copy(loginMode = Sdk.Mode.HYBRID.name), semesters, semesters[2].semesterId, true) - .blockingGet() + `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true) + `when`(preferencesRepository.gradeAverageMode).thenReturn("only_one_semester") + `when`(semesterRepository.getSemesters(student)).thenReturn(Single.just(semesters)) + `when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGradeWithModifier to secondSummariesWithModifier)) - assertEquals(3.375, averages.single { it.first == "Język polski" }.second, .0) + val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet() + + assertEquals(3.375, items.single { it.subject == "Język polski" }.average, .0) } @Test fun allYearFirstSemesterTest() { - doReturn("all_year").`when`(preferencesRepository).gradeAverageMode - doReturn(Single.just(emptyList())).`when`(gradeSummaryRepository).getGradesSummary(student, semesters[1], true) + `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true) + `when`(preferencesRepository.gradeAverageMode).thenReturn("all_year") + `when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(firstGrades to firstSummaries)) - val averages = gradeAverageProvider.getGradeAverage(student, semesters, semesters[1].semesterId, true) - .blockingGet() + val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[1].semesterId).blockingGet() - assertEquals(2, averages.size) - assertEquals(3.5, averages.single { it.first == "Matematyka" }.second, .0) - assertEquals(3.5, averages.single { it.first == "Fizyka" }.second, .0) + assertEquals(2, items.size) + assertEquals(3.5, items.single { it.subject == "Matematyka" }.average, .0) + assertEquals(3.5, items.single { it.subject == "Fizyka" }.average, .0) } @Test fun allYearSecondSemesterTest() { - doReturn("all_year").`when`(preferencesRepository).gradeAverageMode - doReturn(Single.just(firstGrades)).`when`(gradeRepository).getGrades(student, semesters[1], false) - doReturn(Single.just(emptyList())).`when`(gradeSummaryRepository).getGradesSummary(student, semesters[2], true) + `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true) + `when`(preferencesRepository.gradeAverageMode).thenReturn("all_year") + `when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(firstGrades to firstSummaries)) + `when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGrades to secondSummaries)) - val averages = gradeAverageProvider.getGradeAverage(student, semesters, semesters[2].semesterId, true) - .blockingGet() + val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet() - assertEquals(2, averages.size) - assertEquals(3.0, averages.single { it.first == "Matematyka" }.second, .0) - assertEquals(3.25, averages.single { it.first == "Fizyka" }.second, .0) + assertEquals(2, items.size) + assertEquals(3.0, items.single { it.subject == "Matematyka" }.average, .0) + assertEquals(3.25, items.single { it.subject == "Fizyka" }.average, .0) } @Test(expected = IllegalArgumentException::class) fun incorrectAverageModeTest() { - doReturn("test_mode").`when`(preferencesRepository).gradeAverageMode + `when`(preferencesRepository.gradeAverageMode).thenReturn("test_mode") - gradeAverageProvider.getGradeAverage(student, semesters, semesters[2].semesterId, true).blockingGet() + gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).blockingGet() } @Test - fun onlyOneSemester_averageFromSummary() { - doReturn("all_year").`when`(preferencesRepository).gradeAverageMode - doReturn(Single.just(firstGrades)).`when`(gradeRepository).getGrades(student, semesters[1], false) - doReturn(Single.just(listOf( - getSummary(22, "Matematyka", 3.1), - getSummary(22, "Fizyka", 3.26) - ))).`when`(gradeSummaryRepository).getGradesSummary(student, semesters[2], true) + fun allYearSemester_averageFromSummary() { + `when`(preferencesRepository.gradeAverageMode).thenReturn("all_year") + `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(false) + `when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(firstGrades to listOf( + getSummary(22, "Matematyka", 3.0), + getSummary(22, "Fizyka", 3.5) + ))) + `when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGrades to listOf( + getSummary(22, "Matematyka", 3.5), + getSummary(22, "Fizyka", 4.0) + ))) - val averages = gradeAverageProvider.getGradeAverage(student, semesters, semesters[2].semesterId, true) - .blockingGet() + val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet() - assertEquals(2, averages.size) - assertEquals(3.1, averages.single { it.first == "Matematyka" }.second, .0) - assertEquals(3.26, averages.single { it.first == "Fizyka" }.second, .0) + assertEquals(2, items.size) + assertEquals(3.25, items.single { it.subject == "Matematyka" }.average, .0) + assertEquals(3.75, items.single { it.subject == "Fizyka" }.average, .0) } @Test fun onlyOneSemester_averageFromSummary_forceCalc() { - doReturn(true).`when`(preferencesRepository).gradeAverageForceCalc - doReturn("all_year").`when`(preferencesRepository).gradeAverageMode - doReturn(Single.just(firstGrades)).`when`(gradeRepository).getGrades(student, semesters[1], false) - doReturn(Single.just(listOf( - getSummary(22, "Matematyka", 3.1), - getSummary(22, "Fizyka", 3.26) - ))).`when`(gradeSummaryRepository).getGradesSummary(student, semesters[2], true) + `when`(preferencesRepository.gradeAverageMode).thenReturn("all_year") + `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true) + `when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(firstGrades to firstSummaries)) + `when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGrades to listOf( + getSummary(22, "Matematyka", 1.1), + getSummary(22, "Fizyka", 7.26) + ))) - val averages = gradeAverageProvider.getGradeAverage(student, semesters, semesters[2].semesterId, true) - .blockingGet() + val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet() - assertEquals(2, averages.size) - assertEquals(3.0, averages.single { it.first == "Matematyka" }.second, .0) - assertEquals(3.25, averages.single { it.first == "Fizyka" }.second, .0) + assertEquals(2, items.size) + assertEquals(3.0, items.single { it.subject == "Matematyka" }.average, .0) + assertEquals(3.25, items.single { it.subject == "Fizyka" }.average, .0) } private fun getGrade(semesterId: Int, subject: String, value: Double, modifier: Double = 0.0): Grade { @@ -221,12 +248,12 @@ class GradeAverageProviderTest { ) } - private fun getSummary(semesterId: Int, subject: String, value: Double): GradeSummary { + private fun getSummary(semesterId: Int, subject: String, average: Double): GradeSummary { return GradeSummary( studentId = 101, semesterId = semesterId, subject = subject, - average = value, + average = average, pointsSum = "", proposedPoints = "", finalPoints = "", From 45fc76a9a506cfc02847d3b2e95482501ab85789 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Sun, 10 May 2020 11:34:54 +0200 Subject: [PATCH 15/85] Update translations (#794) --- .../main/res/values-de/preferences_values.xml | 32 +- app/src/main/res/values-de/strings.xml | 79 +---- .../main/res/values-pl/preferences_values.xml | 32 +- app/src/main/res/values-pl/strings.xml | 74 +--- .../main/res/values-ru/preferences_values.xml | 19 +- app/src/main/res/values-ru/strings.xml | 196 ++++------- .../main/res/values-uk/preferences_values.xml | 19 +- app/src/main/res/values-uk/strings.xml | 315 ++++++++---------- app/src/main/res/values/strings.xml | 2 +- 9 files changed, 273 insertions(+), 495 deletions(-) diff --git a/app/src/main/res/values-de/preferences_values.xml b/app/src/main/res/values-de/preferences_values.xml index 8eae84194..28ad08bc2 100644 --- a/app/src/main/res/values-de/preferences_values.xml +++ b/app/src/main/res/values-de/preferences_values.xml @@ -1,5 +1,18 @@ + + Licht + Dunkel + Schwarz (AMOLED) + + + System Sprache + Polski + English + Pусский + Українська + Deutsch + 15 Minuten 30 Minuten @@ -9,22 +22,6 @@ 12 Stunden 24 Stunden - - - Licht - Dunkel - Schwarz (AMOLED) - - - - System Sprache - Polski - English - Pусский - Українська - Deutsch - - 0,0 0,25 @@ -32,18 +29,15 @@ 0,5 0,75 - Dzienniczek+ Wulkanowy Farben der Bewertungen im Logbuch - Durchschnittsnote für das 2. Semester Durchschnitt der Bewertungen für das ganze Jahr - Nicht zeigen Alle zeigen diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 239c40437..e294593ca 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -1,8 +1,6 @@ Wulkanowy - - Anmelden Wulkanowy @@ -21,11 +19,8 @@ Eintragen und Erfolgen Hausaufgaben Wählen Sie ein Konto - Semester %d, %d/%d - - Melden Sie sich mit dem Studenten- oder Elternkonto an Geben Sie das Symbol @@ -56,24 +51,25 @@ Das Symbol finden Sie auf der Registerseite unter Uczeń → Dostęp Mobilny → Zarejestruj urządzenie mobilne Wählen Sie die Studenten aus, die sich bei der Anwendung anmelden sollen. Andere Optionen + In this mode, a lucky number does not work, a class grade stats, summary of attendance, excuse for absence, completed lessons, school information and preview of the list of registered devices + This mode displays the same data as it appears on the register website + The combination of the best features of the other two modes. It works faster than scraper and provides features not available in the Mobile API mode. It is in the experimental phase Datenschutzerklärung Probleme bei der Anmeldung? Kontaktieren Sie uns! Email Discord email senden + Describe details of problem: + Make sure the correct UONET+ register is selected! Ich habe mein Passwort vergessen. Ihr Konto wiederherstellen Wiederherstellen Student ist bereits angemeldet - - Kundenbetreuer Anmelden Die Sitzung ist abgelaufen Die Sitzung ist abgelaufen, bitte loggen Sie sich erneut ein - - Note Semester %d @@ -112,8 +108,6 @@ Du hast %1$d Note bekommen Du hast %1$d Noten bekommen - - Lektion Klassenzimmer @@ -121,17 +115,13 @@ Stunden Änderungen Kein Unterricht an diesem Tag - - - + Beendete Lektionen Beendete Lektionen anzeigen Keine Informationen über beendete Lektionen Thema Abwesenheit Ressourcen - - Übersicht über die Schulbesuch Aus schulischen Gründen abwesend @@ -152,19 +142,13 @@ Abwesenheit erfolgreich entschuldigt! Sie müssen mindestens eine Abwesenheit auswählen! Verzeihung - - Schulbesuch Gesamt - - Diese Woche keine Prüfungen Form Eintrittsdatum - - Posteingang Gesendet @@ -198,8 +182,6 @@ Du hast %1$d nachricht bekommen Du hast %1$d nachrichten bekommen - - Keine Informationen über Eintragen Punkte @@ -215,23 +197,17 @@ Du hast %1$d Eintrag bekommen Du hast %1$d Eintragen bekommen - - Keine Informationen über Hausaufgaben Gemacht Unvollständig Anhänge - - Glückliche Nummer - "Die heutige Glücksnummer ist " + Die heutige Glücksnummer ist Keine Information über die Glücksnummer. Glücksnummer für heute - "Die heutige Glücksnummer ist: " - - + Die heutige Glücksnummer ist: Mobile Geräte Keine Geräte @@ -241,12 +217,8 @@ Token Symbol PIN - - Schule und Lehrer - - Schule Keine Informationen über die Schule @@ -257,21 +229,15 @@ Name des Pädagogen Auf Karte anzeigen Rufen Sie an - - Lehrerinnen und Lehrer Keine Informationen über Lehrer Kein Thema - - Konto hinzufügen Abmelden Wollen Sie sich von einem aktiven Studenten abmelden? Abmeldung von Student - - Version der App Mitarbeiter @@ -288,21 +254,14 @@ Besuchen Sie die Website und helfen Sie bei der Entwicklung der Anwendung Lizenzen Lizenzen der in der Anwendung verwendeten Bibliotheken - - Lizenz - - - + Avatar Sehen Sie mehr auf GitHub - Logs teilen Aktualisieren - - Inhalt Wiederhol @@ -319,15 +278,11 @@ Thema Zurück Nächste - - Keine Lektionen Thema wählen Licht Dunkel - - Erscheinungsbild Standard Ansicht @@ -347,12 +302,18 @@ An Feiertagen suspendiert Aktualisierungsintervall Nur Wi-Fi + Sync now + Synced! + Sync failed + Sync in progress + Synchronization + Manual sync doesn\'t refresh app views. + \nTo see the synced data relaunch the app after syncing. + Andere Wert des Plus Wert des Minus Antwort mit Nachrichtenhistorie - - Neue Einträge im Klassenbuch Neue Noten @@ -361,8 +322,6 @@ Neue Eintragen Push-Benachrichtigungen Debuggen - - Schwarz Rot @@ -370,13 +329,9 @@ Grün Violett Keine Farbe - - Kopiert lösen - - Keine Internetverbindung Das Zeitlimit für die Verbindung zum Klassenbuch ist abgelaufen diff --git a/app/src/main/res/values-pl/preferences_values.xml b/app/src/main/res/values-pl/preferences_values.xml index 5553760a1..475f7327f 100644 --- a/app/src/main/res/values-pl/preferences_values.xml +++ b/app/src/main/res/values-pl/preferences_values.xml @@ -1,5 +1,18 @@ + + Jasny + Ciemny + Czarny (AMOLED) + + + Język systemu + Polski + English + Pусский + Українська + Deutsch + 15 minut 30 minut @@ -9,22 +22,6 @@ 12 godzin 24 godziny - - - Jasny - Ciemny - Czarny (AMOLED) - - - - Język systemu - Polski - English - Pусский - Українська - Deutsch - - 0,0 0,25 @@ -32,18 +29,15 @@ 0,5 0,75 - Dzienniczek+ Wulkanowy Kolory ocen w dzienniku - Średnia ocen z 2 semestru Średnia ocen z całego roku - Nie pokazuj Pokazuj wszystkie diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 742d61ed2..30d80acdb 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -1,9 +1,6 @@ - Wulkanowy - - Logowanie Wulkanowy @@ -22,11 +19,8 @@ Uwagi i osiągnięcia Zadania domowe Wybierz konto - Semestr %d, %d/%d - - Zaloguj się za pomocą konta ucznia lub rodzica Podaj symbol @@ -35,13 +29,13 @@ Login, PESEL lub e-mail Hasło Dziennik UONET+ - Symbol Mobilne API Scraper Hybrydowe Token PIN Klucz API + Symbol Zaloguj To hasło jest za krótkie Dane logowania są niepoprawne. Upewnij się, że został wybrany odpowiedni dziennik UONET+ w polu poniżej @@ -65,20 +59,17 @@ Email Discord Wyślij email + Opisz problem: Upewnij się, że został wybrany odpowiedni dziennik UONET+! Nie pamiętam hasła Przywróć swoje konto Przywróć Uczeń jest już zalogowany - - Menadżer kont Zaloguj się Sesja wygasła Sesja wygasła, zaloguj się ponownie - - Ocena Semestr %d @@ -123,8 +114,6 @@ Masz %1$d nowych ocen Masz %1$d nowych ocen - - Lekcja Sala @@ -132,17 +121,13 @@ Godziny Zmiany Brak lekcji w tym dniu - - - + Lekcje zrealizowane Zobacz lekcje zrealizowane Brak informacji o lekcjach zrealizowanych Temat Nieobecność Zasoby - - Podsumowanie frekwencji Nieobecność z przyczyn szkolnych @@ -165,19 +150,13 @@ Usprawiedliwiono pomyślnie! Musisz wybrać co najmniej jedną nieobecność! Usprawiedliw - - Frekwencja Razem - - Brak sprawdzianów w tym tygodniu Typ Data wpisu - - Odebrane Wysłane @@ -217,8 +196,6 @@ Masz %1$d nowych wiadomości Masz %1$d nowych wiadomości - - Brak informacji o uwagach Punkty @@ -240,23 +217,17 @@ Masz %1$d nowych uwag Masz %1$d nowych uwag - - Brak zadań domowych Wykonane Niewykonane Załączniki - - Szczęśliwy numerek Dzisiejszym szczęśliwym numerkiem jest Brak informacji o szczęśliwym numerku Szczęśliwy numerek na dzisiaj Dziś szczęśliwym numerkiem jest: %d - - Dostęp mobilny Brak urządzeń @@ -266,12 +237,8 @@ Token Symbol PIN - - Szkoła i nauczyciele - - Szkoła Brak informacji o szkole @@ -282,21 +249,15 @@ Imię i nazwisko pedagoga Pokaż na mapie Zadzwoń - - Nauczyciele Brak informacji o nauczycielach Brak przedmiotu - - Dodaj konto Wyloguj Czy chcesz wylogować aktualnego ucznia? Wylogowanie ucznia - - Wersja aplikacji Twórcy @@ -313,21 +274,14 @@ Odwiedź stronę i pomóż rozwijać aplikację Licencje Licencje użytych bibliotek w aplikacji - - Licencja - - - + Awatar Zobacz więcej na GitHub - Udostępnij logi Odśwież - - Treść Ponów @@ -344,15 +298,11 @@ Przedmiot Poprzedni Następny - - Brak lekcji Wybierz motyw Jasny Ciemny - - Wygląd Domyślny widok @@ -364,11 +314,9 @@ Pokazuj lekcje całej klasy Schemat kolorów ocen Język aplikacji - Powiadomienia Pokazuj powiadomienia Pokazuj powiadomienia debugowania - Synchronizacja Automatyczna aktualizacja Zawieszona na wakacjach @@ -379,27 +327,21 @@ Synchronizacja nie powiodła się Synchronizacja w trakcie Synchronizacja - - Ręczna synchronizacja nie odświeża widoków w aplikacji. + Ręczna synchronizacja nie odświeża widoków w aplikacji. \nAby zobaczyć zsynchronizowane informacje uruchom ponownie aplikację po zsynchronizowaniu. - Inne Wartość plusa Wartość minusa Odpowiadaj z historią wiadomości - - Nowe wpisy w dzienniku - Szczęśliwy numerek Nowe oceny + Szczęśliwy numerek Nowe wiadomości Nowe uwagi Powiadomienia push Debugowanie - - Czarny Czerwony @@ -407,13 +349,9 @@ Zielony Fioletowy Brak koloru - - Skopiowano Cofnij - - Brak połączenia z internetem Upłynął limit czasu na połączenie z dziennikiem diff --git a/app/src/main/res/values-ru/preferences_values.xml b/app/src/main/res/values-ru/preferences_values.xml index 23cb5ac4d..6c1522682 100644 --- a/app/src/main/res/values-ru/preferences_values.xml +++ b/app/src/main/res/values-ru/preferences_values.xml @@ -1,14 +1,5 @@ - - 15 минут - 30 минут - 1 час - 2 часа - 6 часов - 12 часов - 24 часа - Светлая Тёмная @@ -22,6 +13,15 @@ Українська Deutsch + + 15 минут + 30 минут + 1 час + 2 часа + 6 часов + 12 часов + 24 часа + 0,0 0,25 @@ -38,7 +38,6 @@ Средняя оценка со 2 семестра Средняя оценка с целого года - Не показывать Показать все diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index e2f13f30c..a4666d730 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -1,9 +1,6 @@ - - + Wulkanowy - - Авторизация Wulkanowy @@ -12,21 +9,18 @@ Тесты Расписание Настройки - Ещё + Другое О приложении Просмотр журнала - Творцы + Разработчики Лицензии Сообщения Новое сообщение Предупреждения и свершения Домашние задания Выберите аккаунт - - Семестр %d, %d/%d - - + %d семестр, %d/%d Авторизируйтесь при помощи аккаунта ученика или родителя Впишите \"symbol\" @@ -35,72 +29,73 @@ Логин, PESEL или электронная почта Пароль Дневник UONET+ - Мобильный API + Mobile API Scraper - Гибрид + Hybrid Token PIN - клавиша API + Ключ API Symbol Войти Слишком короткий пароль - Указаны неверные данные - Недействительный PIN - Недействительный token + Указаны неверные данные. Убедитесь, что вы выбрали нужный дневник + Неправильный PIN + Неправильный token Токен просрочен Неверный адрес электронной почты Неправильный логин - Недействительный symbol - Не удалось найти ученика. Пожалуйста, проверьте \"symbol\" - Это поле обязательно + Неправильный symbol + Не удалось найти ученика. Проверьте \"symbol\" + Обязательное поле Данный ученик уже авторизован Вы можете найти \"symbol\" в Uczeń → Dostęp Mobilny → Zarejestruj urządzenie mobilne Выберите учеников для авторизации в приложении Другие варианты + В этом режиме не работают: счастливый номер, статистика класса по оценкам, статистика посещаемости и уроков, информация о школе и список зарегистрированных устройств + Scraper - режим, который показывает данные так же, как и сайт дневника + Hybrid - это комбинация лучших функций остальных двух режимов. Он работает быстрее, чем Scraper, и вводит функции, которых нет в режиме Mobile API. Находится в экспериментальной стадии Политика приватности Проблемы с авторизацией? Свяжитесь с нами! Электронная почта Discord Отправить письмо - восстановление - Я не помню пароль + Опишите детали проблемы: + Убедитесь, что выбран нужный дневник! + Забыли пароль? Восстановите свой аккаунт + Восстановить Студент уже вошел в систему - - Менеджер аккаунтов Войти Сеанс истёк Сеанс истёк, пожалуйста, авторизируйтесь ещё раз - - Оценка Семестр %d Сменить семестр Оценки отсутствуют - Вес - Bес: %s + Стоимость + Стоимость: %s Комментарий Новые оценки отсутствуют Количество новых оценок: %1$d Средняя оценка: %1$.2f - точек: %s + Баллы: %s Средняя оценка отсутствует Ожидаемая оценка: %1$s Итоговая оценка: %1$s - Сумма очков + Сумма баллов Итоговая оценка Ожидаемая оценка Рассчитанная средняя оценка Итоговая средняя оценка Итоги Класс - Пометить как \"прочитанное* + Пометить как \"прочитанное\" Частичные - Семестровые - Очки + За семестр + Баллы %d оценка %d оценки @@ -108,7 +103,7 @@ %d оценок - Новая оценка + Новая оценка Новые оценки Новые оценки Новые оценки @@ -119,34 +114,28 @@ Вы получили %1$d оценок Вы получили %1$d оценок - - Урок Аудитория Группа Часы Изменения - Нету уроков в данный день - - - + Нет уроков в данный день + Проведённые уроки Просмотреть проведённые уроки Нет информации о проведённых уроках Тема Отсутствие Ресурсы - - Итоговая посещаемость Отсутствие по школьным причинам - Оправданное отсутствие - Неоправданное отсутствие + Отсутствие по уважительной причине + Отсутствие по неуважительной причине Освобождение - Оправданное опоздание - Неоправданное опоздание + Опоздание по уважительным причинам + Опоздание по неуважительным причинам Присутствие Урок № Данные не найдены @@ -158,29 +147,23 @@ Причина отсутствия (необязательно) Послать - Отсутствие оправдано успешно! - Вы должны выбрать хотя бы одно отсутствие! - Oбосновывать - - + Статус отсутствия изменён + Выберите хотя-бы одно отсутствие + Изменить статус Посещаемость - вместе - - + Общая - Тесты на этой неделе не запланированы + Нет тестов на этой неделе Тип Дата записи - - Полученные Отправленные Корзина - (нету темы) + (нет темы) Нет сообщений - Произошла ошибка во время получения текста сообщения + Произошла ошибка во время получения содержания сообщения От: Кому: Дата: %s @@ -202,7 +185,7 @@ %d сообщений - Новое сообщение + Новое сообщение Новые сообщения Новые сообщения Новые сообщения @@ -213,11 +196,9 @@ Вы получили %1$d новых сообщений Вы получили %1$d новых сообщений - - Нет данных о предупреждениях - точек + Баллы %d предупреждение %d предупреждения @@ -225,7 +206,7 @@ %d предупреждений - Новое предупреждение + Новое предупреждение Новые предупреждения Новых предупреждений Новых предупреждений @@ -236,23 +217,17 @@ Вы получили %1$d предупреждений Вы получили %1$d предупреждений - - Нет домашних заданий сделанный Не сделано Вложения - - Счастливый номер Сегодняшний счастливый номер Нет данных о счастливом номере Сегодняшний счастливый номер Сегодняшний счастливый номер: %d - - Мобильные устройства Нет устройств @@ -262,12 +237,8 @@ Token Symbol PIN - - Школа и учителя - - Школа Нет информации о школе @@ -278,56 +249,42 @@ Педагог Показать на карте Позвонить - - Учителя Нет информации о учителях Нет предмета - - Добавить аккаунт Выйти Вы точно хотите выйти из данного аккаунта? Выйти - - Версия приложения - Творцы - Список Wulkanowy программистов - Сообщить о ошибке - Отправить сообщение о ошибке через электронную почту + Разработчики + Список разработчиков \"Wulkanowy\" + Возникла ошибка? + Сообщить о ошибке FAQ - Читайте часто задаваемые вопросы + Часто задаваемые вопросы Сервер Discord Присоединиться к сообществу приложения Политика приватности - Правила сбора личных данных + Правила хранения личных данных Домашняя страница - Посетить страницу и помочь в развитии приложения + Помочь в развитии приложения Лицензии - Лицензии использованных в приложении библиотек - - + Лицензии использованных библиотек Лицензия - - - + Aватар - Смотрите больше на GitHub - - + Страница проекта на GitHub - Share logs - Обновление - - + Поделиться логами + Обновить Содержание - Снова + Повторить Описание Нет описания Учитель @@ -341,19 +298,15 @@ Предмет Предыдущий Следующий - - Нет уроков Выбрать тему Светлая Тёмная - - - Внешний вид - При входе открывать - Рассчитывание средней годовой оценки + Вид + Окно по умолчанию + Способ определения средней годовой оценки Принудительно высчитать среднюю оценку через приложение Показывать присутствия в посещаемости Тема приложения @@ -361,33 +314,34 @@ Показать уроки всего класса Схема цветов оценок Язык приложения - Уведомления Показывать уведомления Показывать дебаг-уведомления - Синхронизация Автоматическая синхронизация Приостановить синхронизации во время каникул Интервал синхронизации Только через Wi-Fi - + Синхронизировать + Синхронизировано! + Синхронизация не удалась + Идёт синхронизация + Синхронизация + Ручная синхронизация не обновляет данные в приложении. + \nЧтобы увидеть обновлённые данные, перезапустите приложение. + Другие - Вес плюса - Вес минуса - Ответить с историей сообщений - - + Стоимость плюса + Стоимость минуса + Отвечать с историей сообщений Новые данные в дневнике Новые оценки Счастливый номер Новые сообщения Новые заметки - Push-уведомления + Показывать push-уведомления Дебаг - - Чёрный Красный @@ -395,17 +349,13 @@ Зелёный Фиолетовый Нет цвета - - Скопировано Отменить - - Нет интернет-подключения Слишком долгое ожидание соединения с дневником - Авторизация не удалась. Пожалуйста, попробуйте ещё раз или перезапустите дневник + Авторизация не удалась. Попробуйте ещё раз или перезапустите дневник Требуется смена пароля Технический перерыв в журнале UONET + продолжается. Попробуйте позже Произошла неожиданная ошибка diff --git a/app/src/main/res/values-uk/preferences_values.xml b/app/src/main/res/values-uk/preferences_values.xml index e09ae8cdd..3c70c9d09 100644 --- a/app/src/main/res/values-uk/preferences_values.xml +++ b/app/src/main/res/values-uk/preferences_values.xml @@ -1,14 +1,5 @@ - - 15 хвилин - 30 хвилин - 1 година - 2 години - 6 годин - 12 годин - 24 години - Світла Темна @@ -22,6 +13,15 @@ Українська Deutsch + + 15 хвилин + 30 хвилин + 1 година + 2 години + 6 годин + 12 годин + 24 години + 0,0 0,25 @@ -38,7 +38,6 @@ Середня оцінка з 2 семестру Середня оцінка за весь рік - Не показувати Показати все diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index d9db3f068..e1117a884 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -1,9 +1,6 @@ - - + Wulkanowy - - Авторизація Wulkanowy @@ -12,83 +9,80 @@ Тести Розклад Налаштування - Ще + Інше Про додаток Переглядач журналів - Творці + Розробники Ліцензії Повідомлення Нове повідомлення Нотатки та досягнення Домашні завдання - Виберіть акаунт - - + Оберіть аккаунт - Семестр %d, %d/%d - - + %d семестр, %d/%d - Авторизуйтеся за допомогою акаунта учня або батьків + Авторизуйтеся за допомогою аккаунта учня або батька Впишіть \"symbol\" Ім\'я користувача Електронна пошта Логін, PESEL або електронна пошта Пароль Щоденник - Мобільний API + Мobile API Scraper - Гібрид + Hybrid Token PIN Ключ API Symbol - Ввійти - Дуже короткий пароль - Вказані неправильні дані - Недійсний PIN - Недійсний token + Увійти + Занадто короткий пароль + Вказані невірні дані. Впевніться, що ви увібрали потрібний щоденник + Неправильний PIN + Неправильний token + Минув термін дії токену Недійсна адреса електронної пошти - Невірний логін - Токен протермінований - Недійсний symbol + Неправильний логін + Неправильний symbol Не вдалося знайти учня. Будь ласка, перевірте \"symbol\" - Це поле обов\'язкове - Даний учень вже авторизований + Обов\'язкове поле + Даного учня вже авторизовано Ви можете знайти \"symbol\" в Uczeń → Dostęp Mobilny → Zarejestruj urządzenie mobilne Виберіть учнів для авторизації в додатку Інші варіанти + У цьому режимі не працюють: щасливий номер, статистика класу по оцінкам, статистика відвідуваності і уроків, інформація про школу і список зареєстрованних пристроїв + Scraper - режим, котрий показує дані так само, як і сторінка щоденника + Hybrid - це комбінація найкращих функцій інших двох режимів. Він працює швидше, ніж Scraper, и впроваджує функції, котрих нема в режимі Mobile API. Знаходиться в експериментальній стадії Політика приватності Проблеми з авторизацією? Зв\'яжіться з нами! Електронна пошта Discord - Відправити листа - Я забув свій пароль - Відновіть свій акаунт + Відправити лист + Опишіть деталі помилки: + Переконайтесь, що обрано потрібний щоденник! + Забули пароль? + Відновіть свій аккаунт Відновити - Учень вже ввійшов в систему - - + Учень вже увійшов до системи - Менеджер акаунтів - Ввійти - Сесія закінчилася - Сесія закінчилася, будь ласка, авторизуйтеся знову - - + Менеджер аккаунтів + Увійти + Минув термін дії сесії + Минув термін дії сесії, авторизуйтеся знову Оцінка - Семестр %d + %d семестр Змінити семестр - Оцінки відсутні - Вага - Вага: %s + Брак оцінок + Вартість + Вартість: %s Коментар - Нові оцінки відсутні + Брак нових оцінок Кількість нових оцінок: %1$d Середня оцінка: %1$.2f точок: %s - Середня оцінка відсутня + Брак середньої оцінки Очікувана оцінка: %1$s Підсумкова оцінка: %1$s Сума балів @@ -96,9 +90,9 @@ Очікувана оцінка Розрахована середня оцінка Підсумкова середня оцінка - Підсумки + Підсумок Клас - Відмітити як \"прочитане* + Позначити як прочитане Поточні Семестрові Бали @@ -109,7 +103,7 @@ %d оцінок - Нова оцінка + Нова оцінка Нові оцінки Нові оцінки Нові оцінки @@ -118,39 +112,33 @@ Ви отримали %1$d оцінку Ви отримали %1$d оцінки Ви отримали %1$d оцінок - ви отримали %1$d оцінок + Ви отримали %1$d оцінок - - Урок Аудиторія Група Години Зміни - Цього дня уроків немає - - - - Проведені уроки - Подивитися проведені уроки - Інформації про проведені уроки немає + Брак уроків у цей день + + Уроки, що відбулися + Показати уроки, що відбулися + Брак інформації о уроках, що відбулися Тема Відсутність Ресурси - - - Підсумкова відвідуваність + Підсумок відвідуваності Відсутність зі шкільних причин - Виправдана відсутність - Невиправдана відсутність + Відсутність з поважних причин + Відсутність з не поважних причин Звільнення - Виправдане запізнення - Невиправдане запізнення + Спізнення з поважних причин + Спізнення з не поважних причин Присутність - урок № - Дані не знайдені + Номер уроку + Брак записів %1$d відсутність %1$d відсутності @@ -159,43 +147,37 @@ Причина відсутності (необов’язково) Надіслати - Відсутність виправдана успішно! - Ви повинні вибрати хоча б одну відсутність! - Oбґрунтовувати - - + Змінено статус відсутності + Оберіть хоча б одну відсутність + Змінити статус Відвідуваність Загальна - - - Тести на цьому тижні не заплановані + Брак тестів у цьому тижні Тип Дата запису - - Отримані Відправлені Кошик - (нема теми) + (брак теми) Нема повідомлень - Сталася помилка під час отримання тексту повідомлення + З\'явилася помилка підчас завантаження змісту повідомлення Від: Кому: Дата: %s - Відповідь + Відповісти Переслати Видалити - Перемістити в кошик + Перемістити у кошик Видалити назавжди - Повідомлення успішно видалено + Повідомлення було успішно видалено Тема Зміст - Повідомлення успішно відправлено - Ви повинні вибрати щонайменше одного отримувача - Текст повідомлення повинен містити щонайменше 3 знаки + Повідомлення було успішно відправлено + Необхідно обрати принаймні 1 адресата + Зміст повідомлення мусить складатися принаймні з 3 знаків %d повідомлення %d повідомлення @@ -203,7 +185,7 @@ %d повідомлень - Нове повідомлення + Нове повідомлення Нові повідомлення Нові повідомлення Нові повідомлення @@ -214,123 +196,97 @@ Ви отримали %1$d нових повідомлень Ви отримали %1$d нових повідомлень - - - Немає даних про нотатки - точок + Брак інформації о зауваженнях + Бали - %d нотатка - %d нотатки - %d нотаток - %d нотатки + %d зауваження + %d зауваження + %d зауважень + %d зауважень - Нова нотатка + Нова нотатка Нові нотатки Нових нотаток Нових нотаток - Ви отримали %1$d нотатку - Ви отримали %1$d нотатки - Ви отримали %1$d нотаток - Ви отримали %1$d нотаток + %1$d нове зауваження + %1$d нових зауваження + %1$d нових зауважень + %1$d нових зауважень - - - Немає домашніх завдань - зроблений - Не зроблено - Вкладення - - + Брак домашніх завдань + Позначити як зроблене + Позначити як не зроблене + Додатки Щасливий номер - Сьогодні щасливий номер - Немає даних про щасливий номер - Сьогодні щасливий номер - Сьогодні щасливий номер: %d - - + Сьогоднішній щасливий номер + Брак інформації о щасливому номері + Сьогоднішній щасливий номер + Сьогоднішнім щасливим номером є %d Мобільні пристрої - Пристрої відсутні + Брак пристроїв Видалити - Пристрій видалений + Пристрій видалено QR-код Token Symbol PIN - - Школа та вчителі - - Школа - Немає інформації про школу + Брак інформації про школу Назва школи Адреса школи Телефон Директор Викладач - Показати на карті + Показати на мапі Зателефонувати - - Вчителі - Немає інформації про вчителів - Нема предмету - - + Брак інформації про вчителів + Брак предмету - Додати акаунт + Додати аккаунт Вийти - Ви дійсно бажаєте вийти з даного акаунту? - Вийти - - + Ви впевнені, що хочете вийти з цього аккаунту? + Вийти з аккаунту учня - Версія додатку - Tворці - Список Wulkanowy програмістів - Повідомити про помилку - Відправити повідомлення про помилку електронною поштою + Версія додатка + Розробники + Список розробників \"Wulkanowy\" + Виникла помилка? + Повідомити о помилці за допомогою e-mail FAQ - Читайте запитання, які часто задають + Запитання, які часто задають Сервер Discord Приєднатися до спільноти додатка - Політика приватності - Правила збору особистих даних + Політика конфіденційності + Правила зберігання особистих даних Домашня сторінка - Відвідати сторінку та допомогти в розвитку додатку + Допомогти розвитку додатка Ліцензії - Ліцензії використаних в додатку бібліотек - - + Ліцензії вжитих бібліотек Ліцензія - - - + Аватар - Дивіться більше на GitHub - - + Сторінка проекту на GitHub - Поділитися журналами + Поділитися логами Оновити - - Зміст - Знову + Повторити Опис - Нема опису + Брак опису Вчитель Дата Дата запису @@ -342,71 +298,64 @@ Предмет Попередній Наступний - - - Немає уроків - Вибрати тему - Світла + Брак уроків + Увібрати тему + Яскрава Темна - - - Зовнішній вигляд - При вході відкривати - Розрахунок середньої річної оцінки + Вигляд + Вікно за замовчуванням + Спосіб облічування оцінки на кінець року Примусово розрахувати середню оцінку через додаток Показувати присутність у відвідуваності Тема додатку Більше оцінок Показати уроки всього класу - Колірна гама оцінок + Схема кольорів оцінок Мова додатку - Повідомлення Показувати повідомлення Показувати дебаг-повідомлення - Синхронізація Автоматична синхронізація - Призупинити синхронізації на час канікул - Інтервал синхронізації + Призупинено на час канікул + Інтервал оновлення Тільки через Wi-Fi - + Синхронізувати + Синхронізовано! + Синхронізація не вдалася + Триває синхронізація + Синхронізація + Ручна синхронізація не оновлює дані в додатку. + \nЩоб побачити оновлені дані, перезавантажте додаток. + Інші - Вага плюса + Вартість плюсу Вага мінуса Відповісти з історією повідомлень - - Нові дані в щоденнику - Щасливий номер Нові оцінки + Щасливий номер Нові повідомлення Нові нотатки - Повідомлення pushs + Показувати push-повідомлення Дебаг - - Чорний Червоний Голубий Зелений Фіолетовий - Нема кольору - - + Брак кольору Скопійовано Відмінити - - - Відсутнє інтернет-підключення + Брак з\'єднання з інтернетом Занадто довге очікування з\'єднання з щоденником - Авторизація не відбулася. Будь ласка, авторизуйтеся знову або перезавантажте щоденник + Аутентифікація не вдалася. Спробуйте ще раз або запустіть додаток знову Потрібно змінити пароль Технічна перерва в журналі UONET + продовжується. Спробуйте пізніше Відбулася несподівана помилка diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c7b41f698..429cd7404 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -55,7 +55,7 @@ Student not found. Check the symbol This field is required Selected student is already logged in - The symbol can be found on the register page in Uczeń → Dostęp Mobilny → Zarejestruj urządzenie mobilne + The symbol can be found on the register page in Uczeń → Dostęp Mobilny → Zarejestruj urządzenie mobilne Select students to log in to the application Other options In this mode, a lucky number does not work, a class grade stats, summary of attendance, excuse for absence, completed lessons, school information and preview of the list of registered devices From 6ac5c6a0b4b7dac8edd4454b0efad1b80ff088af Mon Sep 17 00:00:00 2001 From: Mateusz Idziejczak <53345435+PanTajemnic@users.noreply.github.com> Date: Sun, 10 May 2020 12:00:21 +0200 Subject: [PATCH 16/85] Add widget system theme option (#759) --- .../LuckyNumberWidgetConfigureActivity.kt | 8 ++++- .../LuckyNumberWidgetConfigurePresenter.kt | 2 +- .../LuckyNumberWidgetProvider.kt | 34 +++++++++++++------ .../TimetableWidgetConfigureActivity.kt | 8 ++++- .../timetablewidget/TimetableWidgetFactory.kt | 28 ++++++++------- .../TimetableWidgetProvider.kt | 22 ++++++++++-- .../res/layout/activity_widget_configure.xml | 2 -- .../main/res/layout/item_widget_timetable.xml | 22 ++++++------ .../res/layout/item_widget_timetable_dark.xml | 33 +++++------------- .../layout/item_widget_timetable_small.xml | 5 +-- app/src/main/res/layout/widget_timetable.xml | 7 ---- .../main/res/layout/widget_timetable_dark.xml | 7 ---- app/src/main/res/values-de/strings.xml | 1 + app/src/main/res/values-pl/strings.xml | 1 + app/src/main/res/values-ru/strings.xml | 1 + app/src/main/res/values-uk/strings.xml | 1 + app/src/main/res/values/api_hosts.xml | 2 +- app/src/main/res/values/strings.xml | 1 + 18 files changed, 99 insertions(+), 86 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigureActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigureActivity.kt index d7d5c4ff1..961ac6338 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigureActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigureActivity.kt @@ -4,6 +4,7 @@ import android.appwidget.AppWidgetManager.ACTION_APPWIDGET_UPDATE import android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID import android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_IDS import android.content.Intent +import android.os.Build import android.os.Bundle import android.widget.Toast import androidx.appcompat.app.AlertDialog @@ -14,6 +15,7 @@ import io.github.wulkanowy.databinding.ActivityWidgetConfigureBinding import io.github.wulkanowy.ui.base.BaseActivity import io.github.wulkanowy.ui.base.WidgetConfigureAdapter import io.github.wulkanowy.ui.modules.login.LoginActivity +import io.github.wulkanowy.utils.AppInfo import javax.inject.Inject class LuckyNumberWidgetConfigureActivity : @@ -26,6 +28,9 @@ class LuckyNumberWidgetConfigureActivity : @Inject override lateinit var presenter: LuckyNumberWidgetConfigurePresenter + @Inject + lateinit var appInfo: AppInfo + private var dialog: AlertDialog? = null override fun onCreate(savedInstanceState: Bundle?) { @@ -48,10 +53,11 @@ class LuckyNumberWidgetConfigureActivity : } override fun showThemeDialog() { - val items = arrayOf( + var items = arrayOf( getString(R.string.widget_timetable_theme_light), getString(R.string.widget_timetable_theme_dark) ) + if (appInfo.systemVersion >= Build.VERSION_CODES.Q) items += (getString(R.string.widget_timetable_theme_system)) dialog = AlertDialog.Builder(this, R.style.WulkanowyTheme_WidgetAccountSwitcher) .setTitle(R.string.widget_timetable_theme_title) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigurePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigurePresenter.kt index 1bb7447a2..468f9b575 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigurePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigurePresenter.kt @@ -40,7 +40,7 @@ class LuckyNumberWidgetConfigurePresenter @Inject constructor( registerStudent(selectedStudent) } - fun onDismissThemeView(){ + fun onDismissThemeView() { view?.finishView() } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetProvider.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetProvider.kt index e4b1072b6..55a048b32 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetProvider.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetProvider.kt @@ -8,6 +8,7 @@ import android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH import android.appwidget.AppWidgetProvider import android.content.Context import android.content.Intent +import android.content.res.Configuration import android.os.Bundle import android.view.View.GONE import android.view.View.VISIBLE @@ -62,14 +63,12 @@ class LuckyNumberWidgetProvider : AppWidgetProvider() { override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray?) { super.onUpdate(context, appWidgetManager, appWidgetIds) appWidgetIds?.forEach { appWidgetId -> - val savedTheme = sharedPref.getLong(getThemeWidgetKey(appWidgetId), 0) - val layoutId = if (savedTheme == 0L) R.layout.widget_luckynumber else R.layout.widget_luckynumber_dark val luckyNumber = getLuckyNumber(sharedPref.getLong(getStudentWidgetKey(appWidgetId), 0), appWidgetId) val appIntent = PendingIntent.getActivity(context, MainView.Section.LUCKY_NUMBER.id, MainActivity.getStartIntent(context, MainView.Section.LUCKY_NUMBER, true), FLAG_UPDATE_CURRENT) - val remoteView = RemoteViews(context.packageName, layoutId).apply { + val remoteView = RemoteViews(context.packageName, getCorrectLayoutId(appWidgetId, context)).apply { setTextViewText(R.id.luckyNumberWidgetNumber, luckyNumber?.luckyNumber?.toString() ?: "#") setOnClickPendingIntent(R.id.luckyNumberWidgetContainer, appIntent) } @@ -82,17 +81,19 @@ class LuckyNumberWidgetProvider : AppWidgetProvider() { override fun onDeleted(context: Context?, appWidgetIds: IntArray?) { super.onDeleted(context, appWidgetIds) appWidgetIds?.forEach { appWidgetId -> - if (appWidgetId != 0) sharedPref.delete(getStudentWidgetKey(appWidgetId)) + with(sharedPref) { + delete(getHeightWidgetKey(appWidgetId)) + delete(getStudentWidgetKey(appWidgetId)) + delete(getThemeWidgetKey(appWidgetId)) + delete(getWidthWidgetKey(appWidgetId)) + } } } override fun onAppWidgetOptionsChanged(context: Context, appWidgetManager: AppWidgetManager, appWidgetId: Int, newOptions: Bundle?) { super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions) - val savedTheme = sharedPref.getLong(getThemeWidgetKey(appWidgetId), 0) - val layoutId = if (savedTheme == 0L) R.layout.widget_luckynumber else R.layout.widget_luckynumber_dark - - val remoteView = RemoteViews(context.packageName, layoutId) + val remoteView = RemoteViews(context.packageName, getCorrectLayoutId(appWidgetId, context)) setStyles(remoteView, appWidgetId, newOptions) appWidgetManager.updateAppWidget(appWidgetId, remoteView) @@ -102,8 +103,10 @@ class LuckyNumberWidgetProvider : AppWidgetProvider() { val width = options?.getInt(OPTION_APPWIDGET_MIN_WIDTH) ?: sharedPref.getLong(getWidthWidgetKey(appWidgetId), 74).toInt() val height = options?.getInt(OPTION_APPWIDGET_MAX_HEIGHT) ?: sharedPref.getLong(getHeightWidgetKey(appWidgetId), 74).toInt() - sharedPref.putLong(getWidthWidgetKey(appWidgetId), width.toLong()) - sharedPref.putLong(getHeightWidgetKey(appWidgetId), height.toLong()) + with(sharedPref) { + putLong(getWidthWidgetKey(appWidgetId), width.toLong()) + putLong(getHeightWidgetKey(appWidgetId), height.toLong()) + } val rows = getCellsForSize(height) val cols = getCellsForSize(width) @@ -162,4 +165,15 @@ class LuckyNumberWidgetProvider : AppWidgetProvider() { null } } + + private fun getCorrectLayoutId(appWidgetId: Int, context: Context): Int { + val savedTheme = sharedPref.getLong(getThemeWidgetKey(appWidgetId), 0) + val isSystemDarkMode = context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES + + return if (savedTheme == 1L || (savedTheme == 2L && isSystemDarkMode)) { + R.layout.widget_luckynumber_dark + } else { + R.layout.widget_luckynumber + } + } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigureActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigureActivity.kt index 75548c9cc..3dc7a3a8c 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigureActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigureActivity.kt @@ -4,6 +4,7 @@ import android.appwidget.AppWidgetManager.ACTION_APPWIDGET_UPDATE import android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID import android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_IDS import android.content.Intent +import android.os.Build import android.os.Bundle import android.widget.Toast import android.widget.Toast.LENGTH_LONG @@ -16,6 +17,7 @@ import io.github.wulkanowy.ui.base.BaseActivity import io.github.wulkanowy.ui.base.WidgetConfigureAdapter import io.github.wulkanowy.ui.modules.login.LoginActivity import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.EXTRA_FROM_PROVIDER +import io.github.wulkanowy.utils.AppInfo import javax.inject.Inject class TimetableWidgetConfigureActivity : @@ -28,6 +30,9 @@ class TimetableWidgetConfigureActivity : @Inject override lateinit var presenter: TimetableWidgetConfigurePresenter + @Inject + lateinit var appInfo: AppInfo + private var dialog: AlertDialog? = null override fun onCreate(savedInstanceState: Bundle?) { @@ -50,10 +55,11 @@ class TimetableWidgetConfigureActivity : } override fun showThemeDialog() { - val items = arrayOf( + var items = arrayOf( getString(R.string.widget_timetable_theme_light), getString(R.string.widget_timetable_theme_dark) ) + if (appInfo.systemVersion >= Build.VERSION_CODES.Q) items += getString(R.string.widget_timetable_theme_system) dialog = AlertDialog.Builder(this, R.style.WulkanowyTheme_WidgetAccountSwitcher) .setTitle(R.string.widget_timetable_theme_title) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetFactory.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetFactory.kt index fb8c59586..dd223ad8b 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetFactory.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetFactory.kt @@ -18,9 +18,9 @@ import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository import io.github.wulkanowy.data.repositories.semester.SemesterRepository import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.data.repositories.timetable.TimetableRepository +import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getCurrentThemeWidgetKey import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getDateWidgetKey import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getStudentWidgetKey -import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getThemeWidgetKey import io.github.wulkanowy.utils.SchedulersProvider import io.github.wulkanowy.utils.getCompatColor import io.github.wulkanowy.utils.toFormattedString @@ -41,9 +41,7 @@ class TimetableWidgetFactory( private var lessons = emptyList() - private var savedTheme: Long? = null - - private var layoutId: Int? = null + private var savedCurrentTheme: Long? = null private var primaryColor: Int? = null @@ -71,28 +69,32 @@ class TimetableWidgetFactory( val studentId = sharedPref.getLong(getStudentWidgetKey(appWidgetId), 0) updateTheme(appWidgetId) - updateLessons(date, studentId) } } private fun updateTheme(appWidgetId: Int) { - savedTheme = sharedPref.getLong(getThemeWidgetKey(appWidgetId), 0) - layoutId = if (savedTheme == 0L) R.layout.item_widget_timetable else R.layout.item_widget_timetable_dark + savedCurrentTheme = sharedPref.getLong(getCurrentThemeWidgetKey(appWidgetId), 0) - primaryColor = if (savedTheme == 0L) R.color.colorPrimary else R.color.colorPrimaryLight - textColor = if (savedTheme == 0L) android.R.color.black else android.R.color.white - timetableChangeColor = if (savedTheme == 0L) R.color.timetable_change_dark else R.color.timetable_change_light + if (savedCurrentTheme == 0L) { + primaryColor = R.color.colorPrimary + textColor = android.R.color.black + timetableChangeColor = R.color.timetable_change_dark + } else { + primaryColor = R.color.colorPrimaryLight + textColor = android.R.color.white + timetableChangeColor = R.color.timetable_change_light + } } private fun getItemLayout(lesson: Timetable): Int { return when { prefRepository.showWholeClassPlan == "small" && !lesson.isStudentPlan -> { - if (savedTheme == 0L) R.layout.item_widget_timetable_small + if (savedCurrentTheme == 0L) R.layout.item_widget_timetable_small else R.layout.item_widget_timetable_small_dark } - savedTheme == 0L -> R.layout.item_widget_timetable - else -> R.layout.item_widget_timetable_dark + savedCurrentTheme == 1L -> R.layout.item_widget_timetable_dark + else -> R.layout.item_widget_timetable } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetProvider.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetProvider.kt index 62192a1b0..79888f83d 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetProvider.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetProvider.kt @@ -13,6 +13,7 @@ import android.content.Context import android.content.Intent import android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK import android.content.Intent.FLAG_ACTIVITY_NEW_TASK +import android.content.res.Configuration import android.widget.RemoteViews import dagger.android.AndroidInjection import io.github.wulkanowy.R @@ -71,6 +72,8 @@ class TimetableWidgetProvider : BroadcastReceiver() { fun getStudentWidgetKey(appWidgetId: Int) = "timetable_widget_student_$appWidgetId" fun getThemeWidgetKey(appWidgetId: Int) = "timetable_widget_theme_$appWidgetId" + + fun getCurrentThemeWidgetKey(appWidgetId: Int) = "timetable_widget_current_theme_$appWidgetId" } override fun onReceive(context: Context, intent: Intent) { @@ -110,14 +113,23 @@ class TimetableWidgetProvider : BroadcastReceiver() { with(sharedPref) { delete(getStudentWidgetKey(appWidgetId)) delete(getDateWidgetKey(appWidgetId)) + delete(getThemeWidgetKey(appWidgetId)) + delete(getCurrentThemeWidgetKey(appWidgetId)) } } } @SuppressLint("DefaultLocale") private fun updateWidget(context: Context, appWidgetId: Int, date: LocalDate, student: Student?) { - val savedTheme = sharedPref.getLong(getThemeWidgetKey(appWidgetId), 0) - val layoutId = if (savedTheme == 0L) R.layout.widget_timetable else R.layout.widget_timetable_dark + val savedConfigureTheme = sharedPref.getLong(getThemeWidgetKey(appWidgetId), 0) + val isSystemDarkMode = context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES + var currentTheme = 0L + var layoutId = R.layout.widget_timetable + + if (savedConfigureTheme == 1L || (savedConfigureTheme == 2L && isSystemDarkMode)) { + currentTheme = 1L + layoutId = R.layout.widget_timetable_dark + } val nextNavIntent = createNavIntent(context, appWidgetId, appWidgetId, BUTTON_NEXT) val prevNavIntent = createNavIntent(context, -appWidgetId, appWidgetId, BUTTON_PREV) @@ -150,7 +162,11 @@ class TimetableWidgetProvider : BroadcastReceiver() { setPendingIntentTemplate(R.id.timetableWidgetList, appIntent) } - sharedPref.putLong(getDateWidgetKey(appWidgetId), date.toEpochDay(), true) + with(sharedPref) { + putLong(getCurrentThemeWidgetKey(appWidgetId), currentTheme) + putLong(getDateWidgetKey(appWidgetId), date.toEpochDay(), true) + } + with(appWidgetManager) { notifyAppWidgetViewDataChanged(appWidgetId, R.id.timetableWidgetList) updateAppWidget(appWidgetId, remoteView) diff --git a/app/src/main/res/layout/activity_widget_configure.xml b/app/src/main/res/layout/activity_widget_configure.xml index 548b86041..9a463284e 100644 --- a/app/src/main/res/layout/activity_widget_configure.xml +++ b/app/src/main/res/layout/activity_widget_configure.xml @@ -10,9 +10,7 @@ android:layout_width="match_parent" android:layout_height="64dp" android:paddingStart="24dp" - android:paddingLeft="24dp" android:paddingEnd="24dp" - android:paddingRight="24dp" android:text="@string/account_title" android:textSize="20sp" android:textStyle="bold" diff --git a/app/src/main/res/layout/item_widget_timetable.xml b/app/src/main/res/layout/item_widget_timetable.xml index c69bade8a..33f686dac 100644 --- a/app/src/main/res/layout/item_widget_timetable.xml +++ b/app/src/main/res/layout/item_widget_timetable.xml @@ -12,12 +12,12 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@android:color/white" - android:paddingTop="6dp" - android:paddingBottom="6dp" - android:paddingLeft="6dp" android:paddingStart="6dp" + android:paddingLeft="6dp" + android:paddingTop="6dp" android:paddingEnd="12dp" - android:paddingRight="12dp"> + android:paddingRight="12dp" + android:paddingBottom="6dp"> + tools:visibility="gone" /> + android:paddingBottom="6dp"> + tools:visibility="gone" /> + @@ -60,7 +55,6 @@ android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_marginStart="10dp" - android:layout_marginLeft="10dp" android:backgroundTint="@color/colorPrimaryDark" android:contentDescription="@string/all_prev" android:src="@drawable/ic_widget_chevron" @@ -72,7 +66,6 @@ android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_toEndOf="@id/timetableWidgetPrev" - android:layout_toRightOf="@id/timetableWidgetPrev" android:backgroundTint="@color/colorPrimaryDark" android:contentDescription="@string/all_next" android:rotation="180" diff --git a/app/src/main/res/layout/widget_timetable_dark.xml b/app/src/main/res/layout/widget_timetable_dark.xml index 3a301eb94..5533eaeee 100644 --- a/app/src/main/res/layout/widget_timetable_dark.xml +++ b/app/src/main/res/layout/widget_timetable_dark.xml @@ -16,9 +16,7 @@ android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_toStartOf="@id/timetableWidgetAccount" - android:layout_toLeftOf="@id/timetableWidgetAccount" android:layout_toEndOf="@id/timetableWidgetNext" - android:layout_toRightOf="@id/timetableWidgetNext" android:orientation="vertical"> @@ -60,7 +55,6 @@ android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_marginStart="10dp" - android:layout_marginLeft="10dp" android:backgroundTint="@color/colorWidgetNavButton" android:contentDescription="@string/all_prev" android:src="@drawable/ic_widget_chevron" @@ -72,7 +66,6 @@ android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_toEndOf="@id/timetableWidgetPrev" - android:layout_toRightOf="@id/timetableWidgetPrev" android:backgroundTint="@color/colorWidgetNavButton" android:contentDescription="@string/all_next" android:rotation="180" diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index e294593ca..8af2ed6f0 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -283,6 +283,7 @@ Thema wählen Licht Dunkel + Systemthema Erscheinungsbild Standard Ansicht diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 30d80acdb..82aa81180 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -303,6 +303,7 @@ Wybierz motyw Jasny Ciemny + Motyw systemu Wygląd Domyślny widok diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index a4666d730..426bfe241 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -303,6 +303,7 @@ Выбрать тему Светлая Тёмная + Системная тема Вид Окно по умолчанию diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index e1117a884..0a5ac733b 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -303,6 +303,7 @@ Увібрати тему Яскрава Темна + Тема системи Вигляд Вікно за замовчуванням diff --git a/app/src/main/res/values/api_hosts.xml b/app/src/main/res/values/api_hosts.xml index 5ce26b1d6..f661b88c5 100644 --- a/app/src/main/res/values/api_hosts.xml +++ b/app/src/main/res/values/api_hosts.xml @@ -36,7 +36,7 @@ https://vulcan.net.pl/ https://vulcan.net.pl/ https://vulcan.net.pl/ - http://fakelog.cf/?standard + http://fakelog.tk/?standard Default diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 429cd7404..c2cbeada2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -336,6 +336,7 @@ Choose theme Light Dark + System Theme From 52d66ac30bcbba199dd7046d2b4a77a94951ec5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Wed, 13 May 2020 11:25:50 +0200 Subject: [PATCH 17/85] New Crowdin translations (#797) --- app/src/main/res/values-de/strings.xml | 28 +++++++++++++------------- app/src/main/res/values-ru/strings.xml | 4 ++-- app/src/main/res/values-uk/strings.xml | 2 +- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 8af2ed6f0..f57805469 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -48,19 +48,19 @@ Student nicht gefunden. Überprüfen Sie das Symbol Dieses Datenfeld ist erforderlich Ausgewählter Student ist bereits angemeldet. - Das Symbol finden Sie auf der Registerseite unter Uczeń → Dostęp Mobilny → Zarejestruj urządzenie mobilne + Das Symbol finden Sie auf der Registerseite unter Uczeń → Dostęp Mobilny → Zarejestruj urządzenie mobilne Wählen Sie die Studenten aus, die sich bei der Anwendung anmelden sollen. Andere Optionen - In this mode, a lucky number does not work, a class grade stats, summary of attendance, excuse for absence, completed lessons, school information and preview of the list of registered devices - This mode displays the same data as it appears on the register website - The combination of the best features of the other two modes. It works faster than scraper and provides features not available in the Mobile API mode. It is in the experimental phase + In diesem Modus funktioniert eine Glücknummer, eine Klassenstatistik, eine Zusammenfassung der Anwesenheit, eine Entschuldigung für die Abwesenheit, abgeschlossene Lektionen, Schulinformationen und eine Vorschau der Liste der registrierten Geräte nicht + In diesem Modus werden dieselben Daten angezeigt, die auf der Klassenbuch-Website angezeigt werden + Die Kombination der besten Eigenschaften der beiden anderen Modus. Es arbeitet schneller als Scraper und bietet Funktionen, die im mobilen API-Modus nicht verfügbar sind. Es ist in der experimentellen Phase Datenschutzerklärung Probleme bei der Anmeldung? Kontaktieren Sie uns! Email Discord email senden - Describe details of problem: - Make sure the correct UONET+ register is selected! + Beschreiben Sie die Details des Problems: + Stellen Sie sicher, dass das richtige UONET + Klassenbuch ausgewählt ist! Ich habe mein Passwort vergessen. Ihr Konto wiederherstellen Wiederherstellen @@ -257,7 +257,7 @@ Lizenz - Avatar + Benutzerbild Sehen Sie mehr auf GitHub Logs teilen @@ -303,13 +303,13 @@ An Feiertagen suspendiert Aktualisierungsintervall Nur Wi-Fi - Sync now - Synced! - Sync failed - Sync in progress - Synchronization - Manual sync doesn\'t refresh app views. - \nTo see the synced data relaunch the app after syncing. + Jetzt synchronisieren + Synchronisiert! + Synchronisierung fehlgeschlagen + Synchronisierung läuft + Synchronisation + Die manuelle Synchronisierung aktualisiert die App-Ansichten nicht. + \nUm die synchronisierten Daten anzuzeigen, starten Sie die App nach der Synchronisierung neu. Andere Wert des Plus diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 426bfe241..e68454550 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -48,7 +48,7 @@ Не удалось найти ученика. Проверьте \"symbol\" Обязательное поле Данный ученик уже авторизован - Вы можете найти \"symbol\" в Uczeń → Dostęp Mobilny → Zarejestruj urządzenie mobilne + Вы можете найти \"symbol\" на странице VULCAN по пути Uczeń → Dostęp Mobilny → Zarejestruj urządzenie mobilne Выберите учеников для авторизации в приложении Другие варианты В этом режиме не работают: счастливый номер, статистика класса по оценкам, статистика посещаемости и уроков, информация о школе и список зарегистрированных устройств @@ -303,7 +303,7 @@ Выбрать тему Светлая Тёмная - Системная тема + Тема системы Вид Окно по умолчанию diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 0a5ac733b..303eb8b9a 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -48,7 +48,7 @@ Не вдалося знайти учня. Будь ласка, перевірте \"symbol\" Обов\'язкове поле Даного учня вже авторизовано - Ви можете знайти \"symbol\" в Uczeń → Dostęp Mobilny → Zarejestruj urządzenie mobilne + Ви можете знайти \"symbol\" на сторінцi VULCAN стежкою Uczeń → Dostęp Mobilny → Zarejestruj urządzenie mobilne Виберіть учнів для авторизації в додатку Інші варіанти У цьому режимі не працюють: щасливий номер, статистика класу по оцінкам, статистика відвідуваності і уроків, інформація про школу і список зареєстрованних пристроїв From f7b5b9c413a63ce9bcb8183166f13d33839e4452 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sat, 16 May 2020 22:06:00 +0200 Subject: [PATCH 18/85] Add fullscreen mode to homework dialog (#806) --- .../details/HomeworkDetailsAdapter.kt | 16 +++++++++++++ .../homework/details/HomeworkDetailsDialog.kt | 4 ++++ .../login/recover/LoginRecoverFragment.kt | 1 - app/src/main/res/drawable/ic_fullscreen.xml | 5 ++++ .../main/res/drawable/ic_fullscreen_exit.xml | 5 ++++ .../layout/item_homework_dialog_details.xml | 24 +++++++++++++++++++ 6 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 app/src/main/res/drawable/ic_fullscreen.xml create mode 100644 app/src/main/res/drawable/ic_fullscreen_exit.xml diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsAdapter.kt index 923ab953a..5d6ee162a 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsAdapter.kt @@ -1,6 +1,8 @@ package io.github.wulkanowy.ui.modules.homework.details import android.view.LayoutInflater +import android.view.View.GONE +import android.view.View.VISIBLE import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView import io.github.wulkanowy.data.db.entities.Homework @@ -29,6 +31,10 @@ class HomeworkDetailsAdapter @Inject constructor() : var onAttachmentClickListener: (url: String) -> Unit = {} + var onFullScreenClickListener = {} + + var onFullScreenExitClickListener = {} + override fun getItemCount() = 1 + if (attachments.isNotEmpty()) attachments.size + 1 else 0 override fun getItemViewType(position: Int) = when (position) { @@ -61,6 +67,16 @@ class HomeworkDetailsAdapter @Inject constructor() : homeworkDialogSubject.text = homework?.subject homeworkDialogTeacher.text = homework?.teacher homeworkDialogContent.text = homework?.content + homeworkDialogFullScreen.setOnClickListener { + homeworkDialogFullScreen.visibility = GONE + homeworkDialogFullScreenExit.visibility = VISIBLE + onFullScreenClickListener() + } + homeworkDialogFullScreenExit.setOnClickListener { + homeworkDialogFullScreen.visibility = VISIBLE + homeworkDialogFullScreenExit.visibility = GONE + onFullScreenExitClickListener() + } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsDialog.kt index 07f21ef85..7b3b9821a 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsDialog.kt @@ -5,6 +5,8 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.view.ViewGroup.LayoutParams.MATCH_PARENT +import android.view.ViewGroup.LayoutParams.WRAP_CONTENT import androidx.recyclerview.widget.LinearLayoutManager import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Homework @@ -63,6 +65,8 @@ class HomeworkDetailsDialog : BaseDialogFragment(), Homew layoutManager = LinearLayoutManager(context) adapter = detailsAdapter.apply { onAttachmentClickListener = { context.openInternetBrowser(it, ::showMessage) } + onFullScreenClickListener = { dialog?.window?.setLayout(MATCH_PARENT, MATCH_PARENT) } + onFullScreenExitClickListener = { dialog?.window?.setLayout(WRAP_CONTENT, WRAP_CONTENT) } homework = this@HomeworkDetailsDialog.homework } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverFragment.kt index 2accf1fe6..06c91cbea 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverFragment.kt @@ -195,7 +195,6 @@ class LoginRecoverFragment : } override fun onDestroyView() { - binding.loginRecoverWebView.destroy() presenter.onDetachView() super.onDestroyView() diff --git a/app/src/main/res/drawable/ic_fullscreen.xml b/app/src/main/res/drawable/ic_fullscreen.xml new file mode 100644 index 000000000..86b7649b6 --- /dev/null +++ b/app/src/main/res/drawable/ic_fullscreen.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_fullscreen_exit.xml b/app/src/main/res/drawable/ic_fullscreen_exit.xml new file mode 100644 index 000000000..bb7140f29 --- /dev/null +++ b/app/src/main/res/drawable/ic_fullscreen_exit.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout/item_homework_dialog_details.xml b/app/src/main/res/layout/item_homework_dialog_details.xml index bfd246c83..3b5705bf8 100644 --- a/app/src/main/res/layout/item_homework_dialog_details.xml +++ b/app/src/main/res/layout/item_homework_dialog_details.xml @@ -1,4 +1,5 @@ + + + + Date: Sat, 16 May 2020 22:21:14 +0200 Subject: [PATCH 19/85] Add option to hide/show chart list in grade class stats (#807) --- .../preferences/PreferencesRepository.kt | 3 +++ .../statistics/GradeStatisticsAdapter.kt | 6 +++-- .../statistics/GradeStatisticsFragment.kt | 16 ++++++------- .../statistics/GradeStatisticsPresenter.kt | 23 ++++++++----------- .../grade/statistics/GradeStatisticsView.kt | 2 +- .../res/layout/fragment_grade_statistics.xml | 6 ++--- app/src/main/res/values-pl/strings.xml | 1 + .../main/res/values/preferences_defaults.xml | 1 + app/src/main/res/values/preferences_keys.xml | 1 + app/src/main/res/values/strings.xml | 1 + app/src/main/res/xml/scheme_preferences.xml | 9 ++++++-- 11 files changed, 39 insertions(+), 30 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/preferences/PreferencesRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/preferences/PreferencesRepository.kt index 523caf6c9..b7a539925 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/preferences/PreferencesRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/preferences/PreferencesRepository.kt @@ -26,6 +26,9 @@ class PreferencesRepository @Inject constructor( val isGradeExpandable: Boolean get() = !getBoolean(R.string.pref_key_expand_grade, R.bool.pref_default_expand_grade) + val showAllSubjectsOnStatisticsList: Boolean + get() = getBoolean(R.string.pref_key_grade_statistics_list, R.bool.pref_default_grade_statistics_list) + val appThemeKey = context.getString(R.string.pref_key_app_theme) val appTheme: String get() = getString(appThemeKey, R.string.pref_default_app_theme) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsAdapter.kt index 8c1f0b0d6..dbb60910a 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsAdapter.kt @@ -32,6 +32,8 @@ class GradeStatisticsAdapter @Inject constructor() : var theme: String = "vulcan" + var showAllSubjectsOnList: Boolean = false + private val vulcanGradeColors = listOf( 6 to R.color.grade_vulcan_six, 5 to R.color.grade_vulcan_five, @@ -59,7 +61,7 @@ class GradeStatisticsAdapter @Inject constructor() : "6, 6-", "5, 5-, 5+", "4, 4-, 4+", "3, 3-, 3+", "2, 2-, 2+", "1, 1+" ) - override fun getItemCount() = items.size + override fun getItemCount() = if (showAllSubjectsOnList) items.size else (if (items.isEmpty()) 0 else 1) override fun getItemViewType(position: Int) = items[position].type.id @@ -82,7 +84,7 @@ class GradeStatisticsAdapter @Inject constructor() : private fun bindPieChart(holder: PieViewHolder, partials: List) { with(holder.binding.gradeStatisticsPieTitle) { text = partials.firstOrNull()?.subject - visibility = if (items.size == 1) GONE else VISIBLE + visibility = if (items.size == 1 || !showAllSubjectsOnList) GONE else VISIBLE } val gradeColors = when (theme) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsFragment.kt index c6786dbcc..3a8f40073 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsFragment.kt @@ -80,17 +80,17 @@ class GradeStatisticsFragment : } } - override fun updateData(items: List, theme: String) { - statisticsAdapter.theme = theme - statisticsAdapter.items = items - statisticsAdapter.notifyDataSetChanged() + override fun updateData(items: List, theme: String, showAllSubjectsOnStatisticsList: Boolean) { + with(statisticsAdapter) { + this.showAllSubjectsOnList = showAllSubjectsOnStatisticsList + this.theme = theme + this.items = items + notifyDataSetChanged() + } } override fun showSubjects(show: Boolean) { - with(binding) { - gradeStatisticsSubjectsContainer.visibility = if (show) View.VISIBLE else View.INVISIBLE - gradeStatisticsTypeSwitch.visibility = if (show) View.VISIBLE else View.INVISIBLE - } + binding.gradeStatisticsSubjectsContainer.visibility = if (show) View.VISIBLE else View.GONE } override fun clearView() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsPresenter.kt index 90c4e38ef..5cc733cd0 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsPresenter.kt @@ -128,10 +128,7 @@ class GradeStatisticsPresenter @Inject constructor( .observeOn(schedulers.mainThread) .subscribe({ Timber.i("Loading grade stats subjects result: Success") - view?.run { - updateSubjects(it) - showSubjects(true) - } + view?.updateSubjects(it) }, { Timber.i("Loading grade stats subjects result: An exception occurred") errorHandler.dispatch(it) @@ -140,22 +137,21 @@ class GradeStatisticsPresenter @Inject constructor( } private fun loadDataByType(semesterId: Int, subjectName: String, type: ViewType, forceRefresh: Boolean = false) { - currentSubjectName = subjectName + currentSubjectName = if (preferencesRepository.showAllSubjectsOnStatisticsList) "Wszystkie" else subjectName currentType = type - loadData(semesterId, subjectName, type, forceRefresh) - } - private fun loadData(semesterId: Int, subjectName: String, type: ViewType, forceRefresh: Boolean) { Timber.i("Loading grade stats data started") disposable.add(studentRepository.getCurrentStudent() .flatMap { student -> semesterRepository.getSemesters(student).flatMap { semesters -> val semester = semesters.first { item -> item.semesterId == semesterId } - when (type) { - ViewType.SEMESTER -> gradeStatisticsRepository.getGradesStatistics(student, semester, subjectName, true, forceRefresh) - ViewType.PARTIAL -> gradeStatisticsRepository.getGradesStatistics(student, semester, subjectName, false, forceRefresh) - ViewType.POINTS -> gradeStatisticsRepository.getGradesPointsStatistics(student, semester, subjectName, forceRefresh) + with(gradeStatisticsRepository) { + when (type) { + ViewType.SEMESTER -> getGradesStatistics(student, semester, currentSubjectName, true, forceRefresh) + ViewType.PARTIAL -> getGradesStatistics(student, semester, currentSubjectName, false, forceRefresh) + ViewType.POINTS -> getGradesPointsStatistics(student, semester, currentSubjectName, forceRefresh) + } } } } @@ -175,7 +171,8 @@ class GradeStatisticsPresenter @Inject constructor( showEmpty(it.isEmpty()) showContent(it.isNotEmpty()) showErrorView(false) - updateData(it, preferencesRepository.gradeColorTheme) + updateData(it, preferencesRepository.gradeColorTheme, preferencesRepository.showAllSubjectsOnStatisticsList) + showSubjects(!preferencesRepository.showAllSubjectsOnStatisticsList) } analytics.logEvent("load_grade_statistics", "items" to it.size, "force_refresh" to forceRefresh) }) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsView.kt index 9ba8524c2..26b4a119a 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsView.kt @@ -13,7 +13,7 @@ interface GradeStatisticsView : BaseView { fun updateSubjects(data: ArrayList) - fun updateData(items: List, theme: String) + fun updateData(items: List, theme: String, showAllSubjectsOnStatisticsList: Boolean) fun showSubjects(show: Boolean) diff --git a/app/src/main/res/layout/fragment_grade_statistics.xml b/app/src/main/res/layout/fragment_grade_statistics.xml index ecc2e3e00..b3fcac448 100644 --- a/app/src/main/res/layout/fragment_grade_statistics.xml +++ b/app/src/main/res/layout/fragment_grade_statistics.xml @@ -12,7 +12,7 @@ android:layout_height="wrap_content" android:background="?android:windowBackground" android:padding="5dp" - android:visibility="invisible" + android:visibility="gone" tools:ignore="UnusedAttribute" tools:listitem="@layout/item_attendance_summary" tools:visibility="visible"> @@ -59,9 +59,7 @@ android:orientation="horizontal" android:paddingStart="16dp" android:paddingTop="5dp" - android:paddingEnd="16dp" - android:visibility="invisible" - tools:visibility="visible"> + android:paddingEnd="16dp"> Pokazuj obecność we frekwencji Motyw aplikacji Rozwiń oceny + Pokazuj listę wykresów w ocenach klasy Pokazuj lekcje całej klasy Schemat kolorów ocen Język aplikacji diff --git a/app/src/main/res/values/preferences_defaults.xml b/app/src/main/res/values/preferences_defaults.xml index be31b440f..29e8751e4 100644 --- a/app/src/main/res/values/preferences_defaults.xml +++ b/app/src/main/res/values/preferences_defaults.xml @@ -5,6 +5,7 @@ only_one_semester false false + false light vulcan system diff --git a/app/src/main/res/values/preferences_keys.xml b/app/src/main/res/values/preferences_keys.xml index 559e2159c..6d683f3f8 100644 --- a/app/src/main/res/values/preferences_keys.xml +++ b/app/src/main/res/values/preferences_keys.xml @@ -7,6 +7,7 @@ expand_grade grade_average_mode grade_average_always_calc + grade_statistics_list app_language services_enable services_interval diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c2cbeada2..4deba2130 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -347,6 +347,7 @@ Show presence in attendance Application theme Expand grades + Show chart list in class grades Show whole class lessons Grades color scheme App language diff --git a/app/src/main/res/xml/scheme_preferences.xml b/app/src/main/res/xml/scheme_preferences.xml index bb9eb5fcd..b4adabb98 100644 --- a/app/src/main/res/xml/scheme_preferences.xml +++ b/app/src/main/res/xml/scheme_preferences.xml @@ -29,6 +29,11 @@ app:iconSpaceReserved="false" app:key="@string/pref_key_expand_grade" app:title="@string/pref_view_expand_grade" /> + + app:key="@string/pref_key_services_force_sync" + app:title="@string/pref_services_force_sync" /> Date: Sat, 16 May 2020 20:46:11 +0000 Subject: [PATCH 20/85] Bump firebase-analytics from 17.4.0 to 17.4.1 (#809) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 0f35bc822..194296d48 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -178,7 +178,7 @@ dependencies { implementation 'com.wdullaer:materialdatetimepicker:4.2.3' implementation "io.coil-kt:coil:0.10.1" - playImplementation 'com.google.firebase:firebase-analytics:17.4.0' + playImplementation 'com.google.firebase:firebase-analytics:17.4.1' playImplementation 'com.google.firebase:firebase-inappmessaging-display-ktx:19.0.6' playImplementation "com.google.firebase:firebase-inappmessaging-ktx:19.0.6" playImplementation 'com.google.firebase:firebase-messaging:20.1.6' From 9bf5c2dc40c6ca8b684527ecc910a20e8ff8f603 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Sat, 16 May 2020 20:48:15 +0000 Subject: [PATCH 21/85] Bump firebase-crashlytics-gradle from 2.0.0 to 2.1.0 (#810) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index b00d3d797..2b92bc7d8 100644 --- a/build.gradle +++ b/build.gradle @@ -11,7 +11,7 @@ buildscript { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath 'com.android.tools.build:gradle:3.6.3' classpath 'com.google.gms:google-services:4.3.3' - classpath 'com.google.firebase:firebase-crashlytics-gradle:2.0.0' + classpath 'com.google.firebase:firebase-crashlytics-gradle:2.1.0' classpath "com.github.triplet.gradle:play-publisher:2.7.5" classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.8" classpath "gradle.plugin.com.star-zero.gradle:githook:1.2.0" From 45265d025d9f7955db9e4e0d305a07284ad4d94b Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Sat, 16 May 2020 20:52:37 +0000 Subject: [PATCH 22/85] Bump appcompat from 1.2.0-beta01 to 1.2.0-rc01 (#811) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 194296d48..c315ac365 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -127,7 +127,7 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation "androidx.core:core-ktx:1.2.0" implementation "androidx.activity:activity-ktx:1.1.0" - implementation "androidx.appcompat:appcompat:1.2.0-beta01" + implementation "androidx.appcompat:appcompat:1.2.0-rc01" implementation "androidx.appcompat:appcompat-resources:1.1.0" implementation "androidx.fragment:fragment-ktx:1.2.4" implementation "androidx.annotation:annotation:1.1.0" From 78a90591fd2ed4b2ab7fd49565fcf6ff397d6f2d Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Sat, 16 May 2020 21:06:45 +0000 Subject: [PATCH 23/85] Bump coil from 0.10.1 to 0.11.0 (#812) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index c315ac365..89febc8e3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -176,7 +176,7 @@ dependencies { implementation "fr.bipi.treessence:treessence:0.3.2" implementation "com.mikepenz:aboutlibraries-core:$about_libraries" implementation 'com.wdullaer:materialdatetimepicker:4.2.3' - implementation "io.coil-kt:coil:0.10.1" + implementation "io.coil-kt:coil:0.11.0" playImplementation 'com.google.firebase:firebase-analytics:17.4.1' playImplementation 'com.google.firebase:firebase-inappmessaging-display-ktx:19.0.6' From 6cd1877af781e5355a5103bc526c294ba213fdf5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Wed, 20 May 2020 12:34:29 +0200 Subject: [PATCH 24/85] Fix notifications on android 8.0 (#814) --- app/build.gradle | 2 +- .../main/java/io/github/wulkanowy/services/sync/SyncManager.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 89febc8e3..acf4ddd02 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -122,7 +122,7 @@ configurations.all { } dependencies { - implementation "io.github.wulkanowy:sdk:445905b" + implementation "io.github.wulkanowy:sdk:46619e3" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation "androidx.core:core-ktx:1.2.0" diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/SyncManager.kt b/app/src/main/java/io/github/wulkanowy/services/sync/SyncManager.kt index dda2abe0c..965ed0ad9 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/SyncManager.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/SyncManager.kt @@ -42,7 +42,7 @@ class SyncManager @Inject constructor( init { if (now().isHolidays) stopSyncWorker() - if (SDK_INT > O) { + if (SDK_INT >= O) { channels.forEach { it.create() } notificationManager.deleteNotificationChannel("new_entries_channel") } From 115da641671e91d5678e1fd3d201b531cd074f84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Wed, 20 May 2020 14:12:32 +0200 Subject: [PATCH 25/85] Add search in messages (#804) --- app/src/main/AndroidManifest.xml | 3 +- .../modules/message/tab/MessageTabAdapter.kt | 38 ++++++++++++++-- .../modules/message/tab/MessageTabFragment.kt | 40 +++++++++++++---- .../message/tab/MessageTabPresenter.kt | 43 ++++++++++++++++--- .../ui/modules/message/tab/MessageTabView.kt | 2 + app/src/main/res/drawable/ic_search.xml | 9 ++++ app/src/main/res/layout/activity_main.xml | 4 +- .../main/res/menu/action_menu_message_tab.xml | 11 +++++ app/src/main/res/values-pl/strings.xml | 1 + app/src/main/res/values/strings.xml | 2 + 10 files changed, 133 insertions(+), 20 deletions(-) create mode 100644 app/src/main/res/drawable/ic_search.xml create mode 100644 app/src/main/res/menu/action_menu_message_tab.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4478f4087..4dd70721e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -39,7 +39,8 @@ android:name=".ui.modules.main.MainActivity" android:configChanges="orientation|screenSize" android:label="@string/main_title" - android:theme="@style/WulkanowyTheme.NoActionBar" /> + android:theme="@style/WulkanowyTheme.NoActionBar" + android:windowSoftInputMode="adjustPan" /> () { - var items = mutableListOf() - var onClickListener: (Message, position: Int) -> Unit = { _, _ -> } - override fun getItemCount() = items.size + private val items = SortedList(Message::class.java, object : + SortedListAdapterCallback(this) { + + override fun compare(item1: Message, item2: Message): Int { + return item2.date.compareTo(item1.date) + } + + override fun areContentsTheSame(oldItem: Message?, newItem: Message?): Boolean { + return oldItem == newItem + } + + override fun areItemsTheSame(item1: Message, item2: Message): Boolean { + return item1 == item2 + } + }) + + fun replaceAll(models: List) { + items.beginBatchedUpdates() + for (i in items.size() - 1 downTo 0) { + val model = items.get(i) + if (model !in models) { + items.remove(model) + } + } + items.addAll(models) + items.endBatchedUpdates() + } + + fun updateItem(position: Int, item: Message) { + items.updateItemAt(position, item) + } + + override fun getItemCount() = items.size() override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder( ItemMessageBinding.inflate(LayoutInflater.from(parent.context), parent, false) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabFragment.kt index 3fdf16845..909bb6873 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabFragment.kt @@ -1,10 +1,13 @@ package io.github.wulkanowy.ui.modules.message.tab import android.os.Bundle +import android.view.Menu +import android.view.MenuInflater import android.view.View import android.view.View.GONE import android.view.View.INVISIBLE import android.view.View.VISIBLE +import androidx.appcompat.widget.SearchView import androidx.recyclerview.widget.LinearLayoutManager import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Message @@ -39,7 +42,12 @@ class MessageTabFragment : BaseFragment(R.layout.frag } override val isViewEmpty - get() = tabAdapter.items.isEmpty() + get() = tabAdapter.itemCount == 0 + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setHasOptionsMenu(true) + } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -65,18 +73,28 @@ class MessageTabFragment : BaseFragment(R.layout.frag } } + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + super.onCreateOptionsMenu(menu, inflater) + inflater.inflate(R.menu.action_menu_message_tab, menu) + + val searchView = menu.findItem(R.id.action_search).actionView as SearchView + searchView.queryHint = getString(R.string.all_search_hint) + searchView.maxWidth = Int.MAX_VALUE + searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener { + override fun onQueryTextSubmit(query: String) = false + override fun onQueryTextChange(query: String): Boolean { + presenter.onSearchQueryTextChange(query) + return true + } + }) + } + override fun updateData(data: List) { - with(tabAdapter) { - items = data.toMutableList() - notifyDataSetChanged() - } + tabAdapter.replaceAll(data) } override fun updateItem(item: Message, position: Int) { - with(tabAdapter) { - items[position] = item - notifyItemChanged(position) - } + tabAdapter.updateItem(position, item) } override fun showProgress(show: Boolean) { @@ -87,6 +105,10 @@ class MessageTabFragment : BaseFragment(R.layout.frag binding.messageTabSwipe.isEnabled = enable } + override fun resetListPosition() { + binding.messageTabRecycler.scrollToPosition(0) + } + override fun showContent(show: Boolean) { binding.messageTabRecycler.visibility = if (show) VISIBLE else INVISIBLE } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt index 37b45d03d..f96fb6c2a 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.ui.modules.message.tab +import android.annotation.SuppressLint import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.repositories.message.MessageFolder import io.github.wulkanowy.data.repositories.message.MessageRepository @@ -9,6 +10,7 @@ import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.FirebaseAnalyticsHelper import io.github.wulkanowy.utils.SchedulersProvider +import io.github.wulkanowy.utils.toFormattedString import timber.log.Timber import javax.inject.Inject @@ -25,6 +27,10 @@ class MessageTabPresenter @Inject constructor( private lateinit var lastError: Throwable + private var lastSearchQuery = "" + + private var messages = emptyList() + fun onAttachView(view: MessageTabView, folder: MessageFolder) { super.onAttachView(view) view.initView() @@ -89,12 +95,8 @@ class MessageTabPresenter @Inject constructor( } .subscribe({ Timber.i("Loading $folder message result: Success") - view?.run { - showEmpty(it.isEmpty()) - showContent(it.isNotEmpty()) - showErrorView(false) - updateData(it) - } + messages = it + onSearchQueryTextChange(lastSearchQuery) analytics.logEvent("load_messages", "items" to it.size, "folder" to folder.name) }) { Timber.i("Loading $folder message result: An exception occurred") @@ -113,4 +115,33 @@ class MessageTabPresenter @Inject constructor( } else showError(message, error) } } + + @SuppressLint("DefaultLocale") + fun onSearchQueryTextChange(query: String) { + lastSearchQuery = query + + val lowerCaseQuery = query.toLowerCase() + val filteredList = mutableListOf() + messages.forEach { + if (lowerCaseQuery in it.subject.toLowerCase() || + lowerCaseQuery in it.sender.toLowerCase() || + lowerCaseQuery in it.recipient.toLowerCase() || + lowerCaseQuery in it.date.toFormattedString() + ) { + filteredList.add(it) + } + } + + updateData(filteredList) + } + + private fun updateData(data: List) { + view?.run { + showEmpty(data.isEmpty()) + showContent(data.isNotEmpty()) + showErrorView(false) + updateData(data) + resetListPosition() + } + } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabView.kt index 94ece8ec2..f521191cf 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabView.kt @@ -9,6 +9,8 @@ interface MessageTabView : BaseView { fun initView() + fun resetListPosition() + fun updateData(data: List) fun updateItem(item: Message, position: Int) diff --git a/app/src/main/res/drawable/ic_search.xml b/app/src/main/res/drawable/ic_search.xml new file mode 100644 index 000000000..cd9985cb1 --- /dev/null +++ b/app/src/main/res/drawable/ic_search.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index d07dbbd8a..2ea0a4d39 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,5 +1,6 @@ @@ -8,7 +9,8 @@ android:id="@+id/mainToolbar" style="@style/Widget.MaterialComponents.Toolbar.Surface" android:layout_width="match_parent" - android:layout_height="wrap_content" /> + android:layout_height="wrap_content" + app:contentInsetStartWithNavigation="0dp" /> + + + diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 523221f01..bff422927 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -298,6 +298,7 @@ Przedmiot Poprzedni Następny + Szukaj Brak lekcji Wybierz motyw diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4deba2130..0a837c46b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -329,6 +329,8 @@ Subject Prev Next + Search + Search... From 29226dd93e9dfcf72ec80e476331d1bcc43c6e05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Wed, 20 May 2020 15:11:01 +0200 Subject: [PATCH 26/85] Add notification about upcoming lesson (#578) --- .../timetable/TimetableRepositoryTest.kt | 20 ++- app/src/main/AndroidManifest.xml | 2 + .../github/wulkanowy/data/RepositoryModule.kt | 2 + .../preferences/PreferencesRepository.kt | 4 + .../timetable/TimetableRepository.kt | 10 +- .../io/github/wulkanowy/di/BindingModule.kt | 4 + .../wulkanowy/services/ServicesModule.kt | 11 ++ .../alarm/TimetableNotificationReceiver.kt | 117 ++++++++++++++++++ .../TimetableNotificationSchedulerHelper.kt | 109 ++++++++++++++++ .../sync/channels/UpcomingLessonsChannel.kt | 31 +++++ .../ui/modules/settings/SettingsPresenter.kt | 6 +- .../github/wulkanowy/utils/TimeExtension.kt | 8 ++ .../res/drawable-hdpi/ic_stat_timetable.png | Bin 0 -> 312 bytes .../res/drawable-mdpi/ic_stat_timetable.png | Bin 0 -> 275 bytes .../res/drawable-xhdpi/ic_stat_timetable.png | Bin 0 -> 358 bytes .../res/drawable-xxhdpi/ic_stat_timetable.png | Bin 0 -> 459 bytes .../drawable-xxxhdpi/ic_stat_timetable.png | Bin 0 -> 659 bytes app/src/main/res/values-pl/strings.xml | 5 + .../main/res/values/preferences_defaults.xml | 1 + app/src/main/res/values/preferences_keys.xml | 1 + app/src/main/res/values/strings.xml | 5 + app/src/main/res/xml/scheme_preferences.xml | 5 + 22 files changed, 333 insertions(+), 8 deletions(-) create mode 100644 app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationReceiver.kt create mode 100644 app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationSchedulerHelper.kt create mode 100644 app/src/main/java/io/github/wulkanowy/services/sync/channels/UpcomingLessonsChannel.kt create mode 100644 app/src/main/res/drawable-hdpi/ic_stat_timetable.png create mode 100644 app/src/main/res/drawable-mdpi/ic_stat_timetable.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_stat_timetable.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_stat_timetable.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_stat_timetable.png diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/timetable/TimetableRepositoryTest.kt b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/timetable/TimetableRepositoryTest.kt index fdf193a26..75f2f0b83 100644 --- a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/timetable/TimetableRepositoryTest.kt +++ b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/timetable/TimetableRepositoryTest.kt @@ -8,12 +8,15 @@ import androidx.test.filters.SdkSuppress import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings import io.github.wulkanowy.data.db.AppDatabase import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.repositories.TestInternetObservingStrategy import io.github.wulkanowy.data.repositories.getStudent +import io.github.wulkanowy.services.alarm.TimetableNotificationSchedulerHelper import io.github.wulkanowy.sdk.Sdk import io.mockk.MockKAnnotations import io.mockk.every import io.mockk.impl.annotations.MockK +import io.mockk.mockk import io.reactivex.Single import org.junit.After import org.junit.Before @@ -34,11 +37,17 @@ class TimetableRepositoryTest { .strategy(TestInternetObservingStrategy()) .build() + @MockK + private lateinit var studentMock: Student + private val student = getStudent() @MockK private lateinit var semesterMock: Semester + @MockK + private lateinit var timetableNotificationSchedulerHelper: TimetableNotificationSchedulerHelper + private lateinit var timetableRemote: TimetableRemote private lateinit var timetableLocal: TimetableLocal @@ -52,10 +61,17 @@ class TimetableRepositoryTest { timetableLocal = TimetableLocal(testDb.timetableDao) timetableRemote = TimetableRemote(mockSdk) + every { timetableNotificationSchedulerHelper.scheduleNotifications(any(), any()) } returns mockk() + every { timetableNotificationSchedulerHelper.cancelScheduled(any(), any()) } returns mockk() + + every { studentMock.studentId } returns 1 + every { studentMock.studentName } returns "Jan Kowalski" + every { semesterMock.studentId } returns 1 every { semesterMock.diaryId } returns 2 every { semesterMock.schoolYear } returns 2019 every { semesterMock.semesterId } returns 1 + every { mockSdk.switchDiary(any(), any()) } returns mockSdk } @@ -80,7 +96,7 @@ class TimetableRepositoryTest { createTimetableRemote(of(2019, 3, 5, 10, 30), 4, "", "W-F") )) - val lessons = TimetableRepository(settings, timetableLocal, timetableRemote) + val lessons = TimetableRepository(settings, timetableLocal, timetableRemote, timetableNotificationSchedulerHelper) .getTimetable(student, semesterMock, LocalDate.of(2019, 3, 5), LocalDate.of(2019, 3, 5), true) .blockingGet() @@ -126,7 +142,7 @@ class TimetableRepositoryTest { createTimetableRemote(of(2019, 12, 25, 10, 40), 4, "126", "Matematyka", "Paweł Czwartkowski", true) )) - val lessons = TimetableRepository(settings, timetableLocal, timetableRemote) + val lessons = TimetableRepository(settings, timetableLocal, timetableRemote, timetableNotificationSchedulerHelper) .getTimetable(student, semesterMock, LocalDate.of(2019, 12, 23), LocalDate.of(2019, 12, 25), true) .blockingGet() diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4dd70721e..4ec2f7816 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -92,6 +92,8 @@ android:resource="@xml/provider_widget_lucky_number" /> + + > { @@ -31,8 +33,8 @@ class TimetableRepository @Inject constructor( local.getTimetable(semester, monday, friday) .toSingle(emptyList()) .doOnSuccess { old -> - local.deleteTimetable(old.uniqueSubtract(new)) - local.saveTimetable(new.uniqueSubtract(old).map { item -> + local.deleteTimetable(old.uniqueSubtract(new).also { schedulerHelper.cancelScheduled(it) }) + local.saveTimetable(new.uniqueSubtract(old).also { schedulerHelper.scheduleNotifications(it, student) }.map { item -> item.also { new -> old.singleOrNull { new.start == it.start }?.let { old -> return@map new.copy( @@ -45,7 +47,7 @@ class TimetableRepository @Inject constructor( } }.flatMap { local.getTimetable(semester, monday, friday).toSingle(emptyList()) - }).map { list -> list.filter { it.date in start..end } } + }).map { list -> list.filter { it.date in start..end }.also { schedulerHelper.scheduleNotifications(it, student) } } } } } diff --git a/app/src/main/java/io/github/wulkanowy/di/BindingModule.kt b/app/src/main/java/io/github/wulkanowy/di/BindingModule.kt index ba8c78d3f..1b462964d 100644 --- a/app/src/main/java/io/github/wulkanowy/di/BindingModule.kt +++ b/app/src/main/java/io/github/wulkanowy/di/BindingModule.kt @@ -4,6 +4,7 @@ import dagger.Module import dagger.android.ContributesAndroidInjector import io.github.wulkanowy.di.scopes.PerActivity import io.github.wulkanowy.ui.base.ErrorDialog +import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver import io.github.wulkanowy.ui.modules.login.LoginActivity import io.github.wulkanowy.ui.modules.login.LoginModule import io.github.wulkanowy.ui.modules.luckynumberwidget.LuckyNumberWidgetConfigureActivity @@ -48,4 +49,7 @@ internal abstract class BindingModule { @ContributesAndroidInjector abstract fun bindLuckyNumberWidgetProvider(): LuckyNumberWidgetProvider + + @ContributesAndroidInjector + abstract fun bindTimetableNotificationReceiver(): TimetableNotificationReceiver } diff --git a/app/src/main/java/io/github/wulkanowy/services/ServicesModule.kt b/app/src/main/java/io/github/wulkanowy/services/ServicesModule.kt index c7c573e27..b87f0e683 100644 --- a/app/src/main/java/io/github/wulkanowy/services/ServicesModule.kt +++ b/app/src/main/java/io/github/wulkanowy/services/ServicesModule.kt @@ -1,7 +1,9 @@ package io.github.wulkanowy.services +import android.app.AlarmManager import android.content.Context import androidx.core.app.NotificationManagerCompat +import androidx.core.content.getSystemService import androidx.work.WorkManager import com.squareup.inject.assisted.dagger2.AssistedModule import dagger.Binds @@ -15,6 +17,7 @@ import io.github.wulkanowy.services.sync.channels.LuckyNumberChannel import io.github.wulkanowy.services.sync.channels.NewGradesChannel import io.github.wulkanowy.services.sync.channels.NewMessagesChannel import io.github.wulkanowy.services.sync.channels.NewNotesChannel +import io.github.wulkanowy.services.sync.channels.UpcomingLessonsChannel import io.github.wulkanowy.services.sync.channels.PushChannel import io.github.wulkanowy.services.sync.works.AttendanceSummaryWork import io.github.wulkanowy.services.sync.works.AttendanceWork @@ -46,6 +49,10 @@ abstract class ServicesModule { @Singleton @Provides fun provideNotificationManager(context: Context) = NotificationManagerCompat.from(context) + + @Singleton + @Provides + fun provideAlarmManager(context: Context): AlarmManager = context.getSystemService()!! } @ContributesAndroidInjector @@ -126,4 +133,8 @@ abstract class ServicesModule { @Binds @IntoSet abstract fun providePushChannel(channel: PushChannel): Channel + + @Binds + @IntoSet + abstract fun provideUpcomingLessonsChannel(channel: UpcomingLessonsChannel): Channel } diff --git a/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationReceiver.kt b/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationReceiver.kt new file mode 100644 index 000000000..0130f4673 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationReceiver.kt @@ -0,0 +1,117 @@ +package io.github.wulkanowy.services.alarm + +import android.annotation.SuppressLint +import android.app.PendingIntent +import android.app.PendingIntent.FLAG_UPDATE_CURRENT +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.os.Build +import android.os.Build.VERSION_CODES.N +import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationManagerCompat +import dagger.android.AndroidInjection +import io.github.wulkanowy.R +import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.services.sync.channels.UpcomingLessonsChannel.Companion.CHANNEL_ID +import io.github.wulkanowy.ui.modules.main.MainActivity +import io.github.wulkanowy.ui.modules.main.MainView +import io.github.wulkanowy.utils.SchedulersProvider +import io.github.wulkanowy.utils.getCompatColor +import io.github.wulkanowy.utils.toLocalDateTime +import timber.log.Timber +import javax.inject.Inject + +class TimetableNotificationReceiver : BroadcastReceiver() { + + @Inject + lateinit var studentRepository: StudentRepository + + @Inject + lateinit var schedulers: SchedulersProvider + + companion object { + const val NOTIFICATION_TYPE_CURRENT = 1 + const val NOTIFICATION_TYPE_UPCOMING = 2 + const val NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION = 3 + + const val NOTIFICATION_ID = "id" + + const val STUDENT_NAME = "student_name" + const val STUDENT_ID = "student_id" + const val LESSON_TYPE = "type" + const val LESSON_TITLE = "title" + const val LESSON_ROOM = "room" + const val LESSON_NEXT_TITLE = "next_title" + const val LESSON_NEXT_ROOM = "next_room" + const val LESSON_START = "start_timestamp" + const val LESSON_END = "end_timestamp" + } + + @SuppressLint("CheckResult") + override fun onReceive(context: Context, intent: Intent) { + Timber.d("Receiving intent... ${intent.toUri(0)}") + AndroidInjection.inject(this, context) + + studentRepository.getCurrentStudent(false) + .subscribeOn(schedulers.backgroundThread) + .observeOn(schedulers.mainThread) + .subscribe({ + val studentId = intent.getIntExtra(STUDENT_ID, 0) + if (it.studentId == studentId) prepareNotification(context, intent) + else Timber.d("Notification studentId($studentId) differs from current(${it.studentId})") + }, { Timber.e(it) }) + } + + private fun prepareNotification(context: Context, intent: Intent) { + val type = intent.getIntExtra(LESSON_TYPE, 0) + val notificationId = intent.getIntExtra(NOTIFICATION_ID, MainView.Section.TIMETABLE.id) + + if (type == NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION) { + return NotificationManagerCompat.from(context).cancel(notificationId) + } + + val studentId = intent.getIntExtra(STUDENT_ID, 0) + val studentName = intent.getStringExtra(STUDENT_NAME) + + val subject = intent.getStringExtra(LESSON_TITLE) + val room = intent.getStringExtra(LESSON_ROOM) + + val start = intent.getLongExtra(LESSON_START, 0) + val end = intent.getLongExtra(LESSON_END, 0) + + val nextSubject = intent.getStringExtra(LESSON_NEXT_TITLE) + val nextRoom = intent.getStringExtra(LESSON_NEXT_ROOM) + + Timber.d("TimetableNotification receive: type: $type, subject: $subject, start: ${start.toLocalDateTime()}, student: $studentId") + + showNotification(context, notificationId, studentName, + if (type == NOTIFICATION_TYPE_CURRENT) end else start, end - start, + context.getString(if (type == NOTIFICATION_TYPE_CURRENT) R.string.timetable_now else R.string.timetable_next, "($room) $subject".removePrefix("()")), + nextSubject?.let { context.getString(R.string.timetable_later, "($nextRoom) $nextSubject".removePrefix("()")) } + ) + } + + private fun showNotification(context: Context, notificationId: Int, studentName: String?, countDown: Long, timeout: Long, title: String, next: String?) { + NotificationManagerCompat.from(context).notify(notificationId, NotificationCompat.Builder(context, CHANNEL_ID) + .setContentTitle(title) + .setContentText(next) + .setAutoCancel(false) + .setOngoing(true) + .setWhen(countDown) + .apply { + if (Build.VERSION.SDK_INT >= N) setUsesChronometer(true) + } + .setTimeoutAfter(timeout) + .setSmallIcon(R.drawable.ic_stat_timetable) + .setColor(context.getCompatColor(R.color.colorPrimary)) + .setStyle(NotificationCompat.InboxStyle().also { + it.setSummaryText(studentName) + it.addLine(next) + }) + .setContentIntent(PendingIntent.getActivity(context, MainView.Section.TIMETABLE.id, + MainActivity.getStartIntent(context, MainView.Section.TIMETABLE, true), FLAG_UPDATE_CURRENT)) + .build() + ) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationSchedulerHelper.kt b/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationSchedulerHelper.kt new file mode 100644 index 000000000..5374c4767 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationSchedulerHelper.kt @@ -0,0 +1,109 @@ +package io.github.wulkanowy.services.alarm + +import android.app.AlarmManager +import android.app.AlarmManager.RTC_WAKEUP +import android.app.PendingIntent +import android.app.PendingIntent.FLAG_CANCEL_CURRENT +import android.content.Context +import android.content.Intent +import androidx.core.app.AlarmManagerCompat +import androidx.core.app.NotificationManagerCompat +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.db.entities.Timetable +import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository +import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.LESSON_END +import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.LESSON_NEXT_ROOM +import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.LESSON_NEXT_TITLE +import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.LESSON_ROOM +import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.LESSON_START +import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.LESSON_TITLE +import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.LESSON_TYPE +import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.NOTIFICATION_ID +import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.NOTIFICATION_TYPE_CURRENT +import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION +import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.NOTIFICATION_TYPE_UPCOMING +import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.STUDENT_ID +import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.STUDENT_NAME +import io.github.wulkanowy.ui.modules.main.MainView +import io.github.wulkanowy.utils.toTimestamp +import org.threeten.bp.LocalDateTime +import org.threeten.bp.LocalDateTime.now +import timber.log.Timber +import javax.inject.Inject + +class TimetableNotificationSchedulerHelper @Inject constructor( + private val context: Context, + private val alarmManager: AlarmManager, + private val preferencesRepository: PreferencesRepository +) { + + private fun getRequestCode(time: LocalDateTime, studentId: Int) = (time.toTimestamp() * studentId).toInt() + + private fun getUpcomingLessonTime(index: Int, day: List, lesson: Timetable): LocalDateTime { + return day.getOrNull(index - 1)?.end ?: lesson.start.minusMinutes(30) + } + + fun cancelScheduled(lessons: List, studentId: Int = 1) { + lessons.sortedBy { it.start }.forEachIndexed { index, lesson -> + val upcomingTime = getUpcomingLessonTime(index, lessons, lesson) + cancelScheduledTo(upcomingTime..lesson.start, getRequestCode(upcomingTime, studentId)) + cancelScheduledTo(lesson.start..lesson.end, getRequestCode(lesson.start, studentId)) + + Timber.d("TimetableNotification canceled: type 1 & 2, subject: ${lesson.subject}, start: ${lesson.start}, student: $studentId") + } + } + + private fun cancelScheduledTo(range: ClosedRange, requestCode: Int) { + if (now() in range) cancelNotification() + alarmManager.cancel(PendingIntent.getBroadcast(context, requestCode, Intent(), FLAG_CANCEL_CURRENT)) + } + + fun cancelNotification() = NotificationManagerCompat.from(context).cancel(MainView.Section.TIMETABLE.id) + + fun scheduleNotifications(lessons: List, student: Student) { + if (!preferencesRepository.isUpcomingLessonsNotificationsEnable) return cancelScheduled(lessons, student.studentId) + + lessons.groupBy { it.date } + .map { it.value.sortedBy { lesson -> lesson.start } } + .map { it.filter { lesson -> !lesson.canceled && lesson.isStudentPlan } } + .map { day -> + day.forEachIndexed { index, lesson -> + val intent = createIntent(student, lesson, day.getOrNull(index + 1)) + + if (lesson.start > now()) { + scheduleBroadcast(intent, student.studentId, NOTIFICATION_TYPE_UPCOMING, getUpcomingLessonTime(index, day, lesson)) + } + + if (lesson.end > now()) { + scheduleBroadcast(intent, student.studentId, NOTIFICATION_TYPE_CURRENT, lesson.start) + if (day.lastIndex == index) { + scheduleBroadcast(intent, student.studentId, NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION, lesson.end) + } + } + } + } + } + + private fun createIntent(student: Student, lesson: Timetable, nextLesson: Timetable?): Intent { + return Intent(context, TimetableNotificationReceiver::class.java).apply { + putExtra(STUDENT_ID, student.studentId) + putExtra(STUDENT_NAME, student.studentName) + putExtra(LESSON_ROOM, lesson.room) + putExtra(LESSON_START, lesson.start.toTimestamp()) + putExtra(LESSON_END, lesson.end.toTimestamp()) + putExtra(LESSON_TITLE, lesson.subject) + putExtra(LESSON_NEXT_TITLE, nextLesson?.subject) + putExtra(LESSON_NEXT_ROOM, nextLesson?.room) + } + } + + private fun scheduleBroadcast(intent: Intent, studentId: Int, notificationType: Int, time: LocalDateTime) { + AlarmManagerCompat.setExactAndAllowWhileIdle(alarmManager, RTC_WAKEUP, time.toTimestamp(), + PendingIntent.getBroadcast(context, getRequestCode(time, studentId), intent.also { + it.putExtra(NOTIFICATION_ID, MainView.Section.TIMETABLE.id) + it.putExtra(LESSON_TYPE, notificationType) + }, FLAG_CANCEL_CURRENT) + ) + Timber.d("TimetableNotification scheduled: type: $notificationType, subject: ${intent.getStringExtra(LESSON_TITLE)}, start: $time, student: $studentId") + } +} diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/channels/UpcomingLessonsChannel.kt b/app/src/main/java/io/github/wulkanowy/services/sync/channels/UpcomingLessonsChannel.kt new file mode 100644 index 000000000..a292c8b53 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/services/sync/channels/UpcomingLessonsChannel.kt @@ -0,0 +1,31 @@ +package io.github.wulkanowy.services.sync.channels + +import android.annotation.TargetApi +import android.app.Notification.VISIBILITY_PUBLIC +import android.app.NotificationChannel +import android.app.NotificationManager.IMPORTANCE_DEFAULT +import android.content.Context +import androidx.core.app.NotificationManagerCompat +import io.github.wulkanowy.R +import javax.inject.Inject + +@TargetApi(26) +class UpcomingLessonsChannel @Inject constructor( + private val notificationManager: NotificationManagerCompat, + private val context: Context +) : Channel { + + companion object { + const val CHANNEL_ID = "lesson_channel" + } + + override fun create() { + notificationManager.createNotificationChannel( + NotificationChannel(CHANNEL_ID, context.getString(R.string.channel_upcoming_lessons), IMPORTANCE_DEFAULT).apply { + lockscreenVisibility = VISIBILITY_PUBLIC + setShowBadge(false) + enableVibration(false) + } + ) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsPresenter.kt index c8545ac0e..09fc2d707 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsPresenter.kt @@ -4,6 +4,7 @@ import androidx.work.WorkInfo import com.chuckerteam.chucker.api.ChuckerCollector import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.services.alarm.TimetableNotificationSchedulerHelper import io.github.wulkanowy.services.sync.SyncManager import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler @@ -20,6 +21,7 @@ class SettingsPresenter @Inject constructor( errorHandler: ErrorHandler, studentRepository: StudentRepository, private val preferencesRepository: PreferencesRepository, + private val timetableNotificationHelper: TimetableNotificationSchedulerHelper, private val analytics: FirebaseAnalyticsHelper, private val syncManager: SyncManager, private val chuckerCollector: ChuckerCollector, @@ -36,17 +38,17 @@ class SettingsPresenter @Inject constructor( fun onSharedPreferenceChanged(key: String) { Timber.i("Change settings $key") - with(preferencesRepository) { + preferencesRepository.apply { when (key) { serviceEnableKey -> with(syncManager) { if (isServiceEnabled) startPeriodicSyncWorker() else stopSyncWorker() } servicesIntervalKey, servicesOnlyWifiKey -> syncManager.startPeriodicSyncWorker(true) isDebugNotificationEnableKey -> chuckerCollector.showNotification = isDebugNotificationEnable appThemeKey -> view?.recreateView() + isUpcomingLessonsNotificationsEnableKey -> if (!isUpcomingLessonsNotificationsEnable) timetableNotificationHelper.cancelNotification() appLanguageKey -> view?.run { updateLanguage(if (appLanguage == "system") appInfo.systemLanguage else appLanguage) recreateView() } - else -> Unit } } analytics.logEvent("setting_changed", "name" to key) diff --git a/app/src/main/java/io/github/wulkanowy/utils/TimeExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/TimeExtension.kt index a91f823fa..8d022fc5d 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/TimeExtension.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/TimeExtension.kt @@ -4,9 +4,13 @@ import org.threeten.bp.DayOfWeek.FRIDAY import org.threeten.bp.DayOfWeek.MONDAY import org.threeten.bp.DayOfWeek.SATURDAY import org.threeten.bp.DayOfWeek.SUNDAY +import org.threeten.bp.Instant.ofEpochMilli import org.threeten.bp.LocalDate import org.threeten.bp.LocalDateTime +import org.threeten.bp.LocalDateTime.ofInstant import org.threeten.bp.Month +import org.threeten.bp.ZoneId +import org.threeten.bp.ZoneOffset import org.threeten.bp.format.DateTimeFormatter.ofPattern import org.threeten.bp.format.TextStyle.FULL_STANDALONE import org.threeten.bp.temporal.TemporalAdjusters.firstInMonth @@ -18,6 +22,10 @@ private const val DATE_PATTERN = "dd.MM.yyyy" fun String.toLocalDate(format: String = DATE_PATTERN): LocalDate = LocalDate.parse(this, ofPattern(format)) +fun LocalDateTime.toTimestamp() = atZone(ZoneId.systemDefault()).withZoneSameInstant(ZoneOffset.UTC).toInstant().toEpochMilli() + +fun Long.toLocalDateTime() = ofInstant(ofEpochMilli(this), ZoneId.systemDefault()) + fun LocalDate.toFormattedString(format: String = DATE_PATTERN): String = format(ofPattern(format)) fun LocalDateTime.toFormattedString(format: String = DATE_PATTERN): String = format(ofPattern(format)) diff --git a/app/src/main/res/drawable-hdpi/ic_stat_timetable.png b/app/src/main/res/drawable-hdpi/ic_stat_timetable.png new file mode 100644 index 0000000000000000000000000000000000000000..201419d5d48501c657d592ea41c27763c5e6eb7f GIT binary patch literal 312 zcmeAS@N?(olHy`uVBq!ia0y~yU{C>J4mJh`hKCF@W-u@?{P%Qm45_&Fc7`F>Ap?=N z=r00ane_AyI=%>-W8?OI0sD(k8Ji|Pt1og{FK=w@4hS&#*BJgn=}W7N_a*s#2Os{e zIqZE|iY58jAB`X{Mj_T>J;j#Hg)u=higSy$w*(k4dT(Hr)UbXk*=(lz_g$+H%;Jzqs#YF15yVeRuwq zE7ecAWDnfFw0pvV1> literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/ic_stat_timetable.png b/app/src/main/res/drawable-mdpi/ic_stat_timetable.png new file mode 100644 index 0000000000000000000000000000000000000000..dcfe95f26582f0cb2cee7b00e660fd2f50f7523e GIT binary patch literal 275 zcmeAS@N?(olHy`uVBq!ia0y~yV2}V|4mJh`h6m-gKNuJoZh5*mhE&{2PLSC4;Q#;s ziyYSQ9;vCg^1<cM~h eL$(bA!bd+c{ zby-xvC_Z7WiIBNN_Ri)N+cs!*2JdLj)ez-#^xnBPLvzDDmEasTsXFV)vF}ym7yr0l zGx^Mh)geg|&;CzRIcebPZm@jvywqTYxgG}UUi^z36j=?nv#}*`hNWo=y*|HB(ahYr ze8oD!75|ey1{bh|fAHDV|7u60@QjtF9xT5eBxF8(@A7Sdx**5bbGy4<@u@O#Cof2U z*z}?294l*=ihzW@@+O57K?htl0yG-$%x8>SEb?ROf!Ru)He7!eJ2-z(NxSqwahU_( z`iuGJ9435!-`K$<@<7+6vA^x?!NU)QXTF-}&7$_;(TB<(Jja9@&2BXAZa%vDoNk6E z%c@_zU)|oft-bzwRW|Rg1xmIdq8F|x?Q1oD@LKO`-gW>xpPn@89S-al9}J9&9Wzrv2P0#Vl$e6gEebN^sp`A%uuf+pE@`?LPvNh1aZ28J0D&tvqu=S#`0Ut!R1IYIt|c-!&SixdPJIb0U3 z3)UqBpnn8S9&;y4#&Jx9qZ*Fh5se{?pDopY|U#x-iRT zit}{2!v?=sr7mEue0=HYtuJz->z+CuYW(vieOhCc==VAQ~LV|G6={L=x|_Bx)8$Z^)j$IbzUvV5eiJbg$HJBXHjkBa8Y0qd{N&R zWoj3+CF0plc91TmfGwwM92FoYd=FGkpP4QXQrF=iEdRl)PVLX5L+x*(*sP}YbF%N6 z?mn%t?C}1KDG77*D;3Lin17yM@a<`KQ)ApA&bSRcCfd`VwC$O~|8Q5|&v}p2-<&Gl zsgb@Wysa(gRPIjo+k*QYuJRfGiu}7hua^fDbjKb^@7i+j?NW~ATR>b-S3j3^P6qC8z3Ln>~)oonkI<0#=a zy@Q3tGf|KuTTu6sdyqy)rifw`$2GN@4oB#><@d%FjQ2cV^f3S*IBpAmB)Qao)1Fxr}|wH(xV6DzQMU zmEm~sic$v_1qMbA2L>jA28Nlwj|V#Xf7U%Ucq^PnlncZ-f4JXEuwCuM=1O_`WyUEPQ3&EvRF`?k-?HEqT9Xh5Gc@ zh7Lvo4GdsG2L`4MJgLgZ+%U9Coxk0=I^Q;5`oYpS)9yc+X0y#) z?f;gKSuXk&SJQTFko+6`DEx`^Pn$17Z{Pptm)IbBc$I95<{}2w*)MnRe{!Py+tH?D z$2Yj;P0-!s+;^hTQny=B*SXv5@{Y4VZl&d&fJOKf{(6S(WqGodziny Zmiany Brak lekcji w tym dniu + Teraz: %s + Za chwilę: %s + Później: %s Lekcje zrealizowane Zobacz lekcje zrealizowane @@ -319,6 +322,7 @@ Język aplikacji Powiadomienia Pokazuj powiadomienia + Pokazuj powiadomienia o następnych lekcjach Pokazuj powiadomienia debugowania Synchronizacja Automatyczna aktualizacja @@ -344,6 +348,7 @@ Nowe wiadomości Nowe uwagi Powiadomienia push + Nadchodzące lekcje Debugowanie Czarny diff --git a/app/src/main/res/values/preferences_defaults.xml b/app/src/main/res/values/preferences_defaults.xml index 29e8751e4..c8704a50b 100644 --- a/app/src/main/res/values/preferences_defaults.xml +++ b/app/src/main/res/values/preferences_defaults.xml @@ -13,6 +13,7 @@ 60 false true + false false 0.33 0.33 diff --git a/app/src/main/res/values/preferences_keys.xml b/app/src/main/res/values/preferences_keys.xml index 6d683f3f8..1d43f79fd 100644 --- a/app/src/main/res/values/preferences_keys.xml +++ b/app/src/main/res/values/preferences_keys.xml @@ -14,6 +14,7 @@ services_disable_wifi_only services_force_sync notifications_enable + notifications_upcoming_lessons_enable notification_debug grade_modifier_plus grade_modifier_minus diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0a837c46b..bfaece677 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -130,6 +130,9 @@ Hours Changes No lessons this day + Now: %s + Next: %s + Later: %s @@ -356,6 +359,7 @@ Notifications Show notifications + Show upcoming lesson notifications Show debug notifications Synchronization @@ -386,6 +390,7 @@ New messages New notes Push notifications + Upcoming lessons Debug diff --git a/app/src/main/res/xml/scheme_preferences.xml b/app/src/main/res/xml/scheme_preferences.xml index b4adabb98..d890fdb24 100644 --- a/app/src/main/res/xml/scheme_preferences.xml +++ b/app/src/main/res/xml/scheme_preferences.xml @@ -96,6 +96,11 @@ app:iconSpaceReserved="false" app:key="@string/pref_key_notifications_enable" app:title="@string/pref_notify_switch" /> + Date: Wed, 20 May 2020 16:06:24 +0200 Subject: [PATCH 27/85] Add lesson time left display (#550) --- .../preferences/PreferencesRepository.kt | 3 + .../login/recover/LoginRecoverFragment.kt | 2 + .../ui/modules/timetable/TimetableAdapter.kt | 125 +++++++++++++++--- .../ui/modules/timetable/TimetableFragment.kt | 7 +- .../modules/timetable/TimetablePresenter.kt | 2 +- .../ui/modules/timetable/TimetableView.kt | 2 +- .../wulkanowy/utils/TimetableExtension.kt | 29 ++++ .../background_timetable_time_left.xml | 6 + app/src/main/res/layout/item_timetable.xml | 56 +++++++- app/src/main/res/values-pl/strings.xml | 6 + .../main/res/values/preferences_defaults.xml | 1 + app/src/main/res/values/preferences_keys.xml | 1 + app/src/main/res/values/strings.xml | 6 + app/src/main/res/xml/scheme_preferences.xml | 5 + .../repositories => }/TestEnityCreator.kt | 29 +++- .../attendance/AttendanceRemoteTest.kt | 3 +- .../CompletedLessonsRemoteTest.kt | 2 +- .../data/repositories/exam/ExamRemoteTest.kt | 2 +- .../GradeStatisticsRemoteTest.kt | 2 +- .../luckynumber/LuckyNumberRemoteTest.kt | 2 +- .../MobileDeviceRepositoryTest.kt | 2 +- .../semester/SemesterRepositoryTest.kt | 2 +- .../timetable/TimetableRemoteTest.kt | 2 +- .../modules/grade/GradeAverageProviderTest.kt | 2 +- .../wulkanowy/utils/TimetableExtensionTest.kt | 43 ++++++ 25 files changed, 309 insertions(+), 33 deletions(-) create mode 100644 app/src/main/java/io/github/wulkanowy/utils/TimetableExtension.kt create mode 100644 app/src/main/res/drawable/background_timetable_time_left.xml rename app/src/test/java/io/github/wulkanowy/{data/repositories => }/TestEnityCreator.kt (67%) create mode 100644 app/src/test/java/io/github/wulkanowy/utils/TimetableExtensionTest.kt diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/preferences/PreferencesRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/preferences/PreferencesRepository.kt index b916bf96f..6b13563a4 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/preferences/PreferencesRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/preferences/PreferencesRepository.kt @@ -75,6 +75,9 @@ class PreferencesRepository @Inject constructor( val showWholeClassPlan: String get() = getString(R.string.pref_key_timetable_show_whole_class, R.string.pref_default_timetable_show_whole_class) + val showTimetableTimers: Boolean + get() = getBoolean(R.string.pref_key_timetable_show_timers, R.bool.pref_default_timetable_show_timers) + private fun getString(id: Int, default: Int) = getString(context.getString(id), default) private fun getString(id: String, default: Int) = sharedPref.getString(id, context.getString(default)) ?: context.getString(default) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverFragment.kt index 06c91cbea..97e45be9b 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverFragment.kt @@ -179,6 +179,8 @@ class LoginRecoverFragment : loadDataWithBaseURL(url, html, "text/html", "UTF-8", null) addJavascriptInterface(object { + + @Suppress("UNUSED") @JavascriptInterface fun captchaCallback(reCaptchaResponse: String) { activity?.runOnUiThread { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableAdapter.kt index b4b2671e0..5354442aa 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableAdapter.kt @@ -2,6 +2,8 @@ package io.github.wulkanowy.ui.modules.timetable import android.graphics.Paint import android.view.LayoutInflater +import android.view.View.GONE +import android.view.View.VISIBLE import android.view.ViewGroup import android.widget.TextView import androidx.recyclerview.widget.RecyclerView @@ -10,8 +12,16 @@ import io.github.wulkanowy.data.db.entities.Timetable import io.github.wulkanowy.databinding.ItemTimetableBinding import io.github.wulkanowy.databinding.ItemTimetableSmallBinding import io.github.wulkanowy.utils.getThemeAttrColor +import io.github.wulkanowy.utils.isJustFinished +import io.github.wulkanowy.utils.isShowTimeUntil +import io.github.wulkanowy.utils.left import io.github.wulkanowy.utils.toFormattedString +import io.github.wulkanowy.utils.until +import org.threeten.bp.LocalDateTime +import timber.log.Timber +import java.util.Timer import javax.inject.Inject +import kotlin.concurrent.timer class TimetableAdapter @Inject constructor() : RecyclerView.Adapter() { @@ -20,12 +30,28 @@ class TimetableAdapter @Inject constructor() : RecyclerView.Adapter() + var items = mutableListOf() + set(value) { + field = value + resetTimers() + } var onClickListener: (Timetable) -> Unit = {} var showWholeClassPlan: String = "no" + var showTimers: Boolean = false + + private val timers = mutableMapOf() + + private fun resetTimers() { + Timber.d("Timetable timers reset") + with(timers) { + forEach { (_, timer) -> timer.cancel() } + clear() + } + } + override fun getItemCount() = items.size override fun getItemViewType(position: Int) = when { @@ -43,11 +69,16 @@ class TimetableAdapter @Inject constructor() : RecyclerView.Adapter bindNormalView(holder.binding, lesson) + is ItemViewHolder -> bindNormalView(holder.binding, lesson, position) is SmallItemViewHolder -> bindSmallView(holder.binding, lesson) } } @@ -68,7 +99,7 @@ class TimetableAdapter @Inject constructor() : RecyclerView.Adapter i < position && !item.isStudentPlan }.size)?.let { + if (!it.canceled && it.isStudentPlan) it.end + else null + } + } + + private fun updateTimeLeft(binding: ItemTimetableBinding, lesson: Timetable, position: Int) { + with(binding) { + when { + // before lesson + lesson.isShowTimeUntil(getPreviousLesson(position)) -> { + Timber.d("Show time until lesson: $position") + timetableItemTimeLeft.visibility = GONE + with(timetableItemTimeUntil) { + visibility = VISIBLE + text = context.getString(R.string.timetable_time_until, + if (lesson.until.seconds <= 60) { + context.getString(R.string.timetable_seconds, lesson.until.seconds.toString(10)) + } else { + context.getString(R.string.timetable_minutes, lesson.until.toMinutes().toString(10)) + } + ) + } + } + // after lesson start + lesson.left != null -> { + Timber.d("Show time left lesson: $position") + timetableItemTimeUntil.visibility = GONE + with(timetableItemTimeLeft) { + visibility = VISIBLE + text = context.getString( + R.string.timetable_time_left, + if (lesson.left!!.seconds < 60) { + context.getString(R.string.timetable_seconds, lesson.left?.seconds?.toString(10)) + } else { + context.getString(R.string.timetable_minutes, lesson.left?.toMinutes()?.toString(10)) + } + ) + } + } + // right after lesson finish + lesson.isJustFinished -> { + Timber.d("Show just finished lesson: $position") + timetableItemTimeUntil.visibility = GONE + timetableItemTimeLeft.visibility = VISIBLE + timetableItemTimeLeft.text = root.context.getString(R.string.timetable_finished) + } + else -> { + timetableItemTimeUntil.visibility = GONE + timetableItemTimeLeft.visibility = GONE + } + } + } + } + private fun bindSubjectStyle(subjectView: TextView, lesson: Timetable) { subjectView.paintFlags = if (lesson.canceled) subjectView.paintFlags or Paint.STRIKE_THRU_TEXT_FLAG else subjectView.paintFlags and Paint.STRIKE_THRU_TEXT_FLAG.inv() @@ -93,20 +188,20 @@ class TimetableAdapter @Inject constructor() : RecyclerView.Adapter(R.layout.fragme else false } - override fun updateData(data: List, showWholeClassPlanType: String) { + override fun updateData(data: List, showWholeClassPlanType: String, showTimetableTimers: Boolean) { with(timetableAdapter) { - items = data + items = data.toMutableList() + showTimers = showTimetableTimers showWholeClassPlan = showWholeClassPlanType notifyDataSetChanged() } @@ -96,7 +97,7 @@ class TimetableFragment : BaseFragment(R.layout.fragme override fun clearData() { with(timetableAdapter) { - items = emptyList() + items = mutableListOf() notifyDataSetChanged() } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt index 03d79081d..50c123646 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt @@ -149,7 +149,7 @@ class TimetablePresenter @Inject constructor( .subscribe({ Timber.i("Loading timetable result: Success") view?.apply { - updateData(it, prefRepository.showWholeClassPlan) + updateData(it, prefRepository.showWholeClassPlan, prefRepository.showTimetableTimers) showEmpty(it.isEmpty()) showErrorView(false) showContent(it.isNotEmpty()) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableView.kt index 8399498c6..1efa320fc 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableView.kt @@ -12,7 +12,7 @@ interface TimetableView : BaseView { fun initView() - fun updateData(data: List, showWholeClassPlanType: String) + fun updateData(data: List, showWholeClassPlanType: String, showTimetableTimers: Boolean) fun updateNavigationDay(date: String) diff --git a/app/src/main/java/io/github/wulkanowy/utils/TimetableExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/TimetableExtension.kt new file mode 100644 index 000000000..ccb2afeb0 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/utils/TimetableExtension.kt @@ -0,0 +1,29 @@ +package io.github.wulkanowy.utils + +import io.github.wulkanowy.data.db.entities.Timetable +import org.threeten.bp.Duration +import org.threeten.bp.Duration.between +import org.threeten.bp.LocalDateTime +import org.threeten.bp.LocalDateTime.now + +fun Timetable.isShowTimeUntil(previousLessonEnd: LocalDateTime?) = when { + !isStudentPlan -> false + canceled -> false + now().isAfter(start) -> false + previousLessonEnd != null && now().isBefore(previousLessonEnd) -> false + else -> between(now(), start) <= Duration.ofMinutes(60) +} + +inline val Timetable.left: Duration? + get() = when { + canceled -> null + !isStudentPlan -> null + end.isAfter(now()) && start.isBefore(now()) -> between(now(), end) + else -> null + } + +inline val Timetable.until: Duration + get() = between(now(), start) + +inline val Timetable.isJustFinished: Boolean + get() = end.isBefore(now()) && end.plusSeconds(15).isAfter(now()) && !canceled diff --git a/app/src/main/res/drawable/background_timetable_time_left.xml b/app/src/main/res/drawable/background_timetable_time_left.xml new file mode 100644 index 000000000..dd974a4c1 --- /dev/null +++ b/app/src/main/res/drawable/background_timetable_time_left.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_timetable.xml b/app/src/main/res/layout/item_timetable.xml index b15e46754..f2218105e 100644 --- a/app/src/main/res/layout/item_timetable.xml +++ b/app/src/main/res/layout/item_timetable.xml @@ -29,12 +29,12 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="10dp" - android:layout_marginEnd="40dp" + android:layout_marginEnd="16dp" android:ellipsize="end" android:maxLines="1" android:textColor="?android:textColorPrimary" android:textSize="15sp" - app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintEnd_toStartOf="@id/timetableItemTimeBarrier" app:layout_constraintStart_toEndOf="@+id/timetableItemTimeStart" app:layout_constraintTop_toTopOf="parent" tools:text="@tools:sample/lorem" /> @@ -106,4 +106,56 @@ tools:text="Lekcja odwołana: uczniowie zwolnieni do domu" tools:visibility="visible" /> + + + + + diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index e6fe6da64..a4af0dec4 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -121,6 +121,11 @@ Godziny Zmiany Brak lekcji w tym dniu + %s min + %s sek + jeszcze %1$s + za %1$s + Zakończona Teraz: %s Za chwilę: %s Później: %s @@ -317,6 +322,7 @@ Motyw aplikacji Rozwiń oceny Pokazuj listę wykresów w ocenach klasy + Oznaczaj bieżącą lekcję na planie Pokazuj lekcje całej klasy Schemat kolorów ocen Język aplikacji diff --git a/app/src/main/res/values/preferences_defaults.xml b/app/src/main/res/values/preferences_defaults.xml index c8704a50b..a82b14eb7 100644 --- a/app/src/main/res/values/preferences_defaults.xml +++ b/app/src/main/res/values/preferences_defaults.xml @@ -19,4 +19,5 @@ 0.33 true no + false diff --git a/app/src/main/res/values/preferences_keys.xml b/app/src/main/res/values/preferences_keys.xml index 1d43f79fd..868a3c898 100644 --- a/app/src/main/res/values/preferences_keys.xml +++ b/app/src/main/res/values/preferences_keys.xml @@ -20,4 +20,5 @@ grade_modifier_minus fill_message_content show_whole_class_plan + timetable_show_timers diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bfaece677..06f136c7c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -130,6 +130,11 @@ Hours Changes No lessons this day + %s min + %s sec + %1$s left + in %1$s + Finished Now: %s Next: %s Later: %s @@ -352,6 +357,7 @@ Show presence in attendance Application theme Expand grades + Mark current lesson in timetable Show chart list in class grades Show whole class lessons Grades color scheme diff --git a/app/src/main/res/xml/scheme_preferences.xml b/app/src/main/res/xml/scheme_preferences.xml index d890fdb24..a138177f4 100644 --- a/app/src/main/res/xml/scheme_preferences.xml +++ b/app/src/main/res/xml/scheme_preferences.xml @@ -29,6 +29,11 @@ app:iconSpaceReserved="false" app:key="@string/pref_key_expand_grade" app:title="@string/pref_view_expand_grade" /> + Date: Wed, 20 May 2020 16:59:26 +0200 Subject: [PATCH 28/85] Add app killer manager to settings (#808) --- app/build.gradle | 3 ++- app/src/main/AndroidManifest.xml | 3 ++- .../ui/modules/settings/SettingsFragment.kt | 21 +++++++++++++++++++ .../ui/modules/settings/SettingsPresenter.kt | 4 ++++ .../ui/modules/settings/SettingsView.kt | 1 + app/src/main/res/values-pl/strings.xml | 3 +++ app/src/main/res/values/preferences_keys.xml | 1 + app/src/main/res/values/strings.xml | 3 +++ app/src/main/res/xml/scheme_preferences.xml | 12 +++++++---- 9 files changed, 45 insertions(+), 6 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index acf4ddd02..c20c80c59 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -177,11 +177,12 @@ dependencies { implementation "com.mikepenz:aboutlibraries-core:$about_libraries" implementation 'com.wdullaer:materialdatetimepicker:4.2.3' implementation "io.coil-kt:coil:0.11.0" + implementation "io.github.wulkanowy:AppKillerManager:c33b658" playImplementation 'com.google.firebase:firebase-analytics:17.4.1' playImplementation 'com.google.firebase:firebase-inappmessaging-display-ktx:19.0.6' playImplementation "com.google.firebase:firebase-inappmessaging-ktx:19.0.6" - playImplementation 'com.google.firebase:firebase-messaging:20.1.6' + playImplementation 'com.google.firebase:firebase-messaging:20.1.7' playImplementation 'com.google.firebase:firebase-crashlytics:17.0.0' playImplementation 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4ec2f7816..802cf1ad2 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -18,7 +18,8 @@ android:supportsRtl="false" android:theme="@style/WulkanowyTheme" android:usesCleartextTraffic="true" - tools:ignore="GoogleAppIndexingWarning,UnusedAttribute"> + tools:ignore="GoogleAppIndexingWarning,UnusedAttribute" + tools:replace="android:supportsRtl,android:allowBackup"> (getString(R.string.pref_key_notifications_fix_issues))?.run { + isVisible = AppKillerManager.isDeviceSupported() && AppKillerManager.isAnyActionAvailable(requireContext()) + setOnPreferenceClickListener { + presenter.onFixSyncIssuesClicked() + true + } + } } override fun onActivityCreated(savedInstanceState: Bundle?) { @@ -119,6 +127,19 @@ class SettingsFragment : PreferenceFragmentCompat(), .show() } + override fun showFixSyncDialog() { + AlertDialog.Builder(requireContext()) + .setTitle(R.string.pref_notify_fix_sync_issues) + .setMessage(R.string.pref_notify_fix_sync_issues_message) + .setNegativeButton(android.R.string.cancel) { _, _ -> } + .setPositiveButton(R.string.pref_notify_fix_sync_issues_settings_button) { _, _ -> + AppKillerManager.doActionPowerSaving(requireContext()) + AppKillerManager.doActionAutoStart(requireContext()) + AppKillerManager.doActionNotification(requireContext()) + } + .show() + } + override fun onResume() { super.onResume() preferenceScreen.sharedPreferences.registerOnSharedPreferenceChangeListener(this) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsPresenter.kt index 09fc2d707..bccb6f0bb 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsPresenter.kt @@ -58,6 +58,10 @@ class SettingsPresenter @Inject constructor( view?.showForceSyncDialog() } + fun onFixSyncIssuesClicked() { + view?.showFixSyncDialog() + } + fun onForceSyncDialogSubmit() { view?.run { val successString = syncSuccessString diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsView.kt index 4a1b0c766..3786ba4b2 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsView.kt @@ -19,4 +19,5 @@ interface SettingsView : BaseView { fun setSyncInProgress(inProgress: Boolean) fun showForceSyncDialog() + fun showFixSyncDialog() } diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index a4af0dec4..03ccaa2d9 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -329,6 +329,9 @@ Powiadomienia Pokazuj powiadomienia Pokazuj powiadomienia o następnych lekcjach + Napraw problemy z synchronizacją i powiadomieniami + Na twoim urządzeniu mogą występować problemy z synchronizacją danych i powiadomieniami.\n\nBy je naprawić, dodaj Wulkanowego do autostartu i wyłącz optymalizację/oszczędzanie baterii w ustawieniach systemowych telefonu. + Przejdź do ustawień Pokazuj powiadomienia debugowania Synchronizacja Automatyczna aktualizacja diff --git a/app/src/main/res/values/preferences_keys.xml b/app/src/main/res/values/preferences_keys.xml index 868a3c898..6cb877ec2 100644 --- a/app/src/main/res/values/preferences_keys.xml +++ b/app/src/main/res/values/preferences_keys.xml @@ -13,6 +13,7 @@ services_interval services_disable_wifi_only services_force_sync + notifications_fix_issues notifications_enable notifications_upcoming_lessons_enable notification_debug diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 06f136c7c..6e08fded6 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -366,6 +366,9 @@ Notifications Show notifications Show upcoming lesson notifications + Fix synchronization & notifications issues + Your device may have data synchronization issues and with notifications.\n\nTo fix them, you need to add Wulkanowy to the autostart and turn off battery optimization/saving in the phone settings. + Go to settings Show debug notifications Synchronization diff --git a/app/src/main/res/xml/scheme_preferences.xml b/app/src/main/res/xml/scheme_preferences.xml index a138177f4..c05910f99 100644 --- a/app/src/main/res/xml/scheme_preferences.xml +++ b/app/src/main/res/xml/scheme_preferences.xml @@ -1,4 +1,4 @@ - + + app:key="@string/pref_key_timetable_show_timers" + app:title="@string/pref_view_timetable_show_timers" /> + app:title="@string/pref_view_grade_statistics_list" /> + Date: Wed, 20 May 2020 22:48:09 +0200 Subject: [PATCH 29/85] New Crowdin translations (#813) --- app/src/main/res/values-de/strings.xml | 17 +++++++++++++++++ app/src/main/res/values-pl/strings.xml | 5 +++-- app/src/main/res/values-ru/strings.xml | 17 +++++++++++++++++ app/src/main/res/values-uk/strings.xml | 17 +++++++++++++++++ 4 files changed, 54 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index f57805469..f92186c4e 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -115,6 +115,14 @@ Stunden Änderungen Kein Unterricht an diesem Tag + %s min + %s sek + noch %1$s + in %1$s + Fertig + Jetzt: %s + In einem Moment: %s + Später: %s Beendete Lektionen Beendete Lektionen anzeigen @@ -278,6 +286,8 @@ Thema Zurück Nächste + Suchen + Suchen... Keine Lektionen Thema wählen @@ -292,11 +302,17 @@ Anwesenheit in Schulbesuch zeigen Thema der Anwendung Noten erweitern + Aktuelle Lektion im Stundenplan markieren + Liste der Diagramme in Klassenbewertungen anzeigen Unterricht der ganzen Klasse anzeigen Farbschema der Noten App Sprache Benachrichtigungen Benachrichtigungen anzeigen + Benachrichtigungen über bevorstehende Lektionen anzeigen + Synchronisierungs- und Benachrichtigungsprobleme reparieren + Ihr Gerät hat möglicherweise Probleme mit der Datensynchronisierung und Benachrichtigungen.\n\nUm diese zu reparieren, fügen Sie Wulkanowy zum Autostart hinzu und deaktivieren Sie die Batterieoptimierung in den Systemeinstellungen des Geräts. + Gehe zu den Einstellungen Debug-Benachrichtigungen anzeigen Synchronisierung Automatische Aktualisierung @@ -322,6 +338,7 @@ Neue Nachrichten Neue Eintragen Push-Benachrichtigungen + Bevorstehende Lektionen Debuggen Schwarz diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 03ccaa2d9..6bf0c324e 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -307,6 +307,7 @@ Poprzedni Następny Szukaj + Szukaj... Brak lekcji Wybierz motyw @@ -321,14 +322,14 @@ Pokazuj obecność we frekwencji Motyw aplikacji Rozwiń oceny - Pokazuj listę wykresów w ocenach klasy Oznaczaj bieżącą lekcję na planie + Pokazuj listę wykresów w ocenach klasy Pokazuj lekcje całej klasy Schemat kolorów ocen Język aplikacji Powiadomienia Pokazuj powiadomienia - Pokazuj powiadomienia o następnych lekcjach + Pokazuj powiadomienia o nadchodzących lekcjach Napraw problemy z synchronizacją i powiadomieniami Na twoim urządzeniu mogą występować problemy z synchronizacją danych i powiadomieniami.\n\nBy je naprawić, dodaj Wulkanowego do autostartu i wyłącz optymalizację/oszczędzanie baterii w ustawieniach systemowych telefonu. Przejdź do ustawień diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index e68454550..c0666e2c1 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -121,6 +121,14 @@ Часы Изменения Нет уроков в данный день + %s min + %s sec + %1$s left + in %1$s + Finished + Now: %s + Next: %s + Later: %s Проведённые уроки Просмотреть проведённые уроки @@ -298,6 +306,8 @@ Предмет Предыдущий Следующий + Search + Search... Нет уроков Выбрать тему @@ -312,11 +322,17 @@ Показывать присутствия в посещаемости Тема приложения Больше оценок + Mark current lesson in timetable + Show chart list in class grades Показать уроки всего класса Схема цветов оценок Язык приложения Уведомления Показывать уведомления + Show upcoming lesson notifications + Fix synchronization & notifications issues + Your device may have data synchronization issues and with notifications.\n\nTo fix them, you need to add Wulkanowy to the autostart and turn off battery optimization/saving in the phone settings. + Go to settings Показывать дебаг-уведомления Синхронизация Автоматическая синхронизация @@ -342,6 +358,7 @@ Новые сообщения Новые заметки Показывать push-уведомления + Upcoming lessons Дебаг Чёрный diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 303eb8b9a..8a9918124 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -121,6 +121,14 @@ Години Зміни Брак уроків у цей день + %s min + %s sec + %1$s left + in %1$s + Finished + Now: %s + Next: %s + Later: %s Уроки, що відбулися Показати уроки, що відбулися @@ -298,6 +306,8 @@ Предмет Попередній Наступний + Search + Search... Брак уроків Увібрати тему @@ -312,11 +322,17 @@ Показувати присутність у відвідуваності Тема додатку Більше оцінок + Mark current lesson in timetable + Show chart list in class grades Показати уроки всього класу Схема кольорів оцінок Мова додатку Повідомлення Показувати повідомлення + Show upcoming lesson notifications + Fix synchronization & notifications issues + Your device may have data synchronization issues and with notifications.\n\nTo fix them, you need to add Wulkanowy to the autostart and turn off battery optimization/saving in the phone settings. + Go to settings Показувати дебаг-повідомлення Синхронізація Автоматична синхронізація @@ -342,6 +358,7 @@ Нові повідомлення Нові нотатки Показувати push-повідомлення + Upcoming lessons Дебаг Чорний From 7850412ba9853627cc4ea625f467238c9faa6d4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Wed, 20 May 2020 23:08:32 +0200 Subject: [PATCH 30/85] Fix crash in timetable on api < 21 (#816) --- .../res/drawable/background_timetable_time_left.xml | 3 +-- app/src/main/res/layout/item_timetable.xml | 10 +++------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/app/src/main/res/drawable/background_timetable_time_left.xml b/app/src/main/res/drawable/background_timetable_time_left.xml index dd974a4c1..0f3326112 100644 --- a/app/src/main/res/drawable/background_timetable_time_left.xml +++ b/app/src/main/res/drawable/background_timetable_time_left.xml @@ -1,6 +1,5 @@ - - \ No newline at end of file + diff --git a/app/src/main/res/layout/item_timetable.xml b/app/src/main/res/layout/item_timetable.xml index f2218105e..4e278261a 100644 --- a/app/src/main/res/layout/item_timetable.xml +++ b/app/src/main/res/layout/item_timetable.xml @@ -117,9 +117,6 @@ android:id="@+id/timetableItemTimeUntil" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_alignTop="@id/timetableItemSubject" - android:layout_alignBottom="@id/timetableItemSubject" - android:layout_alignParentEnd="true" android:layout_marginStart="4dp" android:ellipsize="end" android:gravity="center" @@ -138,22 +135,21 @@ android:id="@+id/timetableItemTimeLeft" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_alignTop="@id/timetableItemSubject" - android:layout_alignBottom="@id/timetableItemSubject" android:layout_alignParentEnd="true" android:layout_marginStart="4dp" android:background="@drawable/background_timetable_time_left" android:ellipsize="end" android:gravity="center" - android:maxLines="1" android:includeFontPadding="false" + android:maxLines="1" android:paddingLeft="7dp" - android:paddingRight="7dp" android:paddingTop="2dp" + android:paddingRight="7dp" android:paddingBottom="2dp" android:textColor="?colorOnPrimary" android:textSize="13sp" android:visibility="gone" + app:backgroundTint="?colorPrimary" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" tools:text="jeszcze 15 min" From 4c1c4f8a435400ac8169c8076d15db2127c9298f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Thu, 21 May 2020 00:59:05 +0200 Subject: [PATCH 31/85] Version 0.18.0 --- .travis.yml | 2 +- app/build.gradle | 8 ++++---- app/src/main/play/release-notes/pl-PL/default.txt | 12 ++++++++---- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index 88b711a6c..ee3e6f3af 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,7 +14,7 @@ cache: branches: only: - develop - - 0.17.4 + - 0.18.0 android: licenses: diff --git a/app/build.gradle b/app/build.gradle index c20c80c59..c1e8b581f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -17,8 +17,8 @@ android { testApplicationId "io.github.tests.wulkanowy" minSdkVersion 17 targetSdkVersion 29 - versionCode 57 - versionName "0.17.4" + versionCode 59 + versionName "0.18.0" multiDexEnabled true testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary = true @@ -122,7 +122,7 @@ configurations.all { } dependencies { - implementation "io.github.wulkanowy:sdk:46619e3" + implementation "io.github.wulkanowy:sdk:0.18.0" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation "androidx.core:core-ktx:1.2.0" @@ -177,7 +177,7 @@ dependencies { implementation "com.mikepenz:aboutlibraries-core:$about_libraries" implementation 'com.wdullaer:materialdatetimepicker:4.2.3' implementation "io.coil-kt:coil:0.11.0" - implementation "io.github.wulkanowy:AppKillerManager:c33b658" + implementation "io.github.wulkanowy:AppKillerManager:3.0.0" playImplementation 'com.google.firebase:firebase-analytics:17.4.1' playImplementation 'com.google.firebase:firebase-inappmessaging-display-ktx:19.0.6' diff --git a/app/src/main/play/release-notes/pl-PL/default.txt b/app/src/main/play/release-notes/pl-PL/default.txt index 6e72dd828..427ad4dc0 100644 --- a/app/src/main/play/release-notes/pl-PL/default.txt +++ b/app/src/main/play/release-notes/pl-PL/default.txt @@ -1,6 +1,10 @@ -- naprawiliśmy wyświetlanie przycisku oznaczania zadania domowego jako wykonanego -- naprawiliśmy rzadki błąd ze stabilnością przy wysyłaniu wiadomości -- naprawiliśmy błąd po logowaniu w domyślnym trybie, jeśli wcześniej użytkownik zalogowany był w trybie Mobilnego API -- ulepszyliśmy wygląd okienka ze szczegółami błędu +Wersja 0.18.0 +- naprawiliśmy odświeżanie zadań domowych +- naprawiliśmy powiadomienia na androidzie 8.0 +- oceny powinny się teraz odświeżać trochę szybciej +- dodaliśmy tryb pełnoekranowy w zadaniach +- dodaliśmy wyszukiwanie w wiadomościach +- dodaliśmy opcje oznaczania bieżącej lekcji na planie/w powiadomieniu (domyślnie wyłączone) +- dodaliśmy testową opcję naprawy powiadomień na np. Huawei, Xiaomi (znajdziesz ją w ustawieniach) Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases From d558c4db6694b9d292356498ca8c21df831be465 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Sat, 23 May 2020 16:34:26 +0200 Subject: [PATCH 32/85] New Crowdin translations (#817) --- app/src/main/res/values-ru/strings.xml | 34 +++++++++++++------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index c0666e2c1..965b75ab7 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -121,14 +121,14 @@ Часы Изменения Нет уроков в данный день - %s min - %s sec - %1$s left - in %1$s - Finished - Now: %s - Next: %s - Later: %s + %s мин + %s сек + %1$s осталось + через %1$s + Окончен + Сейчас: %s + Следующий: %s + Позже: %s Проведённые уроки Просмотреть проведённые уроки @@ -306,8 +306,8 @@ Предмет Предыдущий Следующий - Search - Search... + Поиск + Поиск... Нет уроков Выбрать тему @@ -322,17 +322,17 @@ Показывать присутствия в посещаемости Тема приложения Больше оценок - Mark current lesson in timetable - Show chart list in class grades + Отмечать текущий урок в расписании + Показывать диаграммы в оценках класса Показать уроки всего класса Схема цветов оценок Язык приложения Уведомления Показывать уведомления - Show upcoming lesson notifications - Fix synchronization & notifications issues - Your device may have data synchronization issues and with notifications.\n\nTo fix them, you need to add Wulkanowy to the autostart and turn off battery optimization/saving in the phone settings. - Go to settings + Показывать уведомления о будущих уроках + Исправить проблемы с синхронизацией и уведомлениями + На вашем устройстве могут быть проблемы с синхронизацией данных и уведомлениями.\n\nЧтобы их исправить, вам необходимо добавить Wulkanowy в авто-старт и выключить оптимизацию/экономию батареи в настройках устройства. + Перейти в настройски Показывать дебаг-уведомления Синхронизация Автоматическая синхронизация @@ -358,7 +358,7 @@ Новые сообщения Новые заметки Показывать push-уведомления - Upcoming lessons + Будущие уроки Дебаг Чёрный From c3a6f8253a7a7ce8a05828b46b04808c164ec6d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sun, 24 May 2020 19:23:35 +0200 Subject: [PATCH 33/85] Fix grade sorting (#825) --- .../grade/details/GradeDetailsPresenter.kt | 27 ++++++++++--------- .../grade/summary/GradeSummaryPresenter.kt | 12 +++------ 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt index 26c222643..2212bfd61 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt @@ -171,19 +171,22 @@ class GradeDetailsPresenter @Inject constructor( } private fun createGradeItems(items: List): List { - return items.filter { it.grades.isNotEmpty() }.map { (subject, average, points, _, grades) -> - val subItems = grades.map { - GradeDetailsItem(it, ViewType.ITEM) - } + return items + .filter { it.grades.isNotEmpty() } + .sortedBy { it.subject } + .map { (subject, average, points, _, grades) -> + val subItems = grades + .sortedByDescending { it.date } + .map { GradeDetailsItem(it, ViewType.ITEM) } - listOf(GradeDetailsItem(GradeDetailsHeader( - subject = subject, - average = average, - pointsSum = points, - newGrades = grades.filter { grade -> !grade.isRead }.size, - grades = subItems - ), ViewType.HEADER)) + if (preferencesRepository.isGradeExpandable) emptyList() else subItems - }.flatten() + listOf(GradeDetailsItem(GradeDetailsHeader( + subject = subject, + average = average, + pointsSum = points, + newGrades = grades.filter { grade -> !grade.isRead }.size, + grades = subItems + ), ViewType.HEADER)) + if (preferencesRepository.isGradeExpandable) emptyList() else subItems + }.flatten() } private fun updateGrade(grade: Grade) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt index 9b8372136..26f922dee 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt @@ -103,14 +103,8 @@ class GradeSummaryPresenter @Inject constructor( } private fun createGradeSummaryItems(items: List): List { - return items.map { - it.summary.copy(average = it.average) - } - } - - private fun checkEmpty(gradeSummary: GradeSummary, averages: List>): Boolean { - return gradeSummary.run { - finalGrade.isBlank() && predictedGrade.isBlank() && averages.singleOrNull { it.first == subject } == null - } + return items + .sortedBy { it.subject } + .map { it.summary.copy(average = it.average) } } } From 9c013161789dedf410e646af59d8c9dd4d4cdb1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sun, 24 May 2020 19:24:01 +0200 Subject: [PATCH 34/85] Fix mark message as read in search mode (#828) --- .../wulkanowy/ui/modules/message/tab/MessageTabAdapter.kt | 2 +- .../wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabAdapter.kt index a889dfef6..6064f10ae 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabAdapter.kt @@ -77,7 +77,7 @@ class MessageTabAdapter @Inject constructor() : } messageItemAttachmentIcon.visibility = if (item.hasAttachments) View.VISIBLE else View.GONE - root.setOnClickListener { onClickListener(item, position) } + root.setOnClickListener { onClickListener(item, holder.adapterPosition) } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt index f96fb6c2a..6513adcde 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt @@ -64,7 +64,7 @@ class MessageTabPresenter @Inject constructor( } fun onMessageItemSelected(message: Message, position: Int) { - Timber.i("Select message ${message.id} item") + Timber.i("Select message ${message.id} item (position: $position)") view?.run { openMessage(message) if (message.unread) { @@ -132,6 +132,8 @@ class MessageTabPresenter @Inject constructor( } } + Timber.d("Applying filter. Full list: ${messages.size}, filtered: ${filteredList.size}") + updateData(filteredList) } From f737018548fad22c8d25aaeb887103cbc6f92fca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sun, 24 May 2020 19:33:04 +0200 Subject: [PATCH 35/85] Add debug statements to get/update methods in grade details adapter (#827) --- .../ui/modules/grade/GradePresenter.kt | 1 + .../grade/details/GradeDetailsAdapter.kt | 19 +++++++++++++++++-- .../grade/details/GradeDetailsPresenter.kt | 2 +- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradePresenter.kt index 78885ebd5..ec66e2bd9 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradePresenter.kt @@ -133,6 +133,7 @@ class GradePresenter @Inject constructor( } private fun loadChild(index: Int, forceRefresh: Boolean = false) { + Timber.d("Load grade tab child. Selected semester: $selectedIndex, semesters: ${semesters.joinToString { it.semesterName.toString() }}") semesters.first { it.semesterName == selectedIndex }.semesterId.also { if (forceRefresh || loadedSemesterId[index] != it) { Timber.i("Load grade child view index: $index") diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsAdapter.kt index 7adab547e..d5e05f3b9 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsAdapter.kt @@ -14,6 +14,7 @@ import io.github.wulkanowy.databinding.ItemGradeDetailsBinding import io.github.wulkanowy.ui.base.BaseExpandableAdapter import io.github.wulkanowy.utils.getBackgroundColor import io.github.wulkanowy.utils.toFormattedString +import timber.log.Timber import javax.inject.Inject class GradeDetailsAdapter @Inject constructor() : BaseExpandableAdapter() { @@ -38,12 +39,26 @@ class GradeDetailsAdapter @Inject constructor() : BaseExpandableAdapter 1) { + Timber.e("Header with subject $subject found ${candidates.size} times! Items: $candidates, expanded: $expandedPosition") + } + + return candidates.first() } fun updateHeaderItem(item: GradeDetailsItem) { @@ -92,7 +107,7 @@ class GradeDetailsAdapter @Inject constructor() : BaseExpandableAdapter bindItemViewHolder( binding = holder.binding, grade = items[position].value as Grade, - position = position + position = holder.adapterPosition ) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt index 2212bfd61..b674bf771 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt @@ -43,7 +43,7 @@ class GradeDetailsPresenter @Inject constructor( } fun onGradeItemSelected(grade: Grade, position: Int) { - Timber.i("Select grade item ${grade.id}") + Timber.i("Select grade item ${grade.id}, position: $position") view?.apply { showGradeDialog(grade, preferencesRepository.gradeColorTheme) if (!grade.isRead) { From cec1068f2e3cc0279537edbcad5f957cc35f4538 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sun, 24 May 2020 19:34:10 +0200 Subject: [PATCH 36/85] Fix crash in timetable time left (#826) --- .../ui/modules/timetable/TimetableAdapter.kt | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableAdapter.kt index 5354442aa..85ded2025 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableAdapter.kt @@ -132,41 +132,46 @@ class TimetableAdapter @Inject constructor() : RecyclerView.Adapter { + isShowTimeUntil -> { Timber.d("Show time until lesson: $position") timetableItemTimeLeft.visibility = GONE with(timetableItemTimeUntil) { visibility = VISIBLE text = context.getString(R.string.timetable_time_until, - if (lesson.until.seconds <= 60) { - context.getString(R.string.timetable_seconds, lesson.until.seconds.toString(10)) + if (until.seconds <= 60) { + context.getString(R.string.timetable_seconds, until.seconds.toString(10)) } else { - context.getString(R.string.timetable_minutes, lesson.until.toMinutes().toString(10)) + context.getString(R.string.timetable_minutes, until.toMinutes().toString(10)) } ) } } // after lesson start - lesson.left != null -> { + left != null -> { Timber.d("Show time left lesson: $position") timetableItemTimeUntil.visibility = GONE with(timetableItemTimeLeft) { visibility = VISIBLE text = context.getString( R.string.timetable_time_left, - if (lesson.left!!.seconds < 60) { - context.getString(R.string.timetable_seconds, lesson.left?.seconds?.toString(10)) + if (left.seconds < 60) { + context.getString(R.string.timetable_seconds, left.seconds.toString(10)) } else { - context.getString(R.string.timetable_minutes, lesson.left?.toMinutes()?.toString(10)) + context.getString(R.string.timetable_minutes, left.toMinutes().toString(10)) } ) } } // right after lesson finish - lesson.isJustFinished -> { + isJustFinished -> { Timber.d("Show just finished lesson: $position") timetableItemTimeUntil.visibility = GONE timetableItemTimeLeft.visibility = VISIBLE From 7fa14e507739a6a43c78816bf5127ddffccc10f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sun, 24 May 2020 19:36:40 +0200 Subject: [PATCH 37/85] Set app_name in build.gradle (#830) --- app/build.gradle | 2 ++ app/src/debug/res/values/strings.xml | 3 --- app/src/main/res/layout/fragment_attendance.xml | 4 ++-- app/src/main/res/layout/fragment_exam.xml | 4 ++-- app/src/main/res/layout/fragment_homework.xml | 4 ++-- app/src/main/res/layout/fragment_timetable.xml | 4 ++-- .../res/layout/fragment_timetable_completed.xml | 16 ++++++++-------- app/src/main/res/values-de/strings.xml | 1 - app/src/main/res/values-pl/strings.xml | 1 - app/src/main/res/values-ru/strings.xml | 1 - app/src/main/res/values-uk/strings.xml | 1 - app/src/main/res/values/strings.xml | 4 ---- 12 files changed, 18 insertions(+), 27 deletions(-) delete mode 100644 app/src/debug/res/values/strings.xml diff --git a/app/build.gradle b/app/build.gradle index c1e8b581f..ea86127ba 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -20,6 +20,7 @@ android { versionCode 59 versionName "0.18.0" multiDexEnabled true + resValue "string", "app_name", "Wulkanowy" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary = true manifestPlaceholders = [ @@ -56,6 +57,7 @@ android { signingConfig signingConfigs.release } debug { + resValue "string", "app_name", "Wulkanowy DEV " + defaultConfig.versionCode applicationIdSuffix ".dev" versionNameSuffix "-dev" testCoverageEnabled = project.hasProperty('coverage') diff --git a/app/src/debug/res/values/strings.xml b/app/src/debug/res/values/strings.xml deleted file mode 100644 index c90641ddb..000000000 --- a/app/src/debug/res/values/strings.xml +++ /dev/null @@ -1,3 +0,0 @@ - - Wulkanowy DEV - diff --git a/app/src/main/res/layout/fragment_attendance.xml b/app/src/main/res/layout/fragment_attendance.xml index cd8b60364..39b00d306 100644 --- a/app/src/main/res/layout/fragment_attendance.xml +++ b/app/src/main/res/layout/fragment_attendance.xml @@ -153,8 +153,8 @@ android:background="?selectableItemBackgroundBorderless" android:fontFamily="sans-serif" android:gravity="center" - android:text="@string/app_name" - android:textSize="16sp" /> + android:textSize="16sp" + tools:text="@tools:sample/date/ddmmyy" /> + android:textSize="16sp" + tools:text="@tools:sample/date/ddmmyy" /> + android:textSize="16sp" + tools:text="@tools:sample/date/ddmmyy" /> + android:textSize="16sp" + tools:text="@tools:sample/date/ddmmyy" /> + app:srcCompat="@drawable/ic_chevron_left" /> + android:textSize="16sp" + tools:text="@tools:sample/date/ddmmyy" /> + app:srcCompat="@drawable/ic_chevron_right" /> diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index f92186c4e..084e3a86d 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -1,6 +1,5 @@ - Wulkanowy Anmelden Wulkanowy diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 6bf0c324e..4bcfbb774 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -1,6 +1,5 @@ - Wulkanowy Logowanie Wulkanowy diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 965b75ab7..41771a3cf 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -1,6 +1,5 @@ - Wulkanowy Авторизация Wulkanowy diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 8a9918124..652249c68 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -1,6 +1,5 @@ - Wulkanowy Авторизація Wulkanowy diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6e08fded6..2f13e031d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,9 +1,5 @@ - - Wulkanowy - - Login Wulkanowy From 3541ab81b865f906e7eddb95ff1c657448a9b76e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sun, 24 May 2020 19:47:20 +0200 Subject: [PATCH 38/85] Destroy webview in password recover before setting binding to null (#829) --- app/build.gradle | 2 +- .../login/recover/LoginRecoverFragment.kt | 48 +++++++++++-------- 2 files changed, 28 insertions(+), 22 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index ea86127ba..0e95493c9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -124,7 +124,7 @@ configurations.all { } dependencies { - implementation "io.github.wulkanowy:sdk:0.18.0" + implementation "io.github.wulkanowy:sdk:fd51552" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation "androidx.core:core-ktx:1.2.0" diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverFragment.kt index 97e45be9b..e27c845a6 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverFragment.kt @@ -22,6 +22,10 @@ import javax.inject.Inject class LoginRecoverFragment : BaseFragment(R.layout.fragment_login_recover), LoginRecoverView { + private var _binding: FragmentLoginRecoverBinding? = null + + private val bindingLocal: FragmentLoginRecoverBinding get() = _binding!! + @Inject lateinit var presenter: LoginRecoverPresenter @@ -36,13 +40,13 @@ class LoginRecoverFragment : private lateinit var hostSymbols: Array override val recoverHostValue: String - get() = hostValues.getOrNull(hostKeys.indexOf(binding.loginRecoverHost.text.toString())).orEmpty() + get() = hostValues.getOrNull(hostKeys.indexOf(bindingLocal.loginRecoverHost.text.toString())).orEmpty() override val formHostSymbol: String - get() = hostSymbols.getOrNull(hostKeys.indexOf(binding.loginRecoverHost.text.toString())).orEmpty() + get() = hostSymbols.getOrNull(hostKeys.indexOf(bindingLocal.loginRecoverHost.text.toString())).orEmpty() override val recoverNameValue: String - get() = binding.loginRecoverName.text.toString().trim() + get() = bindingLocal.loginRecoverName.text.toString().trim() override val emailHintString: String get() = getString(R.string.login_email_hint) @@ -55,7 +59,7 @@ class LoginRecoverFragment : override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - binding = FragmentLoginRecoverBinding.bind(view) + _binding = FragmentLoginRecoverBinding.bind(view) presenter.onAttachView(this) } @@ -64,7 +68,7 @@ class LoginRecoverFragment : hostValues = resources.getStringArray(R.array.hosts_values) hostSymbols = resources.getStringArray(R.array.hosts_symbols) - with(binding) { + with(bindingLocal) { loginRecoverWebView.setBackgroundColor(Color.TRANSPARENT) loginRecoverName.doOnTextChanged { _, _, _, _ -> presenter.onNameTextChanged() } loginRecoverHost.setOnItemClickListener { _, _, _, _ -> presenter.onHostSelected() } @@ -74,69 +78,69 @@ class LoginRecoverFragment : loginRecoverLogin.setOnClickListener { (activity as LoginActivity).switchView(0) } } - with(binding.loginRecoverHost) { + with(bindingLocal.loginRecoverHost) { setText(hostKeys.getOrNull(0).orEmpty()) setAdapter(LoginSymbolAdapter(context, R.layout.support_simple_spinner_dropdown_item, hostKeys)) - setOnClickListener { if (binding.loginRecoverFormContainer.visibility == GONE) dismissDropDown() } + setOnClickListener { if (bindingLocal.loginRecoverFormContainer.visibility == GONE) dismissDropDown() } } } override fun setDefaultCredentials(username: String) { - binding.loginRecoverName.setText(username) + bindingLocal.loginRecoverName.setText(username) } override fun setErrorNameRequired() { - with(binding.loginRecoverNameLayout) { + with(bindingLocal.loginRecoverNameLayout) { requestFocus() error = getString(R.string.login_field_required) } } override fun setUsernameHint(hint: String) { - binding.loginRecoverNameLayout.hint = hint + bindingLocal.loginRecoverNameLayout.hint = hint } override fun setUsernameError(message: String) { - with(binding.loginRecoverNameLayout) { + with(bindingLocal.loginRecoverNameLayout) { requestFocus() error = message } } override fun clearUsernameError() { - binding.loginRecoverNameLayout.error = null + bindingLocal.loginRecoverNameLayout.error = null } override fun showProgress(show: Boolean) { - binding.loginRecoverProgress.visibility = if (show) VISIBLE else GONE + bindingLocal.loginRecoverProgress.visibility = if (show) VISIBLE else GONE } override fun showRecoverForm(show: Boolean) { - binding.loginRecoverFormContainer.visibility = if (show) VISIBLE else GONE + bindingLocal.loginRecoverFormContainer.visibility = if (show) VISIBLE else GONE } override fun showCaptcha(show: Boolean) { - binding.loginRecoverCaptchaContainer.visibility = if (show) VISIBLE else GONE + bindingLocal.loginRecoverCaptchaContainer.visibility = if (show) VISIBLE else GONE } override fun showErrorView(show: Boolean) { - binding.loginRecoverError.visibility = if (show) VISIBLE else GONE + bindingLocal.loginRecoverError.visibility = if (show) VISIBLE else GONE } override fun setErrorDetails(message: String) { - binding.loginRecoverErrorMessage.text = message + bindingLocal.loginRecoverErrorMessage.text = message } override fun showSuccessView(show: Boolean) { - binding.loginRecoverSuccess.visibility = if (show) VISIBLE else GONE + bindingLocal.loginRecoverSuccess.visibility = if (show) VISIBLE else GONE } override fun setSuccessTitle(title: String) { - binding.loginRecoverSuccessTitle.text = title + bindingLocal.loginRecoverSuccessTitle.text = title } override fun setSuccessMessage(message: String) { - binding.loginRecoverSuccessMessage.text = message + bindingLocal.loginRecoverSuccessMessage.text = message } override fun showSoftKeyboard() { @@ -157,7 +161,7 @@ class LoginRecoverFragment : callback:e =>Android.captchaCallback(e)}) """.trimIndent() - with(binding.loginRecoverWebView) { + with(bindingLocal.loginRecoverWebView) { settings.javaScriptEnabled = true webViewClient = object : WebViewClient() { private var recoverWebViewSuccess: Boolean = true @@ -197,6 +201,8 @@ class LoginRecoverFragment : } override fun onDestroyView() { + bindingLocal.loginRecoverWebView.destroy() + _binding = null presenter.onDetachView() super.onDestroyView() From 428b599be04652cd1aa8948873252ad8cc05deab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sun, 24 May 2020 19:48:14 +0200 Subject: [PATCH 39/85] Improve firebase logging (#832) --- .../github/wulkanowy/utils/CrashlyticsUtils.kt | 2 -- .../wulkanowy/utils/FirebaseAnalyticsHelper.kt | 7 ++++++- .../modules/attendance/AttendancePresenter.kt | 7 ++++++- .../summary/AttendanceSummaryPresenter.kt | 8 +++++++- .../wulkanowy/ui/modules/exam/ExamPresenter.kt | 7 ++++++- .../grade/details/GradeDetailsPresenter.kt | 7 ++++++- .../statistics/GradeStatisticsPresenter.kt | 7 ++++++- .../grade/summary/GradeSummaryPresenter.kt | 7 ++++++- .../ui/modules/homework/HomeworkPresenter.kt | 7 ++++++- .../luckynumber/LuckyNumberPresenter.kt | 7 ++++++- .../wulkanowy/ui/modules/main/MainActivity.kt | 8 ++++++++ .../wulkanowy/ui/modules/main/MainPresenter.kt | 3 ++- .../wulkanowy/ui/modules/main/MainView.kt | 2 ++ .../message/preview/MessagePreviewPresenter.kt | 6 +++++- .../modules/message/tab/MessageTabPresenter.kt | 7 ++++++- .../mobiledevice/MobileDevicePresenter.kt | 7 ++++++- .../wulkanowy/ui/modules/note/NotePresenter.kt | 7 ++++++- .../school/SchoolPresenter.kt | 6 +++++- .../teacher/TeacherPresenter.kt | 7 ++++++- .../ui/modules/timetable/TimetablePresenter.kt | 7 ++++++- .../completed/CompletedLessonsPresenter.kt | 7 ++++++- .../utils/FragNavControlerExtension.kt | 6 +++--- .../github/wulkanowy/utils/CrashlyticsUtils.kt | 18 ++++++++++-------- .../wulkanowy/utils/FirebaseAnalyticsHelper.kt | 5 +++++ 24 files changed, 131 insertions(+), 31 deletions(-) diff --git a/app/src/fdroid/java/io/github/wulkanowy/utils/CrashlyticsUtils.kt b/app/src/fdroid/java/io/github/wulkanowy/utils/CrashlyticsUtils.kt index 60dc6b56a..6351997d0 100644 --- a/app/src/fdroid/java/io/github/wulkanowy/utils/CrashlyticsUtils.kt +++ b/app/src/fdroid/java/io/github/wulkanowy/utils/CrashlyticsUtils.kt @@ -5,8 +5,6 @@ package io.github.wulkanowy.utils import android.content.Context import timber.log.Timber -fun initCrashlytics(context: Context, appInfo: AppInfo) {} - open class TimberTreeNoOp : Timber.Tree() { override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {} } diff --git a/app/src/fdroid/java/io/github/wulkanowy/utils/FirebaseAnalyticsHelper.kt b/app/src/fdroid/java/io/github/wulkanowy/utils/FirebaseAnalyticsHelper.kt index 0b1274f15..f23645bc3 100644 --- a/app/src/fdroid/java/io/github/wulkanowy/utils/FirebaseAnalyticsHelper.kt +++ b/app/src/fdroid/java/io/github/wulkanowy/utils/FirebaseAnalyticsHelper.kt @@ -1,13 +1,18 @@ package io.github.wulkanowy.utils +import android.app.Activity import javax.inject.Inject import javax.inject.Singleton @Singleton +@Suppress("UNUSED_PARAMETER") class FirebaseAnalyticsHelper @Inject constructor() { - @Suppress("UNUSED_PARAMETER") fun logEvent(name: String, vararg params: Pair) { // do nothing } + + fun setCurrentScreen(activity: Activity, name: String?) { + // do nothing + } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt index 3a1fb0ceb..437e06c92 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt @@ -215,7 +215,12 @@ class AttendancePresenter @Inject constructor( showContent(it.isNotEmpty()) showExcuseButton(it.any { item -> item.excusable }) } - analytics.logEvent("load_attendance", "items" to it.size, "force_refresh" to forceRefresh) + analytics.logEvent( + "load_data", + "type" to "attendance", + "items" to it.size, + "force_refresh" to forceRefresh + ) }) { Timber.i("Loading attendance result: An exception occurred") errorHandler.dispatch(it) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryPresenter.kt index 72dfc327e..33e18c2e7 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryPresenter.kt @@ -99,7 +99,13 @@ class AttendanceSummaryPresenter @Inject constructor( showContent(it.isNotEmpty()) updateDataSet(it) } - analytics.logEvent("load_attendance_summary", "items" to it.size, "force_refresh" to forceRefresh, "item_id" to subjectId) + analytics.logEvent( + "load_data", + "type" to "attendance_summary", + "items" to it.size, + "force_refresh" to forceRefresh, + "item_id" to subjectId + ) }) { Timber.i("Loading attendance summary result: An exception occurred") errorHandler.dispatch(it) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamPresenter.kt index 495602fc5..1c47cc7db 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamPresenter.kt @@ -131,7 +131,12 @@ class ExamPresenter @Inject constructor( showErrorView(false) showContent(it.isNotEmpty()) } - analytics.logEvent("load_exam", "items" to it.size, "force_refresh" to forceRefresh) + analytics.logEvent( + "load_data", + "type" to "exam", + "items" to it.size, + "force_refresh" to forceRefresh + ) }) { Timber.i("Loading exam result: An exception occurred") errorHandler.dispatch(it) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt index b674bf771..37f2c935a 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt @@ -152,7 +152,12 @@ class GradeDetailsPresenter @Inject constructor( gradeColorTheme = preferencesRepository.gradeColorTheme ) } - analytics.logEvent("load_grade_details", "items" to grades.size, "force_refresh" to forceRefresh) + analytics.logEvent( + "load_data", + "type" to "grade_details", + "items" to grades.size, + "force_refresh" to forceRefresh + ) }) { Timber.i("Loading grade details result: An exception occurred") errorHandler.dispatch(it) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsPresenter.kt index 5cc733cd0..590e9ce12 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsPresenter.kt @@ -174,7 +174,12 @@ class GradeStatisticsPresenter @Inject constructor( updateData(it, preferencesRepository.gradeColorTheme, preferencesRepository.showAllSubjectsOnStatisticsList) showSubjects(!preferencesRepository.showAllSubjectsOnStatisticsList) } - analytics.logEvent("load_grade_statistics", "items" to it.size, "force_refresh" to forceRefresh) + analytics.logEvent( + "load_data", + "type" to "grade_statistics", + "items" to it.size, + "force_refresh" to forceRefresh + ) }) { Timber.i("Loading grade stats result: An exception occurred") errorHandler.dispatch(it) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt index 26f922dee..9b88689db 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt @@ -49,7 +49,12 @@ class GradeSummaryPresenter @Inject constructor( showErrorView(false) updateData(it) } - analytics.logEvent("load_grade_summary", "items" to it.size, "force_refresh" to forceRefresh) + analytics.logEvent( + "load_data", + "type" to "grade_summary", + "items" to it.size, + "force_refresh" to forceRefresh + ) }) { Timber.i("Loading grade summary result: An exception occurred") errorHandler.dispatch(it) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkPresenter.kt index d39efde14..fafc33191 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkPresenter.kt @@ -124,7 +124,12 @@ class HomeworkPresenter @Inject constructor( showErrorView(false) showContent(it.isNotEmpty()) } - analytics.logEvent("load_homework", "items" to it.size, "force_refresh" to forceRefresh) + analytics.logEvent( + "load_data", + "type" to "homework", + "items" to it.size, + "force_refresh" to forceRefresh + ) }) { Timber.i("Loading homework result: An exception occurred") diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberPresenter.kt index 1d3a17bdc..e932fedc0 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberPresenter.kt @@ -54,7 +54,12 @@ class LuckyNumberPresenter @Inject constructor( showEmpty(false) showErrorView(false) } - analytics.logEvent("load_lucky_number", "lucky_number" to it.luckyNumber, "force_refresh" to forceRefresh) + analytics.logEvent( + "load_item", + "type" to "lucky_number", + "number" to it.luckyNumber, + "force_refresh" to forceRefresh + ) }, { Timber.i("Loading lucky number result: An exception occurred") errorHandler.dispatch(it) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt index 9ce190795..f5b7c47c8 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt @@ -31,6 +31,7 @@ import io.github.wulkanowy.ui.modules.message.MessageFragment import io.github.wulkanowy.ui.modules.more.MoreFragment import io.github.wulkanowy.ui.modules.note.NoteFragment import io.github.wulkanowy.ui.modules.timetable.TimetableFragment +import io.github.wulkanowy.utils.FirebaseAnalyticsHelper import io.github.wulkanowy.utils.dpToPx import io.github.wulkanowy.utils.getThemeAttrColor import io.github.wulkanowy.utils.safelyPopFragments @@ -46,6 +47,9 @@ class MainActivity : BaseActivity(), MainVie @Inject lateinit var navController: FragNavController + @Inject + lateinit var analytics: FirebaseAnalyticsHelper + @Inject lateinit var overlayProvider: Lazy @@ -136,6 +140,10 @@ class MainActivity : BaseActivity(), MainVie } } + override fun setCurrentScreen(name: String?) { + analytics.setCurrentScreen(this, name) + } + override fun onOptionsItemSelected(item: MenuItem?): Boolean { return if (item?.itemId == R.id.mainMenuAccount) presenter.onAccountManagerSelected() else false diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt index f5a490044..233d44917 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt @@ -37,8 +37,9 @@ class MainPresenter @Inject constructor( analytics.logEvent("app_open", "destination" to initMenu?.name) } - fun onViewChange(section: MainView.Section?) { + fun onViewChange(section: MainView.Section?, name: String?) { view?.apply { + setCurrentScreen(name) showActionBarElevation(section != GRADE && section != MESSAGE && section != SCHOOL) currentViewTitle?.let { setViewTitle(it) } currentViewSubtitle?.let { setViewSubTitle(it.ifBlank { null }) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainView.kt index 97b556e3e..7e5831471 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainView.kt @@ -24,6 +24,8 @@ interface MainView : BaseView { fun showAccountPicker() + fun setCurrentScreen(name: String?) + fun showActionBarElevation(show: Boolean) fun notifyMenuViewReselected() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewPresenter.kt index 62ac5a532..24678c70e 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewPresenter.kt @@ -60,7 +60,11 @@ class MessagePreviewPresenter @Inject constructor( setMessageWithAttachment(message) initOptions() } - analytics.logEvent("load_message_preview", "length" to message.message.content.length) + analytics.logEvent( + "load_item", + "type" to "message_preview", + "length" to message.message.content.length + ) }) { Timber.i("Loading message ${message.messageId} preview result: An exception occurred ") retryCallback = { onMessageLoadRetry(message) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt index 6513adcde..221762d16 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt @@ -97,7 +97,12 @@ class MessageTabPresenter @Inject constructor( Timber.i("Loading $folder message result: Success") messages = it onSearchQueryTextChange(lastSearchQuery) - analytics.logEvent("load_messages", "items" to it.size, "folder" to folder.name) + analytics.logEvent( + "load_data", + "type" to "messages", + "items" to it.size, + "folder" to folder.name + ) }) { Timber.i("Loading $folder message result: An exception occurred") errorHandler.dispatch(it) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDevicePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDevicePresenter.kt index d8c99b221..459ca17e8 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDevicePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDevicePresenter.kt @@ -70,7 +70,12 @@ class MobileDevicePresenter @Inject constructor( showEmpty(it.isEmpty()) showErrorView(false) } - analytics.logEvent("load_devices", "items" to it.size, "force_refresh" to forceRefresh) + analytics.logEvent( + "load_data", + "type" to "devices", + "items" to it.size, + "force_refresh" to forceRefresh + ) }) { Timber.i("Loading mobile devices result: An exception occurred") errorHandler.dispatch(it) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/note/NotePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/note/NotePresenter.kt index 00df71b96..7d301c66b 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/note/NotePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/note/NotePresenter.kt @@ -69,7 +69,12 @@ class NotePresenter @Inject constructor( showErrorView(false) showContent(it.isNotEmpty()) } - analytics.logEvent("load_note", "items" to it.size, "force_refresh" to forceRefresh) + analytics.logEvent( + "load_data", + "type" to "note", + "items" to it.size, + "force_refresh" to forceRefresh + ) }, { Timber.i("Loading note result: An exception occurred") errorHandler.dispatch(it) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/school/SchoolPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/school/SchoolPresenter.kt index e2eb614dc..7beff922d 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/school/SchoolPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/school/SchoolPresenter.kt @@ -88,7 +88,11 @@ class SchoolPresenter @Inject constructor( showEmpty(false) showErrorView(false) } - analytics.logEvent("load_school", "force_refresh" to forceRefresh) + analytics.logEvent( + "load_item", + "type" to "school", + "force_refresh" to forceRefresh + ) }, { Timber.i("Loading school result: An exception occurred") errorHandler.dispatch(it) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherPresenter.kt index e888308fd..0d8eec6d4 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherPresenter.kt @@ -75,7 +75,12 @@ class TeacherPresenter @Inject constructor( showEmpty(it.isEmpty()) showErrorView(false) } - analytics.logEvent("load_teachers", "items" to it.size, "force_refresh" to forceRefresh) + analytics.logEvent( + "load_data", + "type" to "teachers", + "items" to it.size, + "force_refresh" to forceRefresh + ) }) { Timber.i("Loading teachers result: An exception occurred") errorHandler.dispatch(it) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt index 50c123646..e1ce005e3 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt @@ -154,7 +154,12 @@ class TimetablePresenter @Inject constructor( showErrorView(false) showContent(it.isNotEmpty()) } - analytics.logEvent("load_timetable", "items" to it.size, "force_refresh" to forceRefresh) + analytics.logEvent( + "load_data", + "type" to "timetable", + "items" to it.size, + "force_refresh" to forceRefresh + ) }) { Timber.i("Loading timetable result: An exception occurred") errorHandler.dispatch(it) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsPresenter.kt index 355411eb5..7243061d6 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsPresenter.kt @@ -134,7 +134,12 @@ class CompletedLessonsPresenter @Inject constructor( showErrorView(false) showContent(it.isNotEmpty()) } - analytics.logEvent("load_completed_lessons", "items" to it.size, "force_refresh" to forceRefresh) + analytics.logEvent( + "load_data", + "type" to "completed_lessons", + "items" to it.size, + "force_refresh" to forceRefresh + ) }) { Timber.i("Loading completed lessons result: An exception occurred") completedLessonsErrorHandler.dispatch(it) diff --git a/app/src/main/java/io/github/wulkanowy/utils/FragNavControlerExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/FragNavControlerExtension.kt index 9cec331f8..9dc1e18a0 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/FragNavControlerExtension.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/FragNavControlerExtension.kt @@ -4,14 +4,14 @@ import androidx.fragment.app.Fragment import com.ncapdevi.fragnav.FragNavController import io.github.wulkanowy.ui.modules.main.MainView -inline fun FragNavController.setOnViewChangeListener(crossinline listener: (section: MainView.Section?) -> Unit) { +inline fun FragNavController.setOnViewChangeListener(crossinline listener: (section: MainView.Section?, name: String?) -> Unit) { transactionListener = object : FragNavController.TransactionListener { override fun onFragmentTransaction(fragment: Fragment?, transactionType: FragNavController.TransactionType) { - listener(fragment?.toSection()) + listener(fragment?.toSection(), fragment?.let { it::class.java.simpleName }) } override fun onTabTransaction(fragment: Fragment?, index: Int) { - listener(fragment?.toSection()) + listener(fragment?.toSection(), fragment?.let { it::class.java.simpleName }) } } } diff --git a/app/src/play/java/io/github/wulkanowy/utils/CrashlyticsUtils.kt b/app/src/play/java/io/github/wulkanowy/utils/CrashlyticsUtils.kt index e374577df..e87177c1d 100644 --- a/app/src/play/java/io/github/wulkanowy/utils/CrashlyticsUtils.kt +++ b/app/src/play/java/io/github/wulkanowy/utils/CrashlyticsUtils.kt @@ -6,20 +6,14 @@ import fr.bipi.tressence.base.FormatterPriorityTree import fr.bipi.tressence.common.StackTraceRecorder import io.github.wulkanowy.sdk.exception.FeatureDisabledException import io.github.wulkanowy.sdk.exception.FeatureNotAvailableException +import java.io.InterruptedIOException +import java.net.SocketTimeoutException import java.net.UnknownHostException class CrashlyticsTree : FormatterPriorityTree(Log.VERBOSE) { private val crashlytics by lazy { FirebaseCrashlytics.getInstance() } - override fun skipLog(priority: Int, tag: String?, message: String, t: Throwable?): Boolean { - if (t is FeatureDisabledException || t is FeatureNotAvailableException || t is UnknownHostException) { - return true - } - - return super.skipLog(priority, tag, message, t) - } - override fun log(priority: Int, tag: String?, message: String, t: Throwable?) { if (skipLog(priority, tag, message, t)) return @@ -31,6 +25,14 @@ class CrashlyticsExceptionTree : FormatterPriorityTree(Log.ERROR) { private val crashlytics by lazy { FirebaseCrashlytics.getInstance() } + override fun skipLog(priority: Int, tag: String?, message: String, t: Throwable?): Boolean { + if (t is FeatureDisabledException || t is FeatureNotAvailableException || t is UnknownHostException || t is SocketTimeoutException || t is InterruptedIOException) { + return true + } + + return super.skipLog(priority, tag, message, t) + } + override fun log(priority: Int, tag: String?, message: String, t: Throwable?) { if (skipLog(priority, tag, message, t)) return diff --git a/app/src/play/java/io/github/wulkanowy/utils/FirebaseAnalyticsHelper.kt b/app/src/play/java/io/github/wulkanowy/utils/FirebaseAnalyticsHelper.kt index 6810e66ac..b0b2fda4d 100644 --- a/app/src/play/java/io/github/wulkanowy/utils/FirebaseAnalyticsHelper.kt +++ b/app/src/play/java/io/github/wulkanowy/utils/FirebaseAnalyticsHelper.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.utils +import android.app.Activity import android.content.Context import android.os.Bundle import com.google.firebase.analytics.FirebaseAnalytics @@ -24,4 +25,8 @@ class FirebaseAnalyticsHelper @Inject constructor(private val context: Context) analytics.logEvent(name, this) } } + + fun setCurrentScreen(activity: Activity, name: String?) { + analytics.setCurrentScreen(activity, name, null) + } } From 2cdde78c54468cf58ade166ca4972d4d98e92287 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sun, 24 May 2020 19:48:56 +0200 Subject: [PATCH 40/85] Allow access to saturday in timetable and attendance (#833) --- .../repositories/attendance/AttendanceRepository.kt | 10 +++++----- .../completedlessons/CompletedLessonsRepository.kt | 10 +++++----- .../data/repositories/exam/ExamRepository.kt | 10 +++++----- .../data/repositories/homework/HomeworkRepository.kt | 4 ++-- .../repositories/timetable/TimetableRepository.kt | 12 ++++++------ .../wulkanowy/services/sync/works/AttendanceWork.kt | 4 ++-- .../services/sync/works/CompletedLessonWork.kt | 4 ++-- .../github/wulkanowy/services/sync/works/ExamWork.kt | 4 ++-- .../wulkanowy/services/sync/works/HomeworkWork.kt | 4 ++-- .../wulkanowy/services/sync/works/TimetableWork.kt | 4 ++-- .../wulkanowy/ui/modules/exam/ExamPresenter.kt | 6 +++--- .../ui/modules/homework/HomeworkPresenter.kt | 4 ++-- .../github/wulkanowy/utils/SchooldaysRangeLimiter.kt | 2 +- .../java/io/github/wulkanowy/utils/TimeExtension.kt | 4 ++-- .../io/github/wulkanowy/utils/TimeExtensionTest.kt | 12 ++++++------ 15 files changed, 47 insertions(+), 47 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/attendance/AttendanceRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/attendance/AttendanceRepository.kt index 22fe7fa51..68e7c5f12 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/attendance/AttendanceRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/attendance/AttendanceRepository.kt @@ -5,7 +5,7 @@ import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.Inter import io.github.wulkanowy.data.db.entities.Attendance import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.utils.friday +import io.github.wulkanowy.utils.sunday import io.github.wulkanowy.utils.monday import io.github.wulkanowy.utils.uniqueSubtract import io.reactivex.Single @@ -22,19 +22,19 @@ class AttendanceRepository @Inject constructor( ) { fun getAttendance(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean): Single> { - return local.getAttendance(semester, start.monday, end.friday).filter { !forceRefresh } + return local.getAttendance(semester, start.monday, end.sunday).filter { !forceRefresh } .switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings).flatMap { - if (it) remote.getAttendance(student, semester, start.monday, end.friday) + if (it) remote.getAttendance(student, semester, start.monday, end.sunday) else Single.error(UnknownHostException()) }.flatMap { newAttendance -> - local.getAttendance(semester, start.monday, end.friday) + local.getAttendance(semester, start.monday, end.sunday) .toSingle(emptyList()) .doOnSuccess { oldAttendance -> local.deleteAttendance(oldAttendance.uniqueSubtract(newAttendance)) local.saveAttendance(newAttendance.uniqueSubtract(oldAttendance)) } }.flatMap { - local.getAttendance(semester, start.monday, end.friday) + local.getAttendance(semester, start.monday, end.sunday) .toSingle(emptyList()) }).map { list -> list.filter { it.date in start..end } } } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/completedlessons/CompletedLessonsRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/completedlessons/CompletedLessonsRepository.kt index f31e30552..72cc93eb4 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/completedlessons/CompletedLessonsRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/completedlessons/CompletedLessonsRepository.kt @@ -5,7 +5,7 @@ import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.Inter import io.github.wulkanowy.data.db.entities.CompletedLesson import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.utils.friday +import io.github.wulkanowy.utils.sunday import io.github.wulkanowy.utils.monday import io.github.wulkanowy.utils.uniqueSubtract import io.reactivex.Single @@ -22,20 +22,20 @@ class CompletedLessonsRepository @Inject constructor( ) { fun getCompletedLessons(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean = false): Single> { - return local.getCompletedLessons(semester, start.monday, end.friday).filter { !forceRefresh } + return local.getCompletedLessons(semester, start.monday, end.sunday).filter { !forceRefresh } .switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings) .flatMap { - if (it) remote.getCompletedLessons(student, semester, start.monday, end.friday) + if (it) remote.getCompletedLessons(student, semester, start.monday, end.sunday) else Single.error(UnknownHostException()) }.flatMap { new -> - local.getCompletedLessons(semester, start.monday, end.friday) + local.getCompletedLessons(semester, start.monday, end.sunday) .toSingle(emptyList()) .doOnSuccess { old -> local.deleteCompleteLessons(old.uniqueSubtract(new)) local.saveCompletedLessons(new.uniqueSubtract(old)) } }.flatMap { - local.getCompletedLessons(semester, start.monday, end.friday) + local.getCompletedLessons(semester, start.monday, end.sunday) .toSingle(emptyList()) }).map { list -> list.filter { it.date in start..end } } } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/exam/ExamRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/exam/ExamRepository.kt index 452a15510..f29e4fdf8 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/exam/ExamRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/exam/ExamRepository.kt @@ -5,7 +5,7 @@ import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.Inter 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 io.github.wulkanowy.utils.friday +import io.github.wulkanowy.utils.sunday import io.github.wulkanowy.utils.monday import io.github.wulkanowy.utils.uniqueSubtract import io.reactivex.Single @@ -22,20 +22,20 @@ class ExamRepository @Inject constructor( ) { fun getExams(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean = false): Single> { - return local.getExams(semester, start.monday, end.friday).filter { !forceRefresh } + return local.getExams(semester, start.monday, end.sunday).filter { !forceRefresh } .switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings) .flatMap { - if (it) remote.getExams(student, semester, start.monday, end.friday) + if (it) remote.getExams(student, semester, start.monday, end.sunday) else Single.error(UnknownHostException()) }.flatMap { new -> - local.getExams(semester, start.monday, end.friday) + local.getExams(semester, start.monday, end.sunday) .toSingle(emptyList()) .doOnSuccess { old -> local.deleteExams(old.uniqueSubtract(new)) local.saveExams(new.uniqueSubtract(old)) } }.flatMap { - local.getExams(semester, start.monday, end.friday) + local.getExams(semester, start.monday, end.sunday) .toSingle(emptyList()) }).map { list -> list.filter { it.date in start..end } } } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/homework/HomeworkRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/homework/HomeworkRepository.kt index 4454fd88e..7e8fd5c30 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/homework/HomeworkRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/homework/HomeworkRepository.kt @@ -5,7 +5,7 @@ import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.Inter import io.github.wulkanowy.data.db.entities.Homework import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.utils.friday +import io.github.wulkanowy.utils.sunday import io.github.wulkanowy.utils.monday import io.github.wulkanowy.utils.uniqueSubtract import io.reactivex.Completable @@ -23,7 +23,7 @@ class HomeworkRepository @Inject constructor( ) { fun getHomework(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean = false): Single> { - return Single.fromCallable { start.monday to end.friday }.flatMap { (monday, friday) -> + return Single.fromCallable { start.monday to end.sunday }.flatMap { (monday, friday) -> local.getHomework(semester, monday, friday).filter { !forceRefresh } .switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings) .flatMap { diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/timetable/TimetableRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/timetable/TimetableRepository.kt index 082b03cf8..4a7a0eb24 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/timetable/TimetableRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/timetable/TimetableRepository.kt @@ -6,7 +6,7 @@ import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Timetable import io.github.wulkanowy.services.alarm.TimetableNotificationSchedulerHelper -import io.github.wulkanowy.utils.friday +import io.github.wulkanowy.utils.sunday import io.github.wulkanowy.utils.monday import io.github.wulkanowy.utils.uniqueSubtract import io.reactivex.Single @@ -24,13 +24,13 @@ class TimetableRepository @Inject constructor( ) { fun getTimetable(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean = false): Single> { - return Single.fromCallable { start.monday to end.friday }.flatMap { (monday, friday) -> - local.getTimetable(semester, monday, friday).filter { !forceRefresh } + return Single.fromCallable { start.monday to end.sunday }.flatMap { (monday, sunday) -> + local.getTimetable(semester, monday, sunday).filter { !forceRefresh } .switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings).flatMap { - if (it) remote.getTimetable(student, semester, monday, friday) + if (it) remote.getTimetable(student, semester, monday, sunday) else Single.error(UnknownHostException()) }.flatMap { new -> - local.getTimetable(semester, monday, friday) + local.getTimetable(semester, monday, sunday) .toSingle(emptyList()) .doOnSuccess { old -> local.deleteTimetable(old.uniqueSubtract(new).also { schedulerHelper.cancelScheduled(it) }) @@ -46,7 +46,7 @@ class TimetableRepository @Inject constructor( }) } }.flatMap { - local.getTimetable(semester, monday, friday).toSingle(emptyList()) + local.getTimetable(semester, monday, sunday).toSingle(emptyList()) }).map { list -> list.filter { it.date in start..end }.also { schedulerHelper.scheduleNotifications(it, student) } } } } diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/AttendanceWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/AttendanceWork.kt index e8579ddba..063b74825 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/AttendanceWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/AttendanceWork.kt @@ -3,7 +3,7 @@ package io.github.wulkanowy.services.sync.works import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.repositories.attendance.AttendanceRepository -import io.github.wulkanowy.utils.friday +import io.github.wulkanowy.utils.sunday import io.github.wulkanowy.utils.monday import io.reactivex.Completable import org.threeten.bp.LocalDate.now @@ -12,7 +12,7 @@ import javax.inject.Inject class AttendanceWork @Inject constructor(private val attendanceRepository: AttendanceRepository) : Work { override fun create(student: Student, semester: Semester): Completable { - return attendanceRepository.getAttendance(student, semester, now().monday, now().friday, true) + return attendanceRepository.getAttendance(student, semester, now().monday, now().sunday, true) .ignoreElement() } } diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/CompletedLessonWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/CompletedLessonWork.kt index 0da597e0a..26f79a0d5 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/CompletedLessonWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/CompletedLessonWork.kt @@ -3,7 +3,7 @@ package io.github.wulkanowy.services.sync.works import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.repositories.completedlessons.CompletedLessonsRepository -import io.github.wulkanowy.utils.friday +import io.github.wulkanowy.utils.sunday import io.github.wulkanowy.utils.monday import io.reactivex.Completable import org.threeten.bp.LocalDate.now @@ -14,7 +14,7 @@ class CompletedLessonWork @Inject constructor( ) : Work { override fun create(student: Student, semester: Semester): Completable { - return completedLessonsRepository.getCompletedLessons(student, semester, now().monday, now().friday, true) + return completedLessonsRepository.getCompletedLessons(student, semester, now().monday, now().sunday, true) .ignoreElement() } } diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/ExamWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/ExamWork.kt index c6110bbbf..1ef97f59c 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/ExamWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/ExamWork.kt @@ -3,7 +3,7 @@ package io.github.wulkanowy.services.sync.works import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.repositories.exam.ExamRepository -import io.github.wulkanowy.utils.friday +import io.github.wulkanowy.utils.sunday import io.github.wulkanowy.utils.monday import io.reactivex.Completable import org.threeten.bp.LocalDate.now @@ -12,6 +12,6 @@ import javax.inject.Inject class ExamWork @Inject constructor(private val examRepository: ExamRepository) : Work { override fun create(student: Student, semester: Semester): Completable { - return examRepository.getExams(student, semester, now().monday, now().friday, true).ignoreElement() + return examRepository.getExams(student, semester, now().monday, now().sunday, true).ignoreElement() } } diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/HomeworkWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/HomeworkWork.kt index cf3484394..186d57b03 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/HomeworkWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/HomeworkWork.kt @@ -3,7 +3,7 @@ package io.github.wulkanowy.services.sync.works import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.repositories.homework.HomeworkRepository -import io.github.wulkanowy.utils.friday +import io.github.wulkanowy.utils.sunday import io.github.wulkanowy.utils.monday import io.reactivex.Completable import org.threeten.bp.LocalDate.now @@ -12,6 +12,6 @@ import javax.inject.Inject class HomeworkWork @Inject constructor(private val homeworkRepository: HomeworkRepository) : Work { override fun create(student: Student, semester: Semester): Completable { - return homeworkRepository.getHomework(student, semester, now().monday, now().friday, true).ignoreElement() + return homeworkRepository.getHomework(student, semester, now().monday, now().sunday, true).ignoreElement() } } diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/TimetableWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/TimetableWork.kt index 0990ed67a..d79d24033 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/TimetableWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/TimetableWork.kt @@ -3,7 +3,7 @@ package io.github.wulkanowy.services.sync.works import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.repositories.timetable.TimetableRepository -import io.github.wulkanowy.utils.friday +import io.github.wulkanowy.utils.sunday import io.github.wulkanowy.utils.monday import io.reactivex.Completable import org.threeten.bp.LocalDate.now @@ -12,7 +12,7 @@ import javax.inject.Inject class TimetableWork @Inject constructor(private val timetableRepository: TimetableRepository) : Work { override fun create(student: Student, semester: Semester): Completable { - return timetableRepository.getTimetable(student, semester, now().monday, now().friday, true) + return timetableRepository.getTimetable(student, semester, now().monday, now().sunday, true) .ignoreElement() } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamPresenter.kt index 1c47cc7db..1140cb020 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamPresenter.kt @@ -8,7 +8,7 @@ import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.FirebaseAnalyticsHelper import io.github.wulkanowy.utils.SchedulersProvider -import io.github.wulkanowy.utils.friday +import io.github.wulkanowy.utils.sunday import io.github.wulkanowy.utils.getLastSchoolDayIfHoliday import io.github.wulkanowy.utils.isHolidays import io.github.wulkanowy.utils.monday @@ -110,7 +110,7 @@ class ExamPresenter @Inject constructor( add(studentRepository.getCurrentStudent() .flatMap { student -> semesterRepository.getCurrentSemester(student).flatMap { semester -> - examRepository.getExams(student, semester, currentDate.monday, currentDate.friday, forceRefresh) + examRepository.getExams(student, semester, currentDate.monday, currentDate.sunday, forceRefresh) } } .map { createExamItems(it) } @@ -181,7 +181,7 @@ class ExamPresenter @Inject constructor( showPreButton(!currentDate.minusDays(7).isHolidays) showNextButton(!currentDate.plusDays(7).isHolidays) updateNavigationWeek("${currentDate.monday.toFormattedString("dd.MM")} - " + - currentDate.friday.toFormattedString("dd.MM")) + currentDate.sunday.toFormattedString("dd.MM")) } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkPresenter.kt index fafc33191..41735a23f 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkPresenter.kt @@ -8,7 +8,7 @@ import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.FirebaseAnalyticsHelper import io.github.wulkanowy.utils.SchedulersProvider -import io.github.wulkanowy.utils.friday +import io.github.wulkanowy.utils.sunday import io.github.wulkanowy.utils.getLastSchoolDayIfHoliday import io.github.wulkanowy.utils.isHolidays import io.github.wulkanowy.utils.monday @@ -175,7 +175,7 @@ class HomeworkPresenter @Inject constructor( showPreButton(!currentDate.minusDays(7).isHolidays) showNextButton(!currentDate.plusDays(7).isHolidays) updateNavigationWeek("${currentDate.monday.toFormattedString("dd.MM")} - " + - currentDate.friday.toFormattedString("dd.MM")) + currentDate.sunday.toFormattedString("dd.MM")) } } } diff --git a/app/src/main/java/io/github/wulkanowy/utils/SchooldaysRangeLimiter.kt b/app/src/main/java/io/github/wulkanowy/utils/SchooldaysRangeLimiter.kt index 922aafbd8..46a707abb 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/SchooldaysRangeLimiter.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/SchooldaysRangeLimiter.kt @@ -17,7 +17,7 @@ class SchooldaysRangeLimiter : DateRangeLimiter { override fun isOutOfRange(year: Int, month: Int, day: Int): Boolean { val date = LocalDate.of(year, month + 1, day) val dayOfWeek = date.dayOfWeek - return dayOfWeek == DayOfWeek.SUNDAY || dayOfWeek == DayOfWeek.SATURDAY || date.isHolidays + return dayOfWeek == DayOfWeek.SUNDAY || date.isHolidays } override fun getStartDate(): Calendar { diff --git a/app/src/main/java/io/github/wulkanowy/utils/TimeExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/TimeExtension.kt index 8d022fc5d..802b2ee0f 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/TimeExtension.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/TimeExtension.kt @@ -92,8 +92,8 @@ inline val LocalDate.weekDayName: String inline val LocalDate.monday: LocalDate get() = with(MONDAY) -inline val LocalDate.friday: LocalDate - get() = with(FRIDAY) +inline val LocalDate.sunday: LocalDate + get() = with(SUNDAY) /** * [Dz.U. 2016 poz. 1335](http://prawo.sejm.gov.pl/isap.nsf/DocDetails.xsp?id=WDU20160001335) diff --git a/app/src/test/java/io/github/wulkanowy/utils/TimeExtensionTest.kt b/app/src/test/java/io/github/wulkanowy/utils/TimeExtensionTest.kt index 024b4727b..72d08c411 100644 --- a/app/src/test/java/io/github/wulkanowy/utils/TimeExtensionTest.kt +++ b/app/src/test/java/io/github/wulkanowy/utils/TimeExtensionTest.kt @@ -38,12 +38,12 @@ class TimeExtensionTest { } @Test - fun fridayTest() { - assertEquals(of(2018, 10, 5), of(2018, 10, 2).friday) - assertEquals(of(2018, 10, 5), of(2018, 10, 5).friday) - assertEquals(of(2018, 10, 5), of(2018, 10, 6).friday) - assertEquals(of(2018, 10, 5), of(2018, 10, 7).friday) - assertEquals(of(2018, 10, 12), of(2018, 10, 8).friday) + fun sundayTestTest() { + assertEquals(of(2018, 10, 7), of(2018, 10, 2).sunday) + assertEquals(of(2018, 10, 7), of(2018, 10, 5).sunday) + assertEquals(of(2018, 10, 7), of(2018, 10, 6).sunday) + assertEquals(of(2018, 10, 7), of(2018, 10, 7).sunday) + assertEquals(of(2018, 10, 14), of(2018, 10, 8).sunday) } @Test From 3308d7fe6f85c76837db1dd761147f64fd1919f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sun, 24 May 2020 19:52:01 +0200 Subject: [PATCH 41/85] Wrap long preference titles (#836) --- app/src/main/res/xml/scheme_preferences.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/src/main/res/xml/scheme_preferences.xml b/app/src/main/res/xml/scheme_preferences.xml index c05910f99..4cdb989c7 100644 --- a/app/src/main/res/xml/scheme_preferences.xml +++ b/app/src/main/res/xml/scheme_preferences.xml @@ -38,6 +38,7 @@ app:defaultValue="@bool/pref_default_grade_statistics_list" app:iconSpaceReserved="false" app:key="@string/pref_key_grade_statistics_list" + app:singleLineTitle="false" app:title="@string/pref_view_grade_statistics_list" /> From 0c4364609bf2cb6110075a95b0f2ecf5c5a88751 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sun, 24 May 2020 20:03:46 +0200 Subject: [PATCH 42/85] Show `check for updates` dialog before report a bug (#834) --- .../io/github/wulkanowy/ui/base/ErrorDialog.kt | 17 ++++++++++++++++- .../wulkanowy/ui/modules/about/AboutFragment.kt | 9 +++++++-- .../ui/modules/about/AboutPresenter.kt | 5 ++++- .../wulkanowy/ui/modules/about/AboutView.kt | 2 ++ .../github/wulkanowy/utils/ContextExtension.kt | 8 +++++++- app/src/main/res/values-de/strings.xml | 3 +++ app/src/main/res/values-pl/strings.xml | 3 +++ app/src/main/res/values-ru/strings.xml | 3 +++ app/src/main/res/values-uk/strings.xml | 3 +++ app/src/main/res/values/strings.xml | 5 +++++ 10 files changed, 53 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/ErrorDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/base/ErrorDialog.kt index 5fd6a86a5..896e4ff1c 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/ErrorDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/ErrorDialog.kt @@ -9,6 +9,7 @@ import android.view.ViewGroup import android.widget.HorizontalScrollView import android.widget.Toast import android.widget.Toast.LENGTH_LONG +import androidx.appcompat.app.AlertDialog import androidx.core.content.getSystemService import io.github.wulkanowy.R import io.github.wulkanowy.databinding.DialogErrorBinding @@ -17,6 +18,7 @@ import io.github.wulkanowy.sdk.exception.FeatureNotAvailableException import io.github.wulkanowy.sdk.exception.ServiceUnavailableException import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.getString +import io.github.wulkanowy.utils.openAppInMarket import io.github.wulkanowy.utils.openEmailClient import io.github.wulkanowy.utils.openInternetBrowser import java.io.InterruptedIOException @@ -74,7 +76,9 @@ class ErrorDialog : BaseDialogFragment() { Toast.makeText(context, R.string.all_copied, LENGTH_LONG).show() } errorDialogCancel.setOnClickListener { dismiss() } - errorDialogReport.setOnClickListener { openEmailClient(stringWriter.toString()) } + errorDialogReport.setOnClickListener { + openConfirmDialog { openEmailClient(stringWriter.toString()) } + } errorDialogMessage.text = resources.getString(error) errorDialogReport.isEnabled = when (error) { is UnknownHostException, @@ -88,6 +92,17 @@ class ErrorDialog : BaseDialogFragment() { } } + private fun openConfirmDialog(callback: () -> Unit) { + AlertDialog.Builder(requireContext()) + .setTitle(R.string.dialog_error_check_update) + .setMessage(R.string.dialog_error_check_update_message) + .setNeutralButton(R.string.about_feedback) { _, _ -> callback() } + .setPositiveButton(R.string.dialog_error_check_update) { _, _ -> + requireContext().openAppInMarket(::showMessage) + } + .show() + } + private fun openEmailClient(content: String) { requireContext().openEmailClient( chooserTitle = getString(R.string.about_feedback), diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutFragment.kt index 3828a2bc4..d85d01e9e 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutFragment.kt @@ -14,6 +14,7 @@ import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.getCompatDrawable +import io.github.wulkanowy.utils.openAppInMarket import io.github.wulkanowy.utils.openEmailClient import io.github.wulkanowy.utils.openInternetBrowser import javax.inject.Inject @@ -98,8 +99,12 @@ class AboutFragment : BaseFragment(R.layout.fragment_about } } + override fun openAppInMarket() { + context?.openAppInMarket(::showMessage) + } + override fun openLogViewer() { - if (appInfo.isDebug) (activity as? MainActivity)?.pushView(LogViewerFragment.newInstance()) + (activity as? MainActivity)?.pushView(LogViewerFragment.newInstance()) } override fun openDiscordInvite() { @@ -115,7 +120,7 @@ class AboutFragment : BaseFragment(R.layout.fragment_about chooserTitle = getString(R.string.about_feedback), email = "wulkanowyinc@gmail.com", subject = "Zgłoszenie błędu", - body = requireContext().getString(R.string.about_feedback_template, + body = getString(R.string.about_feedback_template, "${appInfo.systemManufacturer} ${appInfo.systemModel}", appInfo.systemVersion.toString(), appInfo.versionName ), onActivityNotFound = { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutPresenter.kt index 27237ea6f..ee892adfc 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutPresenter.kt @@ -3,6 +3,7 @@ package io.github.wulkanowy.ui.modules.about import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler +import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.FirebaseAnalyticsHelper import io.github.wulkanowy.utils.SchedulersProvider import timber.log.Timber @@ -12,6 +13,7 @@ class AboutPresenter @Inject constructor( schedulers: SchedulersProvider, errorHandler: ErrorHandler, studentRepository: StudentRepository, + private val appInfo: AppInfo, private val analytics: FirebaseAnalyticsHelper ) : BasePresenter(errorHandler, studentRepository, schedulers) { @@ -27,7 +29,8 @@ class AboutPresenter @Inject constructor( when (name) { versionRes?.first -> { Timber.i("Opening log viewer") - openLogViewer() + if (appInfo.isDebug) openLogViewer() + else openAppInMarket() analytics.logEvent("about_open", "name" to "log_viewer") } feedbackRes?.first -> { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutView.kt index 79b700ea3..4c4b002f9 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutView.kt @@ -25,6 +25,8 @@ interface AboutView : BaseView { fun updateData(data: List>) + fun openAppInMarket() + fun openLogViewer() fun openDiscordInvite() diff --git a/app/src/main/java/io/github/wulkanowy/utils/ContextExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/ContextExtension.kt index 489e7e6fb..2b40cb476 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/ContextExtension.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/ContextExtension.kt @@ -10,7 +10,7 @@ import androidx.annotation.ColorRes import androidx.annotation.DrawableRes import androidx.core.content.ContextCompat import androidx.core.graphics.ColorUtils -import io.github.wulkanowy.R +import io.github.wulkanowy.BuildConfig.APPLICATION_ID @ColorInt fun Context.getThemeAttrColor(@AttrRes colorAttr: Int): Int { @@ -39,6 +39,12 @@ fun Context.openInternetBrowser(uri: String, onActivityNotFound: (uri: String) - } } +fun Context.openAppInMarket(onActivityNotFound: (uri: String) -> Unit) { + openInternetBrowser("market://details?id=${APPLICATION_ID}") { + openInternetBrowser("https://github.com/wulkanowy/wulkanowy/releases", onActivityNotFound) + } +} + fun Context.openEmailClient(chooserTitle: String, email: String, subject: String, body: String, onActivityNotFound: () -> Unit = {}) { val intent = Intent(Intent.ACTION_SENDTO, Uri.parse("mailto:")).apply { putExtra(Intent.EXTRA_EMAIL, arrayOf(email)) diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 084e3a86d..4b1669371 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -269,6 +269,9 @@ Logs teilen Aktualisieren + + Check for updates + Before reporting a bug, check first if an update with the bug fix is available Inhalt Wiederhol diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 4bcfbb774..ffd66912c 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -289,6 +289,9 @@ Udostępnij logi Odśwież + + Sprawdź aktualizację + Przed zgłoszeniem błędu sprawdź wcześniej, czy dostępna jest już aktualizacja z poprawką błędu Treść Ponów diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 41771a3cf..c0c751960 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -289,6 +289,9 @@ Поделиться логами Обновить + + Check for updates + Before reporting a bug, check first if an update with the bug fix is available Содержание Повторить diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 652249c68..a63e5682a 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -289,6 +289,9 @@ Поділитися логами Оновити + + Check for updates + Before reporting a bug, check first if an update with the bug fix is available Зміст Повторити diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2f13e031d..7793cd9c2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -317,6 +317,11 @@ Refresh + + Check for updates + Before reporting a bug, check first if an update with the bug fix is available + + Content Retry From b744a4182bc6754dbcb44c20772043f5355163a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sun, 24 May 2020 20:04:09 +0200 Subject: [PATCH 43/85] Open dontkillmyapp.com if no action found in app killer manager (#835) --- .../ui/modules/settings/SettingsFragment.kt | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsFragment.kt index 0d1d25656..248417fd1 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsFragment.kt @@ -7,6 +7,7 @@ import androidx.appcompat.app.AlertDialog import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat import com.thelittlefireman.appkillermanager.AppKillerManager +import com.thelittlefireman.appkillermanager.exceptions.NoActionFoundException import com.yariksoffice.lingver.Lingver import dagger.android.support.AndroidSupportInjection import io.github.wulkanowy.R @@ -14,6 +15,7 @@ import io.github.wulkanowy.ui.base.BaseActivity import io.github.wulkanowy.ui.base.ErrorDialog import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.utils.AppInfo +import io.github.wulkanowy.utils.openInternetBrowser import javax.inject.Inject class SettingsFragment : PreferenceFragmentCompat(), @@ -133,9 +135,13 @@ class SettingsFragment : PreferenceFragmentCompat(), .setMessage(R.string.pref_notify_fix_sync_issues_message) .setNegativeButton(android.R.string.cancel) { _, _ -> } .setPositiveButton(R.string.pref_notify_fix_sync_issues_settings_button) { _, _ -> - AppKillerManager.doActionPowerSaving(requireContext()) - AppKillerManager.doActionAutoStart(requireContext()) - AppKillerManager.doActionNotification(requireContext()) + try { + AppKillerManager.doActionPowerSaving(requireContext()) + AppKillerManager.doActionAutoStart(requireContext()) + AppKillerManager.doActionNotification(requireContext()) + } catch (e: NoActionFoundException) { + requireContext().openInternetBrowser("https://dontkillmyapp.com/${AppKillerManager.getDevice()?.manufacturer}", ::showMessage) + } } .show() } From 63f2576ff1dd90d855eb38376087b0a6a8061057 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sun, 24 May 2020 20:07:24 +0200 Subject: [PATCH 44/85] Hide advanced login options button (#837) --- app/build.gradle | 2 +- .../wulkanowy/data/repositories/student/StudentRemote.kt | 4 ++-- app/src/main/res/layout/fragment_login_form.xml | 6 +----- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 0e95493c9..7c1309964 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -124,7 +124,7 @@ configurations.all { } dependencies { - implementation "io.github.wulkanowy:sdk:fd51552" + implementation "io.github.wulkanowy:sdk:3c2763c" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation "androidx.core:core-ktx:1.2.0" diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/student/StudentRemote.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/student/StudentRemote.kt index 9b77c9ad3..15c336505 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/student/StudentRemote.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/student/StudentRemote.kt @@ -39,7 +39,7 @@ class StudentRemote @Inject constructor(private val sdk: Sdk) { } fun getStudentsMobileApi(token: String, pin: String, symbol: String): Single> { - return sdk.getStudentsFromMobileApi(token, pin, symbol).map { mapStudents(it, "", "") } + return sdk.getStudentsFromMobileApi(token, pin, symbol, "").map { mapStudents(it, "", "") } } fun getStudentsScrapper(email: String, password: String, scrapperBaseUrl: String, symbol: String): Single> { @@ -47,6 +47,6 @@ class StudentRemote @Inject constructor(private val sdk: Sdk) { } fun getStudentsHybrid(email: String, password: String, scrapperBaseUrl: String, symbol: String): Single> { - return sdk.getStudentsHybrid(email, password, scrapperBaseUrl, symbol).map { mapStudents(it, email, password) } + return sdk.getStudentsHybrid(email, password, scrapperBaseUrl, "", symbol).map { mapStudents(it, email, password) } } } diff --git a/app/src/main/res/layout/fragment_login_form.xml b/app/src/main/res/layout/fragment_login_form.xml index e4a5fab78..d91d7b037 100644 --- a/app/src/main/res/layout/fragment_login_form.xml +++ b/app/src/main/res/layout/fragment_login_form.xml @@ -67,7 +67,6 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginEnd="8dp" - android:layout_marginRight="8dp" android:layout_weight="1" android:text="@string/login_contact_email" app:icon="@drawable/ic_more_messages" /> @@ -78,7 +77,6 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="8dp" - android:layout_marginLeft="8dp" android:layout_weight="1" android:text="@string/about_faq" app:icon="@drawable/ic_about_faq" /> @@ -182,7 +180,6 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginEnd="24dp" - android:layout_marginRight="24dp" android:text="@string/login_recover_button" android:textAppearance="?android:textAppearance" app:backgroundTint="?android:windowBackground" @@ -223,9 +220,9 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginEnd="16dp" - android:layout_marginRight="16dp" android:text="@string/login_advanced" android:textAppearance="?android:textAppearance" + android:visibility="gone" app:backgroundTint="?android:windowBackground" app:fontFamily="sans-serif-medium" app:layout_constraintBottom_toBottomOf="@id/loginFormSignIn" @@ -241,7 +238,6 @@ android:layout_gravity="center_vertical" android:layout_marginTop="48dp" android:layout_marginEnd="24dp" - android:layout_marginRight="24dp" android:layout_marginBottom="16dp" android:text="@string/login_sign_in" app:layout_constraintBottom_toBottomOf="parent" From c71b533645d3fae30816a01a9b7ae67bb2a062a1 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Sun, 24 May 2020 18:10:47 +0000 Subject: [PATCH 45/85] Bump firebase-inappmessaging-display-ktx from 19.0.6 to 19.0.7 (#838) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 7c1309964..3904b6aaa 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -182,7 +182,7 @@ dependencies { implementation "io.github.wulkanowy:AppKillerManager:3.0.0" playImplementation 'com.google.firebase:firebase-analytics:17.4.1' - playImplementation 'com.google.firebase:firebase-inappmessaging-display-ktx:19.0.6' + playImplementation 'com.google.firebase:firebase-inappmessaging-display-ktx:19.0.7' playImplementation "com.google.firebase:firebase-inappmessaging-ktx:19.0.6" playImplementation 'com.google.firebase:firebase-messaging:20.1.7' playImplementation 'com.google.firebase:firebase-crashlytics:17.0.0' From dcbaa170db65e8a1b219ec365e8f88e8080e6e56 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Sun, 24 May 2020 20:36:09 +0200 Subject: [PATCH 46/85] Bump about_libraries from 8.1.2 to 8.1.3 (#845) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 2b92bc7d8..e5577904a 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ buildscript { ext.kotlin_version = '1.3.72' - ext.about_libraries = '8.1.2' + ext.about_libraries = '8.1.3' repositories { mavenCentral() google() From 4c295f2ab4316cc3cf9f0b4301ecb8500b63cae1 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Sun, 24 May 2020 18:42:24 +0000 Subject: [PATCH 47/85] Bump firebase-messaging from 20.1.7 to 20.2.0 (#839) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 3904b6aaa..aceed6733 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -184,7 +184,7 @@ dependencies { playImplementation 'com.google.firebase:firebase-analytics:17.4.1' playImplementation 'com.google.firebase:firebase-inappmessaging-display-ktx:19.0.7' playImplementation "com.google.firebase:firebase-inappmessaging-ktx:19.0.6" - playImplementation 'com.google.firebase:firebase-messaging:20.1.7' + playImplementation 'com.google.firebase:firebase-messaging:20.2.0' playImplementation 'com.google.firebase:firebase-crashlytics:17.0.0' playImplementation 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava' From 699fbff082e618ba6ead131007ef1b41f45eba1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Sun, 24 May 2020 20:53:23 +0200 Subject: [PATCH 48/85] Fix debug channel (#846) --- .../io/github/wulkanowy/services/sync/channels/DebugChannel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/channels/DebugChannel.kt b/app/src/main/java/io/github/wulkanowy/services/sync/channels/DebugChannel.kt index 9b6f213bd..a67350550 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/channels/DebugChannel.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/channels/DebugChannel.kt @@ -22,7 +22,7 @@ class DebugChannel @Inject constructor( } override fun create() { - if (appInfo.isDebug) return + if (!appInfo.isDebug) return notificationManager.createNotificationChannel( NotificationChannel(CHANNEL_ID, context.getString(R.string.channel_debug), IMPORTANCE_DEFAULT) .apply { From 0b4434fdb66d049290b7648955dbb6c9ba6e78d3 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Sun, 24 May 2020 20:53:59 +0200 Subject: [PATCH 49/85] Bump firebase-crashlytics-gradle from 2.1.0 to 2.1.1 (#841) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index e5577904a..696e778f5 100644 --- a/build.gradle +++ b/build.gradle @@ -11,7 +11,7 @@ buildscript { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath 'com.android.tools.build:gradle:3.6.3' classpath 'com.google.gms:google-services:4.3.3' - classpath 'com.google.firebase:firebase-crashlytics-gradle:2.1.0' + classpath 'com.google.firebase:firebase-crashlytics-gradle:2.1.1' classpath "com.github.triplet.gradle:play-publisher:2.7.5" classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.8" classpath "gradle.plugin.com.star-zero.gradle:githook:1.2.0" From c8b32fdb3b4a4bbe7cef10be0503110a409bfebb Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Sun, 24 May 2020 21:02:42 +0200 Subject: [PATCH 50/85] Bump firebase-analytics from 17.4.1 to 17.4.2 (#843) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index aceed6733..4c36f0a64 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -181,7 +181,7 @@ dependencies { implementation "io.coil-kt:coil:0.11.0" implementation "io.github.wulkanowy:AppKillerManager:3.0.0" - playImplementation 'com.google.firebase:firebase-analytics:17.4.1' + playImplementation 'com.google.firebase:firebase-analytics:17.4.2' playImplementation 'com.google.firebase:firebase-inappmessaging-display-ktx:19.0.7' playImplementation "com.google.firebase:firebase-inappmessaging-ktx:19.0.6" playImplementation 'com.google.firebase:firebase-messaging:20.2.0' From 27b1d076c7eb3ad5302739d3cf4a8a9707584c0b Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Sun, 24 May 2020 21:03:35 +0200 Subject: [PATCH 51/85] Bump firebase-inappmessaging-ktx from 19.0.6 to 19.0.7 (#844) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 4c36f0a64..224234fc3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -183,7 +183,7 @@ dependencies { playImplementation 'com.google.firebase:firebase-analytics:17.4.2' playImplementation 'com.google.firebase:firebase-inappmessaging-display-ktx:19.0.7' - playImplementation "com.google.firebase:firebase-inappmessaging-ktx:19.0.6" + playImplementation "com.google.firebase:firebase-inappmessaging-ktx:19.0.7" playImplementation 'com.google.firebase:firebase-messaging:20.2.0' playImplementation 'com.google.firebase:firebase-crashlytics:17.0.0' playImplementation 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava' From 1ee10a5902ea7cbf498183ab4a34ac69863c62d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Sun, 24 May 2020 21:18:49 +0200 Subject: [PATCH 52/85] New Crowdin translations (#847) --- app/src/main/res/values-pl/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index ffd66912c..7f25d8a67 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -47,7 +47,7 @@ Nie znaleziono ucznia. Sprawdź symbol To pole jest wymagane Wybrany uczeń jest już zalogowany - Symbol znajdziesz na stronie dziennika w Uczeń → Dostęp Mobilny → Zarejestruj urządzenie mobilne + Symbol znajdziesz na stronie dziennika w Uczeń → Dostęp Mobilny → Zarejestruj urządzenie mobilne.\n\nUpewnij się, że w polu Dziennik UONET+ na poprzednim ekranie został ustawiony odpowiedni dziennik Wybierz uczniów do zalogowania w aplikacji Inne opcje W tym trybie nie działa szczęśliwy numerek, uczeń na tle klasy, podsumowanie frekwencji, usprawiedliwianie nieobecności, lekcje zrealizowane, informacje o szkole i podgląd listy zarejestrowanych urządzeń @@ -290,7 +290,7 @@ Udostępnij logi Odśwież - Sprawdź aktualizację + Sprawdź dostępność aktualizacji Przed zgłoszeniem błędu sprawdź wcześniej, czy dostępna jest już aktualizacja z poprawką błędu Treść From 4044cdd9a553c76900e1b14758d509ec80cf485d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sun, 24 May 2020 21:20:17 +0200 Subject: [PATCH 53/85] Version 0.18.1 --- .travis.yml | 2 +- app/build.gradle | 6 +++--- app/src/main/play/release-notes/pl-PL/default.txt | 14 ++++++-------- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/.travis.yml b/.travis.yml index ee3e6f3af..d858cf3e0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,7 +14,7 @@ cache: branches: only: - develop - - 0.18.0 + - 0.18.1 android: licenses: diff --git a/app/build.gradle b/app/build.gradle index 224234fc3..31cada744 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -17,8 +17,8 @@ android { testApplicationId "io.github.tests.wulkanowy" minSdkVersion 17 targetSdkVersion 29 - versionCode 59 - versionName "0.18.0" + versionCode 60 + versionName "0.18.1" multiDexEnabled true resValue "string", "app_name", "Wulkanowy" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -124,7 +124,7 @@ configurations.all { } dependencies { - implementation "io.github.wulkanowy:sdk:3c2763c" + implementation "io.github.wulkanowy:sdk:0.18.1" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation "androidx.core:core-ktx:1.2.0" diff --git a/app/src/main/play/release-notes/pl-PL/default.txt b/app/src/main/play/release-notes/pl-PL/default.txt index 427ad4dc0..d61f1b97b 100644 --- a/app/src/main/play/release-notes/pl-PL/default.txt +++ b/app/src/main/play/release-notes/pl-PL/default.txt @@ -1,10 +1,8 @@ -Wersja 0.18.0 -- naprawiliśmy odświeżanie zadań domowych -- naprawiliśmy powiadomienia na androidzie 8.0 -- oceny powinny się teraz odświeżać trochę szybciej -- dodaliśmy tryb pełnoekranowy w zadaniach -- dodaliśmy wyszukiwanie w wiadomościach -- dodaliśmy opcje oznaczania bieżącej lekcji na planie/w powiadomieniu (domyślnie wyłączone) -- dodaliśmy testową opcję naprawy powiadomień na np. Huawei, Xiaomi (znajdziesz ją w ustawieniach) +Wersja 0.18.1 +- naprawiliśmy sortowanie w ocenach +- naprawilismy wiele problemów ze stabilnością +- nazwy opcji w ustawieniach nie są już ucięte +- w zadaniach domowych wyświetlają się teraz pozycje na weekend +- wyłączyliśmy logowanie przez token (bo nie działa i nie wiadomo kiedy będzie działać) Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases From 2ff031005e3d542e1033f16f814771b22a8d41ec Mon Sep 17 00:00:00 2001 From: Piotr Romanowski Date: Sat, 30 May 2020 13:15:28 +0200 Subject: [PATCH 54/85] Fix displaying the feature disabled message in completed lessons (#849) --- .../ui/modules/timetable/completed/CompletedLessonsPresenter.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsPresenter.kt index 7243061d6..92aeb5814 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsPresenter.kt @@ -43,6 +43,7 @@ class CompletedLessonsPresenter @Inject constructor( completedLessonsErrorHandler.showErrorMessage = ::showErrorViewOnError completedLessonsErrorHandler.onFeatureDisabled = { this.view?.showFeatureDisabled() + this.view?.showEmpty(true); Timber.i("Completed lessons feature disabled by school") } loadData(ofEpochDay(date ?: baseDate.toEpochDay())) From df57d16d21dc053bd837c43bbea493192b669d93 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Sat, 30 May 2020 11:16:58 +0000 Subject: [PATCH 55/85] Bump dagger from 2.27 to 2.28 (#850) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 31cada744..2521a10ed 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -114,7 +114,7 @@ play { ext { work_manager = "2.3.4" room = "2.2.5" - dagger = "2.27" + dagger = "2.28" chucker = "3.2.0" mockk = "1.9.2" } From 2149a4db9f7cf77e7d2cdb0592b9625613763acf Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Sat, 30 May 2020 11:17:22 +0000 Subject: [PATCH 56/85] Bump about_libraries from 8.1.3 to 8.1.4 (#851) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 696e778f5..ad2eddaac 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ buildscript { ext.kotlin_version = '1.3.72' - ext.about_libraries = '8.1.3' + ext.about_libraries = '8.1.4' repositories { mavenCentral() google() From 1cfa1f15c0853da971ed7d6e3a49946a044ba9ac Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Sat, 30 May 2020 11:33:43 +0000 Subject: [PATCH 57/85] Bump gradle from 3.6.3 to 4.0.0 (#852) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index ad2eddaac..7b4d04770 100644 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,7 @@ buildscript { } dependencies { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath 'com.android.tools.build:gradle:3.6.3' + classpath 'com.android.tools.build:gradle:4.0.0' classpath 'com.google.gms:google-services:4.3.3' classpath 'com.google.firebase:firebase-crashlytics-gradle:2.1.1' classpath "com.github.triplet.gradle:play-publisher:2.7.5" From ab7d30c9950c9f0eab5581494b8197425cb12394 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Sat, 30 May 2020 12:19:52 +0000 Subject: [PATCH 58/85] Bump sonarqube-gradle-plugin from 2.8 to 3.0 (#853) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 7b4d04770..069c62b24 100644 --- a/build.gradle +++ b/build.gradle @@ -13,7 +13,7 @@ buildscript { classpath 'com.google.gms:google-services:4.3.3' classpath 'com.google.firebase:firebase-crashlytics-gradle:2.1.1' classpath "com.github.triplet.gradle:play-publisher:2.7.5" - classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.8" + classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.0" classpath "gradle.plugin.com.star-zero.gradle:githook:1.2.0" classpath "com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin:${about_libraries}" } From 5c0160a24d2bc3e9e1f9205b2bccec2b2b2138a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Tue, 2 Jun 2020 00:57:22 +0200 Subject: [PATCH 59/85] Don't capture click on login student select checkbox (#862) --- .../login/studentselect/LoginStudentSelectAdapter.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectAdapter.kt index 7383a5ecb..f59260b43 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectAdapter.kt @@ -31,9 +31,14 @@ class LoginStudentSelectAdapter @Inject constructor() : loginItemSchool.text = student.schoolName loginItemName.isEnabled = !alreadySaved loginItemSchool.isEnabled = !alreadySaved - loginItemCheck.isEnabled = !alreadySaved loginItemSignedIn.visibility = if (alreadySaved) View.VISIBLE else View.GONE + with(loginItemCheck) { + isEnabled = !alreadySaved + keyListener = null + isChecked = false + } + root.setOnClickListener { onClickListener(student, alreadySaved) From d8d13c73fb249326a6bd717ed9ed78c020c1c998 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Tue, 2 Jun 2020 01:01:02 +0200 Subject: [PATCH 60/85] Filter out empty items in grade summary (#857) --- .../ui/modules/grade/summary/GradeSummaryPresenter.kt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt index 9b88689db..a61ad4af9 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt @@ -109,7 +109,17 @@ class GradeSummaryPresenter @Inject constructor( private fun createGradeSummaryItems(items: List): List { return items + .filter { !checkEmpty(it) } .sortedBy { it.subject } .map { it.summary.copy(average = it.average) } } + + private fun checkEmpty(gradeSummary: GradeDetailsWithAverage): Boolean { + return gradeSummary.run { + summary.finalGrade.isBlank() + && summary.predictedGrade.isBlank() + && average == .0 + && points == "-" + } + } } From fb554a4a3b3fed614ce11cf66e8ed0572b26593b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Tue, 2 Jun 2020 01:01:58 +0200 Subject: [PATCH 61/85] Fix capitalization in new message activity (#860) --- app/src/main/res/layout/activity_send_message.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/layout/activity_send_message.xml b/app/src/main/res/layout/activity_send_message.xml index f59dcabdf..e0ec52bb6 100644 --- a/app/src/main/res/layout/activity_send_message.xml +++ b/app/src/main/res/layout/activity_send_message.xml @@ -148,7 +148,7 @@ android:hint="@string/message_content" android:imeOptions="flagNoExtractUi" android:importantForAutofill="no" - android:inputType="textMultiLine" + android:inputType="textMultiLine|textCapSentences" android:minHeight="58dp" android:paddingStart="16dp" android:paddingLeft="16dp" From 1db42210e8492a707cea4aaaffd6835e6a93130d Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Tue, 2 Jun 2020 01:02:16 +0200 Subject: [PATCH 62/85] Bump about_libraries from 8.1.4 to 8.1.6 (#861) Bumps `about_libraries` from 8.1.4 to 8.1.6. Updates `aboutlibraries-plugin` from 8.1.4 to 8.1.6 - [Release notes](https://github.com/mikepenz/AboutLibraries/releases) - [Changelog](https://github.com/mikepenz/AboutLibraries/blob/develop/gradle-release.gradle) - [Commits](https://github.com/mikepenz/AboutLibraries/compare/v8.1.4...v8.1.6) Updates `aboutlibraries-core` from 8.1.4 to 8.1.6 - [Release notes](https://github.com/mikepenz/AboutLibraries/releases) - [Changelog](https://github.com/mikepenz/AboutLibraries/blob/develop/gradle-release.gradle) - [Commits](https://github.com/mikepenz/AboutLibraries/compare/v8.1.4...v8.1.6) Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 069c62b24..98cc971e0 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ buildscript { ext.kotlin_version = '1.3.72' - ext.about_libraries = '8.1.4' + ext.about_libraries = '8.1.6' repositories { mavenCentral() google() From 54f41aaa6387da74a559707146d37b746b8603a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Tue, 2 Jun 2020 01:04:02 +0200 Subject: [PATCH 63/85] Fix too many alarms on samsung devices (#859) --- .../services/alarm/TimetableNotificationSchedulerHelper.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationSchedulerHelper.kt b/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationSchedulerHelper.kt index 5374c4767..54b245ddb 100644 --- a/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationSchedulerHelper.kt +++ b/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationSchedulerHelper.kt @@ -3,7 +3,7 @@ package io.github.wulkanowy.services.alarm import android.app.AlarmManager import android.app.AlarmManager.RTC_WAKEUP import android.app.PendingIntent -import android.app.PendingIntent.FLAG_CANCEL_CURRENT +import android.app.PendingIntent.FLAG_UPDATE_CURRENT import android.content.Context import android.content.Intent import androidx.core.app.AlarmManagerCompat @@ -55,7 +55,7 @@ class TimetableNotificationSchedulerHelper @Inject constructor( private fun cancelScheduledTo(range: ClosedRange, requestCode: Int) { if (now() in range) cancelNotification() - alarmManager.cancel(PendingIntent.getBroadcast(context, requestCode, Intent(), FLAG_CANCEL_CURRENT)) + alarmManager.cancel(PendingIntent.getBroadcast(context, requestCode, Intent(), FLAG_UPDATE_CURRENT)) } fun cancelNotification() = NotificationManagerCompat.from(context).cancel(MainView.Section.TIMETABLE.id) @@ -102,7 +102,7 @@ class TimetableNotificationSchedulerHelper @Inject constructor( PendingIntent.getBroadcast(context, getRequestCode(time, studentId), intent.also { it.putExtra(NOTIFICATION_ID, MainView.Section.TIMETABLE.id) it.putExtra(LESSON_TYPE, notificationType) - }, FLAG_CANCEL_CURRENT) + }, FLAG_UPDATE_CURRENT) ) Timber.d("TimetableNotification scheduled: type: $notificationType, subject: ${intent.getStringExtra(LESSON_TITLE)}, start: $time, student: $studentId") } From ba5dbf90d82719c11dbf3e94816bcbd702373f72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Tue, 2 Jun 2020 01:04:41 +0200 Subject: [PATCH 64/85] Fixes in updating adapter items (#854) --- .../grade/details/GradeDetailsAdapter.kt | 59 +++++++++++-------- .../modules/message/tab/MessageTabAdapter.kt | 5 +- 2 files changed, 37 insertions(+), 27 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsAdapter.kt index d5e05f3b9..c129d9485 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsAdapter.kt @@ -7,6 +7,7 @@ import android.view.View import android.view.ViewGroup import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView +import androidx.recyclerview.widget.RecyclerView.NO_POSITION import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.databinding.HeaderGradeDetailsBinding @@ -23,7 +24,7 @@ class GradeDetailsAdapter @Inject constructor() : BaseExpandableAdapter() - private var expandedPosition = RecyclerView.NO_POSITION + private var expandedPosition = NO_POSITION private var isExpandable = false @@ -35,7 +36,7 @@ class GradeDetailsAdapter @Inject constructor() : BaseExpandableAdapter 1) { - Timber.e("Header with subject $subject found ${candidates.size} times! Items: $candidates, expanded: $expandedPosition") + Timber.e("Header with subject $subject found ${candidates.size} times! Expanded: $expandedPosition. Items: $candidates") } return candidates.first() } fun updateHeaderItem(item: GradeDetailsItem) { - headers[headers.indexOf(item)] = item - items[items.indexOf(item)] = item - notifyItemChanged(items.indexOf(item)) + val headerPosition = headers.indexOf(item) + val itemPosition = items.indexOf(item) + + if (headerPosition == NO_POSITION || itemPosition == NO_POSITION) { + Timber.e("Invalid update header positions! Header: $headerPosition, item: $itemPosition") + } + + headers[headerPosition] = item + items[itemPosition] = item + notifyItemChanged(itemPosition) } fun collapseAll() { if (expandedPosition != -1) { refreshList(headers) - expandedPosition = RecyclerView.NO_POSITION + expandedPosition = NO_POSITION } } @Synchronized - private fun refreshList(newItems: List) { + private fun refreshList(newItems: MutableList) { val diffCallback = GradeDetailsDiffUtil(items, newItems) val diffResult = DiffUtil.calculateDiff(diffCallback) - items = newItems.toMutableList() + items = newItems diffResult.dispatchUpdatesTo(this) } @@ -99,23 +103,24 @@ class GradeDetailsAdapter @Inject constructor() : BaseExpandableAdapter bindHeaderViewHolder( - binding = holder.binding, + holder = holder, header = items[position].value as GradeDetailsHeader, - headerPosition = headers.indexOf(items[position]), - adapterPosition = position + position = position ) is ItemViewHolder -> bindItemViewHolder( - binding = holder.binding, - grade = items[position].value as Grade, - position = holder.adapterPosition + holder = holder, + grade = items[position].value as Grade ) } } - private fun bindHeaderViewHolder(binding: HeaderGradeDetailsBinding, header: GradeDetailsHeader, headerPosition: Int, adapterPosition: Int) { - with(binding) { + private fun bindHeaderViewHolder(holder: HeaderViewHolder, header: GradeDetailsHeader, position: Int) { + val headerPosition = headers.indexOf(items[position]) + val adapterPosition = holder.adapterPosition + + with(holder.binding) { gradeHeaderDivider.visibility = if (adapterPosition == 0) View.GONE else View.VISIBLE - gradeHeaderSubject.apply { + with(gradeHeaderSubject) { text = header.subject maxLines = if (headerPosition == expandedPosition) 2 else 1 } @@ -130,7 +135,7 @@ class GradeDetailsAdapter @Inject constructor() : BaseExpandableAdapter Date: Tue, 2 Jun 2020 15:13:41 +0200 Subject: [PATCH 65/85] Revert "Bump sonarqube-gradle-plugin from 2.8 to 3.0" (#864) This reverts commit ab7d30c9950c9f0eab5581494b8197425cb12394. --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 98cc971e0..7e5759d12 100644 --- a/build.gradle +++ b/build.gradle @@ -13,7 +13,7 @@ buildscript { classpath 'com.google.gms:google-services:4.3.3' classpath 'com.google.firebase:firebase-crashlytics-gradle:2.1.1' classpath "com.github.triplet.gradle:play-publisher:2.7.5" - classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.0" + classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.8" classpath "gradle.plugin.com.star-zero.gradle:githook:1.2.0" classpath "com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin:${about_libraries}" } From ff5a47b0dfceddce73067448736da7c0cf3713ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Tue, 2 Jun 2020 15:18:41 +0200 Subject: [PATCH 66/85] New Crowdin translations (#856) --- app/src/main/res/values-ru/strings.xml | 4 +-- app/src/main/res/values-uk/strings.xml | 38 +++++++++++++------------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index c0c751960..6eb8f7741 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -290,8 +290,8 @@ Поделиться логами Обновить - Check for updates - Before reporting a bug, check first if an update with the bug fix is available + Проверить наличие обновлений + Прежде чем сообщать об ошибке, проверьте наличие обновлений Содержание Повторить diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index a63e5682a..ec4dbc155 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -120,14 +120,14 @@ Години Зміни Брак уроків у цей день - %s min - %s sec - %1$s left - in %1$s - Finished - Now: %s - Next: %s - Later: %s + %s хвилин + %s сек + %1$s лишилося + через %1$s + Завершено + Зараз: %s + Наступний: %s + Пізніше: %s Уроки, що відбулися Показати уроки, що відбулися @@ -290,8 +290,8 @@ Поділитися логами Оновити - Check for updates - Before reporting a bug, check first if an update with the bug fix is available + Провірити наявність оновлень + Перед тим, як повідомлювати о помілці, перевірте наявність оновлень Зміст Повторити @@ -308,8 +308,8 @@ Предмет Попередній Наступний - Search - Search... + Пошук + Пошук... Брак уроків Увібрати тему @@ -324,17 +324,17 @@ Показувати присутність у відвідуваності Тема додатку Більше оцінок - Mark current lesson in timetable - Show chart list in class grades + Позначити поточний урок у розкладі + Показувати діаграми в оцінках класу Показати уроки всього класу Схема кольорів оцінок Мова додатку Повідомлення Показувати повідомлення - Show upcoming lesson notifications - Fix synchronization & notifications issues - Your device may have data synchronization issues and with notifications.\n\nTo fix them, you need to add Wulkanowy to the autostart and turn off battery optimization/saving in the phone settings. - Go to settings + Показувати повідомлення о наступних уроках + Виправити помилки з синхронізацією і повідомленнями + На вашому пристрої можуть бути помилки з синхронізацією і повідомленнями\n\nЩоб виправити іх, вам необхідно додати Wulkanowy в авто-старт и вимкнути оптимізацію/экономію батареї в налаштуваннях пристрою. + Перейти до налаштувань Показувати дебаг-повідомлення Синхронізація Автоматична синхронізація @@ -360,7 +360,7 @@ Нові повідомлення Нові нотатки Показувати push-повідомлення - Upcoming lessons + Наступні уроки Дебаг Чорний From 792e44a9d0b263832e5cf70d15cbaf21e723f820 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Tue, 2 Jun 2020 15:50:32 +0200 Subject: [PATCH 67/85] Fix login button state in student select login fragment (#863) --- .../LoginStudentSelectAdapter.kt | 9 ++++++- .../LoginStudentSelectPresenter.kt | 8 +++++- .../layout/fragment_login_student_select.xml | 25 ++++++++++--------- 3 files changed, 28 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectAdapter.kt index f59260b43..3332f2be8 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectAdapter.kt @@ -12,7 +12,13 @@ import javax.inject.Inject class LoginStudentSelectAdapter @Inject constructor() : RecyclerView.Adapter() { + private val checkedList = mutableMapOf() + var items = emptyList>() + set(value) { + field = value + checkedList.clear() + } var onClickListener: (Student, alreadySaved: Boolean) -> Unit = { _, _ -> } @@ -36,7 +42,7 @@ class LoginStudentSelectAdapter @Inject constructor() : with(loginItemCheck) { isEnabled = !alreadySaved keyListener = null - isChecked = false + isChecked = checkedList[position] ?: false } root.setOnClickListener { @@ -45,6 +51,7 @@ class LoginStudentSelectAdapter @Inject constructor() : with(loginItemCheck) { if (isEnabled) { isChecked = !isChecked + checkedList[position] = isChecked } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenter.kt index b2d0aed91..db25c0da3 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenter.kt @@ -22,7 +22,7 @@ class LoginStudentSelectPresenter @Inject constructor( var students = emptyList() - private var selectedStudents = mutableListOf() + private val selectedStudents = mutableListOf() fun onAttachView(view: LoginStudentSelectView, students: Serializable?) { super.onAttachView(view) @@ -69,6 +69,7 @@ class LoginStudentSelectPresenter @Inject constructor( } private fun loadData(students: List) { + resetSelectedState() this.students = students disposable.add(studentRepository.getSavedStudents() .map { savedStudents -> @@ -88,6 +89,11 @@ class LoginStudentSelectPresenter @Inject constructor( ) } + private fun resetSelectedState() { + selectedStudents.clear() + view?.enableSignIn(false) + } + private fun registerStudents(students: List) { disposable.add(studentRepository.saveStudents(students) .map { students.first().apply { id = it.first() } } diff --git a/app/src/main/res/layout/fragment_login_student_select.xml b/app/src/main/res/layout/fragment_login_student_select.xml index 64e06cbd5..f9c6c1572 100644 --- a/app/src/main/res/layout/fragment_login_student_select.xml +++ b/app/src/main/res/layout/fragment_login_student_select.xml @@ -24,10 +24,10 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" + android:visibility="gone" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" - android:visibility="gone" tools:visibility="visible"> + android:layout_marginRight="16dp" + android:orientation="horizontal"> + @@ -95,9 +96,9 @@ android:layout_height="wrap_content" android:layout_marginStart="32dp" android:layout_marginLeft="32dp" + android:layout_marginTop="32dp" android:layout_marginEnd="32dp" android:layout_marginRight="32dp" - android:layout_marginTop="32dp" android:layout_marginBottom="32dp" android:gravity="center_horizontal" android:text="@string/login_select_student" @@ -129,8 +130,8 @@ android:layout_height="wrap_content" android:layout_marginTop="32dp" android:layout_marginEnd="24dp" - android:layout_marginRight="24dp" android:layout_marginBottom="32dp" + android:enabled="false" android:text="@string/login_sign_in" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" From 191b1ad022e19c02bb53231e88e38aba21b63a4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Tue, 2 Jun 2020 15:51:15 +0200 Subject: [PATCH 68/85] Emulate summaries from grade list when summaries are empty (#855) --- app/build.gradle | 2 +- .../ui/modules/grade/GradeAverageProvider.kt | 33 ++- .../grade/summary/GradeSummaryPresenter.kt | 2 +- .../modules/grade/GradeAverageProviderTest.kt | 269 ++++++++++++------ 4 files changed, 215 insertions(+), 91 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 2521a10ed..f29af84fa 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -124,7 +124,7 @@ configurations.all { } dependencies { - implementation "io.github.wulkanowy:sdk:0.18.1" + implementation "io.github.wulkanowy:sdk:d0081b4" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation "androidx.core:core-ktx:1.2.0" diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt index 954b57566..9582a5db3 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt @@ -1,5 +1,7 @@ package io.github.wulkanowy.ui.modules.grade +import io.github.wulkanowy.data.db.entities.Grade +import io.github.wulkanowy.data.db.entities.GradeSummary import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.repositories.grade.GradeRepository @@ -58,15 +60,14 @@ class GradeAverageProvider @Inject constructor( val isAnyAverage = summaries.any { it.average != .0 } val allGrades = details.groupBy { it.subject } - summaries.map { summary -> + summaries.emulateEmptySummaries(student, semester, allGrades.toList(), isAnyAverage).map { summary -> val grades = allGrades[summary.subject].orEmpty() GradeDetailsWithAverage( subject = summary.subject, average = if (!isAnyAverage || preferencesRepository.gradeAverageForceCalc) { - grades.map { - if (student.loginMode == Sdk.Mode.SCRAPPER.name) it.changeModifier(plusModifier, minusModifier) - else it - }.calcAverage() + (if (student.loginMode == Sdk.Mode.SCRAPPER.name) + grades.map { it.changeModifier(plusModifier, minusModifier) } + else grades).calcAverage() } else summary.average, points = summary.pointsSum, summary = summary, @@ -75,4 +76,26 @@ class GradeAverageProvider @Inject constructor( } } } + + private fun List.emulateEmptySummaries(student: Student, semester: Semester, grades: List>>, calcAverage: Boolean): List { + if (isNotEmpty() && size == grades.size) return this + + return grades.mapIndexed { i, (subject, details) -> + singleOrNull { it.subject == subject }?.let { return@mapIndexed it } + GradeSummary( + studentId = student.studentId, + semesterId = semester.semesterId, + position = i, + subject = subject, + predictedGrade = "", + finalGrade = "", + proposedPoints = "", + finalPoints = "", + pointsSum = "", + average = if (calcAverage) (if (student.loginMode == Sdk.Mode.SCRAPPER.name) { + details.map { it.changeModifier(plusModifier, minusModifier) } + } else details).calcAverage() else .0 + ) + } + } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt index a61ad4af9..229a0107d 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt @@ -119,7 +119,7 @@ class GradeSummaryPresenter @Inject constructor( summary.finalGrade.isBlank() && summary.predictedGrade.isBlank() && average == .0 - && points == "-" + && points.isBlank() } } } diff --git a/app/src/test/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProviderTest.kt b/app/src/test/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProviderTest.kt index fcc30593f..ee8634ca8 100644 --- a/app/src/test/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProviderTest.kt +++ b/app/src/test/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProviderTest.kt @@ -1,11 +1,11 @@ package io.github.wulkanowy.ui.modules.grade +import io.github.wulkanowy.createSemesterEntity import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.db.entities.GradeSummary import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.repositories.grade.GradeRepository import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository -import io.github.wulkanowy.createSemesterEntity import io.github.wulkanowy.data.repositories.semester.SemesterRepository import io.github.wulkanowy.sdk.Sdk import io.reactivex.Single @@ -84,8 +84,6 @@ class GradeAverageProviderTest { fun setUp() { MockitoAnnotations.initMocks(this) - `when`(preferencesRepository.gradeMinusModifier).thenReturn(.33) - `when`(preferencesRepository.gradePlusModifier).thenReturn(.33) `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(false) `when`(semesterRepository.getSemesters(student)).thenReturn(Single.just(semesters)) @@ -93,7 +91,83 @@ class GradeAverageProviderTest { } @Test - fun onlyOneSemesterTest() { + fun `force calc current semester average with default modifiers in scraper mode`() { + `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true) + `when`(preferencesRepository.gradeAverageMode).thenReturn("only_one_semester") + `when`(semesterRepository.getSemesters(student)).thenReturn(Single.just(semesters)) + `when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGradeWithModifier to secondSummariesWithModifier)) + + val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet() + + assertEquals(3.5, items.single { it.subject == "Język polski" }.average, .0) // from details and after set custom plus/minus + } + + @Test + fun `force calc current semester average with custom modifiers in scraper mode`() { + val student = student.copy(loginMode = Sdk.Mode.SCRAPPER.name) + + `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true) + `when`(preferencesRepository.gradeMinusModifier).thenReturn(.33) + `when`(preferencesRepository.gradePlusModifier).thenReturn(.33) + + `when`(preferencesRepository.gradeAverageMode).thenReturn("only_one_semester") + `when`(semesterRepository.getSemesters(student)).thenReturn(Single.just(semesters)) + `when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGradeWithModifier to secondSummariesWithModifier)) + + val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet() + + assertEquals(3.5, items.single { it.subject == "Język polski" }.average, .0) // from details and after set custom plus/minus + } + + @Test + fun `force calc current semester average with custom modifiers in api mode`() { + val student = student.copy(loginMode = Sdk.Mode.API.name) + + `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true) + `when`(preferencesRepository.gradeMinusModifier).thenReturn(.33) // useless in this mode + `when`(preferencesRepository.gradePlusModifier).thenReturn(.33) + + `when`(preferencesRepository.gradeAverageMode).thenReturn("only_one_semester") + `when`(semesterRepository.getSemesters(student)).thenReturn(Single.just(semesters)) + `when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGradeWithModifier to secondSummariesWithModifier)) + + val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet() + + assertEquals(3.375, items.single { it.subject == "Język polski" }.average, .0) // (from details): 3.375 + } + + @Test + fun `force calc current semester average with custom modifiers in hybrid mode`() { + val student = student.copy(loginMode = Sdk.Mode.HYBRID.name) + + `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true) + `when`(preferencesRepository.gradeMinusModifier).thenReturn(.33) // useless in this mode + `when`(preferencesRepository.gradePlusModifier).thenReturn(.33) + + `when`(preferencesRepository.gradeAverageMode).thenReturn("only_one_semester") + `when`(semesterRepository.getSemesters(student)).thenReturn(Single.just(semesters)) + `when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGradeWithModifier to secondSummariesWithModifier)) + + val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet() + + assertEquals(3.375, items.single { it.subject == "Język polski" }.average, .0) // (from details): 3.375 + } + + @Test + fun `calc current semester average`() { + `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(false) + `when`(preferencesRepository.gradeAverageMode).thenReturn("only_one_semester") + `when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGrades to secondSummaries)) + + val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet() + + assertEquals(2, items.size) + assertEquals(2.9, items.single { it.subject == "Matematyka" }.average, .0) // from summary: 2,9 + assertEquals(3.4, items.single { it.subject == "Fizyka" }.average, .0) // from details: 3,4 + } + + @Test + fun `force calc current semester average`() { `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true) `when`(preferencesRepository.gradeAverageMode).thenReturn("only_one_semester") `when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGrades to secondSummaries)) @@ -101,65 +175,12 @@ class GradeAverageProviderTest { val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet() assertEquals(2, items.size) - assertEquals(2.5, items.single { it.subject == "Matematyka" }.average, .0) - assertEquals(3.0, items.single { it.subject == "Fizyka" }.average, .0) + assertEquals(2.5, items.single { it.subject == "Matematyka" }.average, .0) // from details: 2,5 + assertEquals(3.0, items.single { it.subject == "Fizyka" }.average, .0) // from details: 3,0 } @Test - fun onlyOneSemester_gradesWithModifiers_default() { - `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true) - `when`(preferencesRepository.gradeAverageMode).thenReturn("only_one_semester") - `when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGradeWithModifier to secondSummariesWithModifier)) - - val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet() - - assertEquals(3.5, items.single { it.subject == "Język polski" }.average, .0) - } - - @Test - fun onlyOneSemester_gradesWithModifiers_api() { - val student = student.copy(loginMode = Sdk.Mode.API.name) - - `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true) - `when`(preferencesRepository.gradeAverageMode).thenReturn("only_one_semester") - `when`(semesterRepository.getSemesters(student)).thenReturn(Single.just(semesters)) - `when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGradeWithModifier to secondSummariesWithModifier)) - - val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet() - - assertEquals(3.375, items.single { it.subject == "Język polski" }.average, .0) - } - - @Test - fun onlyOneSemester_gradesWithModifiers_scrapper() { - val student = student.copy(loginMode = Sdk.Mode.SCRAPPER.name) - - `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true) - `when`(preferencesRepository.gradeAverageMode).thenReturn("only_one_semester") - `when`(semesterRepository.getSemesters(student)).thenReturn(Single.just(semesters)) - `when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGradeWithModifier to secondSummariesWithModifier)) - - val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet() - - assertEquals(3.5, items.single { it.subject == "Język polski" }.average, .0) - } - - @Test - fun onlyOneSemester_gradesWithModifiers_hybrid() { - val student = student.copy(loginMode = Sdk.Mode.HYBRID.name) - - `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true) - `when`(preferencesRepository.gradeAverageMode).thenReturn("only_one_semester") - `when`(semesterRepository.getSemesters(student)).thenReturn(Single.just(semesters)) - `when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGradeWithModifier to secondSummariesWithModifier)) - - val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet() - - assertEquals(3.375, items.single { it.subject == "Język polski" }.average, .0) - } - - @Test - fun allYearFirstSemesterTest() { + fun `force calc full year average when current is first`() { `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true) `when`(preferencesRepository.gradeAverageMode).thenReturn("all_year") `when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(firstGrades to firstSummaries)) @@ -167,33 +188,19 @@ class GradeAverageProviderTest { val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[1].semesterId).blockingGet() assertEquals(2, items.size) - assertEquals(3.5, items.single { it.subject == "Matematyka" }.average, .0) - assertEquals(3.5, items.single { it.subject == "Fizyka" }.average, .0) - } - - @Test - fun allYearSecondSemesterTest() { - `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true) - `when`(preferencesRepository.gradeAverageMode).thenReturn("all_year") - `when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(firstGrades to firstSummaries)) - `when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGrades to secondSummaries)) - - val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet() - - assertEquals(2, items.size) - assertEquals(3.0, items.single { it.subject == "Matematyka" }.average, .0) - assertEquals(3.25, items.single { it.subject == "Fizyka" }.average, .0) + assertEquals(3.5, items.single { it.subject == "Matematyka" }.average, .0) // (from summary): 3,5 + assertEquals(3.5, items.single { it.subject == "Fizyka" }.average, .0) // (from summary): 3,5 } @Test(expected = IllegalArgumentException::class) - fun incorrectAverageModeTest() { + fun `calc average on invalid mode`() { `when`(preferencesRepository.gradeAverageMode).thenReturn("test_mode") gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).blockingGet() } @Test - fun allYearSemester_averageFromSummary() { + fun `calc full year average`() { `when`(preferencesRepository.gradeAverageMode).thenReturn("all_year") `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(false) `when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(firstGrades to listOf( @@ -208,14 +215,14 @@ class GradeAverageProviderTest { val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet() assertEquals(2, items.size) - assertEquals(3.25, items.single { it.subject == "Matematyka" }.average, .0) - assertEquals(3.75, items.single { it.subject == "Fizyka" }.average, .0) + assertEquals(3.25, items.single { it.subject == "Matematyka" }.average, .0) // (from summaries ↑): 3,0 + 3,5 → 3,25 + assertEquals(3.75, items.single { it.subject == "Fizyka" }.average, .0) // (from summaries ↑): 3,5 + 4,0 → 3,75 } @Test - fun onlyOneSemester_averageFromSummary_forceCalc() { - `when`(preferencesRepository.gradeAverageMode).thenReturn("all_year") + fun `force calc full year average`() { `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true) + `when`(preferencesRepository.gradeAverageMode).thenReturn("all_year") `when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(firstGrades to firstSummaries)) `when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGrades to listOf( getSummary(22, "Matematyka", 1.1), @@ -225,8 +232,102 @@ class GradeAverageProviderTest { val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet() assertEquals(2, items.size) - assertEquals(3.0, items.single { it.subject == "Matematyka" }.average, .0) - assertEquals(3.25, items.single { it.subject == "Fizyka" }.average, .0) + assertEquals(3.0, items.single { it.subject == "Matematyka" }.average, .0) // (from details): 3,5 + 2,5 → 3,0 + assertEquals(3.25, items.single { it.subject == "Fizyka" }.average, .0) // (from details): 3,5 + 3,0 → 3,25 + } + + @Test + fun `calc full year average when no summaries`() { + `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(false) + `when`(preferencesRepository.gradeAverageMode).thenReturn("all_year") + + `when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(firstGrades to emptyList())) + `when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGrades to emptyList())) + + val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet() + + assertEquals(2, items.size) + assertEquals(3.0, items.single { it.subject == "Matematyka" }.average, .0) // (from details): 2,5 + 3,5 → 3,0 + assertEquals(3.25, items.single { it.subject == "Fizyka" }.average, .0) // (from details): 3,5 + 3,0 → 3,25 + } + + @Test + fun `force calc full year average when no summaries`() { + `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true) + `when`(preferencesRepository.gradeAverageMode).thenReturn("all_year") + + `when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(firstGrades to emptyList())) + `when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGrades to emptyList())) + + val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet() + + assertEquals(2, items.size) + assertEquals(3.0, items.single { it.subject == "Matematyka" }.average, .0) // (from details): 3,5 + 2,5 → 3,0 + assertEquals(3.25, items.single { it.subject == "Fizyka" }.average, .0) // (from details): 3,5 + 3,0 → 3,25 + } + + @Test + fun `calc full year average when missing summaries in both semesters`() { + `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(false) + `when`(preferencesRepository.gradeAverageMode).thenReturn("all_year") + + `when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(firstGrades to listOf( + getSummary(22, "Matematyka", 4.0) + ))) + `when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGrades to listOf( + getSummary(23, "Matematyka", 3.0) + ))) + + val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet() + + assertEquals(2, items.size) + assertEquals(3.5, items.single { it.subject == "Matematyka" }.average, .0) // (from summaries ↑): 4,0 + 3,0 → 3,5 + assertEquals(3.25, items.single { it.subject == "Fizyka" }.average, .0) // (from details): 3,5 + 3,0 → 3,25 + } + + @Test + fun `calc full year average when missing summary in second semester`() { + `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(false) + `when`(preferencesRepository.gradeAverageMode).thenReturn("all_year") + + `when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(firstGrades to firstSummaries)) + `when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGrades to secondSummaries.dropLast(1))) + + val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet() + + assertEquals(2, items.size) + assertEquals(3.4, items.single { it.subject == "Matematyka" }.average, .0) // (from summaries): 3,9 + 2,9 → 3,4 + assertEquals(3.05, items.single { it.subject == "Fizyka" }.average, .0) // 3,1 (from summary) + 3,0 (from details) → 3,05 + } + + @Test + fun `calc full year average when missing summary in first semester`() { + `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(false) + `when`(preferencesRepository.gradeAverageMode).thenReturn("all_year") + + `when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(firstGrades to firstSummaries.dropLast(1))) + `when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGrades to secondSummaries)) + + val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet() + + assertEquals(2, items.size) + assertEquals(3.4, items.single { it.subject == "Matematyka" }.average, .0) // (from summaries): 3,9 + 2,9 → 3,4 + assertEquals(3.45, items.single { it.subject == "Fizyka" }.average, .0) // 3,5 (from details) + 3,4 (from summary) → 3,45 + } + + @Test + fun `force calc full year average when missing summary in first semester`() { + `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true) + `when`(preferencesRepository.gradeAverageMode).thenReturn("all_year") + + `when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(firstGrades to firstSummaries.dropLast(1))) + `when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGrades to secondSummaries)) + + val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet() + + assertEquals(2, items.size) + assertEquals(3.0, items.single { it.subject == "Matematyka" }.average, .0) // (from details): 3,5 + 2,5 → 3,0 + assertEquals(3.25, items.single { it.subject == "Fizyka" }.average, .0) // (from details): 3,5 + 3,0 → 3,25 } private fun getGrade(semesterId: Int, subject: String, value: Double, modifier: Double = 0.0): Grade { From 419675066f516786fc78095273c78f142be15b2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Tue, 2 Jun 2020 17:07:52 +0200 Subject: [PATCH 69/85] Version 0.18.2 --- .travis.yml | 2 +- app/build.gradle | 6 +++--- app/src/main/play/release-notes/pl-PL/default.txt | 13 +++++++------ 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/.travis.yml b/.travis.yml index d858cf3e0..4bb842821 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,7 +14,7 @@ cache: branches: only: - develop - - 0.18.1 + - 0.18.2 android: licenses: diff --git a/app/build.gradle b/app/build.gradle index f29af84fa..e2aab017d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -17,8 +17,8 @@ android { testApplicationId "io.github.tests.wulkanowy" minSdkVersion 17 targetSdkVersion 29 - versionCode 60 - versionName "0.18.1" + versionCode 61 + versionName "0.18.2" multiDexEnabled true resValue "string", "app_name", "Wulkanowy" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -124,7 +124,7 @@ configurations.all { } dependencies { - implementation "io.github.wulkanowy:sdk:d0081b4" + implementation "io.github.wulkanowy:sdk:0.18.2" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation "androidx.core:core-ktx:1.2.0" diff --git a/app/src/main/play/release-notes/pl-PL/default.txt b/app/src/main/play/release-notes/pl-PL/default.txt index d61f1b97b..1908d2e26 100644 --- a/app/src/main/play/release-notes/pl-PL/default.txt +++ b/app/src/main/play/release-notes/pl-PL/default.txt @@ -1,8 +1,9 @@ -Wersja 0.18.1 -- naprawiliśmy sortowanie w ocenach -- naprawilismy wiele problemów ze stabilnością -- nazwy opcji w ustawieniach nie są już ucięte -- w zadaniach domowych wyświetlają się teraz pozycje na weekend -- wyłączyliśmy logowanie przez token (bo nie działa i nie wiadomo kiedy będzie działać) +Wersja 0.18.2 +- naprawiliśmy zaznaczanie uczniów przy logowaniu +- naprawiliśmy odświeżanie planu lekcji na samsungach +- naprawiliśmy wysyłanie wiadomości +- poprawiliśmy oznaczanie nowych wiadomości jako przeczytanych +- w podsumowaniu ocen nie będą się już pokazywać „puste” przedmioty +- w polu pisania wiadomości pierwsza litera w zdaniu będzie teraz domyślnie duża Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases From 522a36d67027dc775f69c773a6272438fa04bd1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Wed, 10 Jun 2020 16:26:45 +0200 Subject: [PATCH 70/85] Fix message deleting (#875) --- .../github/wulkanowy/data/repositories/message/MessageRemote.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/message/MessageRemote.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/message/MessageRemote.kt index 1808c048b..2df2e20a3 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/message/MessageRemote.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/message/MessageRemote.kt @@ -75,6 +75,6 @@ class MessageRemote @Inject constructor(private val sdk: Sdk) { } fun deleteMessage(student: Student, message: Message): Single { - return sdk.init(student).deleteMessages(listOf(Pair(message.realId, message.folderId))) + return sdk.init(student).deleteMessages(listOf(message.messageId to message.folderId)) } } From 5c84c8d5b175a219bc8db987acfcae893641dc8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Wed, 10 Jun 2020 17:28:49 +0200 Subject: [PATCH 71/85] Fix force average calc from two semesters (#870) --- app/build.gradle | 2 +- .../preferences/PreferencesRepository.kt | 5 +- .../ui/modules/grade/GradeAverageMode.kt | 11 ++ .../ui/modules/grade/GradeAverageProvider.kt | 58 ++++-- .../main/res/values-de/preferences_values.xml | 1 + .../main/res/values-pl/preferences_values.xml | 5 +- .../main/res/values-ru/preferences_values.xml | 1 + .../main/res/values-uk/preferences_values.xml | 1 + .../main/res/values/preferences_values.xml | 2 + .../modules/grade/GradeAverageProviderTest.kt | 173 ++++++++++++++---- 10 files changed, 206 insertions(+), 53 deletions(-) create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageMode.kt diff --git a/app/build.gradle b/app/build.gradle index e2aab017d..cb735b3b4 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -124,7 +124,7 @@ configurations.all { } dependencies { - implementation "io.github.wulkanowy:sdk:0.18.2" + implementation "io.github.wulkanowy:sdk:a57afdb" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation "androidx.core:core-ktx:1.2.0" diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/preferences/PreferencesRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/preferences/PreferencesRepository.kt index 6b13563a4..7715cde0d 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/preferences/PreferencesRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/preferences/PreferencesRepository.kt @@ -3,6 +3,7 @@ package io.github.wulkanowy.data.repositories.preferences import android.content.Context import android.content.SharedPreferences import io.github.wulkanowy.R +import io.github.wulkanowy.ui.modules.grade.GradeAverageMode import javax.inject.Inject import javax.inject.Singleton @@ -17,8 +18,8 @@ class PreferencesRepository @Inject constructor( val isShowPresent: Boolean get() = getBoolean(R.string.pref_key_attendance_present, R.bool.pref_default_attendance_present) - val gradeAverageMode: String - get() = getString(R.string.pref_key_grade_average_mode, R.string.pref_default_grade_average_mode) + val gradeAverageMode: GradeAverageMode + get() = GradeAverageMode.getByValue(getString(R.string.pref_key_grade_average_mode, R.string.pref_default_grade_average_mode)) val gradeAverageForceCalc: Boolean get() = getBoolean(R.string.pref_key_grade_average_force_calc, R.bool.pref_default_grade_average_force_calc) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageMode.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageMode.kt new file mode 100644 index 000000000..1960c3df7 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageMode.kt @@ -0,0 +1,11 @@ +package io.github.wulkanowy.ui.modules.grade + +enum class GradeAverageMode(val value: String) { + ALL_YEAR("all_year"), + ONE_SEMESTER("only_one_semester"), + BOTH_SEMESTERS("both_semesters"); + + companion object { + fun getByValue(value: String) = values().firstOrNull { it.value == value } ?: ONE_SEMESTER + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt index 9582a5db3..bda098f47 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt @@ -8,6 +8,9 @@ import io.github.wulkanowy.data.repositories.grade.GradeRepository import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository import io.github.wulkanowy.data.repositories.semester.SemesterRepository import io.github.wulkanowy.sdk.Sdk +import io.github.wulkanowy.ui.modules.grade.GradeAverageMode.ALL_YEAR +import io.github.wulkanowy.ui.modules.grade.GradeAverageMode.BOTH_SEMESTERS +import io.github.wulkanowy.ui.modules.grade.GradeAverageMode.ONE_SEMESTER import io.github.wulkanowy.utils.calcAverage import io.github.wulkanowy.utils.changeModifier import io.reactivex.Single @@ -19,21 +22,21 @@ class GradeAverageProvider @Inject constructor( private val preferencesRepository: PreferencesRepository ) { - private val plusModifier = preferencesRepository.gradePlusModifier + private val plusModifier get() = preferencesRepository.gradePlusModifier - private val minusModifier = preferencesRepository.gradeMinusModifier + private val minusModifier get() = preferencesRepository.gradeMinusModifier fun getGradesDetailsWithAverage(student: Student, semesterId: Int, forceRefresh: Boolean = false): Single> { return semesterRepository.getSemesters(student).flatMap { semesters -> when (preferencesRepository.gradeAverageMode) { - "only_one_semester" -> getSemesterDetailsWithAverage(student, semesters.single { it.semesterId == semesterId }, forceRefresh) - "all_year" -> calculateWholeYearAverage(student, semesters, semesterId, forceRefresh) - else -> throw IllegalArgumentException("Incorrect grade average mode: ${preferencesRepository.gradeAverageMode} ") + ONE_SEMESTER -> getSemesterDetailsWithAverage(student, semesters.single { it.semesterId == semesterId }, forceRefresh) + BOTH_SEMESTERS -> calculateBothSemestersAverage(student, semesters, semesterId, forceRefresh) + ALL_YEAR -> calculateAllYearAverage(student, semesters, semesterId, forceRefresh) } } } - private fun calculateWholeYearAverage(student: Student, semesters: List, semesterId: Int, forceRefresh: Boolean): Single> { + private fun calculateBothSemestersAverage(student: Student, semesters: List, semesterId: Int, forceRefresh: Boolean): Single> { val selectedSemester = semesters.single { it.semesterId == semesterId } val firstSemester = semesters.single { it.diaryId == selectedSemester.diaryId && it.semesterName == 1 } @@ -44,11 +47,30 @@ class GradeAverageProvider @Inject constructor( getSemesterDetailsWithAverage(student, firstSemester, forceRefresh).map { secondDetails -> selectedDetails.map { selected -> val second = secondDetails.singleOrNull { it.subject == selected.subject } - selected.copy( - average = if (!isAnyAverage || preferencesRepository.gradeAverageForceCalc) { - (selected.grades + second?.grades.orEmpty()).calcAverage() - } else (selected.average + (second?.average ?: selected.average)) / 2 - ) + selected.copy(average = if (!isAnyAverage || preferencesRepository.gradeAverageForceCalc) { + val selectedGrades = selected.grades.updateModifiers(student).calcAverage() + (selectedGrades + (second?.grades?.updateModifiers(student)?.calcAverage() ?: selectedGrades)) / 2 + } else (selected.average + (second?.average ?: selected.average)) / 2) + } + } + } else Single.just(selectedDetails) + } + } + + private fun calculateAllYearAverage(student: Student, semesters: List, semesterId: Int, forceRefresh: Boolean): Single> { + val selectedSemester = semesters.single { it.semesterId == semesterId } + val firstSemester = semesters.single { it.diaryId == selectedSemester.diaryId && it.semesterName == 1 } + + return getSemesterDetailsWithAverage(student, selectedSemester, forceRefresh).flatMap { selectedDetails -> + val isAnyAverage = selectedDetails.any { it.average != .0 } + + if (selectedSemester != firstSemester) { + getSemesterDetailsWithAverage(student, firstSemester, forceRefresh).map { secondDetails -> + selectedDetails.map { selected -> + val second = secondDetails.singleOrNull { it.subject == selected.subject } + selected.copy(average = if (!isAnyAverage || preferencesRepository.gradeAverageForceCalc) { + (selected.grades.updateModifiers(student) + second?.grades?.updateModifiers(student).orEmpty()).calcAverage() + } else selected.average) } } } else Single.just(selectedDetails) @@ -65,9 +87,7 @@ class GradeAverageProvider @Inject constructor( GradeDetailsWithAverage( subject = summary.subject, average = if (!isAnyAverage || preferencesRepository.gradeAverageForceCalc) { - (if (student.loginMode == Sdk.Mode.SCRAPPER.name) - grades.map { it.changeModifier(plusModifier, minusModifier) } - else grades).calcAverage() + grades.updateModifiers(student).calcAverage() } else summary.average, points = summary.pointsSum, summary = summary, @@ -92,10 +112,14 @@ class GradeAverageProvider @Inject constructor( proposedPoints = "", finalPoints = "", pointsSum = "", - average = if (calcAverage) (if (student.loginMode == Sdk.Mode.SCRAPPER.name) { - details.map { it.changeModifier(plusModifier, minusModifier) } - } else details).calcAverage() else .0 + average = if (calcAverage) details.updateModifiers(student).calcAverage() else .0 ) } } + + private fun List.updateModifiers(student: Student): List { + return if (student.loginMode == Sdk.Mode.SCRAPPER.name) { + map { it.changeModifier(plusModifier, minusModifier) } + } else this + } } diff --git a/app/src/main/res/values-de/preferences_values.xml b/app/src/main/res/values-de/preferences_values.xml index 28ad08bc2..496287984 100644 --- a/app/src/main/res/values-de/preferences_values.xml +++ b/app/src/main/res/values-de/preferences_values.xml @@ -36,6 +36,7 @@ Durchschnittsnote für das 2. Semester + Average of grades from both semesters Durchschnitt der Bewertungen für das ganze Jahr diff --git a/app/src/main/res/values-pl/preferences_values.xml b/app/src/main/res/values-pl/preferences_values.xml index 475f7327f..1d81fb586 100644 --- a/app/src/main/res/values-pl/preferences_values.xml +++ b/app/src/main/res/values-pl/preferences_values.xml @@ -35,8 +35,9 @@ Kolory ocen w dzienniku - Średnia ocen z 2 semestru - Średnia ocen z całego roku + Średnia ocen z drugiego semestru + Średnia średnich z obu semestrów + Średnia wszystkich ocen z całego roku Nie pokazuj diff --git a/app/src/main/res/values-ru/preferences_values.xml b/app/src/main/res/values-ru/preferences_values.xml index 6c1522682..bd8bb844f 100644 --- a/app/src/main/res/values-ru/preferences_values.xml +++ b/app/src/main/res/values-ru/preferences_values.xml @@ -36,6 +36,7 @@ Средняя оценка со 2 семестра + Average of grades from both semesters Средняя оценка с целого года diff --git a/app/src/main/res/values-uk/preferences_values.xml b/app/src/main/res/values-uk/preferences_values.xml index 3c70c9d09..be2179c3e 100644 --- a/app/src/main/res/values-uk/preferences_values.xml +++ b/app/src/main/res/values-uk/preferences_values.xml @@ -36,6 +36,7 @@ Середня оцінка з 2 семестру + Average of grades from both semesters Середня оцінка за весь рік diff --git a/app/src/main/res/values/preferences_values.xml b/app/src/main/res/values/preferences_values.xml index 76427a487..5824658c4 100644 --- a/app/src/main/res/values/preferences_values.xml +++ b/app/src/main/res/values/preferences_values.xml @@ -88,10 +88,12 @@ Average of grades only from the 2nd semester + Average of grades from both semesters Average of grades from the whole year only_one_semester + both_semesters all_year diff --git a/app/src/test/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProviderTest.kt b/app/src/test/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProviderTest.kt index ee8634ca8..984bbbcf8 100644 --- a/app/src/test/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProviderTest.kt +++ b/app/src/test/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProviderTest.kt @@ -86,6 +86,8 @@ class GradeAverageProviderTest { `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(false) `when`(semesterRepository.getSemesters(student)).thenReturn(Single.just(semesters)) + `when`(preferencesRepository.gradeMinusModifier).thenReturn(.33) + `when`(preferencesRepository.gradePlusModifier).thenReturn(.33) gradeAverageProvider = GradeAverageProvider(semesterRepository, gradeRepository, preferencesRepository) } @@ -93,7 +95,7 @@ class GradeAverageProviderTest { @Test fun `force calc current semester average with default modifiers in scraper mode`() { `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true) - `when`(preferencesRepository.gradeAverageMode).thenReturn("only_one_semester") + `when`(preferencesRepository.gradeAverageMode).thenReturn(GradeAverageMode.ONE_SEMESTER) `when`(semesterRepository.getSemesters(student)).thenReturn(Single.just(semesters)) `when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGradeWithModifier to secondSummariesWithModifier)) @@ -110,7 +112,7 @@ class GradeAverageProviderTest { `when`(preferencesRepository.gradeMinusModifier).thenReturn(.33) `when`(preferencesRepository.gradePlusModifier).thenReturn(.33) - `when`(preferencesRepository.gradeAverageMode).thenReturn("only_one_semester") + `when`(preferencesRepository.gradeAverageMode).thenReturn(GradeAverageMode.ONE_SEMESTER) `when`(semesterRepository.getSemesters(student)).thenReturn(Single.just(semesters)) `when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGradeWithModifier to secondSummariesWithModifier)) @@ -127,7 +129,7 @@ class GradeAverageProviderTest { `when`(preferencesRepository.gradeMinusModifier).thenReturn(.33) // useless in this mode `when`(preferencesRepository.gradePlusModifier).thenReturn(.33) - `when`(preferencesRepository.gradeAverageMode).thenReturn("only_one_semester") + `when`(preferencesRepository.gradeAverageMode).thenReturn(GradeAverageMode.ONE_SEMESTER) `when`(semesterRepository.getSemesters(student)).thenReturn(Single.just(semesters)) `when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGradeWithModifier to secondSummariesWithModifier)) @@ -144,7 +146,7 @@ class GradeAverageProviderTest { `when`(preferencesRepository.gradeMinusModifier).thenReturn(.33) // useless in this mode `when`(preferencesRepository.gradePlusModifier).thenReturn(.33) - `when`(preferencesRepository.gradeAverageMode).thenReturn("only_one_semester") + `when`(preferencesRepository.gradeAverageMode).thenReturn(GradeAverageMode.ONE_SEMESTER) `when`(semesterRepository.getSemesters(student)).thenReturn(Single.just(semesters)) `when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGradeWithModifier to secondSummariesWithModifier)) @@ -156,7 +158,7 @@ class GradeAverageProviderTest { @Test fun `calc current semester average`() { `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(false) - `when`(preferencesRepository.gradeAverageMode).thenReturn("only_one_semester") + `when`(preferencesRepository.gradeAverageMode).thenReturn(GradeAverageMode.ONE_SEMESTER) `when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGrades to secondSummaries)) val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet() @@ -169,7 +171,7 @@ class GradeAverageProviderTest { @Test fun `force calc current semester average`() { `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true) - `when`(preferencesRepository.gradeAverageMode).thenReturn("only_one_semester") + `when`(preferencesRepository.gradeAverageMode).thenReturn(GradeAverageMode.ONE_SEMESTER) `when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGrades to secondSummaries)) val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet() @@ -182,7 +184,7 @@ class GradeAverageProviderTest { @Test fun `force calc full year average when current is first`() { `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true) - `when`(preferencesRepository.gradeAverageMode).thenReturn("all_year") + `when`(preferencesRepository.gradeAverageMode).thenReturn(GradeAverageMode.ALL_YEAR) `when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(firstGrades to firstSummaries)) val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[1].semesterId).blockingGet() @@ -192,16 +194,9 @@ class GradeAverageProviderTest { assertEquals(3.5, items.single { it.subject == "Fizyka" }.average, .0) // (from summary): 3,5 } - @Test(expected = IllegalArgumentException::class) - fun `calc average on invalid mode`() { - `when`(preferencesRepository.gradeAverageMode).thenReturn("test_mode") - - gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).blockingGet() - } - @Test - fun `calc full year average`() { - `when`(preferencesRepository.gradeAverageMode).thenReturn("all_year") + fun `calc both semesters average`() { + `when`(preferencesRepository.gradeAverageMode).thenReturn(GradeAverageMode.BOTH_SEMESTERS) `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(false) `when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(firstGrades to listOf( getSummary(22, "Matematyka", 3.0), @@ -222,7 +217,7 @@ class GradeAverageProviderTest { @Test fun `force calc full year average`() { `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true) - `when`(preferencesRepository.gradeAverageMode).thenReturn("all_year") + `when`(preferencesRepository.gradeAverageMode).thenReturn(GradeAverageMode.ALL_YEAR) `when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(firstGrades to firstSummaries)) `when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGrades to listOf( getSummary(22, "Matematyka", 1.1), @@ -237,9 +232,9 @@ class GradeAverageProviderTest { } @Test - fun `calc full year average when no summaries`() { + fun `calc both semesters average when no summaries`() { `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(false) - `when`(preferencesRepository.gradeAverageMode).thenReturn("all_year") + `when`(preferencesRepository.gradeAverageMode).thenReturn(GradeAverageMode.BOTH_SEMESTERS) `when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(firstGrades to emptyList())) `when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGrades to emptyList())) @@ -247,14 +242,14 @@ class GradeAverageProviderTest { val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet() assertEquals(2, items.size) - assertEquals(3.0, items.single { it.subject == "Matematyka" }.average, .0) // (from details): 2,5 + 3,5 → 3,0 - assertEquals(3.25, items.single { it.subject == "Fizyka" }.average, .0) // (from details): 3,5 + 3,0 → 3,25 + assertEquals(3.0, items.single { it.subject == "Matematyka" }.average, .0) // (from details): 3,5 + 2,5 → 3,0 + assertEquals(3.25, items.single { it.subject == "Fizyka" }.average, .0) // (from details): 3,5 + 3,0 → 3,25 } @Test fun `force calc full year average when no summaries`() { `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true) - `when`(preferencesRepository.gradeAverageMode).thenReturn("all_year") + `when`(preferencesRepository.gradeAverageMode).thenReturn(GradeAverageMode.ALL_YEAR) `when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(firstGrades to emptyList())) `when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGrades to emptyList())) @@ -267,9 +262,9 @@ class GradeAverageProviderTest { } @Test - fun `calc full year average when missing summaries in both semesters`() { + fun `calc both semesters average when missing summaries in both semesters`() { `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(false) - `when`(preferencesRepository.gradeAverageMode).thenReturn("all_year") + `when`(preferencesRepository.gradeAverageMode).thenReturn(GradeAverageMode.BOTH_SEMESTERS) `when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(firstGrades to listOf( getSummary(22, "Matematyka", 4.0) @@ -286,9 +281,9 @@ class GradeAverageProviderTest { } @Test - fun `calc full year average when missing summary in second semester`() { + fun `calc both semesters average when missing summary in second semester`() { `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(false) - `when`(preferencesRepository.gradeAverageMode).thenReturn("all_year") + `when`(preferencesRepository.gradeAverageMode).thenReturn(GradeAverageMode.BOTH_SEMESTERS) `when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(firstGrades to firstSummaries)) `when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGrades to secondSummaries.dropLast(1))) @@ -301,9 +296,9 @@ class GradeAverageProviderTest { } @Test - fun `calc full year average when missing summary in first semester`() { + fun `calc both semesters average when missing summary in first semester`() { `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(false) - `when`(preferencesRepository.gradeAverageMode).thenReturn("all_year") + `when`(preferencesRepository.gradeAverageMode).thenReturn(GradeAverageMode.BOTH_SEMESTERS) `when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(firstGrades to firstSummaries.dropLast(1))) `when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGrades to secondSummaries)) @@ -318,7 +313,7 @@ class GradeAverageProviderTest { @Test fun `force calc full year average when missing summary in first semester`() { `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true) - `when`(preferencesRepository.gradeAverageMode).thenReturn("all_year") + `when`(preferencesRepository.gradeAverageMode).thenReturn(GradeAverageMode.ALL_YEAR) `when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(firstGrades to firstSummaries.dropLast(1))) `when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGrades to secondSummaries)) @@ -330,14 +325,130 @@ class GradeAverageProviderTest { assertEquals(3.25, items.single { it.subject == "Fizyka" }.average, .0) // (from details): 3,5 + 3,0 → 3,25 } - private fun getGrade(semesterId: Int, subject: String, value: Double, modifier: Double = 0.0): Grade { + @Test + fun `force calc both semesters average with different average from all grades and from two semesters`() { + `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true) + `when`(preferencesRepository.gradeAverageMode).thenReturn(GradeAverageMode.BOTH_SEMESTERS) + + `when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(listOf( + getGrade(22, "Fizyka", 5.0, weight = 2.0), + getGrade(22, "Fizyka", 6.0, weight = 2.0), + getGrade(22, "Fizyka", 5.0, weight = 4.0), + getGrade(22, "Fizyka", 6.0, weight = 2.0), + getGrade(22, "Fizyka", 6.0, weight = 2.0), + getGrade(22, "Fizyka", 6.0, weight = 4.0), + getGrade(22, "Fizyka", 6.0, weight = 4.0), + getGrade(22, "Fizyka", 6.0, weight = 2.0) + ) to listOf(getSummary(semesterId = 22, subject = "Fizyka", average = .0)))) + `when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(listOf( + getGrade(23, "Fizyka", 5.0, weight = 1.0), + getGrade(23, "Fizyka", 5.0, weight = 2.0), + getGrade(23, "Fizyka", 4.0, modifier = 0.3, weight = 2.0) + ) to listOf(getSummary(semesterId = 23, subject = "Fizyka", average = .0)))) + + val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet() + + assertEquals(5.2296, items.single { it.subject == "Fizyka" }.average, .0001) // (from details): 5.72727272 + 4,732 → 5.229636363636364 + } + + @Test + fun `force calc full year average with different average from all grades and from two semesters`() { + `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true) + `when`(preferencesRepository.gradeAverageMode).thenReturn(GradeAverageMode.ALL_YEAR) + + `when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(listOf( + getGrade(22, "Fizyka", 5.0, weight = 2.0), + getGrade(22, "Fizyka", 6.0, weight = 2.0), + getGrade(22, "Fizyka", 5.0, weight = 4.0), + getGrade(22, "Fizyka", 6.0, weight = 2.0), + getGrade(22, "Fizyka", 6.0, weight = 2.0), + getGrade(22, "Fizyka", 6.0, weight = 4.0), + getGrade(22, "Fizyka", 6.0, weight = 4.0), + getGrade(22, "Fizyka", 6.0, weight = 2.0) + ) to listOf(getSummary(semesterId = 22, subject = "Fizyka", average = .0)))) + `when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(listOf( + getGrade(23, "Fizyka", 5.0, weight = 1.0), + getGrade(23, "Fizyka", 5.0, weight = 2.0), + getGrade(23, "Fizyka", 4.0, modifier = 0.3, weight = 2.0) + ) to listOf(getSummary(semesterId = 23, subject = "Fizyka", average = .0)))) + + val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet() + + assertEquals(5.5429, items.single { it.subject == "Fizyka" }.average, .0001) // (from details): 5.72727272 + 4,732 → .average() + } + + @Test + fun `force calc both semesters average with different average from all grades and from two semesters with custom modifiers`() { + val student = student.copy(loginMode = Sdk.Mode.SCRAPPER.name) + + `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true) + `when`(preferencesRepository.gradeMinusModifier).thenReturn(.33) + `when`(preferencesRepository.gradePlusModifier).thenReturn(.5) + + `when`(preferencesRepository.gradeAverageMode).thenReturn(GradeAverageMode.BOTH_SEMESTERS) + `when`(semesterRepository.getSemesters(student)).thenReturn(Single.just(semesters)) + + `when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(listOf( + getGrade(22, "Fizyka", 5.0, weight = 2.0), + getGrade(22, "Fizyka", 6.0, weight = 2.0), + getGrade(22, "Fizyka", 5.0, weight = 4.0), + getGrade(22, "Fizyka", 6.0, weight = 2.0), + getGrade(22, "Fizyka", 6.0, weight = 2.0), + getGrade(22, "Fizyka", 6.0, weight = 4.0), + getGrade(22, "Fizyka", 6.0, weight = 4.0), + getGrade(22, "Fizyka", 6.0, weight = 2.0) + ) to listOf(getSummary(semesterId = 22, subject = "Fizyka", average = .0)))) + `when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(listOf( + getGrade(23, "Fizyka", 5.0, weight = 1.0), + getGrade(23, "Fizyka", 5.0, weight = 2.0), + getGrade(23, "Fizyka", 4.0, modifier = 0.33, weight = 2.0) + ) to listOf(getSummary(semesterId = 23, subject = "Fizyka", average = .0)))) + + val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet() + + assertEquals(5.2636, items.single { it.subject == "Fizyka" }.average, .0001) // (from details): 5.72727272 + 4,8 → 5.26363636 + } + + @Test + fun `force calc full year average with different average from all grades and from two semesters with custom modifiers`() { + val student = student.copy(loginMode = Sdk.Mode.SCRAPPER.name) + + `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true) + `when`(preferencesRepository.gradeMinusModifier).thenReturn(.33) + `when`(preferencesRepository.gradePlusModifier).thenReturn(.5) + + `when`(preferencesRepository.gradeAverageMode).thenReturn(GradeAverageMode.ALL_YEAR) + `when`(semesterRepository.getSemesters(student)).thenReturn(Single.just(semesters)) + + `when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(listOf( + getGrade(22, "Fizyka", 5.0, weight = 2.0), + getGrade(22, "Fizyka", 6.0, weight = 2.0), + getGrade(22, "Fizyka", 5.0, weight = 4.0), + getGrade(22, "Fizyka", 6.0, weight = 2.0), + getGrade(22, "Fizyka", 6.0, weight = 2.0), + getGrade(22, "Fizyka", 6.0, weight = 4.0), + getGrade(22, "Fizyka", 6.0, weight = 4.0), + getGrade(22, "Fizyka", 6.0, weight = 2.0) + ) to listOf(getSummary(semesterId = 22, subject = "Fizyka", average = .0)))) + `when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(listOf( + getGrade(23, "Fizyka", 5.0, weight = 1.0), + getGrade(23, "Fizyka", 5.0, weight = 2.0), + getGrade(23, "Fizyka", 4.0, modifier = 0.33, weight = 2.0) + ) to listOf(getSummary(semesterId = 23, subject = "Fizyka", average = .0)))) + + val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet() + + assertEquals(5.5555, items.single { it.subject == "Fizyka" }.average, .0001) // (from details): 5.72727272 + 4,8 → .average() + } + + private fun getGrade(semesterId: Int, subject: String, value: Double, modifier: Double = 0.0, weight: Double = 1.0): Grade { return Grade( studentId = 101, semesterId = semesterId, subject = subject, value = value, modifier = modifier, - weightValue = 1.0, + weightValue = weight, teacher = "", date = now(), weight = "", From 674a78b6619a2d94957d874a14f19e540a16c746 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Wed, 10 Jun 2020 19:18:09 +0200 Subject: [PATCH 72/85] Version 0.18.3 --- .travis.yml | 2 +- app/build.gradle | 6 +++--- app/src/main/play/release-notes/pl-PL/default.txt | 11 ++++------- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4bb842821..788aeffa7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,7 +14,7 @@ cache: branches: only: - develop - - 0.18.2 + - 0.18.3 android: licenses: diff --git a/app/build.gradle b/app/build.gradle index cb735b3b4..498c7a376 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -17,8 +17,8 @@ android { testApplicationId "io.github.tests.wulkanowy" minSdkVersion 17 targetSdkVersion 29 - versionCode 61 - versionName "0.18.2" + versionCode 62 + versionName "0.18.3" multiDexEnabled true resValue "string", "app_name", "Wulkanowy" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -124,7 +124,7 @@ configurations.all { } dependencies { - implementation "io.github.wulkanowy:sdk:a57afdb" + implementation "io.github.wulkanowy:sdk:0.18.3" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation "androidx.core:core-ktx:1.2.0" diff --git a/app/src/main/play/release-notes/pl-PL/default.txt b/app/src/main/play/release-notes/pl-PL/default.txt index 1908d2e26..db9e9a9a3 100644 --- a/app/src/main/play/release-notes/pl-PL/default.txt +++ b/app/src/main/play/release-notes/pl-PL/default.txt @@ -1,9 +1,6 @@ -Wersja 0.18.2 -- naprawiliśmy zaznaczanie uczniów przy logowaniu -- naprawiliśmy odświeżanie planu lekcji na samsungach -- naprawiliśmy wysyłanie wiadomości -- poprawiliśmy oznaczanie nowych wiadomości jako przeczytanych -- w podsumowaniu ocen nie będą się już pokazywać „puste” przedmioty -- w polu pisania wiadomości pierwsza litera w zdaniu będzie teraz domyślnie duża +Wersja 0.18.3 +- poprawiliśmy liczenie średniej i dodaliśmy nowy sposób jej liczenia w ustawieniach +- naprawiliśmy usuwanie wiadomości +- naprawiliśmy wysyłanie wiadomości na Lubelskim Portalu Oświatowym Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases From 5529ffcf73712532e8bef74bbf9ed2cfa532fe5e Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Thu, 11 Jun 2020 12:01:52 +0000 Subject: [PATCH 73/85] Bump fragment-ktx from 1.2.4 to 1.2.5 (#878) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 498c7a376..1f616e6fc 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -131,7 +131,7 @@ dependencies { implementation "androidx.activity:activity-ktx:1.1.0" implementation "androidx.appcompat:appcompat:1.2.0-rc01" implementation "androidx.appcompat:appcompat-resources:1.1.0" - implementation "androidx.fragment:fragment-ktx:1.2.4" + implementation "androidx.fragment:fragment-ktx:1.2.5" implementation "androidx.annotation:annotation:1.1.0" implementation "androidx.multidex:multidex:2.0.1" From 8cce81585aec9fafc1c5a0131952aa35219edb7b Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Thu, 11 Jun 2020 12:02:10 +0000 Subject: [PATCH 74/85] Bump firebase-analytics from 17.4.2 to 17.4.3 (#881) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 1f616e6fc..0abc9295e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -181,7 +181,7 @@ dependencies { implementation "io.coil-kt:coil:0.11.0" implementation "io.github.wulkanowy:AppKillerManager:3.0.0" - playImplementation 'com.google.firebase:firebase-analytics:17.4.2' + playImplementation 'com.google.firebase:firebase-analytics:17.4.3' playImplementation 'com.google.firebase:firebase-inappmessaging-display-ktx:19.0.7' playImplementation "com.google.firebase:firebase-inappmessaging-ktx:19.0.7" playImplementation 'com.google.firebase:firebase-messaging:20.2.0' From 00943717a2bdf5ce6fa060c32fb9a2c83cdc0cef Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Thu, 11 Jun 2020 12:02:38 +0000 Subject: [PATCH 75/85] Bump firebase-crashlytics from 17.0.0 to 17.0.1 (#880) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 0abc9295e..86ee94c37 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -185,7 +185,7 @@ dependencies { playImplementation 'com.google.firebase:firebase-inappmessaging-display-ktx:19.0.7' playImplementation "com.google.firebase:firebase-inappmessaging-ktx:19.0.7" playImplementation 'com.google.firebase:firebase-messaging:20.2.0' - playImplementation 'com.google.firebase:firebase-crashlytics:17.0.0' + playImplementation 'com.google.firebase:firebase-crashlytics:17.0.1' playImplementation 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava' releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker" From f151f7bd62aac50f71a5387c4dd819bf4ba8e164 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Thu, 11 Jun 2020 12:05:30 +0000 Subject: [PATCH 76/85] Bump about_libraries from 8.1.6 to 8.2.0 (#879) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 7e5759d12..6b0859822 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ buildscript { ext.kotlin_version = '1.3.72' - ext.about_libraries = '8.1.6' + ext.about_libraries = '8.2.0' repositories { mavenCentral() google() From eedaa637716752ec3250a61da735551dd99a383d Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Thu, 11 Jun 2020 12:05:54 +0000 Subject: [PATCH 77/85] Bump sonarqube-gradle-plugin from 2.8 to 3.0 (#882) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 6b0859822..4de7310b5 100644 --- a/build.gradle +++ b/build.gradle @@ -13,7 +13,7 @@ buildscript { classpath 'com.google.gms:google-services:4.3.3' classpath 'com.google.firebase:firebase-crashlytics-gradle:2.1.1' classpath "com.github.triplet.gradle:play-publisher:2.7.5" - classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.8" + classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.0" classpath "gradle.plugin.com.star-zero.gradle:githook:1.2.0" classpath "com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin:${about_libraries}" } From 30af77614e4b222ab8eee896fd33cc847c7c0c17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Thu, 11 Jun 2020 14:38:04 +0200 Subject: [PATCH 78/85] Fix showing summary summary for subjects without partial grades (#877) --- app/build.gradle | 6 +++--- .../wulkanowy/ui/modules/grade/GradeAverageProvider.kt | 2 +- app/src/main/java/io/github/wulkanowy/utils/SdkExtension.kt | 2 ++ 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 86ee94c37..44896664a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -77,8 +77,8 @@ android { } } - viewBinding { - enabled = true + buildFeatures { + viewBinding = true } lintOptions { @@ -124,7 +124,7 @@ configurations.all { } dependencies { - implementation "io.github.wulkanowy:sdk:0.18.3" + implementation "io.github.wulkanowy:sdk:7dc0761" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation "androidx.core:core-ktx:1.2.0" diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt index bda098f47..af1699323 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt @@ -98,7 +98,7 @@ class GradeAverageProvider @Inject constructor( } private fun List.emulateEmptySummaries(student: Student, semester: Semester, grades: List>>, calcAverage: Boolean): List { - if (isNotEmpty() && size == grades.size) return this + if (isNotEmpty() && size > grades.size) return this return grades.mapIndexed { i, (subject, details) -> singleOrNull { it.subject == subject }?.let { return@mapIndexed it } diff --git a/app/src/main/java/io/github/wulkanowy/utils/SdkExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/SdkExtension.kt index e4d4163b4..63a30db8c 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/SdkExtension.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/SdkExtension.kt @@ -23,6 +23,8 @@ fun Sdk.init(student: Student): Sdk { certKey = student.certificateKey privateKey = student.privateKey + emptyCookieJarInterceptor = true + Timber.d("Sdk in ${student.loginMode} mode reinitialized") return this From a05da2656a3960848e2baef1ce9545e316b6f116 Mon Sep 17 00:00:00 2001 From: Dominik Korsa Date: Fri, 12 Jun 2020 21:35:51 +0200 Subject: [PATCH 79/85] Add account headers in student picker (#871) --- .../repositories/student/StudentRemote.kt | 2 +- .../wulkanowy/ui/modules/account/Account.kt | 3 + .../ui/modules/account/AccountAdapter.kt | 60 +++++++++++-- .../ui/modules/account/AccountDialog.kt | 3 +- .../ui/modules/account/AccountItem.kt | 9 ++ .../ui/modules/account/AccountPresenter.kt | 9 ++ .../ui/modules/account/AccountView.kt | 3 +- app/src/main/res/layout/dialog_account.xml | 89 +++++++++++-------- app/src/main/res/layout/header_account.xml | 28 ++++++ app/src/main/res/layout/item_account.xml | 30 +++++-- app/src/main/res/values-pl/strings.xml | 4 + app/src/main/res/values/strings.xml | 4 + 12 files changed, 185 insertions(+), 59 deletions(-) create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/account/Account.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountItem.kt create mode 100644 app/src/main/res/layout/header_account.xml diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/student/StudentRemote.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/student/StudentRemote.kt index 15c336505..4c0ffd820 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/student/StudentRemote.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/student/StudentRemote.kt @@ -14,7 +14,7 @@ class StudentRemote @Inject constructor(private val sdk: Sdk) { private fun mapStudents(students: List, email: String, password: String): List { return students.map { student -> Student( - email = email, + email = email.ifBlank { student.email }, password = password, isParent = student.isParent, symbol = student.symbol, diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/Account.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/Account.kt new file mode 100644 index 000000000..dbcb499e8 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/Account.kt @@ -0,0 +1,3 @@ +package io.github.wulkanowy.ui.modules.account + +data class Account(val email: String, val isParent: Boolean) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountAdapter.kt index 7df0ca378..27915a710 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountAdapter.kt @@ -3,33 +3,72 @@ package io.github.wulkanowy.ui.modules.account import android.annotation.SuppressLint import android.graphics.PorterDuff import android.view.LayoutInflater +import android.view.View.GONE +import android.view.View.VISIBLE import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.databinding.HeaderAccountBinding import io.github.wulkanowy.databinding.ItemAccountBinding +import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.getThemeAttrColor import javax.inject.Inject -class AccountAdapter @Inject constructor() : RecyclerView.Adapter() { +class AccountAdapter @Inject constructor() : RecyclerView.Adapter() { - var items = emptyList() + var items = emptyList>() var onClickListener: (Student) -> Unit = {} override fun getItemCount() = items.size - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder( - ItemAccountBinding.inflate(LayoutInflater.from(parent.context), parent, false) - ) + override fun getItemViewType(position: Int) = items[position].viewType.id + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + val inflater = LayoutInflater.from(parent.context) + + return when (viewType) { + AccountItem.ViewType.HEADER.id -> HeaderViewHolder(HeaderAccountBinding.inflate(inflater, parent, false)) + AccountItem.ViewType.ITEM.id -> ItemViewHolder(ItemAccountBinding.inflate(inflater, parent, false)) + else -> throw IllegalStateException() + } + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + when (holder) { + is HeaderViewHolder -> bindHeaderViewHolder(holder.binding, items[position].value as Account) + is ItemViewHolder -> bindItemViewHolder(holder.binding, items[position].value as Student) + } + } + + private fun bindHeaderViewHolder(binding: HeaderAccountBinding, account: Account) { + with(binding) { + accountHeaderEmail.text = account.email + accountHeaderType.setText(if (account.isParent) R.string.account_type_parent else R.string.account_type_student) + } + } @SuppressLint("SetTextI18n") - override fun onBindViewHolder(holder: ItemViewHolder, position: Int) { - val student = items[position] - - with(holder.binding) { + private fun bindItemViewHolder(binding: ItemAccountBinding, student: Student) { + with(binding) { accountItemName.text = "${student.studentName} ${student.className}" accountItemSchool.text = student.schoolName + with(accountItemLoginMode) { + visibility = when (Sdk.Mode.valueOf(student.loginMode)) { + Sdk.Mode.API -> { + setText(R.string.account_login_mobile_api) + VISIBLE + } + Sdk.Mode.HYBRID -> { + setText(R.string.account_login_hybrid) + VISIBLE + } + Sdk.Mode.SCRAPPER -> { + GONE + } + } + } with(accountItemImage) { val colorImage = if (student.isCurrent) context.getThemeAttrColor(R.attr.colorPrimary) @@ -42,5 +81,8 @@ class AccountAdapter @Inject constructor() : RecyclerView.Adapter(), AccountView { } } - override fun updateData(data: List) { + override fun updateData(data: List>) { with(accountAdapter) { items = data notifyDataSetChanged() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountItem.kt new file mode 100644 index 000000000..05a4a69ce --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountItem.kt @@ -0,0 +1,9 @@ +package io.github.wulkanowy.ui.modules.account + +data class AccountItem(val value: T, val viewType: ViewType) { + + enum class ViewType(val id: Int) { + HEADER(1), + ITEM(2) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountPresenter.kt index 3416a043f..b5fbcdb63 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountPresenter.kt @@ -83,11 +83,20 @@ class AccountPresenter @Inject constructor( } } + private fun createAccountItems(items: List): List> { + return items.groupBy { Account(it.email, it.isParent) }.map { (account, students) -> + listOf(AccountItem(account, AccountItem.ViewType.HEADER)) + students.map { student -> + AccountItem(student, AccountItem.ViewType.ITEM) + } + }.flatten() + } + private fun loadData() { Timber.i("Loading account data started") disposable.add(studentRepository.getSavedStudents(false) .subscribeOn(schedulers.backgroundThread) .observeOn(schedulers.mainThread) + .map { createAccountItems(it) } .subscribe({ Timber.i("Loading account result: Success") view?.updateData(it) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountView.kt index abb9e1d27..a1f8086cd 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountView.kt @@ -1,13 +1,12 @@ package io.github.wulkanowy.ui.modules.account -import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.ui.base.BaseView interface AccountView : BaseView { fun initView() - fun updateData(data: List) + fun updateData(data: List>) fun dismissView() diff --git a/app/src/main/res/layout/dialog_account.xml b/app/src/main/res/layout/dialog_account.xml index 6e975c80d..d56de3a2e 100644 --- a/app/src/main/res/layout/dialog_account.xml +++ b/app/src/main/res/layout/dialog_account.xml @@ -5,45 +5,60 @@ android:layout_width="300dp" android:layout_height="wrap_content"> - - - + android:orientation="vertical"> - + - + + + + + + + + + + + diff --git a/app/src/main/res/layout/header_account.xml b/app/src/main/res/layout/header_account.xml new file mode 100644 index 000000000..6219c26db --- /dev/null +++ b/app/src/main/res/layout/header_account.xml @@ -0,0 +1,28 @@ + + + + + + + diff --git a/app/src/main/res/layout/item_account.xml b/app/src/main/res/layout/item_account.xml index 614e4d2df..9568b345a 100644 --- a/app/src/main/res/layout/item_account.xml +++ b/app/src/main/res/layout/item_account.xml @@ -3,20 +3,17 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" - android:layout_height="56dp" + android:layout_height="wrap_content" android:background="?selectableItemBackground" android:orientation="horizontal" - android:paddingStart="24dp" - android:paddingLeft="24dp" - android:paddingEnd="24dp" - android:paddingRight="24dp" + android:paddingVertical="8dp" + android:paddingHorizontal="16dp" tools:context=".ui.modules.account.AccountAdapter"> + + diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 7f25d8a67..4cb8997cf 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -265,6 +265,10 @@ Wyloguj Czy chcesz wylogować aktualnego ucznia? Wylogowanie ucznia + Konto ucznia + Konto rodzica + Tryb API mobilne + Tryb hybrydowy Wersja aplikacji Twórcy diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7793cd9c2..29e3460b4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -282,6 +282,10 @@ Logout Do you want to log out of an active student? Student logout + Student account + Parent account + Mobile API mode + Hybrid mode From a52983693722869b56e6afddfb29ddd6f469e3a9 Mon Sep 17 00:00:00 2001 From: Dominik Korsa Date: Sat, 13 Jun 2020 14:12:01 +0200 Subject: [PATCH 80/85] Fix lint errors (#873) --- .../data/db/migrations/Migration13Test.kt | 30 +++++++-------- .../attendance/AttendanceLocalTest.kt | 37 +++++++++++++++++-- .../luckynumber/LuckyNumberLocalTest.kt | 1 - .../wulkanowy/utils/CrashlyticsUtils.kt | 1 - .../github/wulkanowy/data/RepositoryModule.kt | 6 +-- .../appcreator/AppCreatorRepository.kt | 2 +- .../data/repositories/exam/ExamLocal.kt | 2 +- .../repositories/recipient/RecipientLocal.kt | 2 +- .../reportingunit/ReportingUnitLocal.kt | 2 +- .../sync/works/GradeStatisticsWork.kt | 2 +- .../about/logviewer/LogViewerPresenter.kt | 6 +-- .../modules/attendance/AttendancePresenter.kt | 1 - .../completed/CompletedLessonsPresenter.kt | 2 +- .../wulkanowy/utils/LifecycleAwareVariable.kt | 2 +- app/src/main/res/values/strings.xml | 2 +- .../io/github/wulkanowy/TestEnityCreator.kt | 23 ++++++++++++ .../message/MessageRepositoryTest.kt | 13 +++---- 17 files changed, 91 insertions(+), 43 deletions(-) diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/db/migrations/Migration13Test.kt b/app/src/androidTest/java/io/github/wulkanowy/data/db/migrations/Migration13Test.kt index 6f9406f6d..da4284b02 100644 --- a/app/src/androidTest/java/io/github/wulkanowy/data/db/migrations/Migration13Test.kt +++ b/app/src/androidTest/java/io/github/wulkanowy/data/db/migrations/Migration13Test.kt @@ -120,23 +120,23 @@ class Migration13Test : AbstractMigrationTest() { assertEquals(2, first.diaryId) } - getSemesters(db, "SELECT * FROM Semesters WHERE student_id = 2 AND class_id = 5").let { - assertTrue { it.single { it.second }.second } - assertEquals(1970, it[0].first.schoolYear) - assertEquals(of(1970, 1, 1), it[0].first.end) - assertEquals(of(1970, 1, 1), it[0].first.start) - assertFalse(it[0].second) - assertFalse(it[1].second) - assertFalse(it[2].second) - assertTrue(it[3].second) + getSemesters(db, "SELECT * FROM Semesters WHERE student_id = 2 AND class_id = 5").let { semesters -> + assertTrue { semesters.single { it.second }.second } + assertEquals(1970, semesters[0].first.schoolYear) + assertEquals(of(1970, 1, 1), semesters[0].first.end) + assertEquals(of(1970, 1, 1), semesters[0].first.start) + assertFalse(semesters[0].second) + assertFalse(semesters[1].second) + assertFalse(semesters[2].second) + assertTrue(semesters[3].second) } - getSemesters(db, "SELECT * FROM Semesters WHERE student_id = 2 AND class_id = 5").let { - assertTrue { it.single { it.second }.second } - assertFalse(it[0].second) - assertFalse(it[1].second) - assertFalse(it[2].second) - assertTrue(it[3].second) + getSemesters(db, "SELECT * FROM Semesters WHERE student_id = 2 AND class_id = 5").let { semesters -> + assertTrue { semesters.single { it.second }.second } + assertFalse(semesters[0].second) + assertFalse(semesters[1].second) + assertFalse(semesters[2].second) + assertTrue(semesters[3].second) } } diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/attendance/AttendanceLocalTest.kt b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/attendance/AttendanceLocalTest.kt index c7ede6ae5..cdfc524ad 100644 --- a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/attendance/AttendanceLocalTest.kt +++ b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/attendance/AttendanceLocalTest.kt @@ -10,6 +10,7 @@ import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.threeten.bp.LocalDate import org.threeten.bp.LocalDate.now import org.threeten.bp.LocalDate.of import kotlin.test.assertEquals @@ -35,9 +36,18 @@ class AttendanceLocalTest { @Test fun saveAndReadTest() { attendanceLocal.saveAttendance(listOf( - Attendance(1, 2, 3, of(2018, 9, 10), 0, "", "", false, false, false, false, false, false, false, SentExcuseStatus.ACCEPTED.name), - Attendance(1, 2, 3, of(2018, 9, 14), 0, "", "", false, false, false, false, false, false, false, SentExcuseStatus.WAITING.name), - Attendance(1, 2, 3, of(2018, 9, 17), 0, "", "", false, false, false, false, false, false, false, SentExcuseStatus.ACCEPTED.name) + getAttendanceEntity( + of(2018, 9, 10), + SentExcuseStatus.ACCEPTED + ), + getAttendanceEntity( + of(2018, 9, 14), + SentExcuseStatus.WAITING + ), + getAttendanceEntity( + of(2018, 9, 17), + SentExcuseStatus.ACCEPTED + ) )) val attendance = attendanceLocal @@ -50,4 +60,25 @@ class AttendanceLocalTest { assertEquals(attendance[0].date, of(2018, 9, 10)) assertEquals(attendance[1].date, of(2018, 9, 14)) } + + private fun getAttendanceEntity( + date: LocalDate, + excuseStatus: SentExcuseStatus + ) = Attendance( + studentId = 1, + diaryId = 2, + timeId = 3, + date = date, + number = 0, + subject = "", + name = "", + presence = false, + absence = false, + exemption = false, + lateness = false, + excused = false, + deleted = false, + excusable = false, + excuseStatus = excuseStatus.name + ) } diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/luckynumber/LuckyNumberLocalTest.kt b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/luckynumber/LuckyNumberLocalTest.kt index 65ef1fcbf..efad0d4d5 100644 --- a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/luckynumber/LuckyNumberLocalTest.kt +++ b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/luckynumber/LuckyNumberLocalTest.kt @@ -5,7 +5,6 @@ import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import io.github.wulkanowy.data.db.AppDatabase import io.github.wulkanowy.data.db.entities.LuckyNumber -import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import org.junit.After import org.junit.Before diff --git a/app/src/fdroid/java/io/github/wulkanowy/utils/CrashlyticsUtils.kt b/app/src/fdroid/java/io/github/wulkanowy/utils/CrashlyticsUtils.kt index 6351997d0..d03a319a2 100644 --- a/app/src/fdroid/java/io/github/wulkanowy/utils/CrashlyticsUtils.kt +++ b/app/src/fdroid/java/io/github/wulkanowy/utils/CrashlyticsUtils.kt @@ -2,7 +2,6 @@ package io.github.wulkanowy.utils -import android.content.Context import timber.log.Timber open class TimberTreeNoOp : Timber.Tree() { 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 9540372fc..19af1b2f8 100644 --- a/app/src/main/java/io/github/wulkanowy/data/RepositoryModule.kt +++ b/app/src/main/java/io/github/wulkanowy/data/RepositoryModule.kt @@ -1,17 +1,15 @@ package io.github.wulkanowy.data -import android.app.AlarmManager import android.content.Context import android.content.SharedPreferences import android.content.res.AssetManager import android.content.res.Resources -import androidx.core.content.getSystemService import androidx.preference.PreferenceManager -import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings -import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.strategy.WalledGardenInternetObservingStrategy import com.chuckerteam.chucker.api.ChuckerCollector import com.chuckerteam.chucker.api.ChuckerInterceptor import com.chuckerteam.chucker.api.RetentionManager +import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings +import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.strategy.WalledGardenInternetObservingStrategy import dagger.Module import dagger.Provides import io.github.wulkanowy.data.db.AppDatabase diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/appcreator/AppCreatorRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/appcreator/AppCreatorRepository.kt index 6a0b2d32e..76cf698c9 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/appcreator/AppCreatorRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/appcreator/AppCreatorRepository.kt @@ -10,7 +10,7 @@ import javax.inject.Singleton @Singleton class AppCreatorRepository @Inject constructor(private val assets: AssetManager) { fun getAppCreators(): Single> { - return Single.fromCallable> { + return Single.fromCallable { Gson().fromJson( assets.open("contributors.json").bufferedReader().use { it.readText() }, Array::class.java diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/exam/ExamLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/exam/ExamLocal.kt index 0f484d323..389eb5835 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/exam/ExamLocal.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/exam/ExamLocal.kt @@ -13,7 +13,7 @@ class ExamLocal @Inject constructor(private val examDb: ExamDao) { fun getExams(semester: Semester, startDate: LocalDate, endDate: LocalDate): Maybe> { return examDb.loadAll(semester.diaryId, semester.studentId, startDate, endDate) - .filter { !it.isEmpty() } + .filter { it.isNotEmpty() } } fun saveExams(exams: List) { diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/recipient/RecipientLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/recipient/RecipientLocal.kt index 9b1d4ac2f..ff8175440 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/recipient/RecipientLocal.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/recipient/RecipientLocal.kt @@ -12,7 +12,7 @@ import javax.inject.Singleton class RecipientLocal @Inject constructor(private val recipientDb: RecipientDao) { fun getRecipients(student: Student, role: Int, unit: ReportingUnit): Maybe> { - return recipientDb.load(student.studentId, role, unit.realId).filter { !it.isEmpty() } + return recipientDb.load(student.studentId, role, unit.realId).filter { it.isNotEmpty() } } fun saveRecipients(recipients: List): List { diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/reportingunit/ReportingUnitLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/reportingunit/ReportingUnitLocal.kt index 6f9eec3fc..0631c668c 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/reportingunit/ReportingUnitLocal.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/reportingunit/ReportingUnitLocal.kt @@ -11,7 +11,7 @@ import javax.inject.Singleton class ReportingUnitLocal @Inject constructor(private val reportingUnitDb: ReportingUnitDao) { fun getReportingUnits(student: Student): Maybe> { - return reportingUnitDb.load(student.studentId).filter { !it.isEmpty() } + return reportingUnitDb.load(student.studentId).filter { it.isNotEmpty() } } fun getReportingUnit(student: Student, unitId: Int): Maybe { diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeStatisticsWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeStatisticsWork.kt index 327c71740..c4681fb8f 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeStatisticsWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeStatisticsWork.kt @@ -9,7 +9,7 @@ import javax.inject.Inject class GradeStatisticsWork @Inject constructor(private val gradeStatisticsRepository: GradeStatisticsRepository) : Work { override fun create(student: Student, semester: Semester): Completable { - return gradeStatisticsRepository.getGradesStatistics(student, semester, "Wszystkie", false, true) + return gradeStatisticsRepository.getGradesStatistics(student, semester, "Wszystkie", false, forceRefresh = true) .ignoreElement() } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/about/logviewer/LogViewerPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/about/logviewer/LogViewerPresenter.kt index 4485cb3eb..33eb1122d 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/about/logviewer/LogViewerPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/about/logviewer/LogViewerPresenter.kt @@ -25,9 +25,9 @@ class LogViewerPresenter @Inject constructor( disposable.add(loggerRepository.getLogFiles() .subscribeOn(schedulers.backgroundThread) .observeOn(schedulers.mainThread) - .subscribe({ - Timber.i("Loading logs files result: ${it.joinToString { it.name }}") - view?.shareLogs(it) + .subscribe({ files -> + Timber.i("Loading logs files result: ${files.joinToString { it.name }}") + view?.shareLogs(files) }, { Timber.i("Loading logs files result: An exception occurred") errorHandler.dispatch(it) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt index 437e06c92..f58d0617f 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt @@ -20,7 +20,6 @@ import org.threeten.bp.LocalDate import org.threeten.bp.LocalDate.now import org.threeten.bp.LocalDate.ofEpochDay import timber.log.Timber -import java.util.concurrent.TimeUnit.MILLISECONDS import javax.inject.Inject class AttendancePresenter @Inject constructor( diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsPresenter.kt index 92aeb5814..f72d753a9 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsPresenter.kt @@ -43,7 +43,7 @@ class CompletedLessonsPresenter @Inject constructor( completedLessonsErrorHandler.showErrorMessage = ::showErrorViewOnError completedLessonsErrorHandler.onFeatureDisabled = { this.view?.showFeatureDisabled() - this.view?.showEmpty(true); + this.view?.showEmpty(true) Timber.i("Completed lessons feature disabled by school") } loadData(ofEpochDay(date ?: baseDate.toEpochDay())) diff --git a/app/src/main/java/io/github/wulkanowy/utils/LifecycleAwareVariable.kt b/app/src/main/java/io/github/wulkanowy/utils/LifecycleAwareVariable.kt index 2c49ee97c..b96faeb21 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/LifecycleAwareVariable.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/LifecycleAwareVariable.kt @@ -52,4 +52,4 @@ class LifecycleAwareVariableActivity : ReadWriteProperty Fragment.lifecycleAwareVariable() = LifecycleAwareVariable() -fun AppCompatActivity.lifecycleAwareVariable() = LifecycleAwareVariableActivity() +fun lifecycleAwareVariable() = LifecycleAwareVariableActivity() diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 29e3460b4..bec752cf2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -343,7 +343,7 @@ Prev Next Search - Search... + Search… diff --git a/app/src/test/java/io/github/wulkanowy/TestEnityCreator.kt b/app/src/test/java/io/github/wulkanowy/TestEnityCreator.kt index 5d003f273..f7d29220f 100644 --- a/app/src/test/java/io/github/wulkanowy/TestEnityCreator.kt +++ b/app/src/test/java/io/github/wulkanowy/TestEnityCreator.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy +import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Timetable @@ -72,3 +73,25 @@ fun getTimetableEntity( teacher = "", teacherOld = "" ) + +fun getMessageEntity( + messageId: Int, + content: String, + unread: Boolean +) = Message( + studentId = 1, + realId = 1, + messageId = messageId, + sender = "", + senderId = 1, + recipient = "", + subject = "", + content = content, + date = now(), + folderId = 1, + unread = unread, + unreadBy = 1, + readBy = 1, + removed = false, + hasAttachments = false +) diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/message/MessageRepositoryTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/message/MessageRepositoryTest.kt index b7963d430..fcc4188a6 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/message/MessageRepositoryTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/message/MessageRepositoryTest.kt @@ -2,10 +2,10 @@ package io.github.wulkanowy.data.repositories.message import androidx.room.EmptyResultSetException import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings -import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.MessageWithAttachment import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.repositories.UnitTestInternetObservingStrategy +import io.github.wulkanowy.getMessageEntity import io.reactivex.Single import io.reactivex.observers.TestObserver import org.junit.Assert.assertEquals @@ -15,7 +15,6 @@ import org.mockito.Mock import org.mockito.Mockito.`when` import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations -import org.threeten.bp.LocalDateTime.now import java.net.UnknownHostException class MessageRepositoryTest { @@ -44,7 +43,7 @@ class MessageRepositoryTest { @Test fun `throw error when message is not in the db`() { - val testMessage = Message(1, 1, 1, "", 1, "", "", "", now(), 1, false, 1, 1, false, false) + val testMessage = getMessageEntity(1, "", false) `when`(local.getMessageWithAttachment(student, testMessage)).thenReturn(Single.error(EmptyResultSetException("No message in database"))) val message = repo.getMessage(student, testMessage) @@ -55,7 +54,7 @@ class MessageRepositoryTest { @Test fun `get message when content already in db`() { - val testMessage = Message(1, 1, 123, "", 1, "", "", "Test", now(), 1, false, 1, 1, false, false) + val testMessage = getMessageEntity(123, "Test", false) val messageWithAttachment = MessageWithAttachment(testMessage, emptyList()) `when`(local.getMessageWithAttachment(student, testMessage)).thenReturn(Single.just(messageWithAttachment)) @@ -67,7 +66,7 @@ class MessageRepositoryTest { @Test fun `get message when content in db is empty`() { - val testMessage = Message(1, 1, 123, "", 1, "", "", "", now(), 1, true, 1, 1, false, false) + val testMessage = getMessageEntity(123, "", true) val testMessageWithContent = testMessage.copy(content = "Test") val mWa = MessageWithAttachment(testMessage, emptyList()) @@ -86,7 +85,7 @@ class MessageRepositoryTest { @Test fun `get message when content in db is empty and there is no internet connection`() { - val testMessage = Message(1, 1, 123, "", 1, "", "", "", now(), 1, false, 1, 1, false, false) + val testMessage = getMessageEntity(123, "", false) val messageWithAttachment = MessageWithAttachment(testMessage, emptyList()) testObservingStrategy.isInternetConnection = false @@ -100,7 +99,7 @@ class MessageRepositoryTest { @Test fun `get message when content in db is empty, unread and there is no internet connection`() { - val testMessage = Message(1, 1, 123, "", 1, "", "", "", now(), 1, true, 1, 1, false, false) + val testMessage = getMessageEntity(123, "", true) val messageWithAttachment = MessageWithAttachment(testMessage, emptyList()) testObservingStrategy.isInternetConnection = false From a6682c9b73d525133e32accbc2aae89636fad9b2 Mon Sep 17 00:00:00 2001 From: Dominik Korsa Date: Sat, 13 Jun 2020 17:11:18 +0200 Subject: [PATCH 81/85] Add predicted and final grade notifications (#872) --- .../25.json | 2 +- .../26.json | 1768 +++++++++++++++++ .../github/wulkanowy/data/db/AppDatabase.kt | 6 +- .../data/db/entities/GradeSummary.kt | 13 + .../data/db/migrations/Migration26.kt | 14 + .../data/repositories/grade/GradeLocal.kt | 4 + .../repositories/grade/GradeRepository.kt | 39 +- .../services/sync/works/GradeWork.kt | 53 +- app/src/main/res/values-pl/strings.xml | 24 + app/src/main/res/values/strings.xml | 16 + 10 files changed, 1927 insertions(+), 12 deletions(-) create mode 100644 app/schemas/io.github.wulkanowy.data.db.AppDatabase/26.json create mode 100644 app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration26.kt diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/25.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/25.json index e99a11d4a..474824df6 100644 --- a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/25.json +++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/25.json @@ -1741,4 +1741,4 @@ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'd101f5a26a024f62e6fee161e421b882')" ] } -} \ No newline at end of file +} diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/26.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/26.json new file mode 100644 index 000000000..21005f9c6 --- /dev/null +++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/26.json @@ -0,0 +1,1768 @@ +{ + "formatVersion": 1, + "database": { + "version": 26, + "identityHash": "43f8777ffe95a5a2850ee981db3dbe2e", + "entities": [ + { + "tableName": "Students", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `scrapper_base_url` TEXT NOT NULL, `mobile_base_url` TEXT NOT NULL, `login_type` TEXT NOT NULL, `login_mode` TEXT NOT NULL, `certificate_key` TEXT NOT NULL, `private_key` TEXT NOT NULL, `is_parent` INTEGER NOT NULL, `email` TEXT NOT NULL, `password` TEXT NOT NULL, `symbol` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `user_login_id` INTEGER NOT NULL, `student_name` TEXT NOT NULL, `school_id` TEXT NOT NULL, `school_short` TEXT NOT NULL, `school_name` TEXT NOT NULL, `class_name` TEXT NOT NULL, `class_id` INTEGER NOT NULL, `is_current` INTEGER NOT NULL, `registration_date` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "scrapperBaseUrl", + "columnName": "scrapper_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mobileBaseUrl", + "columnName": "mobile_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginType", + "columnName": "login_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginMode", + "columnName": "login_mode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "certificateKey", + "columnName": "certificate_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "privateKey", + "columnName": "private_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isParent", + "columnName": "is_parent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentName", + "columnName": "student_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolSymbol", + "columnName": "school_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolShortName", + "columnName": "school_short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolName", + "columnName": "school_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "className", + "columnName": "class_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isCurrent", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "registrationDate", + "columnName": "registration_date", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Students_email_symbol_student_id_school_id_class_id", + "unique": true, + "columnNames": [ + "email", + "symbol", + "student_id", + "school_id", + "class_id" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Students_email_symbol_student_id_school_id_class_id` ON `${TABLE_NAME}` (`email`, `symbol`, `student_id`, `school_id`, `class_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Semesters", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_current` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `diary_name` TEXT NOT NULL, `school_year` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `semester_name` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "current", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryName", + "columnName": "diary_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolYear", + "columnName": "school_year", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterName", + "columnName": "semester_name", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Semesters_student_id_diary_id_semester_id", + "unique": true, + "columnNames": [ + "student_id", + "diary_id", + "semester_id" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Semesters_student_id_diary_id_semester_id` ON `${TABLE_NAME}` (`student_id`, `diary_id`, `semester_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Exams", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `group` TEXT NOT NULL, `type` TEXT NOT NULL, `description` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Timetable", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `number` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `subjectOld` TEXT NOT NULL, `group` TEXT NOT NULL, `room` TEXT NOT NULL, `roomOld` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacherOld` TEXT NOT NULL, `info` TEXT NOT NULL, `student_plan` INTEGER NOT NULL, `changes` INTEGER NOT NULL, `canceled` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subjectOld", + "columnName": "subjectOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "room", + "columnName": "room", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roomOld", + "columnName": "roomOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherOld", + "columnName": "teacherOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "info", + "columnName": "info", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isStudentPlan", + "columnName": "student_plan", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "changes", + "columnName": "changes", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canceled", + "columnName": "canceled", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Attendance", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `time_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `excused` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `excusable` INTEGER NOT NULL, `excuse_status` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timeId", + "columnName": "time_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excused", + "columnName": "excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deleted", + "columnName": "deleted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excusable", + "columnName": "excusable", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excuseStatus", + "columnName": "excuse_status", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AttendanceSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `subject_id` INTEGER NOT NULL, `month` INTEGER NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `absence_excused` INTEGER NOT NULL, `absence_for_school_reasons` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `lateness_excused` INTEGER NOT NULL, `exemption` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subjectId", + "columnName": "subject_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "month", + "columnName": "month", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceExcused", + "columnName": "absence_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceForSchoolReasons", + "columnName": "absence_for_school_reasons", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "latenessExcused", + "columnName": "lateness_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Grades", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `entry` TEXT NOT NULL, `value` REAL NOT NULL, `modifier` REAL NOT NULL, `comment` TEXT NOT NULL, `color` TEXT NOT NULL, `grade_symbol` TEXT NOT NULL, `description` TEXT NOT NULL, `weight` TEXT NOT NULL, `weightValue` REAL NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "entry", + "columnName": "entry", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "modifier", + "columnName": "modifier", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "comment", + "columnName": "comment", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gradeSymbol", + "columnName": "grade_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weight", + "columnName": "weight", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weightValue", + "columnName": "weightValue", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_predicted_grade_notified` INTEGER NOT NULL, `is_final_grade_notified` INTEGER NOT NULL, `predicted_grade_last_change` INTEGER NOT NULL, `final_grade_last_change` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `position` INTEGER NOT NULL, `subject` TEXT NOT NULL, `predicted_grade` TEXT NOT NULL, `final_grade` TEXT NOT NULL, `proposed_points` TEXT NOT NULL, `final_points` TEXT NOT NULL, `points_sum` TEXT NOT NULL, `average` REAL NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPredictedGradeNotified", + "columnName": "is_predicted_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isFinalGradeNotified", + "columnName": "is_final_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "predictedGradeLastChange", + "columnName": "predicted_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "finalGradeLastChange", + "columnName": "final_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "predictedGrade", + "columnName": "predicted_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalGrade", + "columnName": "final_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "proposedPoints", + "columnName": "proposed_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalPoints", + "columnName": "final_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pointsSum", + "columnName": "points_sum", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "average", + "columnName": "average", + "affinity": "REAL", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `grade` INTEGER NOT NULL, `amount` INTEGER NOT NULL, `is_semester` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "grade", + "columnName": "grade", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "amount", + "columnName": "amount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semester", + "columnName": "is_semester", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesPointsStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `others` REAL NOT NULL, `student` REAL NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "others", + "columnName": "others", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "student", + "columnName": "student", + "affinity": "REAL", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `recipient_name` TEXT NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `date` INTEGER NOT NULL, `folder_id` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `unread_by` INTEGER NOT NULL, `read_by` INTEGER NOT NULL, `removed` INTEGER NOT NULL, `has_attachments` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sender", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "recipient", + "columnName": "recipient_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "folderId", + "columnName": "folder_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unread", + "columnName": "unread", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unreadBy", + "columnName": "unread_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "readBy", + "columnName": "read_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "removed", + "columnName": "removed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hasAttachments", + "columnName": "has_attachments", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MessageAttachments", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `one_drive_id` TEXT NOT NULL, `url` TEXT NOT NULL, `filename` TEXT NOT NULL, PRIMARY KEY(`real_id`))", + "fields": [ + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "oneDriveId", + "columnName": "one_drive_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "filename", + "columnName": "filename", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "real_id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `category` TEXT NOT NULL, `category_type` INTEGER NOT NULL, `is_points_show` INTEGER NOT NULL, `points` INTEGER NOT NULL, `content` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "categoryType", + "columnName": "category_type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPointsShow", + "columnName": "is_points_show", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "points", + "columnName": "points", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Homework", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_done` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `attachments` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isDone", + "columnName": "is_done", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "attachments", + "columnName": "attachments", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Subjects", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `name` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "LuckyNumbers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `lucky_number` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "luckyNumber", + "columnName": "lucky_number", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "CompletedLesson", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `topic` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `substitution` TEXT NOT NULL, `absence` TEXT NOT NULL, `resources` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "topic", + "columnName": "topic", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "substitution", + "columnName": "substitution", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "resources", + "columnName": "resources", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "ReportingUnits", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `short` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `roles` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "senderName", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roles", + "columnName": "roles", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Recipients", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `real_id` TEXT NOT NULL, `name` TEXT NOT NULL, `real_name` TEXT NOT NULL, `login_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `role` INTEGER NOT NULL, `hash` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "realName", + "columnName": "real_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginId", + "columnName": "login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "role", + "columnName": "role", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hash", + "columnName": "hash", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MobileDevices", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `device_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `date` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deviceId", + "columnName": "device_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Teachers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `short_name` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short_name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "School", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `address` TEXT NOT NULL, `contact` TEXT NOT NULL, `headmaster` TEXT NOT NULL, `pedagogue` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "contact", + "columnName": "contact", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "headmaster", + "columnName": "headmaster", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pedagogue", + "columnName": "pedagogue", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '43f8777ffe95a5a2850ee981db3dbe2e')" + ] + } +} \ No newline at end of file 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 762c52f8f..a31b68c0d 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 @@ -68,6 +68,7 @@ import io.github.wulkanowy.data.db.migrations.Migration22 import io.github.wulkanowy.data.db.migrations.Migration23 import io.github.wulkanowy.data.db.migrations.Migration24 import io.github.wulkanowy.data.db.migrations.Migration25 +import io.github.wulkanowy.data.db.migrations.Migration26 import io.github.wulkanowy.data.db.migrations.Migration3 import io.github.wulkanowy.data.db.migrations.Migration4 import io.github.wulkanowy.data.db.migrations.Migration5 @@ -110,7 +111,7 @@ import javax.inject.Singleton abstract class AppDatabase : RoomDatabase() { companion object { - const val VERSION_SCHEMA = 25 + const val VERSION_SCHEMA = 26 fun getMigrations(sharedPrefProvider: SharedPrefProvider): Array { return arrayOf( @@ -137,7 +138,8 @@ abstract class AppDatabase : RoomDatabase() { Migration22(), Migration23(), Migration24(), - Migration25() + Migration25(), + Migration26() ) } 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 index 6e29112b2..dd3126d4d 100644 --- 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 @@ -3,6 +3,7 @@ package io.github.wulkanowy.data.db.entities import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey +import org.threeten.bp.LocalDateTime @Entity(tableName = "GradesSummary") data class GradeSummary( @@ -36,4 +37,16 @@ data class GradeSummary( ) { @PrimaryKey(autoGenerate = true) var id: Long = 0 + + @ColumnInfo(name = "is_predicted_grade_notified") + var isPredictedGradeNotified: Boolean = true + + @ColumnInfo(name = "is_final_grade_notified") + var isFinalGradeNotified: Boolean = true + + @ColumnInfo(name = "predicted_grade_last_change") + var predictedGradeLastChange: LocalDateTime = LocalDateTime.now() + + @ColumnInfo(name = "final_grade_last_change") + var finalGradeLastChange: LocalDateTime = LocalDateTime.now() } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration26.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration26.kt new file mode 100644 index 000000000..7130d86d8 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration26.kt @@ -0,0 +1,14 @@ +package io.github.wulkanowy.data.db.migrations + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase + +class Migration26 : Migration(25, 26) { + + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("ALTER TABLE GradesSummary ADD COLUMN is_predicted_grade_notified INTEGER NOT NULL DEFAULT 1") + database.execSQL("ALTER TABLE GradesSummary ADD COLUMN is_final_grade_notified INTEGER NOT NULL DEFAULT 1") + database.execSQL("ALTER TABLE GradesSummary ADD COLUMN predicted_grade_last_change INTEGER NOT NULL DEFAULT 0") + database.execSQL("ALTER TABLE GradesSummary ADD COLUMN final_grade_last_change INTEGER NOT NULL DEFAULT 0") + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/grade/GradeLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/grade/GradeLocal.kt index 944ed34ae..52ab60178 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/grade/GradeLocal.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/grade/GradeLocal.kt @@ -27,6 +27,10 @@ class GradeLocal @Inject constructor( gradeDb.updateAll(grades) } + fun updateGradesSummary(gradesSummary: List) { + gradeSummaryDb.updateAll(gradesSummary) + } + fun getGradesDetails(semester: Semester): Maybe> { return gradeDb.loadAll(semester.semesterId, semester.studentId).filter { it.isNotEmpty() } } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/grade/GradeRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/grade/GradeRepository.kt index e28350a51..2ba68b967 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/grade/GradeRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/grade/GradeRepository.kt @@ -9,6 +9,7 @@ import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.utils.uniqueSubtract import io.reactivex.Completable import io.reactivex.Single +import org.threeten.bp.LocalDateTime import java.net.UnknownHostException import javax.inject.Inject import javax.inject.Singleton @@ -43,7 +44,31 @@ class GradeRepository @Inject constructor( local.getGradesSummary(semester).toSingle(emptyList()) .doOnSuccess { old -> local.deleteGradesSummary(old.uniqueSubtract(newSummary)) - local.saveGradesSummary(newSummary.uniqueSubtract(old)) + local.saveGradesSummary(newSummary.uniqueSubtract(old) + .onEach { summary -> + val oldSummary = old.find { oldSummary -> oldSummary.subject == summary.subject } + summary.isPredictedGradeNotified = when { + summary.predictedGrade.isEmpty() -> true + notify && oldSummary?.predictedGrade != summary.predictedGrade -> false + else -> true + } + summary.isFinalGradeNotified = when { + summary.finalGrade.isEmpty() -> true + notify && oldSummary?.finalGrade != summary.finalGrade -> false + else -> true + } + + summary.predictedGradeLastChange = when { + oldSummary == null -> LocalDateTime.now() + summary.predictedGrade != oldSummary.predictedGrade -> LocalDateTime.now() + else -> oldSummary.predictedGradeLastChange + } + summary.finalGradeLastChange = when { + oldSummary == null -> LocalDateTime.now() + summary.finalGrade != oldSummary.finalGrade -> LocalDateTime.now() + else -> oldSummary.finalGradeLastChange + } + }) } } }.flatMap { @@ -63,6 +88,14 @@ class GradeRepository @Inject constructor( return local.getGradesDetails(semester).map { it.filter { grade -> !grade.isNotified } }.toSingle(emptyList()) } + fun getNotNotifiedPredictedGrades(semester: Semester): Single> { + return local.getGradesSummary(semester).map { it.filter { gradeSummary -> !gradeSummary.isPredictedGradeNotified } }.toSingle(emptyList()) + } + + fun getNotNotifiedFinalGrades(semester: Semester): Single> { + return local.getGradesSummary(semester).map { it.filter { gradeSummary -> !gradeSummary.isFinalGradeNotified } }.toSingle(emptyList()) + } + fun updateGrade(grade: Grade): Completable { return Completable.fromCallable { local.updateGrades(listOf(grade)) } } @@ -70,4 +103,8 @@ class GradeRepository @Inject constructor( fun updateGrades(grades: List): Completable { return Completable.fromCallable { local.updateGrades(grades) } } + + fun updateGradesSummary(gradesSummary: List): Completable { + return Completable.fromCallable { local.updateGradesSummary(gradesSummary) } + } } diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeWork.kt index 6e90826ad..fcdaad6e6 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeWork.kt @@ -9,6 +9,7 @@ import androidx.core.app.NotificationCompat.PRIORITY_HIGH import androidx.core.app.NotificationManagerCompat import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Grade +import io.github.wulkanowy.data.db.entities.GradeSummary import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.repositories.grade.GradeRepository @@ -30,17 +31,21 @@ class GradeWork @Inject constructor( override fun create(student: Student, semester: Semester): Completable { return gradeRepository.getGrades(student, semester, true, preferencesRepository.isNotificationsEnable) - .flatMap { gradeRepository.getNotNotifiedGrades(semester) } - .flatMapCompletable { - if (it.isNotEmpty()) notify(it) + .ignoreElement() + .concatWith(Completable.concatArray(gradeRepository.getNotNotifiedGrades(semester).flatMapCompletable { + if (it.isNotEmpty()) notifyDetails(it) gradeRepository.updateGrades(it.onEach { grade -> grade.isNotified = true }) - } + }, gradeRepository.getNotNotifiedPredictedGrades(semester).flatMapCompletable { + if (it.isNotEmpty()) notifyPredicted(it) + gradeRepository.updateGradesSummary(it.onEach { grade -> grade.isPredictedGradeNotified = true }) + }, gradeRepository.getNotNotifiedFinalGrades(semester).flatMapCompletable { + if (it.isNotEmpty()) notifyFinal(it) + gradeRepository.updateGradesSummary(it.onEach { grade -> grade.isFinalGradeNotified = true }) + })) } - private fun notify(grades: List) { - notificationManager.notify(Random.nextInt(Int.MAX_VALUE), NotificationCompat.Builder(context, NewGradesChannel.CHANNEL_ID) - .setContentTitle(context.resources.getQuantityString(R.plurals.grade_new_items, grades.size, grades.size)) - .setContentText(context.resources.getQuantityString(R.plurals.grade_notify_new_items, grades.size, grades.size)) + private fun getNotificationBuilder(): NotificationCompat.Builder { + return NotificationCompat.Builder(context, NewGradesChannel.CHANNEL_ID) .setSmallIcon(R.drawable.ic_stat_grade) .setAutoCancel(true) .setPriority(PRIORITY_HIGH) @@ -49,6 +54,12 @@ class GradeWork @Inject constructor( .setContentIntent( PendingIntent.getActivity(context, MainView.Section.GRADE.id, MainActivity.getStartIntent(context, MainView.Section.GRADE, true), FLAG_UPDATE_CURRENT)) + } + + private fun notifyDetails(grades: List) { + notificationManager.notify(Random.nextInt(Int.MAX_VALUE), getNotificationBuilder() + .setContentTitle(context.resources.getQuantityString(R.plurals.grade_new_items, grades.size, grades.size)) + .setContentText(context.resources.getQuantityString(R.plurals.grade_notify_new_items, grades.size, grades.size)) .setStyle(NotificationCompat.InboxStyle().run { setSummaryText(context.resources.getQuantityString(R.plurals.grade_number_item, grades.size, grades.size)) grades.forEach { addLine("${it.subject}: ${it.entry}") } @@ -57,4 +68,30 @@ class GradeWork @Inject constructor( .build() ) } + + private fun notifyPredicted(gradesSummary: List) { + notificationManager.notify(Random.nextInt(Int.MAX_VALUE), getNotificationBuilder() + .setContentTitle(context.resources.getQuantityString(R.plurals.grade_new_items_predicted, gradesSummary.size, gradesSummary.size)) + .setContentText(context.resources.getQuantityString(R.plurals.grade_notify_new_items_predicted, gradesSummary.size, gradesSummary.size)) + .setStyle(NotificationCompat.InboxStyle().run { + setSummaryText(context.resources.getQuantityString(R.plurals.grade_number_item, gradesSummary.size, gradesSummary.size)) + gradesSummary.forEach { addLine("${it.subject}: ${it.predictedGrade}") } + this + }) + .build() + ) + } + + private fun notifyFinal(gradesSummary: List) { + notificationManager.notify(Random.nextInt(Int.MAX_VALUE), getNotificationBuilder() + .setContentTitle(context.resources.getQuantityString(R.plurals.grade_new_items_final, gradesSummary.size, gradesSummary.size)) + .setContentText(context.resources.getQuantityString(R.plurals.grade_notify_new_items_final, gradesSummary.size, gradesSummary.size)) + .setStyle(NotificationCompat.InboxStyle().run { + setSummaryText(context.resources.getQuantityString(R.plurals.grade_number_item, gradesSummary.size, gradesSummary.size)) + gradesSummary.forEach { addLine("${it.subject}: ${it.finalGrade}") } + this + }) + .build() + ) + } } diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 4cb8997cf..2bba1640b 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -107,12 +107,36 @@ Nowe oceny Nowe oceny + + Nowa ocena przewidywana + Nowe oceny przewidywane + Nowe oceny przewidywane + Nowe oceny przewidywane + + + Nowa ocena końcowa + Nowe oceny końcowe + Nowe oceny końcowe + Nowe oceny końcowe + Masz %1$d nową ocenę Masz %1$d nowe oceny Masz %1$d nowych ocen Masz %1$d nowych ocen + + Masz %1$d nową przewidywaną ocenę + Masz %1$d nowe przewidywane oceny + Masz %1$d nowych przewidywanych ocen + Masz %1$d nowych przewidywanych ocen + + + Masz %1$d nową końcową ocenę + Masz %1$d nowe końcowe oceny + Masz %1$d nowych końcowych ocen + Masz %1$d nowych końcowych ocen + Lekcja Sala diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bec752cf2..3efb53cb2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -113,10 +113,26 @@ New grade New grades + + New predicted grade + New predicted grades + + + New final grade + New final grades + You received %1$d grade You received %1$d grades + + You received %1$d predicted grade + You received %1$d predicted grades + + + You received %1$d final grade + You received %1$d final grades + From 924bcb0d647b1664de83427a669f721bf5594d86 Mon Sep 17 00:00:00 2001 From: Dominik Korsa Date: Sun, 14 Jun 2020 00:50:09 +0200 Subject: [PATCH 82/85] Message sharing and printing (#866) --- app/src/main/assets/message-print-page.html | 94 +++++++++++++++++++ app/src/main/assets/wulkanowy-logo-black.svg | 74 +++++++++++++++ .../message/preview/MessagePreviewAdapter.kt | 2 +- .../message/preview/MessagePreviewFragment.kt | 61 ++++++++++++ .../preview/MessagePreviewPresenter.kt | 65 ++++++++++++- .../message/preview/MessagePreviewView.kt | 11 +++ .../wulkanowy/utils/ContextExtension.kt | 13 +++ .../res/drawable/ic_menu_message_print.xml | 13 +++ .../res/drawable/ic_menu_message_share.xml | 10 ++ .../res/menu/action_menu_message_preview.xml | 14 +++ app/src/main/res/values-pl/strings.xml | 2 + app/src/main/res/values/strings.xml | 2 + 12 files changed, 359 insertions(+), 2 deletions(-) create mode 100644 app/src/main/assets/message-print-page.html create mode 100644 app/src/main/assets/wulkanowy-logo-black.svg create mode 100644 app/src/main/res/drawable/ic_menu_message_print.xml create mode 100644 app/src/main/res/drawable/ic_menu_message_share.xml diff --git a/app/src/main/assets/message-print-page.html b/app/src/main/assets/message-print-page.html new file mode 100644 index 000000000..8da7dec6e --- /dev/null +++ b/app/src/main/assets/message-print-page.html @@ -0,0 +1,94 @@ + + + + + %SUBJECT% | Wulkanowy + + + +

%SUBJECT%

+
+
+ %INFO% +
+ +
+
+

Treść wiadomości

+ %CONTENT% +
+ + diff --git a/app/src/main/assets/wulkanowy-logo-black.svg b/app/src/main/assets/wulkanowy-logo-black.svg new file mode 100644 index 000000000..9bfbe2c02 --- /dev/null +++ b/app/src/main/assets/wulkanowy-logo-black.svg @@ -0,0 +1,74 @@ + +image/svg+xml \ No newline at end of file diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewAdapter.kt index 436dee53f..a94d2cfc8 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewAdapter.kt @@ -63,7 +63,7 @@ class MessagePreviewAdapter @Inject constructor() : @SuppressLint("SetTextI18n") private fun bindMessage(holder: MessageViewHolder, message: Message) { with(holder.binding) { - messagePreviewSubject.text = if (message.subject.isNotBlank()) message.subject else root.context.getString(R.string.message_no_subject) + messagePreviewSubject.text = message.subject.ifBlank { root.context.getString(R.string.message_no_subject) } messagePreviewDate.text = root.context.getString(R.string.message_date, message.date.toFormattedString("yyyy-MM-dd HH:mm:ss")) messagePreviewContent.text = message.content messagePreviewAuthor.text = if (message.folderId == MessageFolder.SENT.id) "${root.context.getString(R.string.message_to)} ${message.recipient}" diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewFragment.kt index 99eede15a..575db75b9 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewFragment.kt @@ -1,12 +1,20 @@ package io.github.wulkanowy.ui.modules.message.preview +import android.os.Build import android.os.Bundle +import android.print.PrintAttributes +import android.print.PrintManager import android.view.Menu import android.view.MenuInflater import android.view.MenuItem import android.view.View import android.view.View.GONE import android.view.View.VISIBLE +import android.webkit.WebResourceRequest +import android.webkit.WebView +import android.webkit.WebViewClient +import androidx.annotation.RequiresApi +import androidx.core.content.getSystemService import androidx.recyclerview.widget.LinearLayoutManager import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Message @@ -17,6 +25,8 @@ import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.ui.modules.message.MessageFragment import io.github.wulkanowy.ui.modules.message.send.SendMessageActivity +import io.github.wulkanowy.utils.AppInfo +import io.github.wulkanowy.utils.shareText import javax.inject.Inject class MessagePreviewFragment : @@ -29,18 +39,31 @@ class MessagePreviewFragment : @Inject lateinit var previewAdapter: MessagePreviewAdapter + @Inject + lateinit var appInfo: AppInfo + private var menuReplyButton: MenuItem? = null private var menuForwardButton: MenuItem? = null private var menuDeleteButton: MenuItem? = null + private var menuShareButton: MenuItem? = null + + private var menuPrintButton: MenuItem? = null + override val titleStringId: Int get() = R.string.message_title override val deleteMessageSuccessString: String get() = getString(R.string.message_delete_success) + override val messageNoSubjectString: String + get() = getString(R.string.message_no_subject) + + override val printHTML: String + get() = requireContext().assets.open("message-print-page.html").bufferedReader().use { it.readText() } + companion object { const val MESSAGE_ID_KEY = "message_id" @@ -77,6 +100,8 @@ class MessagePreviewFragment : menuReplyButton = menu.findItem(R.id.messagePreviewMenuReply) menuForwardButton = menu.findItem(R.id.messagePreviewMenuForward) menuDeleteButton = menu.findItem(R.id.messagePreviewMenuDelete) + menuShareButton = menu.findItem(R.id.messagePreviewMenuShare) + menuPrintButton = menu.findItem(R.id.messagePreviewMenuPrint) presenter.onCreateOptionsMenu() } @@ -85,6 +110,8 @@ class MessagePreviewFragment : R.id.messagePreviewMenuReply -> presenter.onReply() R.id.messagePreviewMenuForward -> presenter.onForward() R.id.messagePreviewMenuDelete -> presenter.onMessageDelete() + R.id.messagePreviewMenuShare -> presenter.onShare() + R.id.messagePreviewMenuPrint -> presenter.onPrint() else -> false } } @@ -108,6 +135,8 @@ class MessagePreviewFragment : menuReplyButton?.isVisible = show menuForwardButton?.isVisible = show menuDeleteButton?.isVisible = show + menuShareButton?.isVisible = show + menuPrintButton?.isVisible = show && appInfo.systemVersion >= Build.VERSION_CODES.LOLLIPOP } override fun setDeletedOptionsLabels() { @@ -138,6 +167,38 @@ class MessagePreviewFragment : context?.let { it.startActivity(SendMessageActivity.getStartIntent(it, message)) } } + override fun shareText(text: String, subject: String) { + context?.shareText(text, subject) + } + + @RequiresApi(Build.VERSION_CODES.LOLLIPOP) + override fun printDocument(html: String, jobName: String) { + val webView = WebView(activity) + webView.webViewClient = object : WebViewClient() { + + override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest) = false + + override fun onPageFinished(view: WebView, url: String) { + createWebPrintJob(view, jobName) + } + } + + webView.loadDataWithBaseURL("file:///android_asset/", html, "text/HTML", "UTF-8", null) + } + + @RequiresApi(Build.VERSION_CODES.LOLLIPOP) + private fun createWebPrintJob(webView: WebView, jobName: String) { + activity?.getSystemService()?.let { printManager -> + val printAdapter = webView.createPrintDocumentAdapter(jobName) + + printManager.print( + jobName, + printAdapter, + PrintAttributes.Builder().build() + ) + } + } + override fun popView() { (activity as MainActivity).popView() } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewPresenter.kt index 24678c70e..db7996bca 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewPresenter.kt @@ -1,12 +1,17 @@ package io.github.wulkanowy.ui.modules.message.preview +import android.annotation.SuppressLint +import android.os.Build import io.github.wulkanowy.data.db.entities.Message +import io.github.wulkanowy.data.db.entities.MessageAttachment import io.github.wulkanowy.data.repositories.message.MessageRepository import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler +import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.FirebaseAnalyticsHelper import io.github.wulkanowy.utils.SchedulersProvider +import io.github.wulkanowy.utils.toFormattedString import timber.log.Timber import javax.inject.Inject @@ -15,11 +20,14 @@ class MessagePreviewPresenter @Inject constructor( errorHandler: ErrorHandler, studentRepository: StudentRepository, private val messageRepository: MessageRepository, - private val analytics: FirebaseAnalyticsHelper + private val analytics: FirebaseAnalyticsHelper, + private var appInfo: AppInfo ) : BasePresenter(errorHandler, studentRepository, schedulers) { var message: Message? = null + var attachments: List? = null + private lateinit var lastError: Throwable private var retryCallback: () -> Unit = {} @@ -56,6 +64,7 @@ class MessagePreviewPresenter @Inject constructor( .subscribe({ message -> Timber.i("Loading message ${message.message.messageId} preview result: Success ") this@MessagePreviewPresenter.message = message.message + this@MessagePreviewPresenter.attachments = message.attachments view?.apply { setMessageWithAttachment(message) initOptions() @@ -87,6 +96,60 @@ class MessagePreviewPresenter @Inject constructor( } else false } + fun onShare(): Boolean { + message?.let { + var text = "Temat: ${it.subject.ifBlank { view?.messageNoSubjectString.orEmpty() }}\n" + when (it.sender.isNotEmpty()) { + true -> "Od: ${it.sender}\n" + false -> "Do: ${it.recipient}\n" + } + "Data: ${it.date.toFormattedString("yyyy-MM-dd HH:mm:ss")}\n\n${it.content}" + + attachments?.let { attachments -> + if (attachments.isNotEmpty()) { + text += "\n\nZałączniki:" + + attachments.forEach { attachment -> + text += "\n${attachment.filename}: ${attachment.url}" + } + } + } + + view?.shareText(text, "FW: ${it.subject.ifBlank { view?.messageNoSubjectString.orEmpty() }}") + return true + } + return false + } + + @SuppressLint("NewApi") + fun onPrint(): Boolean { + if (appInfo.systemVersion < Build.VERSION_CODES.LOLLIPOP) return false + message?.let { + val dateString = it.date.toFormattedString("yyyy-MM-dd HH:mm:ss") + val infoContent = "

Data wysłania

$dateString
" + when { + it.sender.isNotEmpty() -> "

Od

${it.sender}
" + else -> "

Do

${it.recipient}
" + } + + val messageContent = "

${it.content}

" + .replace(Regex("[\\n\\r]{2,}"), "

") + .replace(Regex("[\\n\\r]"), "
") + + val jobName = "Wiadomość " + when { + it.sender.isNotEmpty() -> "od ${it.sender}" + else -> "do ${it.recipient}" + } + " $dateString: ${it.subject.ifBlank { view?.messageNoSubjectString.orEmpty() }} | Wulkanowy" + + view?.apply { + val html = printHTML + .replace("%SUBJECT%", it.subject.ifBlank { view?.messageNoSubjectString.orEmpty() }) + .replace("%CONTENT%", messageContent) + .replace("%INFO%", infoContent) + printDocument(html, jobName) + } + return true + } + return false + } + private fun deleteMessage() { message?.let { message -> disposable.add(studentRepository.getCurrentStudent() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewView.kt index 3d620459c..0fdb4bda3 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewView.kt @@ -1,5 +1,7 @@ package io.github.wulkanowy.ui.modules.message.preview +import android.os.Build +import androidx.annotation.RequiresApi import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.MessageWithAttachment import io.github.wulkanowy.ui.base.BaseView @@ -8,6 +10,10 @@ interface MessagePreviewView : BaseView { val deleteMessageSuccessString: String + val messageNoSubjectString: String + + val printHTML: String + fun initView() fun setMessageWithAttachment(item: MessageWithAttachment) @@ -34,5 +40,10 @@ interface MessagePreviewView : BaseView { fun openMessageForward(message: Message?) + fun shareText(text: String, subject: String) + + @RequiresApi(Build.VERSION_CODES.LOLLIPOP) + fun printDocument(html: String, jobName: String) + fun popView() } diff --git a/app/src/main/java/io/github/wulkanowy/utils/ContextExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/ContextExtension.kt index 2b40cb476..cf715e657 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/ContextExtension.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/ContextExtension.kt @@ -71,4 +71,17 @@ fun Context.openDialer(phone: String) { startActivity(intent) } +fun Context.shareText(text: String, subject: String?) { + val sendIntent: Intent = Intent().apply { + action = Intent.ACTION_SEND + putExtra(Intent.EXTRA_TEXT, text) + if (subject != null) { + putExtra(Intent.EXTRA_SUBJECT, subject) + } + type = "text/plain" + } + val shareIntent = Intent.createChooser(sendIntent, null) + startActivity(shareIntent) +} + fun Context.dpToPx(dp: Float) = dp * resources.displayMetrics.densityDpi / DENSITY_DEFAULT diff --git a/app/src/main/res/drawable/ic_menu_message_print.xml b/app/src/main/res/drawable/ic_menu_message_print.xml new file mode 100644 index 000000000..204b0f6e3 --- /dev/null +++ b/app/src/main/res/drawable/ic_menu_message_print.xml @@ -0,0 +1,13 @@ + + + + diff --git a/app/src/main/res/drawable/ic_menu_message_share.xml b/app/src/main/res/drawable/ic_menu_message_share.xml new file mode 100644 index 000000000..67a8ee494 --- /dev/null +++ b/app/src/main/res/drawable/ic_menu_message_share.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/menu/action_menu_message_preview.xml b/app/src/main/res/menu/action_menu_message_preview.xml index dfc12e234..4c1332e10 100644 --- a/app/src/main/res/menu/action_menu_message_preview.xml +++ b/app/src/main/res/menu/action_menu_message_preview.xml @@ -22,4 +22,18 @@ android:title="@string/message_delete" app:iconTint="@color/material_on_surface_emphasis_medium" app:showAsAction="ifRoom" /> + + diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 2bba1640b..a8eccabf0 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -204,6 +204,8 @@ Przenieś do kosza Usuń trwale Wiadomość usunięta pomyślnie + Udostępnij + Drukuj Temat Treść Wiadomość wysłana pomyślnie diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3efb53cb2..1eaebf284 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -210,6 +210,8 @@ Move to trash Delete permanently Message deleted successfully + Share + Print Subject Content Message sent successfully From 6e1ddb482e90008dcf8af110362abedc4d10d42b Mon Sep 17 00:00:00 2001 From: Dominik Korsa Date: Sun, 14 Jun 2020 14:05:24 +0200 Subject: [PATCH 83/85] Message fuzzy search (#869) --- app/build.gradle | 3 +- .../modules/message/tab/MessageTabAdapter.kt | 56 ++++--- .../modules/message/tab/MessageTabFragment.kt | 2 +- .../message/tab/MessageTabPresenter.kt | 147 ++++++++++++------ 4 files changed, 130 insertions(+), 78 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 44896664a..82d92f25a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -142,7 +142,7 @@ dependencies { implementation "androidx.constraintlayout:constraintlayout:1.1.3" implementation "androidx.coordinatorlayout:coordinatorlayout:1.1.0" implementation "com.google.android.material:material:1.1.0" - implementation "com.github.wulkanowy:material-chips-input:2.0.1" + implementation "com.github.wulkanowy:material-chips-input:2.1.1" implementation "com.github.PhilJay:MPAndroidChart:v3.1.0" implementation "me.zhanghai.android.materialprogressbar:library:1.6.1" @@ -180,6 +180,7 @@ dependencies { implementation 'com.wdullaer:materialdatetimepicker:4.2.3' implementation "io.coil-kt:coil:0.11.0" implementation "io.github.wulkanowy:AppKillerManager:3.0.0" + implementation 'me.xdrop:fuzzywuzzy:1.3.1' playImplementation 'com.google.firebase:firebase-analytics:17.4.3' playImplementation 'com.google.firebase:firebase-inappmessaging-display-ktx:19.0.7' diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabAdapter.kt index ece6773fd..b58508a98 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabAdapter.kt @@ -4,10 +4,9 @@ import android.graphics.Typeface import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView.NO_POSITION -import androidx.recyclerview.widget.SortedList -import androidx.recyclerview.widget.SortedListAdapterCallback import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.repositories.message.MessageFolder @@ -20,39 +19,23 @@ class MessageTabAdapter @Inject constructor() : var onClickListener: (Message, position: Int) -> Unit = { _, _ -> } - private val items = SortedList(Message::class.java, object : - SortedListAdapterCallback(this) { + private var items = mutableListOf() - override fun compare(item1: Message, item2: Message): Int { - return item2.date.compareTo(item1.date) - } - - override fun areContentsTheSame(oldItem: Message?, newItem: Message?): Boolean { - return oldItem == newItem - } - - override fun areItemsTheSame(item1: Message, item2: Message): Boolean { - return item1 == item2 - } - }) - - fun replaceAll(models: List) { - items.beginBatchedUpdates() - for (i in items.size() - 1 downTo 0) { - val model = items.get(i) - if (model !in models) { - items.remove(model) - } - } - items.addAll(models) - items.endBatchedUpdates() + fun setDataItems(data: List) { + val diffResult = DiffUtil.calculateDiff(MessageTabDiffUtil(items, data)) + items = data.toMutableList() + diffResult.dispatchUpdatesTo(this) } fun updateItem(position: Int, item: Message) { - items.updateItemAt(position, item) + val currentItem = items[position] + items[position] = item + if (item != currentItem) { + notifyItemChanged(position) + } } - override fun getItemCount() = items.size() + override fun getItemCount() = items.size override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder( ItemMessageBinding.inflate(LayoutInflater.from(parent.context), parent, false) @@ -85,4 +68,19 @@ class MessageTabAdapter @Inject constructor() : } class ItemViewHolder(val binding: ItemMessageBinding) : RecyclerView.ViewHolder(binding.root) + + private class MessageTabDiffUtil(private val old: List, private val new: List) : + DiffUtil.Callback() { + override fun getOldListSize(): Int = old.size + + override fun getNewListSize(): Int = new.size + + override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { + return old[oldItemPosition].id == new[newItemPosition].id + } + + override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { + return old[oldItemPosition] == new[newItemPosition] + } + } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabFragment.kt index 909bb6873..9954c6428 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabFragment.kt @@ -90,7 +90,7 @@ class MessageTabFragment : BaseFragment(R.layout.frag } override fun updateData(data: List) { - tabAdapter.replaceAll(data) + tabAdapter.setDataItems(data) } override fun updateItem(item: Message, position: Int) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt index 221762d16..533f5ac85 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt @@ -1,6 +1,5 @@ package io.github.wulkanowy.ui.modules.message.tab -import android.annotation.SuppressLint import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.repositories.message.MessageFolder import io.github.wulkanowy.data.repositories.message.MessageRepository @@ -11,8 +10,13 @@ import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.FirebaseAnalyticsHelper import io.github.wulkanowy.utils.SchedulersProvider import io.github.wulkanowy.utils.toFormattedString +import io.reactivex.subjects.PublishSubject +import me.xdrop.fuzzywuzzy.FuzzySearch import timber.log.Timber +import java.util.Locale +import java.util.concurrent.TimeUnit import javax.inject.Inject +import kotlin.math.pow class MessageTabPresenter @Inject constructor( schedulers: SchedulersProvider, @@ -31,9 +35,12 @@ class MessageTabPresenter @Inject constructor( private var messages = emptyList() + private val searchQuery = PublishSubject.create() + fun onAttachView(view: MessageTabView, folder: MessageFolder) { super.onAttachView(view) view.initView() + initializeSearchStream() errorHandler.showErrorMessage = ::showErrorViewOnError this.folder = folder } @@ -76,38 +83,35 @@ class MessageTabPresenter @Inject constructor( private fun loadData(forceRefresh: Boolean) { Timber.i("Loading $folder message data started") - disposable.apply { - clear() - add(studentRepository.getCurrentStudent() - .flatMap { student -> - semesterRepository.getCurrentSemester(student) - .flatMap { messageRepository.getMessages(student, it, folder, forceRefresh) } + disposable.add(studentRepository.getCurrentStudent() + .flatMap { student -> + semesterRepository.getCurrentSemester(student) + .flatMap { messageRepository.getMessages(student, it, folder, forceRefresh) } + } + .subscribeOn(schedulers.backgroundThread) + .observeOn(schedulers.mainThread) + .doFinally { + view?.run { + showRefresh(false) + showProgress(false) + enableSwipe(true) + notifyParentDataLoaded() } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .doFinally { - view?.run { - showRefresh(false) - showProgress(false) - enableSwipe(true) - notifyParentDataLoaded() - } - } - .subscribe({ - Timber.i("Loading $folder message result: Success") - messages = it - onSearchQueryTextChange(lastSearchQuery) - analytics.logEvent( - "load_data", - "type" to "messages", - "items" to it.size, - "folder" to folder.name - ) - }) { - Timber.i("Loading $folder message result: An exception occurred") - errorHandler.dispatch(it) - }) - } + } + .subscribe({ + Timber.i("Loading $folder message result: Success") + messages = it + view?.updateData(getFilteredData(lastSearchQuery)) + analytics.logEvent( + "load_data", + "type" to "messages", + "items" to it.size, + "folder" to folder.name + ) + }) { + Timber.i("Loading $folder message result: An exception occurred") + errorHandler.dispatch(it) + }) } private fun showErrorViewOnError(message: String, error: Throwable) { @@ -121,25 +125,36 @@ class MessageTabPresenter @Inject constructor( } } - @SuppressLint("DefaultLocale") fun onSearchQueryTextChange(query: String) { - lastSearchQuery = query + if (query != searchQuery.toString()) + searchQuery.onNext(query) + } - val lowerCaseQuery = query.toLowerCase() - val filteredList = mutableListOf() - messages.forEach { - if (lowerCaseQuery in it.subject.toLowerCase() || - lowerCaseQuery in it.sender.toLowerCase() || - lowerCaseQuery in it.recipient.toLowerCase() || - lowerCaseQuery in it.date.toFormattedString() - ) { - filteredList.add(it) + private fun initializeSearchStream() { + disposable.add(searchQuery + .debounce(250, TimeUnit.MILLISECONDS) + .map { query -> + lastSearchQuery = query + getFilteredData(query) } + .subscribeOn(schedulers.backgroundThread) + .observeOn(schedulers.mainThread) + .subscribe({ + Timber.d("Applying filter. Full list: ${messages.size}, filtered: ${it.size}") + updateData(it) + }) { Timber.e(it) }) + } + + private fun getFilteredData(query: String): List { + return if (query.trim().isEmpty()) { + messages.sortedByDescending { it.date } + } else { + messages + .map { it to calculateMatchRatio(it, query) } + .sortedByDescending { it.second } + .filter { it.second > 5000 } + .map { it.first } } - - Timber.d("Applying filter. Full list: ${messages.size}, filtered: ${filteredList.size}") - - updateData(filteredList) } private fun updateData(data: List) { @@ -151,4 +166,42 @@ class MessageTabPresenter @Inject constructor( resetListPosition() } } + + private fun calculateMatchRatio(message: Message, query: String): Int { + val subjectRatio = FuzzySearch.tokenSortPartialRatio( + query.toLowerCase(Locale.getDefault()), + message.subject + ) + + val senderOrRecipientRatio = FuzzySearch.tokenSortPartialRatio( + query.toLowerCase(Locale.getDefault()), + if (message.sender.isNotEmpty()) message.sender.toLowerCase(Locale.getDefault()) + else message.recipient.toLowerCase(Locale.getDefault()) + ) + + val dateRatio = listOf( + FuzzySearch.ratio( + query.toLowerCase(Locale.getDefault()), + message.date.toFormattedString("dd.MM").toLowerCase(Locale.getDefault()) + ), + FuzzySearch.ratio( + query.toLowerCase(Locale.getDefault()), + message.date.toFormattedString("dd.MM.yyyy").toLowerCase(Locale.getDefault()) + ), + FuzzySearch.ratio( + query.toLowerCase(Locale.getDefault()), + message.date.toFormattedString("d MMMM").toLowerCase(Locale.getDefault()) + ), + FuzzySearch.ratio( + query.toLowerCase(Locale.getDefault()), + message.date.toFormattedString("d MMMM yyyy").toLowerCase(Locale.getDefault()) + ) + ).max() ?: 0 + + + return (subjectRatio.toDouble().pow(2) + + senderOrRecipientRatio.toDouble().pow(2) + + dateRatio.toDouble().pow(2) * 2 + ).toInt() + } } From dfe7981e7fa71321267f2d1daf545a7ae65e9eb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Sun, 14 Jun 2020 22:37:58 +0200 Subject: [PATCH 84/85] New Crowdin translations (#874) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Mikołaj Pich --- .../main/res/values-de/preferences_values.xml | 4 +-- app/src/main/res/values-de/strings.xml | 30 ++++++++++++++--- app/src/main/res/values-pl/strings.xml | 2 +- .../main/res/values-ru/preferences_values.xml | 2 +- app/src/main/res/values-ru/strings.xml | 32 ++++++++++++++++++- .../main/res/values-uk/preferences_values.xml | 2 +- app/src/main/res/values-uk/strings.xml | 32 ++++++++++++++++++- 7 files changed, 93 insertions(+), 11 deletions(-) diff --git a/app/src/main/res/values-de/preferences_values.xml b/app/src/main/res/values-de/preferences_values.xml index 496287984..11935b49b 100644 --- a/app/src/main/res/values-de/preferences_values.xml +++ b/app/src/main/res/values-de/preferences_values.xml @@ -36,8 +36,8 @@ Durchschnittsnote für das 2. Semester - Average of grades from both semesters - Durchschnitt der Bewertungen für das ganze Jahr + Durchschnitt der Noten aus beiden Semestern + Durchschnitt der Noten aus dem ganzen Jahr Nicht zeigen diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 4b1669371..7cc44fd97 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -103,10 +103,26 @@ Neue Note Neue Noten + + New predicted grade + New predicted grades + + + New final grade + New final grades + Du hast %1$d Note bekommen Du hast %1$d Noten bekommen + + You received %1$d predicted grade + You received %1$d predicted grades + + + You received %1$d final grade + You received %1$d final grades + Lektion Klassenzimmer @@ -172,6 +188,8 @@ In den Korb wandern Dauerhaft löschen Nachricht erfolgreich gelöscht + Share + Print Thema Inhalt Nachricht erfolgreich gesendet @@ -214,7 +232,7 @@ Die heutige Glücksnummer ist Keine Information über die Glücksnummer. Glücksnummer für heute - Die heutige Glücksnummer ist: + Die heutige Glücksnummer ist: %d Mobile Geräte Keine Geräte @@ -245,6 +263,10 @@ Abmelden Wollen Sie sich von einem aktiven Studenten abmelden? Abmeldung von Student + Student account + Parent account + Mobile API mode + Hybrid mode Version der App Mitarbeiter @@ -270,8 +292,8 @@ Logs teilen Aktualisieren - Check for updates - Before reporting a bug, check first if an update with the bug fix is available + Auf Updates prüfen + Bevor Sie einen Fehler melden, prüfen Sie zuerst, ob ein Update mit der Fehlerbehebung verfügbar ist Inhalt Wiederhol @@ -289,7 +311,7 @@ Zurück Nächste Suchen - Suchen... + Suchen… Keine Lektionen Thema wählen diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index a8eccabf0..bd4545e9b 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -339,7 +339,7 @@ Poprzedni Następny Szukaj - Szukaj... + Szukaj… Brak lekcji Wybierz motyw diff --git a/app/src/main/res/values-ru/preferences_values.xml b/app/src/main/res/values-ru/preferences_values.xml index bd8bb844f..a41abf350 100644 --- a/app/src/main/res/values-ru/preferences_values.xml +++ b/app/src/main/res/values-ru/preferences_values.xml @@ -37,7 +37,7 @@ Средняя оценка со 2 семестра Average of grades from both semesters - Средняя оценка с целого года + Average of grades from the whole year Не показывать diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 6eb8f7741..15c583173 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -107,12 +107,36 @@ Новые оценки Новые оценки + + New predicted grade + New predicted grades + New predicted grades + New predicted grades + + + New final grade + New final grades + New final grades + New final grades + Вы получили %1$d оценку Вы получили %1$d оценки Вы получили %1$d оценок Вы получили %1$d оценок + + You received %1$d predicted grade + You received %1$d predicted grades + You received %1$d predicted grades + You received %1$d predicted grades + + + You received %1$d final grade + You received %1$d final grades + You received %1$d final grades + You received %1$d final grades + Урок Аудитория @@ -180,6 +204,8 @@ Перенести в корзину Удалить навсегда Сообщение успешно удалено + Share + Print Тема Текст Сообщение успешно отправлено @@ -265,6 +291,10 @@ Выйти Вы точно хотите выйти из данного аккаунта? Выйти + Student account + Parent account + Mobile API mode + Hybrid mode Версия приложения Разработчики @@ -309,7 +339,7 @@ Предыдущий Следующий Поиск - Поиск... + Поиск… Нет уроков Выбрать тему diff --git a/app/src/main/res/values-uk/preferences_values.xml b/app/src/main/res/values-uk/preferences_values.xml index be2179c3e..9942621a5 100644 --- a/app/src/main/res/values-uk/preferences_values.xml +++ b/app/src/main/res/values-uk/preferences_values.xml @@ -37,7 +37,7 @@ Середня оцінка з 2 семестру Average of grades from both semesters - Середня оцінка за весь рік + Average of grades from the whole year Не показувати diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index ec4dbc155..423c4e12f 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -107,12 +107,36 @@ Нові оцінки Нові оцінки + + New predicted grade + New predicted grades + New predicted grades + New predicted grades + + + New final grade + New final grades + New final grades + New final grades + Ви отримали %1$d оцінку Ви отримали %1$d оцінки Ви отримали %1$d оцінок Ви отримали %1$d оцінок + + You received %1$d predicted grade + You received %1$d predicted grades + You received %1$d predicted grades + You received %1$d predicted grades + + + You received %1$d final grade + You received %1$d final grades + You received %1$d final grades + You received %1$d final grades + Урок Аудиторія @@ -180,6 +204,8 @@ Перемістити у кошик Видалити назавжди Повідомлення було успішно видалено + Share + Print Тема Зміст Повідомлення було успішно відправлено @@ -265,6 +291,10 @@ Вийти Ви впевнені, що хочете вийти з цього аккаунту? Вийти з аккаунту учня + Student account + Parent account + Mobile API mode + Hybrid mode Версія додатка Розробники @@ -309,7 +339,7 @@ Попередній Наступний Пошук - Пошук... + Пошук… Брак уроків Увібрати тему From c13f12f729114c5bfd4539f168fcd1dfd383b52c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sun, 14 Jun 2020 22:40:36 +0200 Subject: [PATCH 85/85] Version 0.19.0 --- .travis.yml | 2 +- app/build.gradle | 6 +++--- app/src/main/play/release-notes/pl-PL/default.txt | 10 ++++++---- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index 788aeffa7..a24058083 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,7 +14,7 @@ cache: branches: only: - develop - - 0.18.3 + - 0.19.0 android: licenses: diff --git a/app/build.gradle b/app/build.gradle index 82d92f25a..621e5a4fe 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -17,8 +17,8 @@ android { testApplicationId "io.github.tests.wulkanowy" minSdkVersion 17 targetSdkVersion 29 - versionCode 62 - versionName "0.18.3" + versionCode 63 + versionName "0.19.0" multiDexEnabled true resValue "string", "app_name", "Wulkanowy" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -124,7 +124,7 @@ configurations.all { } dependencies { - implementation "io.github.wulkanowy:sdk:7dc0761" + implementation "io.github.wulkanowy:sdk:0.19.0" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation "androidx.core:core-ktx:1.2.0" diff --git a/app/src/main/play/release-notes/pl-PL/default.txt b/app/src/main/play/release-notes/pl-PL/default.txt index db9e9a9a3..68045da4a 100644 --- a/app/src/main/play/release-notes/pl-PL/default.txt +++ b/app/src/main/play/release-notes/pl-PL/default.txt @@ -1,6 +1,8 @@ -Wersja 0.18.3 -- poprawiliśmy liczenie średniej i dodaliśmy nowy sposób jej liczenia w ustawieniach -- naprawiliśmy usuwanie wiadomości -- naprawiliśmy wysyłanie wiadomości na Lubelskim Portalu Oświatowym +Wersja 0.19.0 +- naprawiliśmy pokazywanie brakujących przedmiotów na liście podsumowania ocen +- ulepszyliśmy wygląd menadżera kont +- ulepszyliśmy wyszukiwarkę wiadomości +- dodaliśmy powiadomienia o proponowanych i końcowych ocenach +- dodaliśmy opcję udostępniania i drukowania wiadomości Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases