From 79bdbbbb16f5f245e7bdff53f935a17da0a31c2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Sun, 4 Mar 2018 12:49:16 +0100 Subject: [PATCH] MVP (#46) --- .bettercodehub.yml | 2 +- .../java/io/github/wulkanowy/api/Vulcan.java | 8 +- .../io/github/wulkanowy/api/login/Login.java | 15 +- app/build.gradle | 21 +- .../db/dao/entities/AccountTest.java | 2 +- .../{ => data}/db/dao/entities/DayTest.java | 2 +- .../{ => data}/db/dao/entities/GradeTest.java | 2 +- .../db/dao/entities/LessonTest.java | 2 +- .../db/dao/entities/SubjectTest.java | 2 +- .../{ => data}/db/dao/entities/WeekTest.java | 2 +- .../wulkanowy/db/dao/DatabaseAccessTest.java | 50 --- .../sync/CurrentAccountLoginTest.java | 106 ----- .../services/sync/FirstAccountLoginTest.java | 99 ----- .../services/sync/GradeSyncTest.java | 88 ----- .../services/sync/SubjectSyncTest.java | 82 ---- .../services/sync/TimetableSyncTest.java | 95 ----- .../services/sync/VulcanSyncTest.java | 30 -- .../wulkanowy/utils/security/SafetyTest.java | 25 -- .../utils/security/ScramblerTest.java | 46 +-- app/src/main/AndroidManifest.xml | 9 +- .../io/github/wulkanowy/WulkanowyApp.java | 42 +- .../io/github/wulkanowy/data/Repository.java | 143 +++++++ .../wulkanowy/data/RepositoryContract.java | 34 ++ .../wulkanowy/data/db/dao/DbHelper.java | 19 + .../{ => data}/db/dao/entities/Account.java | 2 +- .../{ => data}/db/dao/entities/Day.java | 2 +- .../{ => data}/db/dao/entities/Grade.java | 43 +- .../{ => data}/db/dao/entities/Lesson.java | 10 +- .../{ => data}/db/dao/entities/Subject.java | 2 +- .../{ => data}/db/dao/entities/Week.java | 2 +- .../data/db/resources/AppResources.java | 59 +++ .../data/db/resources/ResourcesContract.java | 10 + .../wulkanowy/data/db/shared/SharedPref.java | 33 ++ .../data/db/shared/SharedPrefContract.java | 8 + .../wulkanowy/data/sync/SyncContract.java | 11 + .../wulkanowy/data/sync/grades/GradeSync.java | 72 ++++ .../wulkanowy/data/sync/login/LoginSync.java | 81 ++++ .../data/sync/login/LoginSyncContract.java | 19 + .../data/sync/subjects/SubjectSync.java | 61 +++ .../data/sync/timetable/TimetableSync.java | 119 ++++++ .../sync/timetable/TimetableSyncContract.java | 13 + .../wulkanowy/db/dao/DatabaseAccess.java | 20 - .../di/annotations/ActivityContext.java | 11 + .../di/annotations/ApplicationContext.java | 11 + .../di/annotations/DatabaseInfo.java | 11 + .../wulkanowy/di/annotations/PerActivity.java | 11 + .../wulkanowy/di/annotations/PerFragment.java | 11 + .../di/annotations/SharedPreferencesInfo.java | 11 + .../wulkanowy/di/annotations/SyncGrades.java | 11 + .../di/annotations/SyncSubjects.java | 11 + .../di/component/ActivityComponent.java | 19 + .../di/component/ApplicationComponent.java | 26 ++ .../di/component/FragmentComponent.java | 25 ++ .../wulkanowy/di/modules/ActivityModule.java | 64 +++ .../di/modules/ApplicationModule.java | 104 ++++- .../wulkanowy/di/modules/FragmentModule.java | 76 ++++ .../services/NotificationService.java | 55 +++ .../io/github/wulkanowy/services/SyncJob.java | 114 ++++++ .../io/github/wulkanowy/services/Updater.java | 189 --------- .../wulkanowy/services/jobs/FullSyncJob.java | 80 ---- .../services/jobs/VulcanJobHelper.java | 22 -- .../services/jobs/VulcanService.java | 57 --- .../notifications/NotificationBuilder.java | 60 --- .../services/sync/CurrentAccountLogin.java | 64 --- .../services/sync/FirstAccountLogin.java | 59 --- .../wulkanowy/services/sync/GradesSync.java | 64 --- .../wulkanowy/services/sync/LoginSession.java | 69 ---- .../wulkanowy/services/sync/SubjectsSync.java | 39 -- .../services/sync/TimetableSync.java | 121 ------ .../wulkanowy/services/sync/VulcanSync.java | 105 ----- .../wulkanowy/ui/base/BaseActivity.java | 67 ++++ .../wulkanowy/ui/base/BaseContract.java | 27 ++ .../wulkanowy/ui/base/BaseFragment.java | 104 +++++ .../wulkanowy/ui/base/BasePresenter.java | 35 ++ .../wulkanowy/ui/login/LoginActivity.java | 367 +++++++++--------- .../wulkanowy/ui/login/LoginContract.java | 53 +++ .../wulkanowy/ui/login/LoginPresenter.java | 143 +++++++ .../github/wulkanowy/ui/login/LoginTask.java | 214 ++-------- .../wulkanowy/ui/main/AbstractFragment.java | 189 --------- .../wulkanowy/ui/main/AsyncResponse.java | 14 - .../wulkanowy/ui/main/DashboardActivity.java | 133 ------- .../wulkanowy/ui/main/DatabaseQueryTask.java | 30 -- .../wulkanowy/ui/main/MainActivity.java | 153 ++++++++ .../wulkanowy/ui/main/MainContract.java | 26 ++ .../wulkanowy/ui/main/MainPagerAdapter.java | 31 ++ .../wulkanowy/ui/main/MainPresenter.java | 44 +++ .../ui/main/OnFragmentIsReadyListener.java | 6 + .../github/wulkanowy/ui/main/RefreshTask.java | 56 --- .../main/attendance/AttendanceContract.java | 23 ++ .../main/attendance/AttendanceFragment.java | 48 ++- .../main/attendance/AttendancePresenter.java | 37 ++ .../ui/main/board/BoardFragment.java | 19 - .../ui/main/dashboard/DashboardContract.java | 23 ++ .../ui/main/dashboard/DashboardFragment.java | 48 +++ .../ui/main/dashboard/DashboardPresenter.java | 37 ++ .../ui/main/grades/GradeHeaderItem.java | 117 ++++++ .../ui/main/grades/GradesAdapter.java | 183 --------- .../ui/main/grades/GradesContract.java | 40 ++ .../ui/main/grades/GradesDialogFragment.java | 147 +++---- .../ui/main/grades/GradesFragment.java | 268 +++++-------- .../ui/main/grades/GradesPresenter.java | 157 ++++++++ .../ui/main/grades/GradesSubItem.java | 150 +++++++ .../ui/main/grades/SubjectWithGrades.java | 15 - .../wulkanowy/ui/main/timetable/TabsData.java | 37 ++ .../ui/main/timetable/TimetableContract.java | 35 ++ .../timetable/TimetableDialogFragment.java | 107 +++-- .../ui/main/timetable/TimetableFragment.java | 157 ++++---- .../main/timetable/TimetableFragmentTab.java | 161 -------- .../main/timetable/TimetableHeaderItem.java | 80 ++-- .../main/timetable/TimetablePagerAdapter.java | 28 +- .../ui/main/timetable/TimetablePresenter.java | 107 +++++ .../ui/main/timetable/TimetableSubItem.java | 108 +++--- .../main/timetable/TimetableTabContract.java | 30 ++ .../main/timetable/TimetableTabFragment.java | 145 +++++++ .../main/timetable/TimetableTabPresenter.java | 155 ++++++++ .../wulkanowy/ui/splash/SplashActivity.java | 61 +-- .../wulkanowy/ui/splash/SplashContract.java | 21 + .../wulkanowy/ui/splash/SplashPresenter.java | 29 ++ .../github/wulkanowy/utils/AppConstant.java | 25 ++ .../wulkanowy/utils/AverageCalculator.java | 2 +- .../github/wulkanowy/utils/CommonUtils.java | 40 ++ .../wulkanowy/utils/DataObjectConverter.java | 66 ++-- .../wulkanowy/utils/EntitiesCompare.java | 4 +- .../io/github/wulkanowy/utils/LogUtils.java | 22 ++ .../io/github/wulkanowy/utils/TimeUtils.java | 21 +- .../wulkanowy/utils/async/AbstractTask.java | 63 +++ .../wulkanowy/utils/async/AsyncListeners.java | 23 ++ .../wulkanowy/utils/security/Safety.java | 36 -- .../wulkanowy/utils/security/Scrambler.java | 67 ++-- app/src/main/res/drawable/ic_arrow_down.xml | 12 - app/src/main/res/drawable/ic_logo_splash.png | Bin 33927 -> 0 bytes app/src/main/res/drawable/icon_grade_26dp.xml | 2 +- .../main/res/drawable/img_splash_512px.png | Bin 0 -> 9691 bytes .../main/res/drawable/splash_background.xml | 16 + .../main/res/layout/activity_dashboard.xml | 30 -- app/src/main/res/layout/activity_login.xml | 85 ++-- app/src/main/res/layout/activity_main.xml | 33 ++ app/src/main/res/layout/activity_splash.xml | 34 -- app/src/main/res/layout/fragment_board.xml | 2 +- app/src/main/res/layout/fragment_grades.xml | 33 +- .../main/res/layout/fragment_timetable.xml | 19 +- .../res/layout/fragment_timetable_tab.xml | 13 +- .../{grades_dialog.xml => grade_dialog.xml} | 53 ++- .../{subject_item.xml => grade_header.xml} | 21 +- .../{grade_item.xml => grade_subitem.xml} | 24 +- app/src/main/res/layout/timetable_header.xml | 6 +- app/src/main/res/layout/timetable_subitem.xml | 10 +- app/src/main/res/values/colors.xml | 1 - app/src/main/res/values/strings.xml | 2 +- app/src/main/res/values/styles.xml | 5 +- .../{ => data}/db/dao/entities/GradeTest.java | 2 +- .../main/grades/GradesDialogFragmentTest.java | 22 -- .../ui/main/grades/SubjectWithGradesTest.java | 45 --- .../utils/AverageCalculatorTest.java | 2 +- .../utils/DataObjectConverterTest.java | 8 +- .../wulkanowy/utils/EntitiesCompareTest.java | 5 +- .../github/wulkanowy/utils/TimeUtilsTest.java | 8 +- build.gradle | 2 + 158 files changed, 4369 insertions(+), 3753 deletions(-) rename app/src/androidTest/java/io/github/wulkanowy/{ => data}/db/dao/entities/AccountTest.java (88%) rename app/src/androidTest/java/io/github/wulkanowy/{ => data}/db/dao/entities/DayTest.java (88%) rename app/src/androidTest/java/io/github/wulkanowy/{ => data}/db/dao/entities/GradeTest.java (89%) rename app/src/androidTest/java/io/github/wulkanowy/{ => data}/db/dao/entities/LessonTest.java (92%) rename app/src/androidTest/java/io/github/wulkanowy/{ => data}/db/dao/entities/SubjectTest.java (88%) rename app/src/androidTest/java/io/github/wulkanowy/{ => data}/db/dao/entities/WeekTest.java (87%) delete mode 100644 app/src/androidTest/java/io/github/wulkanowy/db/dao/DatabaseAccessTest.java delete mode 100644 app/src/androidTest/java/io/github/wulkanowy/services/sync/CurrentAccountLoginTest.java delete mode 100644 app/src/androidTest/java/io/github/wulkanowy/services/sync/FirstAccountLoginTest.java delete mode 100644 app/src/androidTest/java/io/github/wulkanowy/services/sync/GradeSyncTest.java delete mode 100644 app/src/androidTest/java/io/github/wulkanowy/services/sync/SubjectSyncTest.java delete mode 100644 app/src/androidTest/java/io/github/wulkanowy/services/sync/TimetableSyncTest.java delete mode 100644 app/src/androidTest/java/io/github/wulkanowy/services/sync/VulcanSyncTest.java delete mode 100644 app/src/androidTest/java/io/github/wulkanowy/utils/security/SafetyTest.java create mode 100644 app/src/main/java/io/github/wulkanowy/data/Repository.java create mode 100644 app/src/main/java/io/github/wulkanowy/data/RepositoryContract.java create mode 100644 app/src/main/java/io/github/wulkanowy/data/db/dao/DbHelper.java rename app/src/main/java/io/github/wulkanowy/{ => data}/db/dao/entities/Account.java (99%) rename app/src/main/java/io/github/wulkanowy/{ => data}/db/dao/entities/Day.java (99%) rename app/src/main/java/io/github/wulkanowy/{ => data}/db/dao/entities/Grade.java (88%) rename app/src/main/java/io/github/wulkanowy/{ => data}/db/dao/entities/Lesson.java (97%) rename app/src/main/java/io/github/wulkanowy/{ => data}/db/dao/entities/Subject.java (99%) rename app/src/main/java/io/github/wulkanowy/{ => data}/db/dao/entities/Week.java (98%) create mode 100644 app/src/main/java/io/github/wulkanowy/data/db/resources/AppResources.java create mode 100644 app/src/main/java/io/github/wulkanowy/data/db/resources/ResourcesContract.java create mode 100644 app/src/main/java/io/github/wulkanowy/data/db/shared/SharedPref.java create mode 100644 app/src/main/java/io/github/wulkanowy/data/db/shared/SharedPrefContract.java create mode 100644 app/src/main/java/io/github/wulkanowy/data/sync/SyncContract.java create mode 100644 app/src/main/java/io/github/wulkanowy/data/sync/grades/GradeSync.java create mode 100644 app/src/main/java/io/github/wulkanowy/data/sync/login/LoginSync.java create mode 100644 app/src/main/java/io/github/wulkanowy/data/sync/login/LoginSyncContract.java create mode 100644 app/src/main/java/io/github/wulkanowy/data/sync/subjects/SubjectSync.java create mode 100644 app/src/main/java/io/github/wulkanowy/data/sync/timetable/TimetableSync.java create mode 100644 app/src/main/java/io/github/wulkanowy/data/sync/timetable/TimetableSyncContract.java delete mode 100644 app/src/main/java/io/github/wulkanowy/db/dao/DatabaseAccess.java create mode 100644 app/src/main/java/io/github/wulkanowy/di/annotations/ActivityContext.java create mode 100644 app/src/main/java/io/github/wulkanowy/di/annotations/ApplicationContext.java create mode 100644 app/src/main/java/io/github/wulkanowy/di/annotations/DatabaseInfo.java create mode 100644 app/src/main/java/io/github/wulkanowy/di/annotations/PerActivity.java create mode 100644 app/src/main/java/io/github/wulkanowy/di/annotations/PerFragment.java create mode 100644 app/src/main/java/io/github/wulkanowy/di/annotations/SharedPreferencesInfo.java create mode 100644 app/src/main/java/io/github/wulkanowy/di/annotations/SyncGrades.java create mode 100644 app/src/main/java/io/github/wulkanowy/di/annotations/SyncSubjects.java create mode 100644 app/src/main/java/io/github/wulkanowy/di/component/ActivityComponent.java create mode 100644 app/src/main/java/io/github/wulkanowy/di/component/ApplicationComponent.java create mode 100644 app/src/main/java/io/github/wulkanowy/di/component/FragmentComponent.java create mode 100644 app/src/main/java/io/github/wulkanowy/di/modules/ActivityModule.java create mode 100644 app/src/main/java/io/github/wulkanowy/di/modules/FragmentModule.java create mode 100644 app/src/main/java/io/github/wulkanowy/services/NotificationService.java create mode 100644 app/src/main/java/io/github/wulkanowy/services/SyncJob.java delete mode 100644 app/src/main/java/io/github/wulkanowy/services/Updater.java delete mode 100644 app/src/main/java/io/github/wulkanowy/services/jobs/FullSyncJob.java delete mode 100644 app/src/main/java/io/github/wulkanowy/services/jobs/VulcanJobHelper.java delete mode 100644 app/src/main/java/io/github/wulkanowy/services/jobs/VulcanService.java delete mode 100644 app/src/main/java/io/github/wulkanowy/services/notifications/NotificationBuilder.java delete mode 100644 app/src/main/java/io/github/wulkanowy/services/sync/CurrentAccountLogin.java delete mode 100644 app/src/main/java/io/github/wulkanowy/services/sync/FirstAccountLogin.java delete mode 100644 app/src/main/java/io/github/wulkanowy/services/sync/GradesSync.java delete mode 100644 app/src/main/java/io/github/wulkanowy/services/sync/LoginSession.java delete mode 100644 app/src/main/java/io/github/wulkanowy/services/sync/SubjectsSync.java delete mode 100644 app/src/main/java/io/github/wulkanowy/services/sync/TimetableSync.java delete mode 100644 app/src/main/java/io/github/wulkanowy/services/sync/VulcanSync.java create mode 100644 app/src/main/java/io/github/wulkanowy/ui/base/BaseActivity.java create mode 100644 app/src/main/java/io/github/wulkanowy/ui/base/BaseContract.java create mode 100644 app/src/main/java/io/github/wulkanowy/ui/base/BaseFragment.java create mode 100644 app/src/main/java/io/github/wulkanowy/ui/base/BasePresenter.java create mode 100644 app/src/main/java/io/github/wulkanowy/ui/login/LoginContract.java create mode 100644 app/src/main/java/io/github/wulkanowy/ui/login/LoginPresenter.java delete mode 100644 app/src/main/java/io/github/wulkanowy/ui/main/AbstractFragment.java delete mode 100644 app/src/main/java/io/github/wulkanowy/ui/main/AsyncResponse.java delete mode 100644 app/src/main/java/io/github/wulkanowy/ui/main/DashboardActivity.java delete mode 100644 app/src/main/java/io/github/wulkanowy/ui/main/DatabaseQueryTask.java create mode 100644 app/src/main/java/io/github/wulkanowy/ui/main/MainActivity.java create mode 100644 app/src/main/java/io/github/wulkanowy/ui/main/MainContract.java create mode 100644 app/src/main/java/io/github/wulkanowy/ui/main/MainPagerAdapter.java create mode 100644 app/src/main/java/io/github/wulkanowy/ui/main/MainPresenter.java create mode 100644 app/src/main/java/io/github/wulkanowy/ui/main/OnFragmentIsReadyListener.java delete mode 100644 app/src/main/java/io/github/wulkanowy/ui/main/RefreshTask.java create mode 100644 app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceContract.java create mode 100644 app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendancePresenter.java delete mode 100644 app/src/main/java/io/github/wulkanowy/ui/main/board/BoardFragment.java create mode 100644 app/src/main/java/io/github/wulkanowy/ui/main/dashboard/DashboardContract.java create mode 100644 app/src/main/java/io/github/wulkanowy/ui/main/dashboard/DashboardFragment.java create mode 100644 app/src/main/java/io/github/wulkanowy/ui/main/dashboard/DashboardPresenter.java create mode 100644 app/src/main/java/io/github/wulkanowy/ui/main/grades/GradeHeaderItem.java delete mode 100644 app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesAdapter.java create mode 100644 app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesContract.java create mode 100644 app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesPresenter.java create mode 100644 app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesSubItem.java delete mode 100644 app/src/main/java/io/github/wulkanowy/ui/main/grades/SubjectWithGrades.java create mode 100644 app/src/main/java/io/github/wulkanowy/ui/main/timetable/TabsData.java create mode 100644 app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableContract.java delete mode 100644 app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableFragmentTab.java create mode 100644 app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetablePresenter.java create mode 100644 app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableTabContract.java create mode 100644 app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableTabFragment.java create mode 100644 app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableTabPresenter.java create mode 100644 app/src/main/java/io/github/wulkanowy/ui/splash/SplashContract.java create mode 100644 app/src/main/java/io/github/wulkanowy/ui/splash/SplashPresenter.java create mode 100644 app/src/main/java/io/github/wulkanowy/utils/AppConstant.java create mode 100644 app/src/main/java/io/github/wulkanowy/utils/CommonUtils.java create mode 100644 app/src/main/java/io/github/wulkanowy/utils/LogUtils.java create mode 100644 app/src/main/java/io/github/wulkanowy/utils/async/AbstractTask.java create mode 100644 app/src/main/java/io/github/wulkanowy/utils/async/AsyncListeners.java delete mode 100644 app/src/main/java/io/github/wulkanowy/utils/security/Safety.java delete mode 100644 app/src/main/res/drawable/ic_arrow_down.xml delete mode 100644 app/src/main/res/drawable/ic_logo_splash.png create mode 100644 app/src/main/res/drawable/img_splash_512px.png create mode 100644 app/src/main/res/drawable/splash_background.xml delete mode 100644 app/src/main/res/layout/activity_dashboard.xml create mode 100644 app/src/main/res/layout/activity_main.xml delete mode 100644 app/src/main/res/layout/activity_splash.xml rename app/src/main/res/layout/{grades_dialog.xml => grade_dialog.xml} (80%) rename app/src/main/res/layout/{subject_item.xml => grade_header.xml} (74%) rename app/src/main/res/layout/{grade_item.xml => grade_subitem.xml} (77%) rename app/src/test/java/io/github/wulkanowy/{ => data}/db/dao/entities/GradeTest.java (97%) delete mode 100644 app/src/test/java/io/github/wulkanowy/ui/main/grades/GradesDialogFragmentTest.java delete mode 100644 app/src/test/java/io/github/wulkanowy/ui/main/grades/SubjectWithGradesTest.java diff --git a/.bettercodehub.yml b/.bettercodehub.yml index c7394280f..cce850d71 100644 --- a/.bettercodehub.yml +++ b/.bettercodehub.yml @@ -1,5 +1,5 @@ exclude: -- /app/src/main/java/io/github/wulkanowy/db/dao/entities/.* +- /app/src/main/java/io/github/wulkanowy/data/db/dao/entities/.* component_depth: 1 languages: - java diff --git a/api/src/main/java/io/github/wulkanowy/api/Vulcan.java b/api/src/main/java/io/github/wulkanowy/api/Vulcan.java index bae3a2f4a..a44e868f7 100644 --- a/api/src/main/java/io/github/wulkanowy/api/Vulcan.java +++ b/api/src/main/java/io/github/wulkanowy/api/Vulcan.java @@ -48,7 +48,7 @@ public class Vulcan { this.login = login; } - public String login(String email, String password, String symbol) + public void login(String email, String password, String symbol) throws BadCredentialsException, AccountPermissionException, LoginErrorException, IOException, VulcanOfflineException { @@ -56,8 +56,6 @@ public class Vulcan { login = getLogin(); this.symbol = login.login(this.email, password, symbol); - - return this.symbol; } public Vulcan login(String email, String password, String symbol, String id) @@ -82,6 +80,10 @@ public class Vulcan { return email; } + public String getSymbol() { + return symbol; + } + private void setFullEndpointInfo(String email) { String[] creds = email.split("\\\\"); diff --git a/api/src/main/java/io/github/wulkanowy/api/login/Login.java b/api/src/main/java/io/github/wulkanowy/api/login/Login.java index 97439395e..e680804ae 100644 --- a/api/src/main/java/io/github/wulkanowy/api/login/Login.java +++ b/api/src/main/java/io/github/wulkanowy/api/login/Login.java @@ -27,14 +27,6 @@ public class Login { this.client = client; } - private String getLoginPageUrl() { - return LOGIN_PAGE_URL; - } - - private String getLoginEndpointPageUrl() { - return LOGIN_ENDPOINT_PAGE_URL; - } - public String login(String email, String password, String symbol) throws BadCredentialsException, LoginErrorException, AccountPermissionException, IOException, VulcanOfflineException { @@ -47,7 +39,7 @@ public class Login { throws IOException, BadCredentialsException { this.symbol = symbol; - Document html = client.postPageByUrl(getLoginPageUrl(), new String[][]{ + Document html = client.postPageByUrl(LOGIN_PAGE_URL, new String[][]{ {"LoginName", email}, {"Password", password} }); @@ -64,8 +56,7 @@ public class Login { this.symbol = findSymbol(defaultSymbol, certificate); client.setSymbol(this.symbol); - Document html = client.postPageByUrl(getLoginEndpointPageUrl() - .replace("{symbol}", this.symbol), new String[][]{ + Document html = client.postPageByUrl(LOGIN_ENDPOINT_PAGE_URL, new String[][]{ {"wa", "wsignin1.0"}, {"wresult", certificate} }); @@ -103,4 +94,4 @@ public class Login { return els.get(1).text(); } -} +} \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 205a056d0..66b90ef8b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -62,29 +62,29 @@ android { } greendao { - schemaVersion 19 + schemaVersion 20 generateTests = true } dependencies { implementation project(':api') - implementation 'com.android.support:appcompat-v7:27.0.2' - implementation 'com.android.support:design:27.0.2' - implementation 'com.android.support:support-v4:27.0.2' - implementation 'com.android.support:recyclerview-v7:27.0.2' - implementation 'com.android.support:cardview-v7:27.0.2' - implementation 'com.android.support:customtabs:27.0.2' + implementation 'com.android.support:appcompat-v7:27.1.0' + implementation 'com.android.support:design:27.1.0' + implementation 'com.android.support:support-v4:27.1.0' + implementation 'com.android.support:recyclerview-v7:27.1.0' + implementation 'com.android.support:cardview-v7:27.1.0' + implementation 'com.android.support:customtabs:27.1.0' implementation 'com.firebase:firebase-jobdispatcher:0.8.5' - implementation 'com.thoughtbot:expandablerecyclerview:1.3' implementation 'org.apache.commons:commons-lang3:3.7' - implementation 'eu.davidea:flexible-adapter:5.0.0-rc3' + implementation 'eu.davidea:flexible-adapter:5.0.0-rc4' + implementation 'eu.davidea:flexible-adapter-ui:1.0.0-b1' implementation 'org.apache.commons:commons-collections4:4.1' implementation 'org.greenrobot:greendao:3.2.2' implementation 'com.jakewharton:butterknife:8.8.1' implementation 'joda-time:joda-time:2.9.9' - implementation 'com.github.javiersantos:AppUpdater:2.6.4' implementation 'com.google.dagger:dagger-android:2.14.1' implementation 'com.google.dagger:dagger-android-support:2.14.1' + implementation 'com.aurelhubert:ahbottomnavigation:2.1.0' implementation('com.crashlytics.sdk.android:crashlytics:2.8.0@aar') { transitive = true @@ -94,6 +94,7 @@ dependencies { } annotationProcessor 'com.google.dagger:dagger-android-processor:2.14.1' + annotationProcessor 'com.google.dagger:dagger-compiler:2.14.1' annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1' debugImplementation 'com.amitshekhar.android:debug-db:1.0.1' diff --git a/app/src/androidTest/java/io/github/wulkanowy/db/dao/entities/AccountTest.java b/app/src/androidTest/java/io/github/wulkanowy/data/db/dao/entities/AccountTest.java similarity index 88% rename from app/src/androidTest/java/io/github/wulkanowy/db/dao/entities/AccountTest.java rename to app/src/androidTest/java/io/github/wulkanowy/data/db/dao/entities/AccountTest.java index 78ea561df..e5330b621 100644 --- a/app/src/androidTest/java/io/github/wulkanowy/db/dao/entities/AccountTest.java +++ b/app/src/androidTest/java/io/github/wulkanowy/data/db/dao/entities/AccountTest.java @@ -1,4 +1,4 @@ -package io.github.wulkanowy.db.dao.entities; +package io.github.wulkanowy.data.db.dao.entities; import org.greenrobot.greendao.test.AbstractDaoTestLongPk; diff --git a/app/src/androidTest/java/io/github/wulkanowy/db/dao/entities/DayTest.java b/app/src/androidTest/java/io/github/wulkanowy/data/db/dao/entities/DayTest.java similarity index 88% rename from app/src/androidTest/java/io/github/wulkanowy/db/dao/entities/DayTest.java rename to app/src/androidTest/java/io/github/wulkanowy/data/db/dao/entities/DayTest.java index 7574addbb..34c4c4c5e 100644 --- a/app/src/androidTest/java/io/github/wulkanowy/db/dao/entities/DayTest.java +++ b/app/src/androidTest/java/io/github/wulkanowy/data/db/dao/entities/DayTest.java @@ -1,4 +1,4 @@ -package io.github.wulkanowy.db.dao.entities; +package io.github.wulkanowy.data.db.dao.entities; import org.greenrobot.greendao.test.AbstractDaoTestLongPk; diff --git a/app/src/androidTest/java/io/github/wulkanowy/db/dao/entities/GradeTest.java b/app/src/androidTest/java/io/github/wulkanowy/data/db/dao/entities/GradeTest.java similarity index 89% rename from app/src/androidTest/java/io/github/wulkanowy/db/dao/entities/GradeTest.java rename to app/src/androidTest/java/io/github/wulkanowy/data/db/dao/entities/GradeTest.java index 7cabfa27c..ea0265591 100644 --- a/app/src/androidTest/java/io/github/wulkanowy/db/dao/entities/GradeTest.java +++ b/app/src/androidTest/java/io/github/wulkanowy/data/db/dao/entities/GradeTest.java @@ -1,4 +1,4 @@ -package io.github.wulkanowy.db.dao.entities; +package io.github.wulkanowy.data.db.dao.entities; import org.greenrobot.greendao.test.AbstractDaoTestLongPk; diff --git a/app/src/androidTest/java/io/github/wulkanowy/db/dao/entities/LessonTest.java b/app/src/androidTest/java/io/github/wulkanowy/data/db/dao/entities/LessonTest.java similarity index 92% rename from app/src/androidTest/java/io/github/wulkanowy/db/dao/entities/LessonTest.java rename to app/src/androidTest/java/io/github/wulkanowy/data/db/dao/entities/LessonTest.java index 4e3523f72..c8690ff94 100644 --- a/app/src/androidTest/java/io/github/wulkanowy/db/dao/entities/LessonTest.java +++ b/app/src/androidTest/java/io/github/wulkanowy/data/db/dao/entities/LessonTest.java @@ -1,4 +1,4 @@ -package io.github.wulkanowy.db.dao.entities; +package io.github.wulkanowy.data.db.dao.entities; import org.greenrobot.greendao.test.AbstractDaoTestLongPk; diff --git a/app/src/androidTest/java/io/github/wulkanowy/db/dao/entities/SubjectTest.java b/app/src/androidTest/java/io/github/wulkanowy/data/db/dao/entities/SubjectTest.java similarity index 88% rename from app/src/androidTest/java/io/github/wulkanowy/db/dao/entities/SubjectTest.java rename to app/src/androidTest/java/io/github/wulkanowy/data/db/dao/entities/SubjectTest.java index 85b986dfc..81a2e724f 100644 --- a/app/src/androidTest/java/io/github/wulkanowy/db/dao/entities/SubjectTest.java +++ b/app/src/androidTest/java/io/github/wulkanowy/data/db/dao/entities/SubjectTest.java @@ -1,4 +1,4 @@ -package io.github.wulkanowy.db.dao.entities; +package io.github.wulkanowy.data.db.dao.entities; import org.greenrobot.greendao.test.AbstractDaoTestLongPk; diff --git a/app/src/androidTest/java/io/github/wulkanowy/db/dao/entities/WeekTest.java b/app/src/androidTest/java/io/github/wulkanowy/data/db/dao/entities/WeekTest.java similarity index 87% rename from app/src/androidTest/java/io/github/wulkanowy/db/dao/entities/WeekTest.java rename to app/src/androidTest/java/io/github/wulkanowy/data/db/dao/entities/WeekTest.java index 6920ae206..86e7a8be5 100644 --- a/app/src/androidTest/java/io/github/wulkanowy/db/dao/entities/WeekTest.java +++ b/app/src/androidTest/java/io/github/wulkanowy/data/db/dao/entities/WeekTest.java @@ -1,4 +1,4 @@ -package io.github.wulkanowy.db.dao.entities; +package io.github.wulkanowy.data.db.dao.entities; import org.greenrobot.greendao.test.AbstractDaoTestLongPk; diff --git a/app/src/androidTest/java/io/github/wulkanowy/db/dao/DatabaseAccessTest.java b/app/src/androidTest/java/io/github/wulkanowy/db/dao/DatabaseAccessTest.java deleted file mode 100644 index c9156676f..000000000 --- a/app/src/androidTest/java/io/github/wulkanowy/db/dao/DatabaseAccessTest.java +++ /dev/null @@ -1,50 +0,0 @@ -package io.github.wulkanowy.db.dao; - -import android.support.test.InstrumentationRegistry; - -import org.greenrobot.greendao.database.Database; -import org.junit.AfterClass; -import org.junit.Assert; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; - -import io.github.wulkanowy.db.dao.entities.DaoMaster; -import io.github.wulkanowy.db.dao.entities.DaoSession; -import io.github.wulkanowy.db.dao.entities.Grade; - -public class DatabaseAccessTest extends DatabaseAccess { - - private static DaoSession daoSession; - - @BeforeClass - public static void setUpClass() { - DaoMaster.DevOpenHelper devOpenHelper = new DaoMaster.DevOpenHelper(InstrumentationRegistry.getTargetContext() - , "wulkanowyTest-db"); - Database database = devOpenHelper.getWritableDb(); - - daoSession = new DaoMaster(database).newSession(); - } - - @Before - public void setUp() { - daoSession.getGradeDao().deleteAll(); - daoSession.clear(); - } - - @Test - public void getNewGradesTest() { - daoSession.getGradeDao().insert(new Grade() - .setIsNew(true)); - - Assert.assertEquals(1, new DatabaseAccess().getNewGrades(daoSession).size()); - } - - @AfterClass - public static void cleanUp() { - daoSession.getAccountDao().deleteAll(); - daoSession.getGradeDao().deleteAll(); - daoSession.getSubjectDao().deleteAll(); - daoSession.clear(); - } -} diff --git a/app/src/androidTest/java/io/github/wulkanowy/services/sync/CurrentAccountLoginTest.java b/app/src/androidTest/java/io/github/wulkanowy/services/sync/CurrentAccountLoginTest.java deleted file mode 100644 index 65da82e21..000000000 --- a/app/src/androidTest/java/io/github/wulkanowy/services/sync/CurrentAccountLoginTest.java +++ /dev/null @@ -1,106 +0,0 @@ -package io.github.wulkanowy.services.sync; - -import android.content.Context; -import android.content.SharedPreferences; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; - -import org.greenrobot.greendao.database.Database; -import org.junit.AfterClass; -import org.junit.Assert; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mockito; - -import java.io.IOException; - -import io.github.wulkanowy.api.Vulcan; -import io.github.wulkanowy.api.login.AccountPermissionException; -import io.github.wulkanowy.api.login.BadCredentialsException; -import io.github.wulkanowy.api.login.LoginErrorException; -import io.github.wulkanowy.api.login.VulcanOfflineException; -import io.github.wulkanowy.db.dao.entities.Account; -import io.github.wulkanowy.db.dao.entities.AccountDao; -import io.github.wulkanowy.db.dao.entities.DaoMaster; -import io.github.wulkanowy.db.dao.entities.DaoSession; -import io.github.wulkanowy.utils.security.CryptoException; -import io.github.wulkanowy.utils.security.Safety; - -@RunWith(AndroidJUnit4.class) -public class CurrentAccountLoginTest { - - private static DaoSession daoSession; - - private Context context; - - private Context targetContext; - - @BeforeClass - public static void setUpClass() { - DaoMaster.DevOpenHelper devOpenHelper = new DaoMaster.DevOpenHelper(InstrumentationRegistry.getTargetContext(), "wulkanowyTest-db"); - Database database = devOpenHelper.getWritableDb(); - daoSession = new DaoMaster(database).newSession(); - - DaoMaster.dropAllTables(database, true); - DaoMaster.createAllTables(database, true); - } - - @Before - public void setUp() { - context = InstrumentationRegistry.getContext(); - targetContext = InstrumentationRegistry.getTargetContext(); - - daoSession.getAccountDao().deleteAll(); - daoSession.clear(); - - setUserIdSharePreferences(0); - } - - @Test(expected = IOException.class) - public void emptyUserIdTest() throws CryptoException, BadCredentialsException, - AccountPermissionException, IOException, LoginErrorException, VulcanOfflineException { - - CurrentAccountLogin currentAccountLogin = new CurrentAccountLogin(context, daoSession, new Vulcan()); - currentAccountLogin.loginCurrentUser(); - } - - @Test - public void loginCurrentUserTest() throws Exception { - AccountDao accountDao = daoSession.getAccountDao(); - - Safety safety = new Safety(); - - Long userId = accountDao.insert(new Account() - .setEmail("TEST@TEST") - .setPassword(safety.encrypt("TEST@TEST", "TEST", context)) - .setSymbol("")); - - setUserIdSharePreferences(userId); - - Vulcan vulcan = Mockito.mock(Vulcan.class); - Mockito.when(vulcan.login("TEST@TEST", "TEST", "TEST_SYMBOL", "TEST_ID")).thenReturn(new Vulcan()); - - CurrentAccountLogin currentAccountLogin = new CurrentAccountLogin(targetContext, daoSession, vulcan); - LoginSession loginSession = currentAccountLogin.loginCurrentUser(); - - Assert.assertNotNull(loginSession); - Assert.assertEquals(loginSession.getUserId(), userId); - Assert.assertNotNull(loginSession.getDaoSession()); - Assert.assertEquals(loginSession.getVulcan(), vulcan); - } - - private void setUserIdSharePreferences(long id) { - SharedPreferences sharedPreferences = targetContext.getSharedPreferences("LoginData", Context.MODE_PRIVATE); - SharedPreferences.Editor editor = sharedPreferences.edit(); - editor.putLong("userId", id); - editor.apply(); - } - - @AfterClass - public static void cleanUp() { - daoSession.getAccountDao().deleteAll(); - daoSession.clear(); - } -} diff --git a/app/src/androidTest/java/io/github/wulkanowy/services/sync/FirstAccountLoginTest.java b/app/src/androidTest/java/io/github/wulkanowy/services/sync/FirstAccountLoginTest.java deleted file mode 100644 index 5a4eb97de..000000000 --- a/app/src/androidTest/java/io/github/wulkanowy/services/sync/FirstAccountLoginTest.java +++ /dev/null @@ -1,99 +0,0 @@ -package io.github.wulkanowy.services.sync; - -import android.content.Context; -import android.content.SharedPreferences; -import android.support.test.InstrumentationRegistry; - -import org.greenrobot.greendao.database.Database; -import org.junit.AfterClass; -import org.junit.Assert; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; -import org.mockito.Mockito; - -import io.github.wulkanowy.api.StudentAndParent; -import io.github.wulkanowy.api.Vulcan; -import io.github.wulkanowy.api.user.BasicInformation; -import io.github.wulkanowy.api.user.PersonalData; -import io.github.wulkanowy.db.dao.entities.Account; -import io.github.wulkanowy.db.dao.entities.DaoMaster; -import io.github.wulkanowy.db.dao.entities.DaoSession; -import io.github.wulkanowy.utils.security.Safety; - -public class FirstAccountLoginTest { - - private static DaoSession daoSession; - - private Context targetContext; - - @BeforeClass - public static void setUpClass() { - DaoMaster.DevOpenHelper devOpenHelper = new DaoMaster.DevOpenHelper(InstrumentationRegistry.getTargetContext(), "wulkanowyTest-db"); - Database database = devOpenHelper.getWritableDb(); - daoSession = new DaoMaster(database).newSession(); - - DaoMaster.dropAllTables(database, true); - DaoMaster.createAllTables(database, true); - } - - @Before - public void setUp() { - targetContext = InstrumentationRegistry.getTargetContext(); - - daoSession.getAccountDao().deleteAll(); - daoSession.clear(); - - setUserIdSharePreferences(0); - } - - @Test - public void loginTest() throws Exception { - StudentAndParent snp = Mockito.mock(StudentAndParent.class); - Mockito.when(snp.getId()).thenReturn("TEST-ID"); - - PersonalData personalData = Mockito.mock(PersonalData.class); - Mockito.doReturn("NAME-TEST").when(personalData).getFirstAndLastName(); - - BasicInformation basicInformation = Mockito.mock(BasicInformation.class); - Mockito.doReturn(personalData).when(basicInformation).getPersonalData(); - - Vulcan vulcan = Mockito.mock(Vulcan.class); - Mockito.doReturn("TEST-SYMBOL").when(vulcan).login(Mockito.anyString(), Mockito.anyString(), Mockito.anyString()); - Mockito.doReturn(snp).when(vulcan).getStudentAndParent(); - Mockito.doReturn(basicInformation).when(vulcan).getBasicInformation(); - - FirstAccountLogin firstAccountLogin = new FirstAccountLogin(targetContext, daoSession, vulcan); - LoginSession loginSession = firstAccountLogin.login("TEST@TEST", "TEST-PASS", "default"); - - Long userId = targetContext.getSharedPreferences("LoginData", Context.MODE_PRIVATE).getLong("userId", 0); - - Assert.assertNotNull(loginSession); - Assert.assertNotEquals(0, userId.longValue()); - Assert.assertEquals(loginSession.getUserId(), userId); - Assert.assertNotNull(loginSession.getDaoSession()); - Assert.assertEquals(loginSession.getVulcan(), vulcan); - - Safety safety = new Safety(); - Account account = daoSession.getAccountDao().load(userId); - Assert.assertNotNull(account); - Assert.assertEquals("TEST@TEST", account.getEmail()); - Assert.assertEquals("NAME-TEST", account.getName()); - Assert.assertEquals("TEST-PASS", safety.decrypt("TEST@TEST", account.getPassword())); - Assert.assertEquals("TEST-SYMBOL", account.getSymbol()); - Assert.assertEquals("TEST-ID", account.getSnpId()); - } - - private void setUserIdSharePreferences(long id) { - SharedPreferences sharedPreferences = targetContext.getSharedPreferences("LoginData", Context.MODE_PRIVATE); - SharedPreferences.Editor editor = sharedPreferences.edit(); - editor.putLong("userId", id); - editor.apply(); - } - - @AfterClass - public static void cleanUp() { - daoSession.getAccountDao().deleteAll(); - daoSession.clear(); - } -} diff --git a/app/src/androidTest/java/io/github/wulkanowy/services/sync/GradeSyncTest.java b/app/src/androidTest/java/io/github/wulkanowy/services/sync/GradeSyncTest.java deleted file mode 100644 index 111767192..000000000 --- a/app/src/androidTest/java/io/github/wulkanowy/services/sync/GradeSyncTest.java +++ /dev/null @@ -1,88 +0,0 @@ -package io.github.wulkanowy.services.sync; - -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; - -import org.greenrobot.greendao.database.Database; -import org.junit.AfterClass; -import org.junit.Assert; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mockito; - -import java.util.ArrayList; -import java.util.List; - -import io.github.wulkanowy.api.Vulcan; -import io.github.wulkanowy.api.grades.Grade; -import io.github.wulkanowy.api.grades.GradesList; -import io.github.wulkanowy.db.dao.entities.Account; -import io.github.wulkanowy.db.dao.entities.DaoMaster; -import io.github.wulkanowy.db.dao.entities.DaoSession; -import io.github.wulkanowy.db.dao.entities.Subject; - -@RunWith(AndroidJUnit4.class) -public class GradeSyncTest { - - private static DaoSession daoSession; - - @BeforeClass - public static void setUpClass() { - DaoMaster.DevOpenHelper devOpenHelper = new DaoMaster.DevOpenHelper(InstrumentationRegistry.getTargetContext(), "wulkanowyTest-db"); - Database database = devOpenHelper.getWritableDb(); - daoSession = new DaoMaster(database).newSession(); - - DaoMaster.dropAllTables(database, true); - DaoMaster.createAllTables(database, true); - } - - @Before - public void setUp() { - daoSession.getAccountDao().deleteAll(); - daoSession.getGradeDao().deleteAll(); - daoSession.getSubjectDao().deleteAll(); - daoSession.clear(); - } - - @Test - public void syncGradesEmptyDatabaseTest() throws Exception { - Long userId = daoSession.getAccountDao().insert(new Account().setEmail("TEST@TEST")); - Long subjectId = daoSession.getSubjectDao().insert(new Subject().setName("Matematyka").setUserId(userId)); - - List gradeList = new ArrayList<>(); - gradeList.add(new Grade().setSubject("Matematyka").setValue("5")); - - GradesList gradesListApi = Mockito.mock(GradesList.class); - Mockito.doReturn(gradeList).when(gradesListApi).getAll(); - - Vulcan vulcan = Mockito.mock(Vulcan.class); - Mockito.doReturn(gradesListApi).when(vulcan).getGradesList(); - - LoginSession loginSession = Mockito.mock(LoginSession.class); - Mockito.doReturn(vulcan).when(loginSession).getVulcan(); - Mockito.doReturn(daoSession).when(loginSession).getDaoSession(); - Mockito.doReturn(userId).when(loginSession).getUserId(); - - GradesSync gradesSync = new GradesSync(); - gradesSync.sync(loginSession); - - io.github.wulkanowy.db.dao.entities.Grade grade = daoSession.getGradeDao().load(1L); - - Assert.assertNotNull(grade); - Assert.assertEquals(userId, grade.getUserId()); - Assert.assertEquals(subjectId, grade.getSubjectId()); - Assert.assertEquals("Matematyka", grade.getSubject()); - Assert.assertEquals("5", grade.getValue()); - Assert.assertFalse(grade.getIsNew()); - } - - @AfterClass - public static void cleanUp() { - daoSession.getAccountDao().deleteAll(); - daoSession.getGradeDao().deleteAll(); - daoSession.getSubjectDao().deleteAll(); - daoSession.clear(); - } -} diff --git a/app/src/androidTest/java/io/github/wulkanowy/services/sync/SubjectSyncTest.java b/app/src/androidTest/java/io/github/wulkanowy/services/sync/SubjectSyncTest.java deleted file mode 100644 index 38e70bc08..000000000 --- a/app/src/androidTest/java/io/github/wulkanowy/services/sync/SubjectSyncTest.java +++ /dev/null @@ -1,82 +0,0 @@ -package io.github.wulkanowy.services.sync; - -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; - -import org.greenrobot.greendao.database.Database; -import org.junit.AfterClass; -import org.junit.Assert; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mockito; - -import java.util.ArrayList; -import java.util.List; - -import io.github.wulkanowy.api.Vulcan; -import io.github.wulkanowy.api.grades.SubjectsList; -import io.github.wulkanowy.db.dao.entities.DaoMaster; -import io.github.wulkanowy.db.dao.entities.DaoSession; -import io.github.wulkanowy.db.dao.entities.Subject; - -@RunWith(AndroidJUnit4.class) -public class SubjectSyncTest { - - private static DaoSession daoSession; - - @BeforeClass - public static void setUpClass() { - DaoMaster.DevOpenHelper devOpenHelper = new DaoMaster.DevOpenHelper(InstrumentationRegistry.getTargetContext(), "wulkanowyTest-db"); - Database database = devOpenHelper.getWritableDb(); - daoSession = new DaoMaster(database).newSession(); - - DaoMaster.dropAllTables(database, true); - DaoMaster.createAllTables(database, true); - } - - @Before - public void setUp() { - daoSession.getSubjectDao().deleteAll(); - daoSession.clear(); - } - - @Test - public void syncSubjectTest() throws Exception { - List subjectList = new ArrayList<>(); - subjectList.add(new io.github.wulkanowy.api.grades.Subject() - .setName("Matematyka") - .setFinalRating("5") - .setPredictedRating("4")); - - SubjectsList subjectsListApi = Mockito.mock(SubjectsList.class); - Mockito.doReturn(subjectList).when(subjectsListApi).getAll(); - - Vulcan vulcan = Mockito.mock(Vulcan.class); - Mockito.doReturn(subjectsListApi).when(vulcan).getSubjectsList(); - - LoginSession loginSession = Mockito.mock(LoginSession.class); - Mockito.doReturn(vulcan).when(loginSession).getVulcan(); - Mockito.doReturn(2L).when(loginSession).getUserId(); - Mockito.doReturn(daoSession).when(loginSession).getDaoSession(); - - SubjectsSync subjectsSync = new SubjectsSync(); - subjectsSync.sync(loginSession); - - Subject subject = daoSession.getSubjectDao().load(1L); - - Assert.assertNotNull(subject); - Assert.assertEquals(2, subject.getUserId().longValue()); - Assert.assertEquals("Matematyka", subject.getName()); - Assert.assertEquals("5", subject.getFinalRating()); - Assert.assertEquals("4", subject.getPredictedRating()); - - } - - @AfterClass - public static void cleanUp() { - daoSession.getSubjectDao().deleteAll(); - daoSession.clear(); - } -} diff --git a/app/src/androidTest/java/io/github/wulkanowy/services/sync/TimetableSyncTest.java b/app/src/androidTest/java/io/github/wulkanowy/services/sync/TimetableSyncTest.java deleted file mode 100644 index d08ae4f0f..000000000 --- a/app/src/androidTest/java/io/github/wulkanowy/services/sync/TimetableSyncTest.java +++ /dev/null @@ -1,95 +0,0 @@ -package io.github.wulkanowy.services.sync; - -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; - -import org.greenrobot.greendao.database.Database; -import org.junit.AfterClass; -import org.junit.Assert; -import org.junit.BeforeClass; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.ArrayList; -import java.util.List; - -import io.github.wulkanowy.api.Vulcan; -import io.github.wulkanowy.api.timetable.Day; -import io.github.wulkanowy.api.timetable.Lesson; -import io.github.wulkanowy.api.timetable.Timetable; -import io.github.wulkanowy.api.timetable.Week; -import io.github.wulkanowy.db.dao.entities.Account; -import io.github.wulkanowy.db.dao.entities.DaoMaster; -import io.github.wulkanowy.db.dao.entities.DaoSession; - -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; - -@RunWith(AndroidJUnit4.class) -public class TimetableSyncTest { - - private static DaoSession daoSession; - - @BeforeClass - public static void setUpClass() { - DaoMaster.DevOpenHelper devOpenHelper = new DaoMaster.DevOpenHelper(InstrumentationRegistry.getTargetContext(), "wulkanowyTest-db"); - Database database = devOpenHelper.getWritableDb(); - daoSession = new DaoMaster(database).newSession(); - - DaoMaster.dropAllTables(database, true); - DaoMaster.createAllTables(database, true); - } - - @Test - public void syncTimetableEmptyDatabaseTest() throws Exception { - Long userId = daoSession.getAccountDao().insert(new Account().setEmail("TEST@TEST")); - - List dayList = new ArrayList<>(); - dayList.add(new Day() - .setDate("20.12.2012") - .setLesson(new Lesson().setSubject("Matematyka").setRoom("20"))); - Week week = new Week().setDays(dayList); - - List nextDayList = new ArrayList<>(); - dayList.add(new Day() - .setDate("24.11.2013") - .setLesson(new Lesson().setSubject("Matematyka").setRoom("22"))); - Week nextWeek = new Week().setDays(nextDayList); - - Timetable timetable = mock(Timetable.class); - doReturn(week).when(timetable).getWeekTable(); - doReturn(nextWeek).when(timetable).getWeekTable(anyString()); - - Vulcan vulcan = mock(Vulcan.class); - doReturn(timetable).when(vulcan).getTimetable(); - - LoginSession loginSession = mock(LoginSession.class); - doReturn(vulcan).when(loginSession).getVulcan(); - doReturn(daoSession).when(loginSession).getDaoSession(); - doReturn(userId).when(loginSession).getUserId(); - - TimetableSync timetableSync = new TimetableSync(); - timetableSync.sync(loginSession, null); - - List dayEntityList = daoSession.getDayDao().loadAll(); - List lessonEntityList = dayEntityList.get(0).getLessons(); - - Assert.assertNotNull(dayEntityList.get(0)); - Assert.assertEquals(userId, dayEntityList.get(0).getUserId()); - Assert.assertEquals(1L, lessonEntityList.get(0).getDayId().longValue()); - Assert.assertEquals("Matematyka", lessonEntityList.get(0).getSubject()); - Assert.assertEquals("20", lessonEntityList.get(0).getRoom()); - Assert.assertEquals("20.12.2012", dayEntityList.get(0).getDate()); - - } - - @AfterClass - public static void cleanUp() { - daoSession.getAccountDao().deleteAll(); - daoSession.getDayDao().deleteAll(); - daoSession.getLessonDao().deleteAll(); - daoSession.getWeekDao().deleteAll(); - daoSession.clear(); - } -} diff --git a/app/src/androidTest/java/io/github/wulkanowy/services/sync/VulcanSyncTest.java b/app/src/androidTest/java/io/github/wulkanowy/services/sync/VulcanSyncTest.java deleted file mode 100644 index df07d462d..000000000 --- a/app/src/androidTest/java/io/github/wulkanowy/services/sync/VulcanSyncTest.java +++ /dev/null @@ -1,30 +0,0 @@ -package io.github.wulkanowy.services.sync; - -import android.support.test.runner.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.io.IOException; - -@RunWith(AndroidJUnit4.class) -public class VulcanSyncTest { - - @Test(expected = IOException.class) - public void syncNoLoginSessionSubjectTest() throws IOException { - VulcanSync vulcanSync = new VulcanSync(new LoginSession()); - vulcanSync.syncSubjectsAndGrades(); - } - - @Test(expected = IOException.class) - public void syncNoLoginSessionGradeTest() throws IOException { - VulcanSync vulcanSync = new VulcanSync(new LoginSession()); - vulcanSync.syncGrades(); - } - - @Test(expected = IOException.class) - public void syncNoLoginSessionTimetableTest() throws IOException { - VulcanSync vulcanSync = new VulcanSync(new LoginSession()); - vulcanSync.syncTimetable(); - } -} diff --git a/app/src/androidTest/java/io/github/wulkanowy/utils/security/SafetyTest.java b/app/src/androidTest/java/io/github/wulkanowy/utils/security/SafetyTest.java deleted file mode 100644 index 17813d423..000000000 --- a/app/src/androidTest/java/io/github/wulkanowy/utils/security/SafetyTest.java +++ /dev/null @@ -1,25 +0,0 @@ -package io.github.wulkanowy.utils.security; - -import android.content.Context; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.SdkSuppress; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; - -import org.junit.Assert; -import org.junit.Test; -import org.junit.runner.RunWith; - -@SmallTest -@RunWith(AndroidJUnit4.class) -public class SafetyTest { - - @Test - @SdkSuppress(minSdkVersion = 18) - public void encryptDecryptTest() throws Exception { - Context targetContext = InstrumentationRegistry.getTargetContext(); - - Safety safety = new Safety(); - Assert.assertEquals("PASS", safety.decrypt("TEST", safety.encrypt("TEST", "PASS", targetContext))); - } -} diff --git a/app/src/androidTest/java/io/github/wulkanowy/utils/security/ScramblerTest.java b/app/src/androidTest/java/io/github/wulkanowy/utils/security/ScramblerTest.java index 16034ed48..2fd1904ba 100644 --- a/app/src/androidTest/java/io/github/wulkanowy/utils/security/ScramblerTest.java +++ b/app/src/androidTest/java/io/github/wulkanowy/utils/security/ScramblerTest.java @@ -1,59 +1,25 @@ package io.github.wulkanowy.utils.security; import android.content.Context; -import android.os.Build; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SdkSuppress; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; import org.junit.Assert; -import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @SmallTest -@SdkSuppress(minSdkVersion = 18) @RunWith(AndroidJUnit4.class) public class ScramblerTest { - private Context targetContext; - - private Scrambler scramblerLoad = new Scrambler(); - - @Before - public void setUp() throws CryptoException { - targetContext = InstrumentationRegistry.getTargetContext(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { - scramblerLoad.loadKeyStore(); - } - } - - @Test(expected = CryptoException.class) - @SdkSuppress(maxSdkVersion = 17) - public void testNoSuchAlgorithm() throws CryptoException { - scramblerLoad.loadKeyStore(); - } - @Test - public void decryptEncryptStringTest() throws CryptoException { - scramblerLoad.generateNewKey("TEST", targetContext); - Assert.assertEquals("pass", - scramblerLoad.decryptString("TEST", scramblerLoad.encryptString("TEST", "pass"))); - } + @SdkSuppress(minSdkVersion = 18) + public void encryptDecryptTest() throws Exception { + Context targetContext = InstrumentationRegistry.getTargetContext(); - @Test(expected = CryptoException.class) - public void decryptEmptyTest() throws CryptoException { - scramblerLoad.decryptString("", ""); + Assert.assertEquals("PASS", Scrambler.decrypt("TEST", + Scrambler.encrypt("TEST", "PASS", targetContext))); } - - @Test(expected = CryptoException.class) - public void encryptEmptyTest() throws CryptoException { - scramblerLoad.encryptString("", ""); - } - - @Test(expected = CryptoException.class) - public void generateNewKeyEmptyTest() throws CryptoException { - scramblerLoad.generateNewKey("", targetContext); - } -} +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4b9199545..f5b89757a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,6 +1,5 @@ @@ -11,8 +10,6 @@ - - + android:theme="@style/WulkanowyTheme.SplashTheme"> @@ -38,12 +35,12 @@ android:label="@string/title_activity_login" android:windowSoftInputMode="adjustResize" /> diff --git a/app/src/main/java/io/github/wulkanowy/WulkanowyApp.java b/app/src/main/java/io/github/wulkanowy/WulkanowyApp.java index 2c7c74ac8..5065279a2 100644 --- a/app/src/main/java/io/github/wulkanowy/WulkanowyApp.java +++ b/app/src/main/java/io/github/wulkanowy/WulkanowyApp.java @@ -1,50 +1,48 @@ package io.github.wulkanowy; import android.app.Application; -import android.content.Context; -import android.content.SharedPreferences; import com.crashlytics.android.Crashlytics; import com.crashlytics.android.core.CrashlyticsCore; import org.greenrobot.greendao.query.QueryBuilder; +import javax.inject.Inject; + import eu.davidea.flexibleadapter.FlexibleAdapter; import eu.davidea.flexibleadapter.utils.Log; import io.fabric.sdk.android.Fabric; -import io.github.wulkanowy.db.dao.entities.DaoMaster; -import io.github.wulkanowy.db.dao.entities.DaoSession; +import io.github.wulkanowy.data.RepositoryContract; +import io.github.wulkanowy.data.db.dao.entities.DaoSession; +import io.github.wulkanowy.di.component.ApplicationComponent; +import io.github.wulkanowy.di.component.DaggerApplicationComponent; +import io.github.wulkanowy.di.modules.ApplicationModule; public class WulkanowyApp extends Application { - public static final String DEBUG_TAG = "WulaknowyActivity"; + protected ApplicationComponent applicationComponent; - private DaoSession daoSession; + @Inject + RepositoryContract repository; @Override public void onCreate() { super.onCreate(); + applicationComponent = DaggerApplicationComponent + .builder() + .applicationModule(new ApplicationModule(this)) + .build(); + applicationComponent.inject(this); + initializeFabric(); if (BuildConfig.DEBUG) { enableDebugLog(); } - initializeFabric(); - - DaoMaster.DevOpenHelper devOpenHelper = new DaoMaster.DevOpenHelper(this, "wulkanowy-db"); - - daoSession = new DaoMaster(devOpenHelper.getWritableDb()).newSession(); - - int schemaVersion = getSharedPreferences("LoginData", Context.MODE_PRIVATE).getInt("schemaVersion", 0); - - if (DaoMaster.SCHEMA_VERSION != schemaVersion) { - SharedPreferences sharedPreferences = getSharedPreferences("LoginData", Context.MODE_PRIVATE); - SharedPreferences.Editor editor = sharedPreferences.edit(); - editor.putLong("userId", 0); - editor.putInt("schemaVersion", DaoMaster.SCHEMA_VERSION); - editor.apply(); - } + } + public ApplicationComponent getApplicationComponent() { + return applicationComponent; } private void enableDebugLog() { @@ -62,6 +60,6 @@ public class WulkanowyApp extends Application { } public DaoSession getDaoSession() { - return daoSession; + return null; } } diff --git a/app/src/main/java/io/github/wulkanowy/data/Repository.java b/app/src/main/java/io/github/wulkanowy/data/Repository.java new file mode 100644 index 000000000..04460a07e --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/Repository.java @@ -0,0 +1,143 @@ +package io.github.wulkanowy.data; + + +import java.io.IOException; +import java.text.ParseException; +import java.util.List; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import io.github.wulkanowy.api.login.AccountPermissionException; +import io.github.wulkanowy.api.login.BadCredentialsException; +import io.github.wulkanowy.api.login.NotLoggedInErrorException; +import io.github.wulkanowy.api.login.VulcanOfflineException; +import io.github.wulkanowy.data.db.dao.entities.Account; +import io.github.wulkanowy.data.db.dao.entities.DaoSession; +import io.github.wulkanowy.data.db.dao.entities.Grade; +import io.github.wulkanowy.data.db.dao.entities.GradeDao; +import io.github.wulkanowy.data.db.dao.entities.Week; +import io.github.wulkanowy.data.db.dao.entities.WeekDao; +import io.github.wulkanowy.data.db.resources.ResourcesContract; +import io.github.wulkanowy.data.db.shared.SharedPrefContract; +import io.github.wulkanowy.data.sync.SyncContract; +import io.github.wulkanowy.data.sync.login.LoginSyncContract; +import io.github.wulkanowy.data.sync.timetable.TimetableSyncContract; +import io.github.wulkanowy.di.annotations.SyncGrades; +import io.github.wulkanowy.di.annotations.SyncSubjects; +import io.github.wulkanowy.utils.security.CryptoException; + +@Singleton +public class Repository implements RepositoryContract { + + private final SharedPrefContract sharedPref; + + private final ResourcesContract resources; + + private final DaoSession daoSession; + + private final LoginSyncContract loginSync; + + private final TimetableSyncContract timetableSync; + + private final SyncContract gradeSync; + + private final SyncContract subjectSync; + + @Inject + Repository(SharedPrefContract sharedPref, + ResourcesContract resources, + DaoSession daoSession, + LoginSyncContract loginSync, + TimetableSyncContract timetableSync, + @SyncGrades SyncContract gradeSync, + @SyncSubjects SyncContract subjectSync) { + this.sharedPref = sharedPref; + this.resources = resources; + this.daoSession = daoSession; + this.loginSync = loginSync; + this.timetableSync = timetableSync; + this.gradeSync = gradeSync; + this.subjectSync = subjectSync; + } + + @Override + public long getCurrentUserId() { + return sharedPref.getCurrentUserId(); + } + + @Override + public String[] getSymbolsKeysArray() { + return resources.getSymbolsKeysArray(); + } + + @Override + public String[] getSymbolsValuesArray() { + return resources.getSymbolsValuesArray(); + } + + @Override + public String getErrorLoginMessage(Exception e) { + return resources.getErrorLoginMessage(e); + } + + @Override + public void loginUser(String email, String password, String symbol) + throws NotLoggedInErrorException, AccountPermissionException, IOException, + CryptoException, VulcanOfflineException, BadCredentialsException { + loginSync.loginUser(email, password, symbol); + } + + @Override + public void loginCurrentUser() throws NotLoggedInErrorException, AccountPermissionException, + IOException, CryptoException, VulcanOfflineException, BadCredentialsException { + loginSync.loginCurrentUser(); + } + + @Override + public void syncGrades() throws NotLoggedInErrorException, IOException, ParseException { + gradeSync.sync(); + } + + @Override + public void syncSubjects() throws NotLoggedInErrorException, IOException, ParseException { + subjectSync.sync(); + } + + @Override + public void syncTimetable() throws NotLoggedInErrorException, IOException, ParseException { + timetableSync.syncTimetable(); + } + + @Override + public void syncTimetable(String date) throws NotLoggedInErrorException, IOException, ParseException { + timetableSync.syncTimetable(date); + } + + @Override + public void syncAll() throws NotLoggedInErrorException, IOException, ParseException { + syncSubjects(); + syncGrades(); + syncTimetable(); + } + + @Override + public Account getCurrentUser() { + return daoSession.getAccountDao().load(sharedPref.getCurrentUserId()); + } + + @Override + public Week getWeek(String date) { + return daoSession.getWeekDao().queryBuilder() + .where(WeekDao.Properties.StartDayDate.eq(date), + WeekDao.Properties.UserId.eq(getCurrentUserId())) + .unique(); + } + + @Override + public List getNewGrades() { + return daoSession.getGradeDao().queryBuilder() + .where(GradeDao.Properties.IsNew.eq(1)) + .list(); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/RepositoryContract.java b/app/src/main/java/io/github/wulkanowy/data/RepositoryContract.java new file mode 100644 index 000000000..fcdf29ed8 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/RepositoryContract.java @@ -0,0 +1,34 @@ +package io.github.wulkanowy.data; + +import java.io.IOException; +import java.text.ParseException; +import java.util.List; + +import javax.inject.Singleton; + +import io.github.wulkanowy.api.login.NotLoggedInErrorException; +import io.github.wulkanowy.data.db.dao.entities.Account; +import io.github.wulkanowy.data.db.dao.entities.Grade; +import io.github.wulkanowy.data.db.dao.entities.Week; +import io.github.wulkanowy.data.db.resources.ResourcesContract; +import io.github.wulkanowy.data.sync.login.LoginSyncContract; +import io.github.wulkanowy.data.sync.timetable.TimetableSyncContract; + +@Singleton +public interface RepositoryContract extends ResourcesContract, LoginSyncContract, + TimetableSyncContract { + + long getCurrentUserId(); + + void syncGrades() throws NotLoggedInErrorException, IOException, ParseException; + + void syncSubjects() throws NotLoggedInErrorException, IOException, ParseException; + + void syncAll() throws NotLoggedInErrorException, IOException, ParseException; + + Account getCurrentUser(); + + Week getWeek(String date); + + List getNewGrades(); +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/DbHelper.java b/app/src/main/java/io/github/wulkanowy/data/db/dao/DbHelper.java new file mode 100644 index 000000000..199a6c8b1 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/DbHelper.java @@ -0,0 +1,19 @@ +package io.github.wulkanowy.data.db.dao; + +import android.content.Context; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import io.github.wulkanowy.data.db.dao.entities.DaoMaster; +import io.github.wulkanowy.di.annotations.ApplicationContext; +import io.github.wulkanowy.di.annotations.DatabaseInfo; + +@Singleton +public class DbHelper extends DaoMaster.DevOpenHelper { + + @Inject + DbHelper(@ApplicationContext Context context, @DatabaseInfo String dbName) { + super(context, dbName); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/db/dao/entities/Account.java b/app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Account.java similarity index 99% rename from app/src/main/java/io/github/wulkanowy/db/dao/entities/Account.java rename to app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Account.java index e5ee45ad4..20a47ac26 100644 --- a/app/src/main/java/io/github/wulkanowy/db/dao/entities/Account.java +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Account.java @@ -1,4 +1,4 @@ -package io.github.wulkanowy.db.dao.entities; +package io.github.wulkanowy.data.db.dao.entities; import org.greenrobot.greendao.DaoException; import org.greenrobot.greendao.annotation.Entity; diff --git a/app/src/main/java/io/github/wulkanowy/db/dao/entities/Day.java b/app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Day.java similarity index 99% rename from app/src/main/java/io/github/wulkanowy/db/dao/entities/Day.java rename to app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Day.java index 3ff68fe44..15a063091 100644 --- a/app/src/main/java/io/github/wulkanowy/db/dao/entities/Day.java +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Day.java @@ -1,4 +1,4 @@ -package io.github.wulkanowy.db.dao.entities; +package io.github.wulkanowy.data.db.dao.entities; import org.greenrobot.greendao.DaoException; import org.greenrobot.greendao.annotation.Entity; diff --git a/app/src/main/java/io/github/wulkanowy/db/dao/entities/Grade.java b/app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Grade.java similarity index 88% rename from app/src/main/java/io/github/wulkanowy/db/dao/entities/Grade.java rename to app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Grade.java index 2a4adaed7..632f9bcc7 100644 --- a/app/src/main/java/io/github/wulkanowy/db/dao/entities/Grade.java +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Grade.java @@ -1,7 +1,4 @@ -package io.github.wulkanowy.db.dao.entities; - -import android.os.Parcel; -import android.os.Parcelable; +package io.github.wulkanowy.data.db.dao.entities; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; @@ -11,13 +8,15 @@ import org.greenrobot.greendao.annotation.Generated; import org.greenrobot.greendao.annotation.Id; import org.greenrobot.greendao.annotation.Property; +import java.io.Serializable; + import io.github.wulkanowy.R; @Entity( nameInDb = "Grades", active = true ) -public class Grade implements Parcelable { +public class Grade implements Serializable { @Id(autoincrement = true) protected Long id; @@ -61,9 +60,7 @@ public class Grade implements Parcelable { @Property(nameInDb = "READ") private boolean read = true; - protected Grade(Parcel source) { - value = source.readString(); - } + private static final long serialVersionUID = 42L; @Generated(hash = 568899968) public Grade(Long id, Long subjectId, Long userId, String subject, String value, @@ -90,36 +87,6 @@ public class Grade implements Parcelable { public Grade() { } - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel parcel, int i) { - parcel.writeString(subject); - parcel.writeString(value); - parcel.writeString(color); - parcel.writeString(symbol); - parcel.writeString(description); - parcel.writeString(weight); - parcel.writeString(date); - parcel.writeString(teacher); - parcel.writeString(semester); - } - - public static final Creator CREATOR = new Creator() { - @Override - public Grade createFromParcel(Parcel source) { - return new Grade(source); - } - - @Override - public Grade[] newArray(int size) { - return new Grade[size]; - } - }; - /** * Used to resolve relations */ diff --git a/app/src/main/java/io/github/wulkanowy/db/dao/entities/Lesson.java b/app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Lesson.java similarity index 97% rename from app/src/main/java/io/github/wulkanowy/db/dao/entities/Lesson.java rename to app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Lesson.java index df0596a73..db9be9445 100644 --- a/app/src/main/java/io/github/wulkanowy/db/dao/entities/Lesson.java +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Lesson.java @@ -1,4 +1,4 @@ -package io.github.wulkanowy.db.dao.entities; +package io.github.wulkanowy.data.db.dao.entities; import org.greenrobot.greendao.DaoException; import org.greenrobot.greendao.annotation.Entity; @@ -7,12 +7,14 @@ import org.greenrobot.greendao.annotation.Id; import org.greenrobot.greendao.annotation.Index; import org.greenrobot.greendao.annotation.Property; +import java.io.Serializable; + @Entity( nameInDb = "Lessons", active = true, - indexes ={@Index(value = "dayId,date,startTime,endTime", unique = true)} + indexes = {@Index(value = "dayId,date,startTime,endTime", unique = true)} ) -public class Lesson { +public class Lesson implements Serializable { @Id(autoincrement = true) private Long id; @@ -65,6 +67,8 @@ public class Lesson { @Property(nameInDb = "IS_NEW_MOVED_IN_CANCELED") private boolean isNewMovedInOrChanged = false; + private static final long serialVersionUID = 42L; + /** * Used to resolve relations */ diff --git a/app/src/main/java/io/github/wulkanowy/db/dao/entities/Subject.java b/app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Subject.java similarity index 99% rename from app/src/main/java/io/github/wulkanowy/db/dao/entities/Subject.java rename to app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Subject.java index 666257f30..6bcce6229 100644 --- a/app/src/main/java/io/github/wulkanowy/db/dao/entities/Subject.java +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Subject.java @@ -1,4 +1,4 @@ -package io.github.wulkanowy.db.dao.entities; +package io.github.wulkanowy.data.db.dao.entities; import org.greenrobot.greendao.DaoException; import org.greenrobot.greendao.annotation.Entity; diff --git a/app/src/main/java/io/github/wulkanowy/db/dao/entities/Week.java b/app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Week.java similarity index 98% rename from app/src/main/java/io/github/wulkanowy/db/dao/entities/Week.java rename to app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Week.java index c8dcbd75e..c82f3277b 100644 --- a/app/src/main/java/io/github/wulkanowy/db/dao/entities/Week.java +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Week.java @@ -1,4 +1,4 @@ -package io.github.wulkanowy.db.dao.entities; +package io.github.wulkanowy.data.db.dao.entities; import org.greenrobot.greendao.DaoException; import org.greenrobot.greendao.annotation.Entity; diff --git a/app/src/main/java/io/github/wulkanowy/data/db/resources/AppResources.java b/app/src/main/java/io/github/wulkanowy/data/db/resources/AppResources.java new file mode 100644 index 000000000..8e7227d36 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/resources/AppResources.java @@ -0,0 +1,59 @@ +package io.github.wulkanowy.data.db.resources; + +import android.content.Context; +import android.content.res.Resources; + +import java.io.IOException; +import java.net.SocketTimeoutException; +import java.net.UnknownHostException; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import io.github.wulkanowy.R; +import io.github.wulkanowy.api.login.NotLoggedInErrorException; +import io.github.wulkanowy.api.login.VulcanOfflineException; +import io.github.wulkanowy.di.annotations.ApplicationContext; +import io.github.wulkanowy.utils.AppConstant; +import io.github.wulkanowy.utils.LogUtils; +import io.github.wulkanowy.utils.security.CryptoException; + +@Singleton +public class AppResources implements ResourcesContract { + + private Resources resources; + + @Inject + AppResources(@ApplicationContext Context context) { + resources = context.getResources(); + } + + @Override + public String[] getSymbolsKeysArray() { + return resources.getStringArray(R.array.symbols); + } + + @Override + public String[] getSymbolsValuesArray() { + return resources.getStringArray(R.array.symbols_values); + } + + @Override + public String getErrorLoginMessage(Exception exception) { + LogUtils.error(AppConstant.APP_NAME + " encountered a error", exception); + + if (exception instanceof CryptoException) { + return resources.getString(R.string.encrypt_failed_text); + } else if (exception instanceof UnknownHostException) { + return resources.getString(R.string.noInternet_text); + } else if (exception instanceof SocketTimeoutException) { + return resources.getString(R.string.generic_timeout_error); + } else if (exception instanceof NotLoggedInErrorException || exception instanceof IOException) { + return resources.getString(R.string.login_denied_text); + } else if (exception instanceof VulcanOfflineException) { + return resources.getString(R.string.error_host_offline); + } else { + return exception.getMessage(); + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/resources/ResourcesContract.java b/app/src/main/java/io/github/wulkanowy/data/db/resources/ResourcesContract.java new file mode 100644 index 000000000..3768ad2c6 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/resources/ResourcesContract.java @@ -0,0 +1,10 @@ +package io.github.wulkanowy.data.db.resources; + +public interface ResourcesContract { + + String[] getSymbolsKeysArray(); + + String[] getSymbolsValuesArray(); + + String getErrorLoginMessage(Exception e); +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/shared/SharedPref.java b/app/src/main/java/io/github/wulkanowy/data/db/shared/SharedPref.java new file mode 100644 index 000000000..ba437f882 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/shared/SharedPref.java @@ -0,0 +1,33 @@ +package io.github.wulkanowy.data.db.shared; + +import android.content.Context; +import android.content.SharedPreferences; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import io.github.wulkanowy.di.annotations.ApplicationContext; +import io.github.wulkanowy.di.annotations.SharedPreferencesInfo; + +@Singleton +public class SharedPref implements SharedPrefContract { + + private static final String SHARED_KEY_USER_ID = "USER_ID"; + + private final SharedPreferences sharedPreferences; + + @Inject + SharedPref(@ApplicationContext Context context, @SharedPreferencesInfo String sharedName) { + sharedPreferences = context.getSharedPreferences(sharedName, Context.MODE_PRIVATE); + } + + @Override + public long getCurrentUserId() { + return sharedPreferences.getLong(SHARED_KEY_USER_ID, 0); + } + + @Override + public void setCurrentUserId(long userId) { + sharedPreferences.edit().putLong(SHARED_KEY_USER_ID, userId).apply(); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/shared/SharedPrefContract.java b/app/src/main/java/io/github/wulkanowy/data/db/shared/SharedPrefContract.java new file mode 100644 index 000000000..7f540acfd --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/shared/SharedPrefContract.java @@ -0,0 +1,8 @@ +package io.github.wulkanowy.data.db.shared; + +public interface SharedPrefContract { + + long getCurrentUserId(); + + void setCurrentUserId(long userId); +} diff --git a/app/src/main/java/io/github/wulkanowy/data/sync/SyncContract.java b/app/src/main/java/io/github/wulkanowy/data/sync/SyncContract.java new file mode 100644 index 000000000..43b37b7e1 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/sync/SyncContract.java @@ -0,0 +1,11 @@ +package io.github.wulkanowy.data.sync; + +import java.io.IOException; +import java.text.ParseException; + +import io.github.wulkanowy.api.login.NotLoggedInErrorException; + +public interface SyncContract { + + void sync() throws NotLoggedInErrorException, IOException, ParseException; +} diff --git a/app/src/main/java/io/github/wulkanowy/data/sync/grades/GradeSync.java b/app/src/main/java/io/github/wulkanowy/data/sync/grades/GradeSync.java new file mode 100644 index 000000000..e0b388849 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/sync/grades/GradeSync.java @@ -0,0 +1,72 @@ +package io.github.wulkanowy.data.sync.grades; + +import java.io.IOException; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import io.github.wulkanowy.api.Vulcan; +import io.github.wulkanowy.api.login.NotLoggedInErrorException; +import io.github.wulkanowy.data.db.dao.entities.Account; +import io.github.wulkanowy.data.db.dao.entities.DaoSession; +import io.github.wulkanowy.data.db.dao.entities.Grade; +import io.github.wulkanowy.data.db.dao.entities.SubjectDao; +import io.github.wulkanowy.data.db.shared.SharedPrefContract; +import io.github.wulkanowy.data.sync.SyncContract; +import io.github.wulkanowy.utils.DataObjectConverter; +import io.github.wulkanowy.utils.EntitiesCompare; +import io.github.wulkanowy.utils.LogUtils; + +@Singleton +public class GradeSync implements SyncContract { + + private final DaoSession daoSession; + + private final Vulcan vulcan; + + private final SharedPrefContract sharedPref; + + @Inject + GradeSync(DaoSession daoSession, SharedPrefContract sharedPref, Vulcan vulcan) { + this.daoSession = daoSession; + this.sharedPref = sharedPref; + this.vulcan = vulcan; + } + + @Override + public void sync() throws IOException, NotLoggedInErrorException, ParseException { + + long userId = sharedPref.getCurrentUserId(); + + Account account = daoSession.getAccountDao().load(userId); + account.resetGradeList(); + account.resetSubjectList(); + + List gradesFromNet = DataObjectConverter + .gradesToGradeEntities(vulcan.getGradesList().getAll()); + List gradesFromDb = account.getGradeList(); + + List updatedGrades = EntitiesCompare.compareGradeList(gradesFromNet, gradesFromDb); + + daoSession.getGradeDao().deleteInTx(gradesFromDb); + + List lastList = new ArrayList<>(); + + for (Grade grade : updatedGrades) { + grade.setUserId(userId); + grade.setSubjectId(daoSession.getSubjectDao().queryBuilder() + .where(SubjectDao.Properties.Name.eq(grade.getSubject()), + SubjectDao.Properties.UserId.eq(userId)) + .build() + .uniqueOrThrow().getId()); + lastList.add(grade); + } + + daoSession.getGradeDao().insertInTx(lastList); + + LogUtils.debug("Synchronization grades (amount = " + lastList.size() + ")"); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/sync/login/LoginSync.java b/app/src/main/java/io/github/wulkanowy/data/sync/login/LoginSync.java new file mode 100644 index 000000000..71838ff54 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/sync/login/LoginSync.java @@ -0,0 +1,81 @@ +package io.github.wulkanowy.data.sync.login; + +import android.content.Context; + +import java.io.IOException; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import io.github.wulkanowy.api.Vulcan; +import io.github.wulkanowy.api.login.AccountPermissionException; +import io.github.wulkanowy.api.login.BadCredentialsException; +import io.github.wulkanowy.api.login.NotLoggedInErrorException; +import io.github.wulkanowy.api.login.VulcanOfflineException; +import io.github.wulkanowy.data.db.dao.entities.Account; +import io.github.wulkanowy.data.db.dao.entities.DaoSession; +import io.github.wulkanowy.data.db.shared.SharedPrefContract; +import io.github.wulkanowy.di.annotations.ApplicationContext; +import io.github.wulkanowy.utils.LogUtils; +import io.github.wulkanowy.utils.security.CryptoException; +import io.github.wulkanowy.utils.security.Scrambler; + +@Singleton +public class LoginSync implements LoginSyncContract { + + private final DaoSession daoSession; + + private final SharedPrefContract sharedPref; + + private final Vulcan vulcan; + + private final Context context; + + @Inject + LoginSync(DaoSession daoSession, SharedPrefContract sharedPref, + Vulcan vulcan, @ApplicationContext Context context) { + this.daoSession = daoSession; + this.sharedPref = sharedPref; + this.vulcan = vulcan; + this.context = context; + } + + @Override + public void loginUser(String email, String password, String symbol) + throws NotLoggedInErrorException, AccountPermissionException, IOException, + CryptoException, VulcanOfflineException, BadCredentialsException { + + LogUtils.debug("Login new user email=" + email); + + vulcan.login(email, password, symbol); + + Account account = new Account() + .setName(vulcan.getBasicInformation().getPersonalData().getFirstAndLastName()) + .setEmail(email) + .setPassword(Scrambler.encrypt(email, password, context)) + .setSymbol(vulcan.getSymbol()) + .setSnpId(vulcan.getStudentAndParent().getId()); + + sharedPref.setCurrentUserId(daoSession.getAccountDao().insert(account)); + } + + @Override + public void loginCurrentUser() throws NotLoggedInErrorException, AccountPermissionException, + IOException, CryptoException, VulcanOfflineException, BadCredentialsException { + + long userId = sharedPref.getCurrentUserId(); + + if (userId == 0) { + throw new IOException("Can't find logged user"); + } + + LogUtils.debug("Login current user id=" + userId); + + Account account = daoSession.getAccountDao().load(userId); + + vulcan.login(account.getEmail(), + Scrambler.decrypt(account.getEmail(), account.getPassword()), + account.getSymbol(), + account.getSnpId()); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/sync/login/LoginSyncContract.java b/app/src/main/java/io/github/wulkanowy/data/sync/login/LoginSyncContract.java new file mode 100644 index 000000000..1aed8b1bc --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/sync/login/LoginSyncContract.java @@ -0,0 +1,19 @@ +package io.github.wulkanowy.data.sync.login; + +import java.io.IOException; + +import io.github.wulkanowy.api.login.AccountPermissionException; +import io.github.wulkanowy.api.login.BadCredentialsException; +import io.github.wulkanowy.api.login.NotLoggedInErrorException; +import io.github.wulkanowy.api.login.VulcanOfflineException; +import io.github.wulkanowy.utils.security.CryptoException; + +public interface LoginSyncContract { + + void loginUser(String email, String password, String symbol) + throws NotLoggedInErrorException, AccountPermissionException, IOException, + CryptoException, VulcanOfflineException, BadCredentialsException; + + void loginCurrentUser() throws NotLoggedInErrorException, AccountPermissionException, IOException, + CryptoException, VulcanOfflineException, BadCredentialsException; +} diff --git a/app/src/main/java/io/github/wulkanowy/data/sync/subjects/SubjectSync.java b/app/src/main/java/io/github/wulkanowy/data/sync/subjects/SubjectSync.java new file mode 100644 index 000000000..d888090bf --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/sync/subjects/SubjectSync.java @@ -0,0 +1,61 @@ +package io.github.wulkanowy.data.sync.subjects; + +import java.io.IOException; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import io.github.wulkanowy.api.Vulcan; +import io.github.wulkanowy.api.login.NotLoggedInErrorException; +import io.github.wulkanowy.data.db.dao.entities.DaoSession; +import io.github.wulkanowy.data.db.dao.entities.Subject; +import io.github.wulkanowy.data.db.dao.entities.SubjectDao; +import io.github.wulkanowy.data.db.shared.SharedPrefContract; +import io.github.wulkanowy.data.sync.SyncContract; +import io.github.wulkanowy.utils.DataObjectConverter; +import io.github.wulkanowy.utils.LogUtils; + +@Singleton +public class SubjectSync implements SyncContract { + + private final SubjectDao subjectDao; + + private final Vulcan vulcan; + + private final SharedPrefContract sharedPref; + + @Inject + SubjectSync(DaoSession daoSession, SharedPrefContract sharedPref, Vulcan vulcan) { + this.subjectDao = daoSession.getSubjectDao(); + this.sharedPref = sharedPref; + this.vulcan = vulcan; + } + + @Override + public void sync() throws NotLoggedInErrorException, IOException, ParseException { + + long userId = sharedPref.getCurrentUserId(); + + List subjectsFromNet = DataObjectConverter + .subjectsToSubjectEntities(vulcan.getSubjectsList().getAll()); + + subjectDao.deleteInTx(subjectDao.queryBuilder() + .where(SubjectDao.Properties.UserId.eq(userId)) + .build() + .list()); + + List lastList = new ArrayList<>(); + + for (Subject subject : subjectsFromNet) { + subject.setUserId(userId); + lastList.add(subject); + } + + subjectDao.insertInTx(lastList); + + LogUtils.debug("Synchronization subjects (amount = " + lastList.size() + ")"); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/sync/timetable/TimetableSync.java b/app/src/main/java/io/github/wulkanowy/data/sync/timetable/TimetableSync.java new file mode 100644 index 000000000..f293f59d3 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/sync/timetable/TimetableSync.java @@ -0,0 +1,119 @@ +package io.github.wulkanowy.data.sync.timetable; + +import java.io.IOException; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import io.github.wulkanowy.api.Vulcan; +import io.github.wulkanowy.api.login.NotLoggedInErrorException; +import io.github.wulkanowy.data.db.dao.entities.DaoSession; +import io.github.wulkanowy.data.db.dao.entities.Day; +import io.github.wulkanowy.data.db.dao.entities.DayDao; +import io.github.wulkanowy.data.db.dao.entities.Lesson; +import io.github.wulkanowy.data.db.dao.entities.LessonDao; +import io.github.wulkanowy.data.db.dao.entities.Week; +import io.github.wulkanowy.data.db.dao.entities.WeekDao; +import io.github.wulkanowy.data.db.shared.SharedPrefContract; +import io.github.wulkanowy.utils.DataObjectConverter; +import io.github.wulkanowy.utils.LogUtils; +import io.github.wulkanowy.utils.TimeUtils; + +@Singleton +public class TimetableSync implements TimetableSyncContract { + + private final DaoSession daoSession; + + private final Vulcan vulcan; + + private final SharedPrefContract sharedPref; + + @Inject + TimetableSync(DaoSession daoSession, SharedPrefContract sharedPref, Vulcan vulcan) { + this.daoSession = daoSession; + this.sharedPref = sharedPref; + this.vulcan = vulcan; + } + + @Override + public void syncTimetable(String date) throws NotLoggedInErrorException, IOException, ParseException { + long userId = sharedPref.getCurrentUserId(); + + io.github.wulkanowy.api.timetable.Week weekFromNet = date == null ? vulcan.getTimetable().getWeekTable() + : vulcan.getTimetable().getWeekTable(String.valueOf(TimeUtils.getNetTicks(date))); + + Week weekFromDb = daoSession.getWeekDao().queryBuilder() + .where(WeekDao.Properties.UserId.eq(userId), + WeekDao.Properties.StartDayDate.eq(weekFromNet.getStartDayDate())) + .unique(); + + Long weekId; + + if (weekFromDb == null) { + Week weekFromNetEntity = DataObjectConverter.weekToWeekEntity(weekFromNet).setUserId(userId); + weekId = daoSession.getWeekDao().insert(weekFromNetEntity); + } else { + weekId = weekFromDb.getId(); + } + + List dayListFromNet = weekFromNet.getDays(); + + List updatedLessonList = new ArrayList<>(); + + for (io.github.wulkanowy.api.timetable.Day dayFromNet : dayListFromNet) { + Day dayFromNetEntity = DataObjectConverter.dayToDayEntity(dayFromNet); + + Day dayFromDb = daoSession.getDayDao().queryBuilder() + .where(DayDao.Properties.UserId.eq(userId), + DayDao.Properties.WeekId.eq(weekId), + DayDao.Properties.Date.eq(dayFromNetEntity.getDate())) + .unique(); + + dayFromNetEntity.setUserId(userId); + dayFromNetEntity.setWeekId(weekId); + + Long dayId; + + if (dayFromDb != null) { + dayFromNetEntity.setId(dayFromDb.getId()); + daoSession.getDayDao().save(dayFromNetEntity); + dayId = dayFromNetEntity.getId(); + } else { + dayId = daoSession.getDayDao().insert(dayFromNetEntity); + } + + List lessonListFromNetEntities = DataObjectConverter + .lessonsToLessonsEntities(dayFromNet.getLessons()); + + for (Lesson lessonFromNetEntity : lessonListFromNetEntities) { + Lesson lessonFromDb = daoSession.getLessonDao().queryBuilder() + .where(LessonDao.Properties.DayId.eq(dayId), + LessonDao.Properties.Date.eq(lessonFromNetEntity.getDate()), + LessonDao.Properties.StartTime.eq(lessonFromNetEntity.getStartTime()), + LessonDao.Properties.EndTime.eq(lessonFromNetEntity.getEndTime())) + .unique(); + + if (lessonFromDb != null) { + lessonFromNetEntity.setId(lessonFromDb.getId()); + } + + lessonFromNetEntity.setDayId(dayFromNetEntity.getId()); + + if (!"".equals(lessonFromNetEntity.getSubject())) { + updatedLessonList.add(lessonFromNetEntity); + } + } + } + daoSession.getLessonDao().saveInTx(updatedLessonList); + + LogUtils.debug("Synchronization lessons (amount = " + updatedLessonList.size() + ")"); + } + + @Override + public void syncTimetable() throws NotLoggedInErrorException, IOException, ParseException { + syncTimetable(null); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/sync/timetable/TimetableSyncContract.java b/app/src/main/java/io/github/wulkanowy/data/sync/timetable/TimetableSyncContract.java new file mode 100644 index 000000000..4c37d2ec8 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/sync/timetable/TimetableSyncContract.java @@ -0,0 +1,13 @@ +package io.github.wulkanowy.data.sync.timetable; + +import java.io.IOException; +import java.text.ParseException; + +import io.github.wulkanowy.api.login.NotLoggedInErrorException; + +public interface TimetableSyncContract { + + void syncTimetable(String date) throws NotLoggedInErrorException, IOException, ParseException; + + void syncTimetable() throws NotLoggedInErrorException, IOException, ParseException; +} diff --git a/app/src/main/java/io/github/wulkanowy/db/dao/DatabaseAccess.java b/app/src/main/java/io/github/wulkanowy/db/dao/DatabaseAccess.java deleted file mode 100644 index 908a5110b..000000000 --- a/app/src/main/java/io/github/wulkanowy/db/dao/DatabaseAccess.java +++ /dev/null @@ -1,20 +0,0 @@ -package io.github.wulkanowy.db.dao; - -import org.greenrobot.greendao.query.Query; - -import java.util.List; - -import io.github.wulkanowy.db.dao.entities.DaoSession; -import io.github.wulkanowy.db.dao.entities.Grade; -import io.github.wulkanowy.db.dao.entities.GradeDao; - -public class DatabaseAccess { - - public List getNewGrades(DaoSession daoSession) { - Query gradeQuery = daoSession.getGradeDao().queryBuilder() - .where(GradeDao.Properties.IsNew.eq(1)) - .build(); - - return gradeQuery.list(); - } -} diff --git a/app/src/main/java/io/github/wulkanowy/di/annotations/ActivityContext.java b/app/src/main/java/io/github/wulkanowy/di/annotations/ActivityContext.java new file mode 100644 index 000000000..2a74c32d3 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/di/annotations/ActivityContext.java @@ -0,0 +1,11 @@ +package io.github.wulkanowy.di.annotations; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.inject.Qualifier; + +@Qualifier +@Retention(RetentionPolicy.RUNTIME) +public @interface ActivityContext { +} diff --git a/app/src/main/java/io/github/wulkanowy/di/annotations/ApplicationContext.java b/app/src/main/java/io/github/wulkanowy/di/annotations/ApplicationContext.java new file mode 100644 index 000000000..04139d998 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/di/annotations/ApplicationContext.java @@ -0,0 +1,11 @@ +package io.github.wulkanowy.di.annotations; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.inject.Qualifier; + +@Qualifier +@Retention(RetentionPolicy.RUNTIME) +public @interface ApplicationContext { +} diff --git a/app/src/main/java/io/github/wulkanowy/di/annotations/DatabaseInfo.java b/app/src/main/java/io/github/wulkanowy/di/annotations/DatabaseInfo.java new file mode 100644 index 000000000..fabcefbaa --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/di/annotations/DatabaseInfo.java @@ -0,0 +1,11 @@ +package io.github.wulkanowy.di.annotations; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.inject.Qualifier; + +@Qualifier +@Retention(RetentionPolicy.RUNTIME) +public @interface DatabaseInfo { +} diff --git a/app/src/main/java/io/github/wulkanowy/di/annotations/PerActivity.java b/app/src/main/java/io/github/wulkanowy/di/annotations/PerActivity.java new file mode 100644 index 000000000..f103994a5 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/di/annotations/PerActivity.java @@ -0,0 +1,11 @@ +package io.github.wulkanowy.di.annotations; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.inject.Scope; + +@Scope +@Retention(RetentionPolicy.RUNTIME) +public @interface PerActivity { +} diff --git a/app/src/main/java/io/github/wulkanowy/di/annotations/PerFragment.java b/app/src/main/java/io/github/wulkanowy/di/annotations/PerFragment.java new file mode 100644 index 000000000..98f364f76 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/di/annotations/PerFragment.java @@ -0,0 +1,11 @@ +package io.github.wulkanowy.di.annotations; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.inject.Scope; + +@Scope +@Retention(RetentionPolicy.RUNTIME) +public @interface PerFragment { +} diff --git a/app/src/main/java/io/github/wulkanowy/di/annotations/SharedPreferencesInfo.java b/app/src/main/java/io/github/wulkanowy/di/annotations/SharedPreferencesInfo.java new file mode 100644 index 000000000..919c77a0b --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/di/annotations/SharedPreferencesInfo.java @@ -0,0 +1,11 @@ +package io.github.wulkanowy.di.annotations; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.inject.Qualifier; + +@Qualifier +@Retention(RetentionPolicy.RUNTIME) +public @interface SharedPreferencesInfo { +} diff --git a/app/src/main/java/io/github/wulkanowy/di/annotations/SyncGrades.java b/app/src/main/java/io/github/wulkanowy/di/annotations/SyncGrades.java new file mode 100644 index 000000000..90e6c02f6 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/di/annotations/SyncGrades.java @@ -0,0 +1,11 @@ +package io.github.wulkanowy.di.annotations; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.inject.Qualifier; + +@Qualifier +@Retention(RetentionPolicy.RUNTIME) +public @interface SyncGrades { +} diff --git a/app/src/main/java/io/github/wulkanowy/di/annotations/SyncSubjects.java b/app/src/main/java/io/github/wulkanowy/di/annotations/SyncSubjects.java new file mode 100644 index 000000000..81d351bea --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/di/annotations/SyncSubjects.java @@ -0,0 +1,11 @@ +package io.github.wulkanowy.di.annotations; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.inject.Qualifier; + +@Qualifier +@Retention(RetentionPolicy.RUNTIME) +public @interface SyncSubjects { +} diff --git a/app/src/main/java/io/github/wulkanowy/di/component/ActivityComponent.java b/app/src/main/java/io/github/wulkanowy/di/component/ActivityComponent.java new file mode 100644 index 000000000..3365a317e --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/di/component/ActivityComponent.java @@ -0,0 +1,19 @@ +package io.github.wulkanowy.di.component; + +import dagger.Component; +import io.github.wulkanowy.di.annotations.PerActivity; +import io.github.wulkanowy.di.modules.ActivityModule; +import io.github.wulkanowy.ui.login.LoginActivity; +import io.github.wulkanowy.ui.main.MainActivity; +import io.github.wulkanowy.ui.splash.SplashActivity; + +@PerActivity +@Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class) +public interface ActivityComponent { + + void inject(SplashActivity splashActivity); + + void inject(LoginActivity loginActivity); + + void inject(MainActivity mainActivity); +} diff --git a/app/src/main/java/io/github/wulkanowy/di/component/ApplicationComponent.java b/app/src/main/java/io/github/wulkanowy/di/component/ApplicationComponent.java new file mode 100644 index 000000000..3439a9496 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/di/component/ApplicationComponent.java @@ -0,0 +1,26 @@ +package io.github.wulkanowy.di.component; + +import android.content.Context; + +import javax.inject.Singleton; + +import dagger.Component; +import io.github.wulkanowy.WulkanowyApp; +import io.github.wulkanowy.data.RepositoryContract; +import io.github.wulkanowy.di.annotations.ApplicationContext; +import io.github.wulkanowy.di.modules.ApplicationModule; +import io.github.wulkanowy.services.SyncJob; + +@Singleton +@Component(modules = ApplicationModule.class) +public interface ApplicationComponent { + + @ApplicationContext + Context getContext(); + + RepositoryContract getRepository(); + + void inject(WulkanowyApp wulkanowyApp); + + void inject(SyncJob syncJob); +} diff --git a/app/src/main/java/io/github/wulkanowy/di/component/FragmentComponent.java b/app/src/main/java/io/github/wulkanowy/di/component/FragmentComponent.java new file mode 100644 index 000000000..01a0fc02f --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/di/component/FragmentComponent.java @@ -0,0 +1,25 @@ +package io.github.wulkanowy.di.component; + +import dagger.Component; +import io.github.wulkanowy.di.annotations.PerFragment; +import io.github.wulkanowy.di.modules.FragmentModule; +import io.github.wulkanowy.ui.main.attendance.AttendanceFragment; +import io.github.wulkanowy.ui.main.dashboard.DashboardFragment; +import io.github.wulkanowy.ui.main.grades.GradesFragment; +import io.github.wulkanowy.ui.main.timetable.TimetableFragment; +import io.github.wulkanowy.ui.main.timetable.TimetableTabFragment; + +@PerFragment +@Component(dependencies = ApplicationComponent.class, modules = FragmentModule.class) +public interface FragmentComponent { + + void inject(GradesFragment gradesFragment); + + void inject(AttendanceFragment attendanceFragment); + + void inject(DashboardFragment dashboardFragment); + + void inject(TimetableFragment timetableFragment); + + void inject(TimetableTabFragment timetableTabFragment); +} diff --git a/app/src/main/java/io/github/wulkanowy/di/modules/ActivityModule.java b/app/src/main/java/io/github/wulkanowy/di/modules/ActivityModule.java new file mode 100644 index 000000000..53dc10864 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/di/modules/ActivityModule.java @@ -0,0 +1,64 @@ +package io.github.wulkanowy.di.modules; + + +import android.content.Context; +import android.support.v7.app.AppCompatActivity; + +import dagger.Module; +import dagger.Provides; +import io.github.wulkanowy.di.annotations.ActivityContext; +import io.github.wulkanowy.di.annotations.PerActivity; +import io.github.wulkanowy.ui.login.LoginContract; +import io.github.wulkanowy.ui.login.LoginPresenter; +import io.github.wulkanowy.ui.main.MainContract; +import io.github.wulkanowy.ui.main.MainPagerAdapter; +import io.github.wulkanowy.ui.main.MainPresenter; +import io.github.wulkanowy.ui.splash.SplashContract; +import io.github.wulkanowy.ui.splash.SplashPresenter; + +@Module +public class ActivityModule { + + private AppCompatActivity activity; + + public ActivityModule(AppCompatActivity activity) { + this.activity = activity; + } + + @ActivityContext + @Provides + Context provideContext() { + return activity; + } + + @Provides + AppCompatActivity provideActivity() { + return activity; + } + + @PerActivity + @Provides + SplashContract.Presenter provideSplashPresenter + (SplashPresenter splashPresenter) { + return splashPresenter; + } + + @PerActivity + @Provides + LoginContract.Presenter provideLoginPresenter + (LoginPresenter loginPresenter) { + return loginPresenter; + } + + @PerActivity + @Provides + MainContract.Presenter provideMainPresenter + (MainPresenter mainPresenter) { + return mainPresenter; + } + + @Provides + MainPagerAdapter provideMainPagerAdapter() { + return new MainPagerAdapter(activity.getSupportFragmentManager()); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/di/modules/ApplicationModule.java b/app/src/main/java/io/github/wulkanowy/di/modules/ApplicationModule.java index 132344637..58e9909b4 100644 --- a/app/src/main/java/io/github/wulkanowy/di/modules/ApplicationModule.java +++ b/app/src/main/java/io/github/wulkanowy/di/modules/ApplicationModule.java @@ -3,13 +3,41 @@ package io.github.wulkanowy.di.modules; import android.app.Application; import android.content.Context; +import com.firebase.jobdispatcher.FirebaseJobDispatcher; +import com.firebase.jobdispatcher.GooglePlayDriver; + +import javax.inject.Singleton; + import dagger.Module; import dagger.Provides; +import io.github.wulkanowy.api.Vulcan; +import io.github.wulkanowy.data.Repository; +import io.github.wulkanowy.data.RepositoryContract; +import io.github.wulkanowy.data.db.dao.DbHelper; +import io.github.wulkanowy.data.db.dao.entities.DaoMaster; +import io.github.wulkanowy.data.db.dao.entities.DaoSession; +import io.github.wulkanowy.data.db.resources.AppResources; +import io.github.wulkanowy.data.db.resources.ResourcesContract; +import io.github.wulkanowy.data.db.shared.SharedPref; +import io.github.wulkanowy.data.db.shared.SharedPrefContract; +import io.github.wulkanowy.data.sync.SyncContract; +import io.github.wulkanowy.data.sync.grades.GradeSync; +import io.github.wulkanowy.data.sync.login.LoginSync; +import io.github.wulkanowy.data.sync.login.LoginSyncContract; +import io.github.wulkanowy.data.sync.subjects.SubjectSync; +import io.github.wulkanowy.data.sync.timetable.TimetableSync; +import io.github.wulkanowy.data.sync.timetable.TimetableSyncContract; +import io.github.wulkanowy.di.annotations.ApplicationContext; +import io.github.wulkanowy.di.annotations.DatabaseInfo; +import io.github.wulkanowy.di.annotations.SharedPreferencesInfo; +import io.github.wulkanowy.di.annotations.SyncGrades; +import io.github.wulkanowy.di.annotations.SyncSubjects; +import io.github.wulkanowy.utils.AppConstant; @Module public class ApplicationModule { - protected final Application application; + private final Application application; public ApplicationModule(Application application) { this.application = application; @@ -20,8 +48,82 @@ public class ApplicationModule { return application; } + @ApplicationContext @Provides Context provideAppContext() { return application; } + + @DatabaseInfo + @Provides + String provideDatabaseName() { + return AppConstant.DATABASE_NAME; + } + + @SharedPreferencesInfo + @Provides + String provideSharedPreferencesName() { + return AppConstant.SHARED_PREFERENCES_NAME; + } + + @Singleton + @Provides + DaoSession provideDaoSession(DbHelper dbHelper) { + return new DaoMaster(dbHelper.getWritableDb()).newSession(); + } + + @Singleton + @Provides + Vulcan provideVulcan() { + return new Vulcan(); + } + + @Singleton + @Provides + RepositoryContract provideRepository(Repository repository) { + return repository; + } + + @Singleton + @Provides + SharedPrefContract provideSharedPref(SharedPref sharedPref) { + return sharedPref; + } + + @Singleton + @Provides + ResourcesContract provideAppResources(AppResources appResources) { + return appResources; + } + + @Singleton + @Provides + LoginSyncContract provideLoginSync(LoginSync loginSync) { + return loginSync; + } + + @SyncGrades + @Singleton + @Provides + SyncContract provideGradesSync(GradeSync gradeSync) { + return gradeSync; + } + + @SyncSubjects + @Singleton + @Provides + SyncContract provideSubjectSync(SubjectSync subjectSync) { + return subjectSync; + } + + @Singleton + @Provides + TimetableSyncContract provideTimetableSync(TimetableSync timetableSync) { + return timetableSync; + } + + @Provides + FirebaseJobDispatcher provideDispatcher() { + return new FirebaseJobDispatcher(new GooglePlayDriver(application)); + } } diff --git a/app/src/main/java/io/github/wulkanowy/di/modules/FragmentModule.java b/app/src/main/java/io/github/wulkanowy/di/modules/FragmentModule.java new file mode 100644 index 000000000..8999bca86 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/di/modules/FragmentModule.java @@ -0,0 +1,76 @@ +package io.github.wulkanowy.di.modules; + +import android.support.v4.app.Fragment; + +import dagger.Module; +import dagger.Provides; +import eu.davidea.flexibleadapter.FlexibleAdapter; +import io.github.wulkanowy.di.annotations.PerFragment; +import io.github.wulkanowy.ui.main.attendance.AttendanceContract; +import io.github.wulkanowy.ui.main.attendance.AttendancePresenter; +import io.github.wulkanowy.ui.main.dashboard.DashboardContract; +import io.github.wulkanowy.ui.main.dashboard.DashboardPresenter; +import io.github.wulkanowy.ui.main.grades.GradeHeaderItem; +import io.github.wulkanowy.ui.main.grades.GradesContract; +import io.github.wulkanowy.ui.main.grades.GradesPresenter; +import io.github.wulkanowy.ui.main.timetable.TimetableContract; +import io.github.wulkanowy.ui.main.timetable.TimetableHeaderItem; +import io.github.wulkanowy.ui.main.timetable.TimetablePagerAdapter; +import io.github.wulkanowy.ui.main.timetable.TimetablePresenter; +import io.github.wulkanowy.ui.main.timetable.TimetableTabContract; +import io.github.wulkanowy.ui.main.timetable.TimetableTabPresenter; + +@Module +public class FragmentModule { + + private final Fragment fragment; + + public FragmentModule(Fragment fragment) { + this.fragment = fragment; + } + + @PerFragment + @Provides + GradesContract.Presenter provideGradesPresenter(GradesPresenter gradesPresenter) { + return gradesPresenter; + } + + @PerFragment + @Provides + AttendanceContract.Presenter provideAttendancePresenter(AttendancePresenter attendancePresenter) { + return attendancePresenter; + } + + @PerFragment + @Provides + DashboardContract.Presenter provideDashboardPresenter(DashboardPresenter dashboardPresenter) { + return dashboardPresenter; + } + + @PerFragment + @Provides + TimetableContract.Presenter provideTimetablePresenter(TimetablePresenter timetablePresenter) { + return timetablePresenter; + } + + @PerFragment + @Provides + TimetableTabContract.Presenter provideTimetableTabPresenter(TimetableTabPresenter timetableTabPresenter) { + return timetableTabPresenter; + } + + @Provides + TimetablePagerAdapter provideTimetablePagerAdapter() { + return new TimetablePagerAdapter(fragment.getChildFragmentManager()); + } + + @Provides + FlexibleAdapter provideGradesAdapter() { + return new FlexibleAdapter<>(null); + } + + @Provides + FlexibleAdapter provideTimetableTabAdapter() { + return new FlexibleAdapter<>(null); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/services/NotificationService.java b/app/src/main/java/io/github/wulkanowy/services/NotificationService.java new file mode 100644 index 000000000..ab721f46c --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/services/NotificationService.java @@ -0,0 +1,55 @@ +package io.github.wulkanowy.services; + + +import android.annotation.TargetApi; +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.content.Context; +import android.os.Build; +import android.support.v4.app.NotificationCompat; + +import java.util.Random; + +class NotificationService { + + private static final String CHANNEL_ID = "Wulkanowy_New_Grade_Channel"; + + private static final String CHANNEL_NAME = "New Grade Channel"; + + private NotificationManager manager; + + private Context context; + + NotificationService(Context context) { + this.context = context; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + createChannel(); + } + } + + void notify(Notification notification) { + getManager().notify(new Random().nextInt(1000), notification); + } + + NotificationCompat.Builder notificationBuilder() { + return new NotificationCompat.Builder(context, CHANNEL_ID); + } + + @TargetApi(26) + private void createChannel() { + NotificationChannel notificationChannel = new NotificationChannel(CHANNEL_ID, CHANNEL_NAME, + NotificationManager.IMPORTANCE_HIGH); + notificationChannel.enableLights(true); + notificationChannel.enableVibration(true); + notificationChannel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC); + getManager().createNotificationChannel(notificationChannel); + } + + private NotificationManager getManager() { + if (manager == null) { + manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + } + return manager; + } +} diff --git a/app/src/main/java/io/github/wulkanowy/services/SyncJob.java b/app/src/main/java/io/github/wulkanowy/services/SyncJob.java new file mode 100644 index 000000000..fad3025e6 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/services/SyncJob.java @@ -0,0 +1,114 @@ +package io.github.wulkanowy.services; + +import android.app.PendingIntent; +import android.content.Context; +import android.support.v4.app.NotificationCompat; + +import com.firebase.jobdispatcher.Constraint; +import com.firebase.jobdispatcher.FirebaseJobDispatcher; +import com.firebase.jobdispatcher.GooglePlayDriver; +import com.firebase.jobdispatcher.JobParameters; +import com.firebase.jobdispatcher.JobService; +import com.firebase.jobdispatcher.Lifetime; +import com.firebase.jobdispatcher.RetryStrategy; +import com.firebase.jobdispatcher.SimpleJobService; +import com.firebase.jobdispatcher.Trigger; + +import java.util.List; + +import javax.inject.Inject; + +import io.github.wulkanowy.R; +import io.github.wulkanowy.WulkanowyApp; +import io.github.wulkanowy.data.RepositoryContract; +import io.github.wulkanowy.data.db.dao.entities.Grade; +import io.github.wulkanowy.ui.main.MainActivity; +import io.github.wulkanowy.utils.LogUtils; + +public class SyncJob extends SimpleJobService { + + private static final int DEFAULT_INTERVAL_START = 60 * 50; + + private static final int DEFAULT_INTERVAL_END = DEFAULT_INTERVAL_START + (60 * 40); + + public static final String EXTRA_INTENT_KEY = "cardId"; + + private List gradeList; + + @Inject + RepositoryContract repository; + + public static void start(Context context) { + FirebaseJobDispatcher dispatcher = new FirebaseJobDispatcher(new GooglePlayDriver(context)); + + dispatcher.mustSchedule(dispatcher.newJobBuilder() + .setLifetime(Lifetime.FOREVER) + .setService(SyncJob.class) + .setTag("SyncJob") + .setRecurring(true) + .setTrigger(Trigger.executionWindow(DEFAULT_INTERVAL_START, DEFAULT_INTERVAL_END)) + .setConstraints(Constraint.ON_ANY_NETWORK) + .setReplaceCurrent(false) + .setRetryStrategy(RetryStrategy.DEFAULT_EXPONENTIAL) + .build()); + } + + @Override + public void onCreate() { + super.onCreate(); + ((WulkanowyApp) getApplication()).getApplicationComponent().inject(this); + } + + @Override + public int onRunJob(JobParameters job) { + try { + repository.loginCurrentUser(); + repository.syncAll(); + + gradeList = repository.getNewGrades(); + + if (!gradeList.isEmpty()) { + showNotification(); + } + return JobService.RESULT_SUCCESS; + } catch (Exception e) { + LogUtils.error("During background synchronization an error occurred", e); + return JobService.RESULT_FAIL_RETRY; + } + } + + private void showNotification() { + NotificationService service = new NotificationService(getApplicationContext()); + + service.notify(service.notificationBuilder() + .setContentTitle(getStringTitle()) + .setContentText(getStringContent()) + .setSmallIcon(R.drawable.ic_notification) + .setAutoCancel(true) + .setDefaults(NotificationCompat.DEFAULT_ALL) + .setPriority(NotificationCompat.PRIORITY_HIGH) + .setColor(getResources().getColor(R.color.colorPrimary)) + .setContentIntent(PendingIntent.getActivity(getApplicationContext(), 0, + MainActivity.getStartIntent(getApplicationContext()).putExtra(EXTRA_INTENT_KEY, 0) + , 0 + )) + .build()); + } + + private String getStringTitle() { + if (gradeList.size() == 1) { + return getResources().getQuantityString(R.plurals.newGradePlurals, 1); + } else { + return getResources().getQuantityString(R.plurals.newGradePlurals, 2); + } + } + + private String getStringContent() { + if (gradeList.size() == 1) { + return gradeList.get(0).getSubject(); + } else { + return getResources().getQuantityString(R.plurals.receivedNewGradePlurals, + gradeList.size(), gradeList.size()); + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/services/Updater.java b/app/src/main/java/io/github/wulkanowy/services/Updater.java deleted file mode 100644 index 5e4b90ac6..000000000 --- a/app/src/main/java/io/github/wulkanowy/services/Updater.java +++ /dev/null @@ -1,189 +0,0 @@ -package io.github.wulkanowy.services; - -import android.Manifest; -import android.app.Activity; -import android.app.DownloadManager; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.PackageManager; -import android.database.Cursor; -import android.net.Uri; -import android.os.Build; -import android.os.Environment; -import android.support.annotation.NonNull; -import android.support.design.widget.Snackbar; -import android.support.v4.app.ActivityCompat; -import android.support.v4.content.ContextCompat; -import android.support.v4.content.FileProvider; -import android.support.v7.app.AlertDialog; -import android.util.Log; - -import com.github.javiersantos.appupdater.AppUpdaterUtils; -import com.github.javiersantos.appupdater.enums.AppUpdaterError; -import com.github.javiersantos.appupdater.enums.UpdateFrom; -import com.github.javiersantos.appupdater.objects.Update; - -import java.io.File; - -import io.github.wulkanowy.BuildConfig; -import io.github.wulkanowy.R; - -public class Updater { - - private static final String DEBUG_TAG = "WulkanowyUpdater"; - - private static final int PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE = 0; - - private Activity activity; - - private Update update; - - private DownloadManager downloadManager; - - private BroadcastReceiver onComplete = new BroadcastReceiver() { - - @Override - public void onReceive(Context context, Intent intent) { - DownloadManager.Query query = new DownloadManager.Query(); - Cursor c = downloadManager.query(query); - if (c.moveToFirst()) { - String uriString = c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI)); - - if (uriString.substring(0, 7).matches("file://")) { - uriString = uriString.substring(7); - } - - File file = new File(uriString); - - Intent install; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - install = new Intent(Intent.ACTION_INSTALL_PACKAGE); - install.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - install.setData(FileProvider.getUriForFile(context, - BuildConfig.APPLICATION_ID + ".fileprovider", file)); - } else { - install = new Intent(Intent.ACTION_VIEW); - install.setDataAndType(Uri.parse("file://" + file.getAbsolutePath()), - "application/vnd.android.package-archive"); - } - - context.startActivity(install); - } - } - }; - - public Updater(Activity activity) { - this.activity = activity; - - downloadManager = (DownloadManager) activity.getSystemService(Context.DOWNLOAD_SERVICE); - activity.registerReceiver(onComplete, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)); - } - - private void downloadUpdate() { - if (ContextCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE) - == PackageManager.PERMISSION_GRANTED) { - startDownload(); - } else { - requestWriteStoragePermission(); - } - } - - private void requestWriteStoragePermission() { - ActivityCompat.requestPermissions(activity, - new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, - PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE); - } - - private void startDownload() { - Snackbar.make(activity.findViewById(R.id.fragment_container), "Downloading started.", Snackbar.LENGTH_SHORT).show(); - - String path = Environment.getExternalStorageDirectory().toString() + File.separator + - Environment.DIRECTORY_DOWNLOADS + File.separator + "wulkanowy"; - - File dir = new File(path); - if(!dir.mkdirs()) { - for (String aChildren : dir.list()) { - new File(dir, aChildren).delete(); - } - } - - DownloadManager.Request request = new DownloadManager.Request(Uri.parse(update.getUrlToDownload().toString())) - .setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI | DownloadManager.Request.NETWORK_MOBILE) - .setAllowedOverRoaming(false) - .setTitle("Wulkanowy v" + update.getLatestVersionCode()) - .setDescription(update.getLatestVersion()) - .setVisibleInDownloadsUi(true) - .setMimeType("application/vnd.android.package-archive") - .setDestinationUri(Uri.fromFile(new File(path + File.separator + update.getLatestVersion() + ".apk"))); - - downloadManager.enqueue(request); - } - - public void onRequestPermissionsResult(int requestCode, @NonNull int[] grantResults) { - if (requestCode == PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE) { - if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - downloadUpdate(); - } else { - Snackbar.make(activity.findViewById(R.id.fragment_container), - "Write storage permission request was denied.", - Snackbar.LENGTH_LONG) - .show(); - } - } - } - - public Updater checkForUpdates() { - AppUpdaterUtils appUpdaterUtils = new AppUpdaterUtils(activity) - .setUpdateFrom(UpdateFrom.JSON) - .setUpdateJSON(BuildConfig.UPDATE_URL) - .withListener(new AppUpdaterUtils.UpdateListener() { - - @Override - public void onSuccess(final Update currentUpdate, Boolean isUpdateAvailable) { - Log.d(DEBUG_TAG, "Latest Version: " + currentUpdate.getLatestVersion()); - Log.d(DEBUG_TAG, "Latest Version Code: " + currentUpdate.getLatestVersionCode().toString()); - Log.d(DEBUG_TAG, "URL: " + currentUpdate.getUrlToDownload().toString()); - Log.d(DEBUG_TAG, "Is update available?: " + Boolean.toString(isUpdateAvailable)); - - update = currentUpdate; - showDialog(isUpdateAvailable); - } - - @Override - public void onFailed(AppUpdaterError error) { - Log.e(DEBUG_TAG, "Something went wrong"); - Log.e(DEBUG_TAG, error.toString()); - } - }); - appUpdaterUtils.start(); - - return this; - } - - private void showDialog(boolean isUpdateAvailable) { - if (isUpdateAvailable) { - new AlertDialog.Builder(activity) - .setTitle("Update is available") - .setMessage("Update to version " + update.getLatestVersionCode().toString() + - " is available. Your version is " + BuildConfig.VERSION_CODE + ". Update?") - .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - downloadUpdate(); - } - }) - .setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - } - }) - .show(); - } - } - - public void onDestroy(Activity activity) { - activity.unregisterReceiver(onComplete); - } -} diff --git a/app/src/main/java/io/github/wulkanowy/services/jobs/FullSyncJob.java b/app/src/main/java/io/github/wulkanowy/services/jobs/FullSyncJob.java deleted file mode 100644 index 0d8dd3a8f..000000000 --- a/app/src/main/java/io/github/wulkanowy/services/jobs/FullSyncJob.java +++ /dev/null @@ -1,80 +0,0 @@ -package io.github.wulkanowy.services.jobs; - -import android.app.PendingIntent; -import android.content.Intent; -import android.support.v4.app.NotificationCompat; - -import com.firebase.jobdispatcher.Constraint; -import com.firebase.jobdispatcher.FirebaseJobDispatcher; -import com.firebase.jobdispatcher.Job; -import com.firebase.jobdispatcher.Lifetime; -import com.firebase.jobdispatcher.RetryStrategy; -import com.firebase.jobdispatcher.Trigger; - -import java.util.List; -import java.util.Random; - -import io.github.wulkanowy.R; -import io.github.wulkanowy.WulkanowyApp; -import io.github.wulkanowy.db.dao.DatabaseAccess; -import io.github.wulkanowy.db.dao.entities.DaoSession; -import io.github.wulkanowy.db.dao.entities.Grade; -import io.github.wulkanowy.services.notifications.NotificationBuilder; -import io.github.wulkanowy.services.sync.VulcanSync; -import io.github.wulkanowy.ui.main.DashboardActivity; - -public class FullSyncJob extends VulcanJobHelper { - - private static final String UNIQUE_TAG = "FullSync"; - - private static final int DEFAULT_INTERVAL_START = 60 * 50; - - private static final int DEFAULT_INTERVAL_END = DEFAULT_INTERVAL_START + (60 * 40); - - @Override - protected Job createJob(FirebaseJobDispatcher dispatcher) { - return dispatcher.newJobBuilder() - .setLifetime(Lifetime.FOREVER) - .setService(SyncService.class) - .setTag(UNIQUE_TAG) - .setRecurring(true) - .setTrigger(Trigger.executionWindow(DEFAULT_INTERVAL_START, DEFAULT_INTERVAL_END)) - .setConstraints(Constraint.ON_ANY_NETWORK) - .setReplaceCurrent(false) - .setRetryStrategy(RetryStrategy.DEFAULT_EXPONENTIAL) - .build(); - } - - public static class SyncService extends VulcanService { - - @Override - public void workToBePerformed() throws Exception { - DaoSession daoSession = ((WulkanowyApp) getApplication()).getDaoSession(); - - VulcanSync synchronization = new VulcanSync() - .loginCurrentUser(getApplicationContext(), daoSession); - synchronization.syncAll(); - List newGradeList = new DatabaseAccess().getNewGrades(daoSession); - - if (newGradeList.size() == 1) { - buildNotify(getResources().getQuantityString(R.plurals.newGradePlurals, 1), - newGradeList.get(0).getSubject()); - } else if (newGradeList.size() > 1) { - buildNotify(getResources().getQuantityString(R.plurals.newGradePlurals, 2), - getResources().getQuantityString(R.plurals.receivedNewGradePlurals, newGradeList.size(), newGradeList.size())); - } - } - - private void buildNotify(String title, String bodyText) { - Intent intent = new Intent(getApplicationContext(), DashboardActivity.class); - intent.putExtra("cardID", 1); - PendingIntent pendingIntent = PendingIntent - .getActivity(getApplicationContext(), 0, intent, 0); - - NotificationBuilder notificationBuilder = new NotificationBuilder(getApplicationContext()); - NotificationCompat.Builder builder = notificationBuilder - .getNotifications(title, bodyText, pendingIntent); - notificationBuilder.getManager().notify(new Random().nextInt(10000), builder.build()); - } - } -} \ No newline at end of file diff --git a/app/src/main/java/io/github/wulkanowy/services/jobs/VulcanJobHelper.java b/app/src/main/java/io/github/wulkanowy/services/jobs/VulcanJobHelper.java deleted file mode 100644 index f435c2e14..000000000 --- a/app/src/main/java/io/github/wulkanowy/services/jobs/VulcanJobHelper.java +++ /dev/null @@ -1,22 +0,0 @@ -package io.github.wulkanowy.services.jobs; - - -import android.content.Context; -import android.util.Log; - -import com.firebase.jobdispatcher.FirebaseJobDispatcher; -import com.firebase.jobdispatcher.GooglePlayDriver; -import com.firebase.jobdispatcher.Job; - -public abstract class VulcanJobHelper { - - public static final String DEBUG_TAG = "SynchronizationService"; - - public final void scheduledJob(Context context) { - FirebaseJobDispatcher dispatcher = new FirebaseJobDispatcher(new GooglePlayDriver(context)); - dispatcher.mustSchedule(createJob(dispatcher)); - Log.i(DEBUG_TAG, "Wulkanowy Job is initiation: " + this.toString()); - } - - protected abstract Job createJob(FirebaseJobDispatcher dispatcher); -} diff --git a/app/src/main/java/io/github/wulkanowy/services/jobs/VulcanService.java b/app/src/main/java/io/github/wulkanowy/services/jobs/VulcanService.java deleted file mode 100644 index 56c93e549..000000000 --- a/app/src/main/java/io/github/wulkanowy/services/jobs/VulcanService.java +++ /dev/null @@ -1,57 +0,0 @@ -package io.github.wulkanowy.services.jobs; - -import android.os.AsyncTask; -import android.util.Log; - -import com.firebase.jobdispatcher.JobParameters; -import com.firebase.jobdispatcher.JobService; - -import java.lang.ref.WeakReference; - -public abstract class VulcanService extends JobService { - - private SyncTask syncTask; - - @Override - public boolean onStartJob(JobParameters params) { - Log.d(VulcanJobHelper.DEBUG_TAG, "Wulkanowy services start"); - syncTask = new SyncTask(this, params); - syncTask.execute(); - return true; - } - - @Override - public boolean onStopJob(JobParameters params) { - Log.e(VulcanJobHelper.DEBUG_TAG, "Wulkanowy services stop"); - if (syncTask != null) { - syncTask.cancel(true); - } - return true; - } - - public abstract void workToBePerformed() throws Exception; - - private static class SyncTask extends AsyncTask { - - private JobParameters jobParameters; - - private WeakReference vulcanService; - - public SyncTask(VulcanService vulcanService, JobParameters jobParameters) { - this.jobParameters = jobParameters; - this.vulcanService = new WeakReference<>(vulcanService); - } - - @Override - protected Void doInBackground(Void... voids) { - try { - vulcanService.get().workToBePerformed(); - } catch (Exception e) { - Log.e(VulcanJobHelper.DEBUG_TAG, "User logging in the background failed", e); - } finally { - vulcanService.get().jobFinished(jobParameters, false); - } - return null; - } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/services/notifications/NotificationBuilder.java b/app/src/main/java/io/github/wulkanowy/services/notifications/NotificationBuilder.java deleted file mode 100644 index 74333f7ed..000000000 --- a/app/src/main/java/io/github/wulkanowy/services/notifications/NotificationBuilder.java +++ /dev/null @@ -1,60 +0,0 @@ -package io.github.wulkanowy.services.notifications; - - -import android.annotation.TargetApi; -import android.app.Notification; -import android.app.NotificationChannel; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.content.Context; -import android.content.ContextWrapper; -import android.os.Build; -import android.support.v4.app.NotificationCompat; - -import io.github.wulkanowy.R; - -public class NotificationBuilder extends ContextWrapper { - - public static final String CHANNEL_ID = "Wulkanowy_New_Grade_Channel"; - - public static final String CHANNEL_NAME = "New Grade Channel"; - - private NotificationManager manager; - - public NotificationBuilder(Context context) { - super(context); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - createChannel(); - } - } - - @TargetApi(26) - private void createChannel() { - NotificationChannel notificationChannel = new NotificationChannel(CHANNEL_ID, CHANNEL_NAME, - NotificationManager.IMPORTANCE_HIGH); - notificationChannel.enableLights(true); - notificationChannel.enableVibration(true); - notificationChannel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC); - getManager().createNotificationChannel(notificationChannel); - } - - public NotificationCompat.Builder getNotifications(String title, String bodyText, PendingIntent pendingIntent) { - return new NotificationCompat.Builder(getApplicationContext(), CHANNEL_ID) - .setContentTitle(title) - .setContentText(bodyText) - .setSmallIcon(R.drawable.ic_notification) - .setAutoCancel(true) - .setContentIntent(pendingIntent) - .setChannelId(CHANNEL_ID) - .setDefaults(NotificationCompat.DEFAULT_ALL) - .setPriority(NotificationCompat.PRIORITY_HIGH) - .setColor(getResources().getColor(R.color.colorPrimary)); - } - - public NotificationManager getManager() { - if (manager == null) { - manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); - } - return manager; - } -} diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/CurrentAccountLogin.java b/app/src/main/java/io/github/wulkanowy/services/sync/CurrentAccountLogin.java deleted file mode 100644 index 30ec717c2..000000000 --- a/app/src/main/java/io/github/wulkanowy/services/sync/CurrentAccountLogin.java +++ /dev/null @@ -1,64 +0,0 @@ -package io.github.wulkanowy.services.sync; - -import android.content.Context; -import android.util.Log; - -import java.io.IOException; - -import io.github.wulkanowy.api.Vulcan; -import io.github.wulkanowy.api.login.AccountPermissionException; -import io.github.wulkanowy.api.login.BadCredentialsException; -import io.github.wulkanowy.api.login.LoginErrorException; -import io.github.wulkanowy.api.login.VulcanOfflineException; -import io.github.wulkanowy.db.dao.entities.Account; -import io.github.wulkanowy.db.dao.entities.AccountDao; -import io.github.wulkanowy.db.dao.entities.DaoSession; -import io.github.wulkanowy.services.jobs.VulcanJobHelper; -import io.github.wulkanowy.utils.security.CryptoException; -import io.github.wulkanowy.utils.security.Safety; - -public class CurrentAccountLogin { - - private final Context context; - - private final DaoSession daoSession; - - private final Vulcan vulcan; - - public CurrentAccountLogin(Context context, DaoSession daoSession, Vulcan vulcan) { - this.context = context; - this.daoSession = daoSession; - this.vulcan = vulcan; - } - - public LoginSession loginCurrentUser() throws CryptoException, - BadCredentialsException, AccountPermissionException, IOException, LoginErrorException, VulcanOfflineException { - - AccountDao accountDao = daoSession.getAccountDao(); - - long userId = context.getSharedPreferences("LoginData", Context.MODE_PRIVATE).getLong("userId", 0); - - if (userId != 0) { - - Log.d(VulcanJobHelper.DEBUG_TAG, "Login current user id=" + String.valueOf(userId)); - - Safety safety = new Safety(); - Account account = accountDao.load(userId); - - vulcan.login( - account.getEmail(), - safety.decrypt(account.getEmail(), account.getPassword()), - account.getSymbol(), - account.getSnpId() - ); - - return new LoginSession() - .setDaoSession(daoSession) - .setUserId(userId) - .setVulcan(vulcan); - } else { - Log.wtf(VulcanJobHelper.DEBUG_TAG, "loginCurrentUser - USERID IS EMPTY"); - throw new IOException("Can't find user with index 0"); - } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/FirstAccountLogin.java b/app/src/main/java/io/github/wulkanowy/services/sync/FirstAccountLogin.java deleted file mode 100644 index bd0571d82..000000000 --- a/app/src/main/java/io/github/wulkanowy/services/sync/FirstAccountLogin.java +++ /dev/null @@ -1,59 +0,0 @@ -package io.github.wulkanowy.services.sync; - -import android.content.Context; -import android.content.SharedPreferences; - -import java.io.IOException; - -import io.github.wulkanowy.api.Vulcan; -import io.github.wulkanowy.api.login.AccountPermissionException; -import io.github.wulkanowy.api.login.BadCredentialsException; -import io.github.wulkanowy.api.login.NotLoggedInErrorException; -import io.github.wulkanowy.api.login.VulcanOfflineException; -import io.github.wulkanowy.db.dao.entities.Account; -import io.github.wulkanowy.db.dao.entities.AccountDao; -import io.github.wulkanowy.db.dao.entities.DaoSession; -import io.github.wulkanowy.utils.security.CryptoException; -import io.github.wulkanowy.utils.security.Safety; - -public class FirstAccountLogin { - - private final Context context; - - private final DaoSession daoSession; - - private final Vulcan vulcan; - - public FirstAccountLogin(Context context, DaoSession daoSession, Vulcan vulcan) { - this.context = context; - this.daoSession = daoSession; - this.vulcan = vulcan; - } - - public LoginSession login(String email, String password, String symbol) - throws NotLoggedInErrorException, AccountPermissionException, IOException, CryptoException, VulcanOfflineException, BadCredentialsException { - - String realSymbol = vulcan.login(email, password, symbol); - - AccountDao accountDao = daoSession.getAccountDao(); - Safety safety = new Safety(); - Account account = new Account() - .setName(vulcan.getBasicInformation().getPersonalData().getFirstAndLastName()) - .setEmail(email) - .setPassword(safety.encrypt(email, password, context)) - .setSymbol(realSymbol) - .setSnpId(vulcan.getStudentAndParent().getId()); - - long userId = accountDao.insert(account); - - SharedPreferences sharedPreferences = context.getSharedPreferences("LoginData", Context.MODE_PRIVATE); - SharedPreferences.Editor editor = sharedPreferences.edit(); - editor.putLong("userId", userId); - editor.apply(); - - return new LoginSession() - .setVulcan(vulcan) - .setUserId(userId) - .setDaoSession(daoSession); - } -} diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/GradesSync.java b/app/src/main/java/io/github/wulkanowy/services/sync/GradesSync.java deleted file mode 100644 index 78069b9da..000000000 --- a/app/src/main/java/io/github/wulkanowy/services/sync/GradesSync.java +++ /dev/null @@ -1,64 +0,0 @@ -package io.github.wulkanowy.services.sync; - -import android.util.Log; - -import org.greenrobot.greendao.query.Query; - -import java.io.IOException; -import java.text.ParseException; -import java.util.ArrayList; -import java.util.List; - -import io.github.wulkanowy.api.grades.GradesList; -import io.github.wulkanowy.api.login.NotLoggedInErrorException; -import io.github.wulkanowy.db.dao.entities.Account; -import io.github.wulkanowy.db.dao.entities.AccountDao; -import io.github.wulkanowy.db.dao.entities.Grade; -import io.github.wulkanowy.db.dao.entities.GradeDao; -import io.github.wulkanowy.db.dao.entities.Subject; -import io.github.wulkanowy.db.dao.entities.SubjectDao; -import io.github.wulkanowy.services.jobs.VulcanJobHelper; -import io.github.wulkanowy.utils.DataObjectConverter; -import io.github.wulkanowy.utils.EntitiesCompare; - -public class GradesSync { - - public void sync(LoginSession loginSession) throws IOException, - ParseException, NotLoggedInErrorException { - - GradesList gradesList = loginSession.getVulcan().getGradesList(); - - GradeDao gradeDao = loginSession.getDaoSession().getGradeDao(); - AccountDao accountDao = loginSession.getDaoSession().getAccountDao(); - SubjectDao subjectDao = loginSession.getDaoSession().getSubjectDao(); - - Account account = accountDao.load(loginSession.getUserId()); - - account.resetGradeList(); - account.resetSubjectList(); - - List gradesFromDb = account.getGradeList(); - List gradeEntitiesList = DataObjectConverter.gradesToGradeEntities(gradesList.getAll()); - List updatedList = EntitiesCompare.compareGradeList(gradeEntitiesList, gradesFromDb); - List lastList = new ArrayList<>(); - - GradeDao.dropTable(gradeDao.getDatabase(), true); - GradeDao.createTable(gradeDao.getDatabase(), false); - - for (Grade grade : updatedList) { - - Query subjectQuery = subjectDao.queryBuilder() - .where(SubjectDao.Properties.Name.eq(grade.getSubject())) - .build(); - - grade.setUserId(loginSession.getUserId()); - grade.setSubjectId((subjectQuery.uniqueOrThrow()).getId()); - - lastList.add(grade); - } - - gradeDao.insertInTx(lastList); - - Log.d(VulcanJobHelper.DEBUG_TAG, "Synchronization grades (amount = " + String.valueOf(lastList.size() + ")")); - } -} diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/LoginSession.java b/app/src/main/java/io/github/wulkanowy/services/sync/LoginSession.java deleted file mode 100644 index 1d3e91ba7..000000000 --- a/app/src/main/java/io/github/wulkanowy/services/sync/LoginSession.java +++ /dev/null @@ -1,69 +0,0 @@ -package io.github.wulkanowy.services.sync; - - -import org.apache.commons.lang3.builder.EqualsBuilder; -import org.apache.commons.lang3.builder.HashCodeBuilder; - -import io.github.wulkanowy.api.Vulcan; -import io.github.wulkanowy.db.dao.entities.DaoSession; - - -public class LoginSession { - - private Long userId; - - private Vulcan vulcan; - - private DaoSession daoSession; - - public Long getUserId() { - return userId; - } - - public LoginSession setUserId(Long userId) { - this.userId = userId; - return this; - } - - public Vulcan getVulcan() { - return vulcan; - } - - public LoginSession setVulcan(Vulcan vulcan) { - this.vulcan = vulcan; - return this; - } - - public DaoSession getDaoSession() { - return daoSession; - } - - public LoginSession setDaoSession(DaoSession daoSession) { - this.daoSession = daoSession; - return this; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - - if (o == null || getClass() != o.getClass()) return false; - - LoginSession that = (LoginSession) o; - - return new EqualsBuilder() - .append(userId, that.userId) - .append(vulcan, that.vulcan) - .append(daoSession, that.daoSession) - .isEquals(); - } - - @Override - public int hashCode() { - return new HashCodeBuilder(17, 37) - .append(userId) - .append(vulcan) - .append(daoSession) - .toHashCode(); - } -} diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/SubjectsSync.java b/app/src/main/java/io/github/wulkanowy/services/sync/SubjectsSync.java deleted file mode 100644 index 065e8106e..000000000 --- a/app/src/main/java/io/github/wulkanowy/services/sync/SubjectsSync.java +++ /dev/null @@ -1,39 +0,0 @@ -package io.github.wulkanowy.services.sync; - -import android.util.Log; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -import io.github.wulkanowy.api.grades.SubjectsList; -import io.github.wulkanowy.api.login.NotLoggedInErrorException; -import io.github.wulkanowy.db.dao.entities.Subject; -import io.github.wulkanowy.db.dao.entities.SubjectDao; -import io.github.wulkanowy.services.jobs.VulcanJobHelper; -import io.github.wulkanowy.utils.DataObjectConverter; - -public class SubjectsSync { - - public void sync(LoginSession loginSession) throws IOException, - NotLoggedInErrorException { - - SubjectsList subjectsList = loginSession.getVulcan().getSubjectsList(); - SubjectDao subjectDao = loginSession.getDaoSession().getSubjectDao(); - - List subjectEntitiesList = DataObjectConverter.subjectsToSubjectEntities(subjectsList.getAll()); - List preparedList = new ArrayList<>(); - - for (Subject subject : subjectEntitiesList) { - subject.setUserId(loginSession.getUserId()); - preparedList.add(subject); - } - - SubjectDao.dropTable(subjectDao.getDatabase(), true); - SubjectDao.createTable(subjectDao.getDatabase(), false); - subjectDao.insertInTx(preparedList); - - - Log.d(VulcanJobHelper.DEBUG_TAG, "Synchronization subjects (amount = " + String.valueOf(subjectEntitiesList.size() + ")")); - } -} diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/TimetableSync.java b/app/src/main/java/io/github/wulkanowy/services/sync/TimetableSync.java deleted file mode 100644 index 0fd13723a..000000000 --- a/app/src/main/java/io/github/wulkanowy/services/sync/TimetableSync.java +++ /dev/null @@ -1,121 +0,0 @@ -package io.github.wulkanowy.services.sync; - - -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.util.Log; - -import org.greenrobot.greendao.query.Query; - -import java.io.IOException; -import java.text.ParseException; -import java.util.ArrayList; -import java.util.List; - -import io.github.wulkanowy.api.login.NotLoggedInErrorException; -import io.github.wulkanowy.api.timetable.Day; -import io.github.wulkanowy.api.timetable.Week; -import io.github.wulkanowy.db.dao.entities.DayDao; -import io.github.wulkanowy.db.dao.entities.Lesson; -import io.github.wulkanowy.db.dao.entities.LessonDao; -import io.github.wulkanowy.db.dao.entities.WeekDao; -import io.github.wulkanowy.services.jobs.VulcanJobHelper; -import io.github.wulkanowy.utils.DataObjectConverter; -import io.github.wulkanowy.utils.TimeUtils; - -public class TimetableSync { - - public void sync(@NonNull LoginSession loginSession, @Nullable String dateOfMonday) throws NotLoggedInErrorException, - IOException, ParseException { - DayDao dayDao = loginSession.getDaoSession().getDayDao(); - LessonDao lessonDao = loginSession.getDaoSession().getLessonDao(); - WeekDao weekDao = loginSession.getDaoSession().getWeekDao(); - - Long weekId; - - Week week = dateOfMonday == null ? loginSession.getVulcan().getTimetable().getWeekTable() - : loginSession.getVulcan().getTimetable() - .getWeekTable(String.valueOf(TimeUtils.getNetTicks(dateOfMonday, "yyyy-MM-dd"))); - - Query weekQuery = weekDao.queryBuilder() - .where(WeekDao.Properties.UserId.eq(loginSession.getUserId()), WeekDao.Properties.StartDayDate.eq(week.getStartDayDate())).build(); - - io.github.wulkanowy.db.dao.entities.Week week1 = weekQuery.unique(); - - if (week1 != null) { - weekId = week1.getId(); - } else { - weekId = weekDao.insert(DataObjectConverter.weekToWeekEntitie(week).setUserId(loginSession.getUserId())); - } - - List dayList = week.getDays(); - - dayDao.saveInTx(getPreparedDaysList(dayList, loginSession.getUserId(), weekId, dayDao)); - - Log.d(VulcanJobHelper.DEBUG_TAG, "Synchronization days (amount = " + dayList.size() + ")"); - - List lessonList = new ArrayList<>(); - lessonList.addAll(getPreparedLessonsList(dayList, dayDao, lessonDao, loginSession.getUserId(), weekId)); - - lessonDao.saveInTx(lessonList); - - Log.d(VulcanJobHelper.DEBUG_TAG, "Synchronization lessons (amount = " + lessonList.size() + ")"); - } - - private List getPreparedLessonsList(List dayList, DayDao dayDao, LessonDao lessonDao, long userId, long weekId) { - List allLessonsList = new ArrayList<>(); - - for (Day day : dayList) { - - Query dayQuery = dayDao.queryBuilder() - .where(DayDao.Properties.Date.eq(day.getDate()), - DayDao.Properties.UserId.eq(userId), - DayDao.Properties.WeekId.eq(weekId)) - .build(); - - List lessonEntityList = DataObjectConverter.lessonsToLessonsEntities(day.getLessons()); - List updatedLessonEntityList = new ArrayList<>(); - - for (Lesson lesson : lessonEntityList) { - Lesson lesson1 = lessonDao.queryBuilder() - .where(LessonDao.Properties.DayId.eq(dayQuery.uniqueOrThrow().getId()), - LessonDao.Properties.Date.eq(lesson.getDate()), - LessonDao.Properties.StartTime.eq(lesson.getStartTime()), - LessonDao.Properties.EndTime.eq(lesson.getEndTime())) - .unique(); - - if (lesson1 != null) { - lesson.setId(lesson1.getId()); - } - - lesson.setDayId(dayQuery.uniqueOrThrow().getId()); - if (!"".equals(lesson.getSubject())) { - updatedLessonEntityList.add(lesson); - } - } - allLessonsList.addAll(updatedLessonEntityList); - } - return allLessonsList; - } - - private List getPreparedDaysList(List dayList, long userId, long weekId, DayDao dayDao) { - List updatedDayList = new ArrayList<>(); - List dayEntityList = DataObjectConverter - .daysToDaysEntities(dayList); - for (io.github.wulkanowy.db.dao.entities.Day day : dayEntityList) { - - Query dayQuery = dayDao.queryBuilder().where(DayDao.Properties.UserId.eq(userId), DayDao.Properties.WeekId.eq(weekId), DayDao.Properties.Date.eq(day.getDate())).build(); - - io.github.wulkanowy.db.dao.entities.Day day1 = dayQuery.unique(); - - if (day1 != null) { - day.setId(day1.getId()); - } - - day.setUserId(userId); - day.setWeekId(weekId); - updatedDayList.add(day); - } - return updatedDayList; - } -} diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/VulcanSync.java b/app/src/main/java/io/github/wulkanowy/services/sync/VulcanSync.java deleted file mode 100644 index ee9ee15df..000000000 --- a/app/src/main/java/io/github/wulkanowy/services/sync/VulcanSync.java +++ /dev/null @@ -1,105 +0,0 @@ -package io.github.wulkanowy.services.sync; - -import android.content.Context; -import android.support.annotation.Nullable; -import android.util.Log; - -import java.io.IOException; - -import io.github.wulkanowy.api.Vulcan; -import io.github.wulkanowy.api.login.AccountPermissionException; -import io.github.wulkanowy.api.login.BadCredentialsException; -import io.github.wulkanowy.api.login.LoginErrorException; -import io.github.wulkanowy.api.login.NotLoggedInErrorException; -import io.github.wulkanowy.api.login.VulcanOfflineException; -import io.github.wulkanowy.db.dao.entities.DaoSession; -import io.github.wulkanowy.services.jobs.VulcanJobHelper; -import io.github.wulkanowy.utils.security.CryptoException; - -public class VulcanSync { - - private LoginSession loginSession; - - public VulcanSync(LoginSession loginSession) { - this.loginSession = loginSession; - } - - public VulcanSync() { - this.loginSession = new LoginSession(); - } - - public void firstLoginSignInStep(Context context, DaoSession daoSession, String email, String password, String symbol) - throws NotLoggedInErrorException, AccountPermissionException, IOException, CryptoException, VulcanOfflineException, BadCredentialsException { - FirstAccountLogin firstAccountLogin = new FirstAccountLogin(context, daoSession, new Vulcan()); - loginSession = firstAccountLogin.login(email, password, symbol); - } - - public VulcanSync loginCurrentUser(Context context, DaoSession daoSession) throws CryptoException, - BadCredentialsException, AccountPermissionException, LoginErrorException, IOException, VulcanOfflineException { - return loginCurrentUser(context, daoSession, new Vulcan()); - } - - public VulcanSync loginCurrentUser(Context context, DaoSession daoSession, Vulcan vulcan) - throws CryptoException, BadCredentialsException, AccountPermissionException, - LoginErrorException, IOException, VulcanOfflineException { - - CurrentAccountLogin currentAccountLogin = new CurrentAccountLogin(context, daoSession, vulcan); - loginSession = currentAccountLogin.loginCurrentUser(); - return this; - } - - public void syncAll() throws IOException { - syncSubjectsAndGrades(); - syncTimetable(); - } - - public void syncGrades() throws IOException { - if (loginSession != null) { - GradesSync gradesSync = new GradesSync(); - try { - gradesSync.sync(loginSession); - } catch (Exception e) { - Log.e(VulcanJobHelper.DEBUG_TAG, "Synchronisation of grades failed", e); - throw new IOException(e.getCause()); - } - } else { - Log.e(VulcanJobHelper.DEBUG_TAG, "Before sync, should login user to log", - new UnsupportedOperationException()); - } - } - - public void syncSubjectsAndGrades() throws IOException { - if (loginSession != null) { - SubjectsSync subjectsSync = new SubjectsSync(); - try { - subjectsSync.sync(loginSession); - syncGrades(); - } catch (Exception e) { - Log.e(VulcanJobHelper.DEBUG_TAG, "Synchronisation of subjects failed", e); - throw new IOException(e.getCause()); - } - } else { - Log.e(VulcanJobHelper.DEBUG_TAG, "Before sync, should login user to log", - new UnsupportedOperationException()); - } - } - - public void syncTimetable() throws IOException { - syncTimetable(null); - } - - public void syncTimetable(@Nullable String date) throws IOException { - if (loginSession != null) { - TimetableSync timetableSync = new TimetableSync(); - try { - timetableSync.sync(loginSession, date); - } catch (Exception e) { - Log.e(VulcanJobHelper.DEBUG_TAG, "Synchronization of timetable failed", e); - throw new IOException(e.getCause()); - } - } else { - Log.e(VulcanJobHelper.DEBUG_TAG, "Before sync, should login user to log", - new UnsupportedOperationException()); - } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BaseActivity.java b/app/src/main/java/io/github/wulkanowy/ui/base/BaseActivity.java new file mode 100644 index 000000000..3fd3dc7af --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/base/BaseActivity.java @@ -0,0 +1,67 @@ +package io.github.wulkanowy.ui.base; + +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v7.app.AppCompatActivity; +import android.widget.Toast; + +import butterknife.Unbinder; +import io.github.wulkanowy.R; +import io.github.wulkanowy.WulkanowyApp; +import io.github.wulkanowy.di.component.ActivityComponent; +import io.github.wulkanowy.di.component.DaggerActivityComponent; +import io.github.wulkanowy.di.modules.ActivityModule; +import io.github.wulkanowy.utils.NetworkUtils; + +public abstract class BaseActivity extends AppCompatActivity implements BaseContract.View { + + private ActivityComponent activityComponent; + + private Unbinder unbinder; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + activityComponent = DaggerActivityComponent.builder() + .activityModule(new ActivityModule(this)) + .applicationComponent(((WulkanowyApp) getApplication()).getApplicationComponent()) + .build(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + if (unbinder != null) { + unbinder.unbind(); + } + } + + @Override + public void onError(int resId) { + onError(getString(resId)); + } + + @Override + public void onError(String message) { + Toast.makeText(this, message, Toast.LENGTH_SHORT).show(); + } + + @Override + public void onNoNetworkError() { + onError(R.string.noInternet_text); + } + + @Override + public boolean isNetworkConnected() { + return NetworkUtils.isOnline(getApplicationContext()); + } + + public ActivityComponent getActivityComponent() { + return activityComponent; + } + + public void setButterKnife(Unbinder unbinder) { + this.unbinder = unbinder; + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BaseContract.java b/app/src/main/java/io/github/wulkanowy/ui/base/BaseContract.java new file mode 100644 index 000000000..2a4dc5696 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/base/BaseContract.java @@ -0,0 +1,27 @@ +package io.github.wulkanowy.ui.base; + +import android.support.annotation.StringRes; + +import io.github.wulkanowy.di.annotations.PerActivity; + +public interface BaseContract { + + interface View { + + void onError(@StringRes int resId); + + void onError(String message); + + void onNoNetworkError(); + + boolean isNetworkConnected(); + } + + @PerActivity + interface Presenter { + + void onStart(V view); + + void onDestroy(); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BaseFragment.java b/app/src/main/java/io/github/wulkanowy/ui/base/BaseFragment.java new file mode 100644 index 000000000..efb9d61a9 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/base/BaseFragment.java @@ -0,0 +1,104 @@ +package io.github.wulkanowy.ui.base; + +import android.content.Context; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.view.View; + +import butterknife.Unbinder; +import io.github.wulkanowy.R; +import io.github.wulkanowy.WulkanowyApp; +import io.github.wulkanowy.di.component.DaggerFragmentComponent; +import io.github.wulkanowy.di.component.FragmentComponent; +import io.github.wulkanowy.di.modules.FragmentModule; + +public abstract class BaseFragment extends Fragment implements BaseContract.View { + + private BaseActivity activity; + + private Unbinder unbinder; + + private FragmentComponent fragmentComponent; + + @Override + public void onAttach(Context context) { + super.onAttach(context); + if (context instanceof BaseActivity) { + activity = (BaseActivity) context; + } + + fragmentComponent = DaggerFragmentComponent.builder() + .fragmentModule(new FragmentModule(this)) + .applicationComponent(((WulkanowyApp) activity.getApplication()).getApplicationComponent()) + .build(); + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setHasOptionsMenu(false); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + setUpOnViewCreated(view); + } + + @Override + public void onDetach() { + activity = null; + super.onDetach(); + } + + @Override + public void onDestroyView() { + if (unbinder != null) { + unbinder.unbind(); + } + super.onDestroyView(); + } + + @Override + public void onError(int resId) { + onError(getString(resId)); + } + + @Override + public void onError(String message) { + if (activity != null) { + activity.onError(message); + } + } + + @Override + public void onNoNetworkError() { + onError(R.string.noInternet_text); + } + + @Override + public boolean isNetworkConnected() { + return activity != null && activity.isNetworkConnected(); + } + + public void setButterKnife(Unbinder unbinder) { + this.unbinder = unbinder; + } + + public void setTitle(String title) { + if (activity != null) { + activity.setTitle(title); + } + } + + public FragmentComponent getFragmentComponent() { + return fragmentComponent; + } + + + protected void setUpOnViewCreated(View fragmentView) { + // do something on view created + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BasePresenter.java b/app/src/main/java/io/github/wulkanowy/ui/base/BasePresenter.java new file mode 100644 index 000000000..dfb60b6fb --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/base/BasePresenter.java @@ -0,0 +1,35 @@ +package io.github.wulkanowy.ui.base; + +import javax.inject.Inject; + +import io.github.wulkanowy.data.RepositoryContract; + +public class BasePresenter implements BaseContract.Presenter { + + private final RepositoryContract repository; + + private V view; + + @Inject + public BasePresenter(RepositoryContract repository) { + this.repository = repository; + } + + @Override + public void onStart(V view) { + this.view = view; + } + + @Override + public void onDestroy() { + view = null; + } + + public final RepositoryContract getRepository() { + return repository; + } + + public V getView() { + return view; + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/login/LoginActivity.java b/app/src/main/java/io/github/wulkanowy/ui/login/LoginActivity.java index 713f71a51..26d1c146a 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/login/LoginActivity.java +++ b/app/src/main/java/io/github/wulkanowy/ui/login/LoginActivity.java @@ -1,226 +1,223 @@ package io.github.wulkanowy.ui.login; -import android.app.Activity; -import android.net.Uri; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.content.Context; +import android.content.Intent; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.customtabs.CustomTabsIntent; -import android.text.TextUtils; -import android.view.KeyEvent; -import android.view.MotionEvent; +import android.support.design.widget.Snackbar; +import android.support.design.widget.TextInputLayout; import android.view.View; -import android.view.View.OnClickListener; import android.view.inputmethod.EditorInfo; import android.widget.ArrayAdapter; import android.widget.AutoCompleteTextView; -import android.widget.Button; import android.widget.EditText; import android.widget.TextView; -import java.util.LinkedHashMap; +import javax.inject.Inject; +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +import butterknife.OnEditorAction; import io.github.wulkanowy.R; -import io.github.wulkanowy.services.Updater; +import io.github.wulkanowy.ui.base.BaseActivity; +import io.github.wulkanowy.ui.main.MainActivity; +import io.github.wulkanowy.utils.AppConstant; +import io.github.wulkanowy.utils.CommonUtils; import io.github.wulkanowy.utils.KeyboardUtils; -/** - * A login screen that offers login via email/password. - */ -public class LoginActivity extends Activity { +public class LoginActivity extends BaseActivity implements LoginContract.View { - private float touchPosition; + @BindView(R.id.login_activity_email_edit) + EditText emailView; - private EditText emailView; + @BindView(R.id.login_activity_pass_edit) + EditText passwordView; - private EditText passwordView; + @BindView(R.id.login_activity_symbol_edit) + AutoCompleteTextView symbolView; - private AutoCompleteTextView symbolView; + @BindView(R.id.login_activity_form_scroll) + View loginFormView; - private Updater updater; + @BindView(R.id.login_activity_progress_container) + View loadingBarView; + + @BindView(R.id.login_activity_progress_text) + TextView loginProgressText; + + @BindView(R.id.login_activity_symbol_text_input) + TextInputLayout symbolLayout; + + @Inject + LoginContract.Presenter presenter; + + private EditText requestedView; + + public static Intent getStartIntent(Context context) { + return new Intent(context, LoginActivity.class); + } @Override - protected void onCreate(Bundle savedInstanceState) { + public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_login); - updater = new Updater(this).checkForUpdates(); + setButterKnife(ButterKnife.bind(this)); + getActivityComponent().inject(this); - // Set up the login form. - emailView = findViewById(R.id.email); - passwordView = findViewById(R.id.password); - symbolView = findViewById(R.id.symbol); + presenter.onStart(this); - passwordView.setOnEditorActionListener(getTextViewSignInListener()); - symbolView.setOnEditorActionListener(getTextViewSignInListener()); + setUpOnCreate(); - populateAutoComplete(); + } - Button signInButton = findViewById(R.id.action_sign_in); - signInButton.setOnClickListener(new OnClickListener() { + protected void setUpOnCreate() { + symbolView.setAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, + getResources().getStringArray(R.array.symbols))); + } + + @OnClick(R.id.login_activity_sign_button) + void onLoginButtonClick() { + presenter.attemptLogin( + emailView.getText().toString(), + passwordView.getText().toString(), + symbolView.getText().toString()); + } + + @OnEditorAction(value = {R.id.login_activity_symbol_edit, R.id.login_activity_pass_edit}) + boolean onEditorAction(int id) { + if (id == EditorInfo.IME_ACTION_DONE || id == EditorInfo.IME_NULL) { + onLoginButtonClick(); + return true; + } + return false; + } + + @OnClick(R.id.login_activity_create_text) + void onCreateAccountButtonClick() { + CommonUtils.openInternalBrowserViewer(getApplicationContext(), + AppConstant.VULCAN_CREATE_ACCOUNT_URL); + } + + @OnClick(R.id.login_activity_forgot_text) + void onForgotPasswordButtonClick() { + CommonUtils.openInternalBrowserViewer(getApplicationContext(), + AppConstant.VULCAN_FORGOT_PASS_URL); + } + + @Override + public void setErrorEmailRequired() { + emailView.requestFocus(); + emailView.setError(getString(R.string.error_field_required)); + requestedView = emailView; + } + + @Override + public void setErrorEmailInvalid() { + emailView.requestFocus(); + emailView.setError(getString(R.string.error_invalid_email)); + requestedView = emailView; + } + + @Override + public void setErrorPassRequired() { + passwordView.requestFocus(); + passwordView.setError(getString(R.string.error_field_required)); + requestedView = passwordView; + } + + @Override + public void setErrorPassInvalid() { + passwordView.requestFocus(); + passwordView.setError(getString(R.string.error_invalid_password)); + requestedView = passwordView; + } + + @Override + public void setErrorPassIncorrect() { + passwordView.requestFocus(); + passwordView.setError(getString(R.string.error_incorrect_password)); + requestedView = passwordView; + } + + @Override + public void setErrorSymbolRequired() { + symbolLayout.setVisibility(View.VISIBLE); + symbolView.setError(getString(R.string.error_bad_account_permission)); + symbolView.requestFocus(); + requestedView = symbolView; + } + + @Override + public void resetViewErrors() { + emailView.setError(null); + passwordView.setError(null); + } + + @Override + public void showSoftInput() { + KeyboardUtils.showSoftInput(requestedView, this); + } + + @Override + public void hideSoftInput() { + KeyboardUtils.hideSoftInput(this); + } + + @Override + public void onError(String message) { + Snackbar.make(findViewById(R.id.login_activity_container), message, + Snackbar.LENGTH_LONG).show(); + } + + @Override + public void setStepOneLoginProgress() { + onLoginProgressUpdate("1", getString(R.string.step_login)); + } + + @Override + public void setStepTwoLoginProgress() { + onLoginProgressUpdate("2", getString(R.string.step_synchronization)); + } + + @Override + public void openMainActivity() { + startActivity(MainActivity.getStartIntent(this)); + finish(); + } + + @Override + public void showLoginProgress(final boolean show) { + int animTime = getResources().getInteger(android.R.integer.config_shortAnimTime); + + loginFormView.setVisibility(show ? View.GONE : View.VISIBLE); + loginFormView.animate().setDuration(animTime).alpha( + show ? 0 : 1).setListener(new AnimatorListenerAdapter() { @Override - public void onClick(View view) { - attemptLogin(); + public void onAnimationEnd(Animator animation) { + loginFormView.setVisibility(show ? View.GONE : View.VISIBLE); } }); - findViewById(R.id.action_create_account).setOnClickListener(getButtonLinkListener( - "https://cufs.vulcan.net.pl/Default/AccountManage/CreateAccount" - )); - - findViewById(R.id.action_forgot_password).setOnClickListener(getButtonLinkListener( - "https://cufs.vulcan.net.pl/Default/AccountManage/UnlockAccount" - )); - } - - @Override - public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults); - updater.onRequestPermissionsResult(requestCode, grantResults); - } - - private TextView.OnEditorActionListener getTextViewSignInListener() { - return new TextView.OnEditorActionListener() { + loadingBarView.setVisibility(show ? View.VISIBLE : View.GONE); + loadingBarView.animate().setDuration(animTime).alpha( + show ? 1 : 0).setListener(new AnimatorListenerAdapter() { @Override - public boolean onEditorAction(TextView textView, int id, KeyEvent keyEvent) { - if (id == EditorInfo.IME_ACTION_DONE || id == EditorInfo.IME_NULL) { - attemptLogin(); - return true; - } - return false; + public void onAnimationEnd(Animator animation) { + loadingBarView.setVisibility(show ? View.VISIBLE : View.GONE); } - }; - } - - private OnClickListener getButtonLinkListener(final String url) { - return new OnClickListener() { - @Override - public void onClick(View view) { - CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder(); - CustomTabsIntent customTabsIntent = builder.build(); - builder.setToolbarColor(getResources().getColor(R.color.colorPrimary)); - customTabsIntent.launchUrl(view.getContext(), Uri.parse(url)); - } - }; - } - - private void populateAutoComplete() { - // Get the string array - String[] countries = getResources().getStringArray(R.array.symbols); - // Create the adapter and set it to the AutoCompleteTextView - ArrayAdapter adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, - countries); - symbolView.setAdapter(adapter); - } - - /** - * Attempts to sign in the account specified by the login form. - */ - private void attemptLogin() { - // Reset errors. - emailView.setError(null); - passwordView.setError(null); - - // Store values at the time of the login attempt. - String email = emailView.getText().toString(); - String password = passwordView.getText().toString(); - String symbol = symbolView.getText().toString(); - - boolean cancel = false; - View focusView = null; - - // Check for a valid password. - if (TextUtils.isEmpty(password)) { - passwordView.setError(getString(R.string.error_field_required)); - focusView = passwordView; - cancel = true; - } else if (!isPasswordValid(password)) { - passwordView.setError(getString(R.string.error_invalid_password)); - focusView = passwordView; - cancel = true; - } - - // Check for a valid email address. - if (TextUtils.isEmpty(email)) { - emailView.setError(getString(R.string.error_field_required)); - focusView = emailView; - cancel = true; - } else if (!isEmailValid(email)) { - emailView.setError(getString(R.string.error_invalid_email)); - focusView = emailView; - cancel = true; - } - - // Check for a valid symbol. - if (TextUtils.isEmpty(symbol)) { - symbol = "Default"; - } - - String[] keys = getResources().getStringArray(R.array.symbols); - String[] values = getResources().getStringArray(R.array.symbols_values); - LinkedHashMap map = new LinkedHashMap<>(); - - for (int i = 0; i < Math.min(keys.length, values.length); ++i) { - map.put(keys[i], values[i]); - } - - if (map.containsKey(symbol)) { - symbol = map.get(symbol); - } - - if (cancel) { - // There was an error; don't attempt login and focus the first - // form field with an error. - focusView.requestFocus(); - } else { - // Show a progress spinner and kick off a background task to - // perform the user login attempt. - LoginTask authTask = new LoginTask(this, email, password, symbol); - authTask.showProgress(true); - authTask.execute(); - KeyboardUtils.hideSoftInput(this); - } - } - - private boolean isEmailValid(String email) { - return email.contains("@") || email.contains("\\\\"); - } - - private boolean isPasswordValid(String password) { - return password.length() > 4; - } - - @Override - public boolean dispatchTouchEvent(MotionEvent ev) { - - if (ev.getAction() == MotionEvent.ACTION_DOWN) { - touchPosition = ev.getY(); - } - if (ev.getAction() == MotionEvent.ACTION_UP) { - float releasePosition = ev.getY(); - - if (touchPosition - releasePosition == 0) { - View view = getCurrentFocus(); - if (view != null && (ev.getAction() == MotionEvent.ACTION_UP - || ev.getAction() == MotionEvent.ACTION_MOVE) && view instanceof EditText - && !view.getClass().getName().startsWith("android.webkit.")) { - - int[] coordinators = new int[2]; - view.getLocationOnScreen(coordinators); - float x = ev.getRawX() + view.getLeft() - coordinators[0]; - float y = ev.getRawY() + view.getTop() - coordinators[1]; - if (x < view.getLeft() || x > view.getRight() || y < view.getTop() - || y > view.getBottom()) { - KeyboardUtils.hideSoftInput(this); - } - } - } - } - return super.dispatchTouchEvent(ev); + }); } @Override public void onDestroy() { super.onDestroy(); - updater.onDestroy(this); + presenter.onDestroy(); + } + + private void onLoginProgressUpdate(String step, String message) { + loginProgressText.setText(String.format("%1$s/2 - %2$s...", step, message)); } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/login/LoginContract.java b/app/src/main/java/io/github/wulkanowy/ui/login/LoginContract.java new file mode 100644 index 000000000..fa074896c --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/login/LoginContract.java @@ -0,0 +1,53 @@ +package io.github.wulkanowy.ui.login; + +import io.github.wulkanowy.data.RepositoryContract; +import io.github.wulkanowy.di.annotations.PerActivity; +import io.github.wulkanowy.ui.base.BaseContract; + +public interface LoginContract { + interface View extends BaseContract.View { + + void setErrorEmailRequired(); + + void setErrorPassRequired(); + + void setErrorSymbolRequired(); + + void setErrorEmailInvalid(); + + void setErrorPassInvalid(); + + void setErrorPassIncorrect(); + + void resetViewErrors(); + + void setStepOneLoginProgress(); + + void setStepTwoLoginProgress(); + + void openMainActivity(); + + void showLoginProgress(boolean show); + + void showSoftInput(); + + void hideSoftInput(); + + } + + @PerActivity + interface Presenter extends BaseContract.Presenter { + + void attemptLogin(String email, String password, String symbol); + + void onStartAsync(); + + void onLoginProgress(int step); + + void onEndAsync(boolean success, Exception exception); + + void onCanceledAsync(); + + RepositoryContract getRepository(); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/login/LoginPresenter.java b/app/src/main/java/io/github/wulkanowy/ui/login/LoginPresenter.java new file mode 100644 index 000000000..c8e847258 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/login/LoginPresenter.java @@ -0,0 +1,143 @@ +package io.github.wulkanowy.ui.login; + +import android.text.TextUtils; + +import java.util.LinkedHashMap; + +import javax.inject.Inject; + +import io.github.wulkanowy.api.login.AccountPermissionException; +import io.github.wulkanowy.api.login.BadCredentialsException; +import io.github.wulkanowy.data.RepositoryContract; +import io.github.wulkanowy.ui.base.BasePresenter; +import io.github.wulkanowy.utils.AppConstant; + +public class LoginPresenter extends BasePresenter + implements LoginContract.Presenter { + + private LoginTask loginAsync; + + @Inject + LoginPresenter(RepositoryContract repository) { + super(repository); + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (loginAsync != null) { + loginAsync.cancel(true); + loginAsync = null; + } + } + + @Override + public void attemptLogin(String email, String password, String symbol) { + getView().resetViewErrors(); + + if (!isAllFieldCorrect(password, email)) { + getView().showSoftInput(); + return; + } + + if (getView().isNetworkConnected()) { + // Dopóki używamy AsyncTask presenter będzie musiał "wiedzieć" o AsyncTaskach + loginAsync = new LoginTask(this, + email, + password, + getNormalizedSymbol(symbol)); + loginAsync.execute(); + + } else { + getView().onNoNetworkError(); + } + + getView().hideSoftInput(); + } + + @Override + public void onStartAsync() { + getView().showLoginProgress(true); + } + + @Override + public void onLoginProgress(int step) { + if (step == 1) { + getView().setStepOneLoginProgress(); + } else if (step == 2) { + getView().setStepTwoLoginProgress(); + } + } + + @Override + public void onEndAsync(boolean success, Exception exception) { + if (success) { + getView().openMainActivity(); + } else if (exception instanceof BadCredentialsException) { + getView().setErrorPassIncorrect(); + getView().showSoftInput(); + getView().showLoginProgress(false); + } else if (exception instanceof AccountPermissionException) { + getView().setErrorSymbolRequired(); + getView().showSoftInput(); + getView().showLoginProgress(false); + } else { + getView().onError(getRepository().getErrorLoginMessage(exception)); + getView().showLoginProgress(false); + } + + } + + @Override + public void onCanceledAsync() { + getView().showLoginProgress(false); + } + + private boolean isEmailValid(String email) { + return email.contains("@") || email.contains("\\\\"); + } + + private boolean isPasswordValid(String password) { + return password.length() > 4; + } + + private String getNormalizedSymbol(String symbol) { + if (TextUtils.isEmpty(symbol)) { + return AppConstant.DEFAULT_SYMBOL; + } + + String[] keys = getRepository().getSymbolsKeysArray(); + String[] values = getRepository().getSymbolsValuesArray(); + LinkedHashMap map = new LinkedHashMap<>(); + + for (int i = 0; i < Math.min(keys.length, values.length); ++i) { + map.put(keys[i], values[i]); + } + + if (map.containsKey(symbol)) { + return map.get(symbol); + } + return AppConstant.DEFAULT_SYMBOL; + } + + private boolean isAllFieldCorrect(String password, String email) { + boolean correct = true; + + if (TextUtils.isEmpty(password)) { + getView().setErrorPassRequired(); + correct = false; + } else if (!isPasswordValid(password)) { + getView().setErrorPassInvalid(); + correct = false; + } + + if (TextUtils.isEmpty(email)) { + getView().setErrorEmailRequired(); + correct = false; + } else if (!isEmailValid(email)) { + getView().setErrorEmailInvalid(); + correct = false; + } + return correct; + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/login/LoginTask.java b/app/src/main/java/io/github/wulkanowy/ui/login/LoginTask.java index 3bf702846..441b8d8d8 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/login/LoginTask.java +++ b/app/src/main/java/io/github/wulkanowy/ui/login/LoginTask.java @@ -1,64 +1,21 @@ package io.github.wulkanowy.ui.login; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.app.Activity; -import android.content.DialogInterface; -import android.content.Intent; import android.os.AsyncTask; -import android.support.design.widget.Snackbar; -import android.support.design.widget.TextInputLayout; -import android.support.v7.app.AlertDialog; -import android.view.View; -import android.widget.EditText; -import android.widget.TextView; -import com.crashlytics.android.Crashlytics; -import com.crashlytics.android.answers.Answers; -import com.crashlytics.android.answers.CustomEvent; +public class LoginTask extends AsyncTask { -import java.io.IOException; -import java.lang.ref.WeakReference; -import java.net.SocketTimeoutException; -import java.net.UnknownHostException; + private String email; -import io.github.wulkanowy.R; -import io.github.wulkanowy.WulkanowyApp; -import io.github.wulkanowy.api.login.AccountPermissionException; -import io.github.wulkanowy.api.login.BadCredentialsException; -import io.github.wulkanowy.api.login.NotLoggedInErrorException; -import io.github.wulkanowy.api.login.VulcanOfflineException; -import io.github.wulkanowy.db.dao.entities.DaoSession; -import io.github.wulkanowy.services.jobs.FullSyncJob; -import io.github.wulkanowy.services.sync.LoginSession; -import io.github.wulkanowy.services.sync.VulcanSync; -import io.github.wulkanowy.ui.main.DashboardActivity; -import io.github.wulkanowy.utils.KeyboardUtils; -import io.github.wulkanowy.utils.NetworkUtils; -import io.github.wulkanowy.utils.security.CryptoException; + private String password; -/** - * Represents an asynchronous login/registration task used to authenticate - * the user. - */ -public class LoginTask extends AsyncTask { + private String symbol; - private final String email; + private LoginContract.Presenter presenter; - private final String password; + private Exception exception; - private final String symbol; - - private WeakReference activity; - - private WeakReference progressView; - - private WeakReference loginFormView; - - private WeakReference showText; - - LoginTask(Activity activity, String email, String password, String symbol) { - this.activity = new WeakReference<>(activity); + LoginTask(LoginContract.Presenter presenter, String email, String password, String symbol) { + this.presenter = presenter; this.email = email; this.password = password; this.symbol = symbol; @@ -66,159 +23,36 @@ public class LoginTask extends AsyncTask { @Override protected void onPreExecute() { - showText = new WeakReference<>((TextView) activity.get().findViewById(R.id.login_progress_text)); + presenter.onStartAsync(); } @Override - protected Integer doInBackground(Void... params) { - if (NetworkUtils.isOnline(activity.get())) { - DaoSession daoSession = ((WulkanowyApp) activity.get().getApplication()).getDaoSession(); - VulcanSync vulcanSync = new VulcanSync(new LoginSession()); + protected Boolean doInBackground(Void... params) { + try { + publishProgress(1); + presenter.getRepository().loginUser(email, password, symbol); - try { - publishProgress("1", activity.get().getResources().getString(R.string.step_login)); - vulcanSync.firstLoginSignInStep(activity.get(), daoSession, email, password, symbol); - - publishProgress("2", activity.get().getResources().getString(R.string.step_synchronization)); - vulcanSync.syncAll(); - } catch (BadCredentialsException e) { - return R.string.login_bad_credentials_text; - } catch (AccountPermissionException e) { - return R.string.error_bad_account_permission; - } catch (CryptoException e) { - return R.string.encrypt_failed_text; - } catch (UnknownHostException e) { - return R.string.noInternet_text; - } catch (SocketTimeoutException e) { - return R.string.generic_timeout_error; - } catch (NotLoggedInErrorException | IOException e) { - return R.string.login_denied_text; - } catch (VulcanOfflineException e) { - return R.string.error_host_offline; - } catch (UnsupportedOperationException e) { - return -1; - } catch (Throwable e) { - Crashlytics.logException(e); - return R.string.login_denied_text; - } - - new FullSyncJob().scheduledJob(activity.get()); - - return R.string.login_accepted_text; - - } else { - return R.string.noInternet_text; + publishProgress(2); + presenter.getRepository().syncAll(); + } catch (Exception e) { + exception = e; + return false; } + return true; } @Override - protected void onProgressUpdate(String... progress) { - showText.get().setText(String.format("%1$s/2 - %2$s...", progress[0], progress[1])); + protected void onProgressUpdate(Integer... progress) { + presenter.onLoginProgress(progress[0]); } @Override - protected void onPostExecute(final Integer messageID) { - showProgress(false); - - switch (messageID) { - // if success - case R.string.login_accepted_text: - logFirstLoginAction(true, activity.get().getString(messageID)); - Intent intent = new Intent(activity.get(), DashboardActivity.class); - activity.get().finish(); - activity.get().startActivity(intent); - break; - - // if bad credentials entered - case R.string.login_bad_credentials_text: - logFirstLoginAction(false, activity.get().getString(messageID)); - EditText passwordView = activity.get().findViewById(R.id.password); - passwordView.setError(activity.get().getString(R.string.error_incorrect_password)); - passwordView.requestFocus(); - KeyboardUtils.showSoftInput(passwordView, activity.get()); - break; - - // if no permission - case R.string.error_bad_account_permission: - logFirstLoginAction(false, activity.get().getString(messageID)); - // Change to visible symbol input view - TextInputLayout symbolLayout = activity.get().findViewById(R.id.to_symbol_input_layout); - symbolLayout.setVisibility(View.VISIBLE); - - EditText symbolView = activity.get().findViewById(R.id.symbol); - symbolView.setError(activity.get().getString(R.string.error_bad_account_permission)); - symbolView.requestFocus(); - KeyboardUtils.showSoftInput(symbolView, activity.get()); - break; - - // if rooted and SDK < 18 - case -1: - logFirstLoginAction(false, "Device rooted"); - final AlertDialog.Builder alertDialog = new AlertDialog.Builder(activity.get()) - .setIcon(android.R.drawable.ic_dialog_alert) - .setTitle(R.string.alert_dialog_blocked_app) - .setMessage(R.string.alert_dialog_blocked_app_message) - .setPositiveButton(R.string.generic_dialog_close, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - dialogInterface.dismiss(); - } - }); - alertDialog.show(); - break; - - default: - logFirstLoginAction(false, activity.get().getString(messageID)); - Snackbar.make(activity.get().findViewById(R.id.fragment_container), - messageID, Snackbar.LENGTH_LONG).show(); - break; - } - } - - private void logFirstLoginAction(boolean success, String message) { - Answers.getInstance().logCustom(new CustomEvent("First login") - .putCustomAttribute("Symbol", symbol) - .putCustomAttribute("Success", success ? 1 : 0) - .putCustomAttribute("Message", message)); + protected void onPostExecute(Boolean success) { + presenter.onEndAsync(success, exception); } @Override protected void onCancelled() { - showProgress(false); - } - - /** - * Shows the progress UI and hides the login form. - */ - void showProgress(final boolean show) { - loginFormView = new WeakReference<>(activity.get().findViewById(R.id.login_form)); - progressView = new WeakReference<>(activity.get().findViewById(R.id.login_progress)); - - int animTime = activity.get().getResources().getInteger(android.R.integer.config_shortAnimTime); - - changeLoginFormVisibility(show, animTime); - changeProgressVisibility(show, animTime); - } - - private void changeLoginFormVisibility(final boolean show, final int animTime) { - loginFormView.get().setVisibility(show ? View.GONE : View.VISIBLE); - loginFormView.get().animate().setDuration(animTime).alpha( - show ? 0 : 1).setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - loginFormView.get().setVisibility(show ? View.GONE : View.VISIBLE); - } - }); - } - - private void changeProgressVisibility(final boolean show, final int animTime) { - progressView.get().setVisibility(show ? View.VISIBLE : View.GONE); - progressView.get().animate().setDuration(animTime).alpha( - show ? 1 : 0).setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - progressView.get().setVisibility(show ? View.VISIBLE : View.GONE); - } - }); + presenter.onCanceledAsync(); } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/AbstractFragment.java b/app/src/main/java/io/github/wulkanowy/ui/main/AbstractFragment.java deleted file mode 100644 index 681aa751e..000000000 --- a/app/src/main/java/io/github/wulkanowy/ui/main/AbstractFragment.java +++ /dev/null @@ -1,189 +0,0 @@ -package io.github.wulkanowy.ui.main; - - -import android.app.Activity; -import android.content.Context; -import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.v4.app.Fragment; -import android.support.v4.widget.SwipeRefreshLayout; -import android.support.v7.widget.RecyclerView; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Toast; - -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.List; - -import eu.davidea.flexibleadapter.FlexibleAdapter; -import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager; -import eu.davidea.flexibleadapter.items.AbstractExpandableHeaderItem; -import io.github.wulkanowy.R; -import io.github.wulkanowy.WulkanowyApp; -import io.github.wulkanowy.db.dao.entities.DaoSession; -import io.github.wulkanowy.utils.NetworkUtils; - -public abstract class AbstractFragment extends Fragment - implements AsyncResponse { - - private FlexibleAdapter flexibleAdapter; - - private List itemList = new ArrayList<>(); - - private WeakReference activityWeakReference; - - private SwipeRefreshLayout swipeRefreshLayout; - - private RecyclerView recyclerViewLayout; - - private DaoSession daoSession; - - private long userId; - - public AbstractFragment() { - //empty constructor for fragments - } - - public long getUserId() { - return userId; - } - - public SwipeRefreshLayout getRefreshLayoutView() { - return swipeRefreshLayout; - } - - public DaoSession getDaoSession() { - return daoSession; - } - - public Activity getActivityWeakReference() { - return activityWeakReference.get(); - } - - public abstract int getLayoutId(); - - public abstract int getRecyclerViewId(); - - public abstract int getLoadingBarId(); - - public abstract int getRefreshLayoutId(); - - public abstract List getItems() throws Exception; - - public abstract void onRefresh() throws Exception; - - public abstract void onPostRefresh(int stringResult); - - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setRetainInstance(true); - } - - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - View view = inflater.inflate(getLayoutId(), container, false); - recyclerViewLayout = view.findViewById(getRecyclerViewId()); - swipeRefreshLayout = view.findViewById(getRefreshLayoutId()); - setUpRefreshLayout(swipeRefreshLayout); - return view; - } - - @Override - public void onActivityCreated(@Nullable Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - - if (getActivity() != null && getView() != null) { - activityWeakReference = new WeakReference(getActivity()); - daoSession = ((WulkanowyApp) getActivity().getApplication()).getDaoSession(); - userId = getActivity().getSharedPreferences("LoginData", Context.MODE_PRIVATE) - .getLong("userId", 0); - - if (itemList != null) - if (itemList.isEmpty()) { - flexibleAdapter = getFlexibleAdapter(itemList); - setAdapterOnRecyclerView(recyclerViewLayout); - if (getUserVisibleHint()) { - new DatabaseQueryTask(this).execute(); - } - } else { - setAdapterOnRecyclerView(recyclerViewLayout); - setLoadingBarInvisible(getView()); - } - } - } - - @Override - public void setUserVisibleHint(boolean isVisibleToUser) { - super.setUserVisibleHint(isVisibleToUser); - if (isResumed() && isVisibleToUser && flexibleAdapter.getItemCount() == 0) { - new DatabaseQueryTask(this).execute(); - } - } - - @Override - public void onQuarryProcessFinish(@NonNull List resultItemList) { - itemList = resultItemList; - flexibleAdapter = getFlexibleAdapter(itemList); - setAdapterOnRecyclerView(recyclerViewLayout); - if (getView() != null) { - setLoadingBarInvisible(getView()); - } - } - - @Override - public void onRefreshProcessFinish(@Nullable List resultItemList, int stringEventId) { - if (resultItemList != null) { - itemList = resultItemList; - updateDataInRecyclerView(); - } - onPostRefresh(stringEventId); - getRefreshLayoutView().setRefreshing(false); - } - - @NonNull - protected FlexibleAdapter getFlexibleAdapter(@NonNull List itemList) { - return new FlexibleAdapter<>(itemList) - .setAutoCollapseOnExpand(true) - .setAutoScrollOnExpand(true) - .expandItemsAtStartUp(); - } - - @NonNull - private SwipeRefreshLayout.OnRefreshListener getDefaultRefreshListener() { - return new SwipeRefreshLayout.OnRefreshListener() { - @Override - public void onRefresh() { - if (NetworkUtils.isOnline(getContext())) { - new RefreshTask(AbstractFragment.this).execute(); - } else { - Toast.makeText(getContext(), R.string.noInternet_text, Toast.LENGTH_SHORT).show(); - swipeRefreshLayout.setRefreshing(false); - } - } - }; - } - - private void updateDataInRecyclerView() { - flexibleAdapter.updateDataSet(itemList); - setAdapterOnRecyclerView(recyclerViewLayout); - } - - protected void setUpRefreshLayout(@NonNull SwipeRefreshLayout swipeRefreshLayout) { - swipeRefreshLayout.setColorSchemeResources(android.R.color.black); - swipeRefreshLayout.setOnRefreshListener(getDefaultRefreshListener()); - } - - protected final void setLoadingBarInvisible(@NonNull View mainView) { - mainView.findViewById(getLoadingBarId()).setVisibility(View.INVISIBLE); - } - - protected void setAdapterOnRecyclerView(@NonNull RecyclerView recyclerView) { - recyclerView.setLayoutManager(new SmoothScrollLinearLayoutManager(getActivityWeakReference())); - recyclerView.setAdapter(flexibleAdapter); - } -} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/AsyncResponse.java b/app/src/main/java/io/github/wulkanowy/ui/main/AsyncResponse.java deleted file mode 100644 index dc95e5a20..000000000 --- a/app/src/main/java/io/github/wulkanowy/ui/main/AsyncResponse.java +++ /dev/null @@ -1,14 +0,0 @@ -package io.github.wulkanowy.ui.main; - -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; - -import java.util.List; - -import eu.davidea.flexibleadapter.items.AbstractExpandableHeaderItem; - -public interface AsyncResponse { - void onQuarryProcessFinish(@NonNull List resultItemList); - - void onRefreshProcessFinish(@Nullable List resultItemList, int stringErrorId); -} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/DashboardActivity.java b/app/src/main/java/io/github/wulkanowy/ui/main/DashboardActivity.java deleted file mode 100644 index 6015de007..000000000 --- a/app/src/main/java/io/github/wulkanowy/ui/main/DashboardActivity.java +++ /dev/null @@ -1,133 +0,0 @@ -package io.github.wulkanowy.ui.main; - -import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.design.widget.BottomNavigationView; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentTransaction; -import android.support.v7.app.AppCompatActivity; -import android.view.MenuItem; - -import io.github.wulkanowy.R; -import io.github.wulkanowy.services.Updater; -import io.github.wulkanowy.ui.main.attendance.AttendanceFragment; -import io.github.wulkanowy.ui.main.board.BoardFragment; -import io.github.wulkanowy.ui.main.grades.GradesFragment; -import io.github.wulkanowy.ui.main.timetable.TimetableFragment; - -public class DashboardActivity extends AppCompatActivity { - - private Fragment currentFragment; - - private GradesFragment gradesFragment = new GradesFragment(); - - private AttendanceFragment attendanceFragment = new AttendanceFragment(); - - private BoardFragment boardFragment = new BoardFragment(); - - private TimetableFragment timetableFragment = new TimetableFragment(); - - private Updater updater; - - private boolean showed; - - private BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener - = new BottomNavigationView.OnNavigationItemSelectedListener() { - - @Override - public boolean onNavigationItemSelected(@NonNull MenuItem item) { - switch (item.getItemId()) { - case R.id.navigation_marks: - setTitle(R.string.grades_text); - currentFragment = gradesFragment; - break; - - case R.id.navigation_attendance: - setTitle(R.string.attendance_text); - currentFragment = attendanceFragment; - break; - - case R.id.navigation_lessonplan: - setTitle(R.string.lessonplan_text); - currentFragment = timetableFragment; - break; - - case R.id.navigation_dashboard: - default: - setTitle(R.string.dashboard_text); - currentFragment = boardFragment; - break; - } - FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); - transaction.replace(R.id.fragment_container, currentFragment); - transaction.commit(); - return true; - } - }; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_dashboard); - - BottomNavigationView navigation = findViewById(R.id.navigation); - navigation.setSelectedItemId(R.id.navigation_marks); - navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener); - - if (savedInstanceState != null) { - currentFragment = getSupportFragmentManager().getFragment(savedInstanceState, "currentFragment"); - setTitle(savedInstanceState.getString("activityTitle")); - } else { - currentFragment = gradesFragment; - setTitle(R.string.grades_text); - } - - int cardID = getIntent().getIntExtra("cardID", 0); - - if (cardID == 1) { - currentFragment = gradesFragment; - } - - getSupportFragmentManager().beginTransaction() - .replace(R.id.fragment_container, currentFragment).commit(); - } - - @Override - protected void onStart() { - super.onStart(); - if (!showed) { - updater = new Updater(this).checkForUpdates(); - showed = true; - } - } - - @Override - public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults); - updater.onRequestPermissionsResult(requestCode, grantResults); - } - - @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - outState.putString("activityTitle", getTitle().toString()); - getSupportFragmentManager().putFragment(outState, "currentFragment", currentFragment); - } - - public void onBackPressed() { - - BottomNavigationView navigation = findViewById(R.id.navigation); - - if (navigation.getSelectedItemId() != R.id.navigation_dashboard) { - navigation.setSelectedItemId(R.id.navigation_dashboard); - } else if (navigation.getSelectedItemId() == R.id.navigation_dashboard) { - moveTaskToBack(true); - } - } - - @Override - public void onDestroy() { - super.onDestroy(); - updater.onDestroy(this); - } -} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/DatabaseQueryTask.java b/app/src/main/java/io/github/wulkanowy/ui/main/DatabaseQueryTask.java deleted file mode 100644 index 7ce4b4fb4..000000000 --- a/app/src/main/java/io/github/wulkanowy/ui/main/DatabaseQueryTask.java +++ /dev/null @@ -1,30 +0,0 @@ -package io.github.wulkanowy.ui.main; - -import android.os.AsyncTask; - -import java.util.List; - -public class DatabaseQueryTask extends AsyncTask> { - - private AbstractFragment abstractFragment; - - public DatabaseQueryTask(AbstractFragment abstractFragment) { - this.abstractFragment = abstractFragment; - } - - @Override - protected List doInBackground(Void... voids) { - try { - return abstractFragment.getItems(); - } catch (Exception e) { - return null; - } - } - - @SuppressWarnings("unchecked") - @Override - protected void onPostExecute(List objects) { - super.onPostExecute(objects); - abstractFragment.onQuarryProcessFinish(objects); - } -} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/MainActivity.java b/app/src/main/java/io/github/wulkanowy/ui/main/MainActivity.java new file mode 100644 index 000000000..6ad696d08 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/MainActivity.java @@ -0,0 +1,153 @@ +package io.github.wulkanowy.ui.main; + +import android.content.Context; +import android.content.Intent; +import android.graphics.Color; +import android.os.Bundle; +import android.support.v7.app.ActionBar; +import android.view.View; + +import com.aurelhubert.ahbottomnavigation.AHBottomNavigation; +import com.aurelhubert.ahbottomnavigation.AHBottomNavigationItem; +import com.aurelhubert.ahbottomnavigation.AHBottomNavigationViewPager; + +import javax.inject.Inject; + +import butterknife.BindView; +import butterknife.ButterKnife; +import io.github.wulkanowy.R; +import io.github.wulkanowy.services.SyncJob; +import io.github.wulkanowy.ui.base.BaseActivity; +import io.github.wulkanowy.ui.main.dashboard.DashboardFragment; +import io.github.wulkanowy.ui.main.grades.GradesFragment; +import io.github.wulkanowy.ui.main.timetable.TimetableFragment; + +public class MainActivity extends BaseActivity implements MainContract.View, + AHBottomNavigation.OnTabSelectedListener, OnFragmentIsReadyListener { + + private int initTabPosition = 0; + + @BindView(R.id.main_activity_nav) + AHBottomNavigation bottomNavigation; + + @BindView(R.id.main_activity_view_pager) + AHBottomNavigationViewPager viewPager; + + @BindView(R.id.main_activity_progress_bar) + View progressBar; + + @Inject + MainPagerAdapter pagerAdapter; + + @Inject + MainContract.Presenter presenter; + + public static Intent getStartIntent(Context context) { + return new Intent(context, MainActivity.class); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + initTabPosition = getIntent().getIntExtra(SyncJob.EXTRA_INTENT_KEY, initTabPosition); + + getActivityComponent().inject(this); + setButterKnife(ButterKnife.bind(this)); + + presenter.onStart(this); + + initiationViewPager(); + initiationBottomNav(); + } + + @Override + public void showProgressBar(boolean show) { + progressBar.setVisibility(show ? View.VISIBLE : View.INVISIBLE); + viewPager.setVisibility(show ? View.INVISIBLE : View.VISIBLE); + bottomNavigation.setVisibility(show ? View.INVISIBLE : View.VISIBLE); + } + + @Override + public void showActionBar() { + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.show(); + } + } + + @Override + public void hideActionBar() { + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.hide(); + } + } + + @Override + public boolean onTabSelected(int position, boolean wasSelected) { + presenter.onTabSelected(position, wasSelected); + return true; + } + + @Override + public void setCurrentPage(int position) { + viewPager.setCurrentItem(position, false); + } + + @Override + public void onFragmentIsReady() { + presenter.onFragmentIsReady(); + } + + private void initiationBottomNav() { + bottomNavigation.addItem(new AHBottomNavigationItem( + getString(R.string.grades_text), + getResources().getDrawable(R.drawable.icon_grade_26dp) + )); + bottomNavigation.addItem(new AHBottomNavigationItem( + getString(R.string.attendance_text), + getResources().getDrawable(R.drawable.icon_attendance_24dp) + )); + bottomNavigation.addItem(new AHBottomNavigationItem( + getString(R.string.dashboard_text), + getResources().getDrawable(R.drawable.ic_dashboard_black_24dp) + )); + bottomNavigation.addItem(new AHBottomNavigationItem( + getString(R.string.lessonplan_text), + getResources().getDrawable(R.drawable.icon_lessonplan_24dp) + )); + bottomNavigation.addItem(new AHBottomNavigationItem( + getString(R.string.settings_text), + getResources().getDrawable(R.drawable.icon_other_24dp) + )); + + bottomNavigation.setAccentColor(getResources().getColor(R.color.colorPrimary)); + bottomNavigation.setInactiveColor(Color.BLACK); + bottomNavigation.setBackgroundColor(getResources().getColor(R.color.colorBackgroundBottomNavi)); + bottomNavigation.setTitleState(AHBottomNavigation.TitleState.ALWAYS_SHOW); + bottomNavigation.setOnTabSelectedListener(this); + bottomNavigation.setCurrentItem(initTabPosition); + bottomNavigation.setBehaviorTranslationEnabled(false); + } + + private void initiationViewPager() { + pagerAdapter.addFragment(new GradesFragment()); + pagerAdapter.addFragment(new DashboardFragment()); + pagerAdapter.addFragment(new DashboardFragment()); + pagerAdapter.addFragment(new TimetableFragment()); + pagerAdapter.addFragment(new DashboardFragment()); + + viewPager.setPagingEnabled(false); + viewPager.setAdapter(pagerAdapter); + viewPager.setOffscreenPageLimit(4); + viewPager.setCurrentItem(initTabPosition, false); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + presenter.onDestroy(); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/MainContract.java b/app/src/main/java/io/github/wulkanowy/ui/main/MainContract.java new file mode 100644 index 000000000..cb1d5d7c7 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/MainContract.java @@ -0,0 +1,26 @@ +package io.github.wulkanowy.ui.main; + +import io.github.wulkanowy.di.annotations.PerActivity; +import io.github.wulkanowy.ui.base.BaseContract; + +public interface MainContract { + + interface View extends BaseContract.View { + + void setCurrentPage(int position); + + void showProgressBar(boolean show); + + void showActionBar(); + + void hideActionBar(); + } + + @PerActivity + interface Presenter extends BaseContract.Presenter { + + void onTabSelected(int position, boolean wasSelected); + + void onFragmentIsReady(); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/MainPagerAdapter.java b/app/src/main/java/io/github/wulkanowy/ui/main/MainPagerAdapter.java new file mode 100644 index 000000000..fcc51fb54 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/MainPagerAdapter.java @@ -0,0 +1,31 @@ +package io.github.wulkanowy.ui.main; + +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentStatePagerAdapter; + +import java.util.ArrayList; +import java.util.List; + +public class MainPagerAdapter extends FragmentStatePagerAdapter { + + private List fragmentList = new ArrayList<>(); + + public MainPagerAdapter(FragmentManager fragmentManager) { + super(fragmentManager); + } + + void addFragment(Fragment fragment) { + fragmentList.add(fragment); + } + + @Override + public Fragment getItem(int position) { + return fragmentList.get(position); + } + + @Override + public int getCount() { + return fragmentList.size(); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/MainPresenter.java b/app/src/main/java/io/github/wulkanowy/ui/main/MainPresenter.java new file mode 100644 index 000000000..149e49e2f --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/MainPresenter.java @@ -0,0 +1,44 @@ +package io.github.wulkanowy.ui.main; + + +import javax.inject.Inject; + +import io.github.wulkanowy.data.RepositoryContract; +import io.github.wulkanowy.ui.base.BasePresenter; + +public class MainPresenter extends BasePresenter + implements MainContract.Presenter { + + private int fragmentCount = 0; + + @Inject + MainPresenter(RepositoryContract repository) { + super(repository); + } + + @Override + public void onStart(MainContract.View view) { + super.onStart(view); + getView().showProgressBar(true); + getView().hideActionBar(); + } + + @Override + public void onTabSelected(int position, boolean wasSelected) { + if (!wasSelected) { + getView().setCurrentPage(position); + } + } + + @Override + public void onFragmentIsReady() { + if (fragmentCount < 5) { + fragmentCount++; + } + + if (fragmentCount == 5) { + getView().showActionBar(); + getView().showProgressBar(false); + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/OnFragmentIsReadyListener.java b/app/src/main/java/io/github/wulkanowy/ui/main/OnFragmentIsReadyListener.java new file mode 100644 index 000000000..21e3ba12f --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/OnFragmentIsReadyListener.java @@ -0,0 +1,6 @@ +package io.github.wulkanowy.ui.main; + +public interface OnFragmentIsReadyListener { + + void onFragmentIsReady(); +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/RefreshTask.java b/app/src/main/java/io/github/wulkanowy/ui/main/RefreshTask.java deleted file mode 100644 index 67697ba9f..000000000 --- a/app/src/main/java/io/github/wulkanowy/ui/main/RefreshTask.java +++ /dev/null @@ -1,56 +0,0 @@ -package io.github.wulkanowy.ui.main; - -import android.os.AsyncTask; -import android.util.Log; - -import java.net.SocketTimeoutException; -import java.net.UnknownHostException; -import java.util.List; - -import io.github.wulkanowy.R; -import io.github.wulkanowy.api.login.VulcanOfflineException; - -public class RefreshTask extends AsyncTask> { - - public static final String DEBUG_TAG = "RefreshTask"; - - private int stringEventId = 0; - - private AbstractFragment abstractFragment; - - public RefreshTask(AbstractFragment abstractFragment) { - this.abstractFragment = abstractFragment; - } - - @Override - protected List doInBackground(Void... voids) { - try { - abstractFragment.onRefresh(); - return abstractFragment.getItems(); - } catch (UnknownHostException e) { - stringEventId = R.string.noInternet_text; - Log.i(DEBUG_TAG, "Synchronization is failed because occur problem with internet", - e.getCause()); - return null; - } catch (SocketTimeoutException e) { - stringEventId = R.string.generic_timeout_error; - Log.i(DEBUG_TAG, "Too long wait for connection with internet", e); - return null; - } catch (VulcanOfflineException e) { - stringEventId = R.string.error_host_offline; - Log.i(DEBUG_TAG, "VULCAN services is offline"); - return null; - } catch (Exception e) { - stringEventId = R.string.refresh_error_text; - Log.e(DEBUG_TAG, "There was a sync problem", e); - return null; - } - } - - @SuppressWarnings("unchecked") - @Override - protected void onPostExecute(List objects) { - super.onPostExecute(objects); - abstractFragment.onRefreshProcessFinish(objects, stringEventId); - } -} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceContract.java b/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceContract.java new file mode 100644 index 000000000..fe4037171 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceContract.java @@ -0,0 +1,23 @@ +package io.github.wulkanowy.ui.main.attendance; + +import io.github.wulkanowy.di.annotations.PerFragment; +import io.github.wulkanowy.ui.base.BaseContract; +import io.github.wulkanowy.ui.main.OnFragmentIsReadyListener; + +public interface AttendanceContract { + + interface View extends BaseContract.View { + + void setActivityTitle(); + + boolean isMenuVisible(); + } + + @PerFragment + interface Presenter extends BaseContract.Presenter { + + void onStart(View view, OnFragmentIsReadyListener listener); + + void onFragmentVisible(boolean isVisible); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceFragment.java b/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceFragment.java index 0da2e99f4..6010d1085 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceFragment.java +++ b/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceFragment.java @@ -2,18 +2,58 @@ package io.github.wulkanowy.ui.main.attendance; import android.os.Bundle; import android.support.annotation.NonNull; -import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import io.github.wulkanowy.R; +import javax.inject.Inject; -public class AttendanceFragment extends Fragment { +import butterknife.ButterKnife; +import io.github.wulkanowy.R; +import io.github.wulkanowy.di.component.FragmentComponent; +import io.github.wulkanowy.ui.base.BaseFragment; +import io.github.wulkanowy.ui.main.OnFragmentIsReadyListener; + +public class AttendanceFragment extends BaseFragment implements AttendanceContract.View { + + @Inject + AttendanceContract.Presenter presenter; + + public AttendanceFragment() { + // empty constructor for fragment + } @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - return inflater.inflate(R.layout.fragment_attendance, container, false); + View view = inflater.inflate(R.layout.fragment_attendance, container, false); + + FragmentComponent component = getFragmentComponent(); + if (component != null) { + component.inject(this); + setButterKnife(ButterKnife.bind(this, view)); + presenter.onStart(this, (OnFragmentIsReadyListener) getActivity()); + } + + return view; + } + + @Override + public void setMenuVisibility(boolean menuVisible) { + super.setMenuVisibility(menuVisible); + if (presenter != null) { + presenter.onFragmentVisible(menuVisible); + } + } + + @Override + public void setActivityTitle() { + setTitle(getString(R.string.dashboard_text)); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + presenter.onDestroy(); } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendancePresenter.java b/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendancePresenter.java new file mode 100644 index 000000000..da43e320f --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendancePresenter.java @@ -0,0 +1,37 @@ +package io.github.wulkanowy.ui.main.attendance; + +import javax.inject.Inject; + +import io.github.wulkanowy.data.RepositoryContract; +import io.github.wulkanowy.ui.base.BasePresenter; +import io.github.wulkanowy.ui.main.OnFragmentIsReadyListener; + +public class AttendancePresenter extends BasePresenter + implements AttendanceContract.Presenter { + + private OnFragmentIsReadyListener listener; + + @Inject + AttendancePresenter(RepositoryContract repository) { + super(repository); + } + + @Override + public void onStart(AttendanceContract.View view, OnFragmentIsReadyListener listener) { + super.onStart(view); + this.listener = listener; + + if (getView().isMenuVisible()) { + getView().setActivityTitle(); + } + + this.listener.onFragmentIsReady(); + } + + @Override + public void onFragmentVisible(boolean isVisible) { + if (isVisible) { + getView().setActivityTitle(); + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/board/BoardFragment.java b/app/src/main/java/io/github/wulkanowy/ui/main/board/BoardFragment.java deleted file mode 100644 index f6b12c68c..000000000 --- a/app/src/main/java/io/github/wulkanowy/ui/main/board/BoardFragment.java +++ /dev/null @@ -1,19 +0,0 @@ -package io.github.wulkanowy.ui.main.board; - -import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.v4.app.Fragment; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import io.github.wulkanowy.R; - -public class BoardFragment extends Fragment { - - @Override - public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - return inflater.inflate(R.layout.fragment_board, container, false); - } -} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/dashboard/DashboardContract.java b/app/src/main/java/io/github/wulkanowy/ui/main/dashboard/DashboardContract.java new file mode 100644 index 000000000..2488eb95c --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/dashboard/DashboardContract.java @@ -0,0 +1,23 @@ +package io.github.wulkanowy.ui.main.dashboard; + +import io.github.wulkanowy.di.annotations.PerActivity; +import io.github.wulkanowy.ui.base.BaseContract; +import io.github.wulkanowy.ui.main.OnFragmentIsReadyListener; + +public interface DashboardContract { + + interface View extends BaseContract.View { + + void setActivityTitle(); + + boolean isMenuVisible(); + } + + @PerActivity + interface Presenter extends BaseContract.Presenter { + + void onStart(View view, OnFragmentIsReadyListener listener); + + void onFragmentVisible(boolean isVisible); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/dashboard/DashboardFragment.java b/app/src/main/java/io/github/wulkanowy/ui/main/dashboard/DashboardFragment.java new file mode 100644 index 000000000..0832b5fb7 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/dashboard/DashboardFragment.java @@ -0,0 +1,48 @@ +package io.github.wulkanowy.ui.main.dashboard; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import javax.inject.Inject; + +import butterknife.ButterKnife; +import io.github.wulkanowy.R; +import io.github.wulkanowy.di.component.FragmentComponent; +import io.github.wulkanowy.ui.base.BaseFragment; +import io.github.wulkanowy.ui.main.OnFragmentIsReadyListener; + +public class DashboardFragment extends BaseFragment implements DashboardContract.View { + + @Inject + DashboardContract.Presenter presenter; + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_board, container, false); + + FragmentComponent component = getFragmentComponent(); + if (component != null) { + component.inject(this); + setButterKnife(ButterKnife.bind(this, view)); + presenter.onStart(this, (OnFragmentIsReadyListener) getActivity()); + } + return view; + } + + @Override + public void setMenuVisibility(boolean menuVisible) { + super.setMenuVisibility(menuVisible); + if (presenter != null) { + presenter.onFragmentVisible(menuVisible); + } + } + + @Override + public void setActivityTitle() { + setTitle(getString(R.string.dashboard_text)); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/dashboard/DashboardPresenter.java b/app/src/main/java/io/github/wulkanowy/ui/main/dashboard/DashboardPresenter.java new file mode 100644 index 000000000..58d89b399 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/dashboard/DashboardPresenter.java @@ -0,0 +1,37 @@ +package io.github.wulkanowy.ui.main.dashboard; + +import javax.inject.Inject; + +import io.github.wulkanowy.data.RepositoryContract; +import io.github.wulkanowy.ui.base.BasePresenter; +import io.github.wulkanowy.ui.main.OnFragmentIsReadyListener; + +public class DashboardPresenter extends BasePresenter + implements DashboardContract.Presenter { + + private OnFragmentIsReadyListener listener; + + @Inject + DashboardPresenter(RepositoryContract repository) { + super(repository); + } + + @Override + public void onStart(DashboardContract.View view, OnFragmentIsReadyListener listener) { + super.onStart(view); + this.listener = listener; + + if (getView().isMenuVisible()) { + getView().setActivityTitle(); + } + + this.listener.onFragmentIsReady(); + } + + @Override + public void onFragmentVisible(boolean isVisible) { + if (isVisible) { + getView().setActivityTitle(); + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradeHeaderItem.java b/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradeHeaderItem.java new file mode 100644 index 000000000..5edabb7b8 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradeHeaderItem.java @@ -0,0 +1,117 @@ +package io.github.wulkanowy.ui.main.grades; + + +import android.content.res.Resources; +import android.view.View; +import android.widget.TextView; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; + +import java.util.List; + +import butterknife.BindView; +import butterknife.ButterKnife; +import eu.davidea.flexibleadapter.FlexibleAdapter; +import eu.davidea.flexibleadapter.items.AbstractExpandableHeaderItem; +import eu.davidea.viewholders.ExpandableViewHolder; +import io.github.wulkanowy.R; +import io.github.wulkanowy.data.db.dao.entities.Subject; +import io.github.wulkanowy.utils.AverageCalculator; + +public class GradeHeaderItem + extends AbstractExpandableHeaderItem { + + private Subject subject; + + GradeHeaderItem(Subject subject) { + this.subject = subject; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + GradeHeaderItem that = (GradeHeaderItem) o; + + return new EqualsBuilder() + .append(subject, that.subject) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(subject) + .toHashCode(); + } + + @Override + public int getLayoutRes() { + return R.layout.grade_header; + } + + @Override + public HeaderViewHolder createViewHolder(View view, FlexibleAdapter adapter) { + return new HeaderViewHolder(view, adapter); + } + + @Override + public void bindViewHolder(FlexibleAdapter adapter, HeaderViewHolder holder, int position, List payloads) { + holder.onBind(subject, getSubItems()); + } + + static class HeaderViewHolder extends ExpandableViewHolder { + + @BindView(R.id.grade_header_subject_text) + TextView subjectName; + + @BindView(R.id.grade_header_average_text) + TextView averageText; + + @BindView(R.id.grade_header_number_of_grade_text) + TextView numberText; + + @BindView(R.id.grade_header_alert_image) + View alertImage; + + Resources resources; + + HeaderViewHolder(View view, FlexibleAdapter adapter) { + super(view, adapter); + ButterKnife.bind(this, view); + resources = view.getResources(); + view.setOnClickListener(this); + } + + void onBind(Subject item, List subItems) { + subjectName.setText(item.getName()); + numberText.setText(resources.getQuantityString(R.plurals.numberOfGradesPlurals, + subItems.size(), subItems.size())); + averageText.setText(getGradesAverageString(item)); + alertImage.setVisibility(isSubItemsRead(subItems) ? View.INVISIBLE : View.VISIBLE); + alertImage.setTag(item.getName()); + } + + private boolean isSubItemsRead(List subItems) { + boolean isRead = true; + + for (GradesSubItem item : subItems) { + isRead = item.getGrade().getRead(); + } + return isRead; + } + + private String getGradesAverageString(Subject item) { + float average = AverageCalculator.calculate(item.getGradeList()); + + if (average < 0) { + return resources.getString(R.string.info_no_average); + } else { + return resources.getString(R.string.info_average_grades, average); + } + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesAdapter.java b/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesAdapter.java deleted file mode 100644 index 36742fec1..000000000 --- a/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesAdapter.java +++ /dev/null @@ -1,183 +0,0 @@ -package io.github.wulkanowy.ui.main.grades; - - -import android.app.Activity; -import android.app.DialogFragment; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.TextView; - -import com.thoughtbot.expandablerecyclerview.ExpandableRecyclerViewAdapter; -import com.thoughtbot.expandablerecyclerview.models.ExpandableGroup; -import com.thoughtbot.expandablerecyclerview.viewholders.ChildViewHolder; -import com.thoughtbot.expandablerecyclerview.viewholders.GroupViewHolder; - -import java.lang.ref.WeakReference; -import java.util.List; - -import io.github.wulkanowy.R; -import io.github.wulkanowy.db.dao.entities.Grade; -import io.github.wulkanowy.utils.AverageCalculator; - -public class GradesAdapter extends ExpandableRecyclerViewAdapter { - - private static int numberOfNotReadGrade; - - private Activity activity; - - public GradesAdapter(List groups, Activity activity) { - super(groups); - this.activity = activity; - } - - @Override - public SubjectViewHolder onCreateGroupViewHolder(ViewGroup parent, int viewType) { - View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.subject_item, parent, false); - return new SubjectViewHolder(view, activity); - } - - @Override - public GradeViewHolder onCreateChildViewHolder(ViewGroup child, int viewType) { - View view = LayoutInflater.from(child.getContext()).inflate(R.layout.grade_item, child, false); - return new GradeViewHolder(view, activity); - } - - @Override - public void onBindGroupViewHolder(SubjectViewHolder holder, int flatPosition, ExpandableGroup group) { - holder.bind(group); - } - - @Override - public void onBindChildViewHolder(GradeViewHolder holder, int flatPosition, ExpandableGroup group, int childIndex) { - holder.bind((Grade) group.getItems().get(childIndex)); - } - - public static class SubjectViewHolder extends GroupViewHolder { - - private WeakReference activity; - - private TextView subjectName; - - private TextView numberOfGrades; - - private TextView averageGrades; - - private ImageView subjectAlertNewGrades; - - public SubjectViewHolder(View itemView, Activity activity) { - super(itemView); - this.activity = new WeakReference<>(activity); - - subjectName = itemView.findViewById(R.id.subject_text); - numberOfGrades = itemView.findViewById(R.id.subject_number_of_grades); - subjectAlertNewGrades = itemView.findViewById(R.id.subject_new_grades_alert); - averageGrades = itemView.findViewById(R.id.subject_grades_average); - } - - @SuppressWarnings("unchecked") - public void bind(ExpandableGroup group) { - int volumeGrades = group.getItemCount(); - List gradeList = group.getItems(); - float average = AverageCalculator.calculate(gradeList); - - itemView.setTag(group.getTitle()); - - if (average < 0) { - averageGrades.setText(R.string.info_no_average); - } else { - averageGrades.setText(activity.get().getResources().getString(R.string.info_average_grades, average)); - } - subjectName.setText(group.getTitle()); - numberOfGrades.setText(activity.get().getResources().getQuantityString(R.plurals.numberOfGradesPlurals, volumeGrades, volumeGrades)); - - for (Grade grade : gradeList) { - if (!grade.getRead()) { - subjectAlertNewGrades.setVisibility(View.VISIBLE); - } else { - subjectAlertNewGrades.setVisibility(View.INVISIBLE); - } - } - } - } - - public static class GradeViewHolder extends ChildViewHolder { - - private WeakReference activity; - - private TextView gradeValue; - - private TextView descriptionGrade; - - private TextView dateGrade; - - private ImageView alertNewGrade; - - private View itemView; - - private Grade gradeItem; - - public GradeViewHolder(View itemView, Activity activity) { - super(itemView); - this.itemView = itemView; - this.activity = new WeakReference<>(activity); - - gradeValue = itemView.findViewById(R.id.grade_text); - descriptionGrade = itemView.findViewById(R.id.description_grade_text); - dateGrade = itemView.findViewById(R.id.grade_date_text); - alertNewGrade = itemView.findViewById(R.id.grade_new_grades_alert); - } - - public void bind(Grade grade) { - gradeValue.setText(grade.getValue()); - gradeValue.setBackgroundResource(grade.getValueColor()); - dateGrade.setText(grade.getDate()); - gradeItem = grade; - - if (grade.getDescription() == null || "".equals(grade.getDescription())) { - if (!"".equals(grade.getSymbol())) { - descriptionGrade.setText(grade.getSymbol()); - } else { - descriptionGrade.setText(R.string.noDescription_text); - } - } else { - descriptionGrade.setText(grade.getDescription()); - } - - if (gradeItem.getRead()) { - alertNewGrade.setVisibility(View.INVISIBLE); - } else { - alertNewGrade.setVisibility(View.VISIBLE); - numberOfNotReadGrade++; - } - - itemView.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - GradesDialogFragment gradesDialogFragment = GradesDialogFragment.newInstance(gradeItem); - gradesDialogFragment.setStyle(DialogFragment.STYLE_NO_TITLE, 0); - gradesDialogFragment.show(activity.get().getFragmentManager(), gradeItem.toString()); - - if (!gradeItem.getRead()) { - numberOfNotReadGrade--; - } - - if (numberOfNotReadGrade == 0) { - View subjectView = activity.get().findViewById(R.id.subject_grade_recycler).findViewWithTag(gradeItem.getSubject()); - if (subjectView != null) { - View subjectAlertNewGrade = subjectView.findViewById(R.id.subject_new_grades_alert); - subjectAlertNewGrade.setVisibility(View.INVISIBLE); - } - } - - gradeItem.setRead(true); - gradeItem.setIsNew(false); - gradeItem.update(); - alertNewGrade.setVisibility(View.INVISIBLE); - } - }); - - } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesContract.java b/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesContract.java new file mode 100644 index 000000000..7deabb5e7 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesContract.java @@ -0,0 +1,40 @@ +package io.github.wulkanowy.ui.main.grades; + +import android.support.v4.widget.SwipeRefreshLayout; + +import java.util.List; + +import io.github.wulkanowy.di.annotations.PerActivity; +import io.github.wulkanowy.ui.base.BaseContract; +import io.github.wulkanowy.ui.main.OnFragmentIsReadyListener; + +public interface GradesContract { + + interface View extends BaseContract.View, SwipeRefreshLayout.OnRefreshListener { + + void updateAdapterList(List headerItems); + + void showNoItem(boolean show); + + void onRefreshSuccessNoGrade(); + + void onRefreshSuccess(int number); + + void hideRefreshingBar(); + + void setActivityTitle(); + + boolean isMenuVisible(); + + } + + @PerActivity + interface Presenter extends BaseContract.Presenter { + + void onFragmentVisible(boolean isVisible); + + void onRefresh(); + + void onStart(View view, OnFragmentIsReadyListener listener); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesDialogFragment.java b/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesDialogFragment.java index e566d5c6a..0d9a699f5 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesDialogFragment.java +++ b/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesDialogFragment.java @@ -1,107 +1,116 @@ package io.github.wulkanowy.ui.main.grades; -import android.app.Dialog; -import android.app.DialogFragment; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.v4.app.DialogFragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.Button; import android.widget.TextView; +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; import io.github.wulkanowy.R; -import io.github.wulkanowy.db.dao.entities.Grade; +import io.github.wulkanowy.data.db.dao.entities.Grade; +import io.github.wulkanowy.utils.CommonUtils; public class GradesDialogFragment extends DialogFragment { + private static final String ARGUMENT_KEY = "Item"; + private Grade grade; + @BindView(R.id.grade_dialog_value) + TextView value; + + @BindView(R.id.grade_dialog_subject) + TextView subject; + + @BindView(R.id.grade_dialog_description_value) + TextView description; + + @BindView(R.id.grade_dialog_weight_value) + TextView weight; + + @BindView(R.id.grade_dialog_teacher_value) + TextView teacher; + + @BindView(R.id.grade_dialog_color_value) + TextView color; + + @BindView(R.id.grade_dialog_date_value) + TextView date; + public GradesDialogFragment() { - setRetainInstance(true); + //empty constructor for fragment } - public static final GradesDialogFragment newInstance(Grade grade) { - return new GradesDialogFragment().setGrade(grade); + public static GradesDialogFragment newInstance(Grade item) { + GradesDialogFragment dialogFragment = new GradesDialogFragment(); + + Bundle bundle = new Bundle(); + bundle.putSerializable(ARGUMENT_KEY, item); + + dialogFragment.setArguments(bundle); + + return dialogFragment; } - public static int colorHexToColorName(String hexColor) { - switch (hexColor) { - case "000000": - return R.string.color_black_text; - - case "F04C4C": - return R.string.color_red_text; - - case "20A4F7": - return R.string.color_blue_text; - - case "6ECD07": - return R.string.color_green_text; - - default: - return R.string.noColor_text; + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (getArguments() != null) { + grade = (Grade) getArguments().getSerializable(ARGUMENT_KEY); } } - private GradesDialogFragment setGrade(Grade grade) { - this.grade = grade; - return this; - } - @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.grades_dialog, container, false); + View view = inflater.inflate(R.layout.grade_dialog, container, false); - TextView gradeText = view.findViewById(R.id.dialog_grade_text); - TextView subjectText = view.findViewById(R.id.subject_dialog_text_value); - TextView descriptionText = view.findViewById(R.id.description_dialog_text_value); - TextView weightText = view.findViewById(R.id.weight_dialog_text_value); - TextView teacherText = view.findViewById(R.id.teacher_dialog_text_value); - TextView dateText = view.findViewById(R.id.date_dialog_text_value); - TextView colorText = view.findViewById(R.id.color_dialog_text_value); - Button closeDialog = view.findViewById(R.id.close_dialog); + ButterKnife.bind(this, view); - subjectText.setText(grade.getSubject()); - gradeText.setText(grade.getValue()); - gradeText.setBackgroundResource(grade.getValueColor()); - weightText.setText(grade.getWeight()); - dateText.setText(grade.getDate()); - colorText.setText(colorHexToColorName(grade.getColor())); + subject.setText(grade.getSubject()); + value.setText(grade.getValue()); + value.setBackgroundResource(grade.getValueColor()); + weight.setText(grade.getWeight()); + date.setText(grade.getDate()); + color.setText(CommonUtils.colorHexToColorName(grade.getColor())); + teacher.setText(getTeacherString()); + description.setText(getDescriptionString()); - if ("".equals(grade.getDescription())) { - if (!"".equals(grade.getSymbol())) { - descriptionText.setText(grade.getSymbol()); - } - } else if (!"".equals(grade.getSymbol())) { - descriptionText.setText(String.format("%1$s - %2$s", grade.getSymbol(), grade.getDescription())); - } else { - descriptionText.setText(grade.getDescription()); - } - - if (!"".equals(grade.getTeacher())) { - teacherText.setText(grade.getTeacher()); - } - - closeDialog.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - dismiss(); - } - }); return view; } - @Override - public void onDestroyView() { - Dialog dialog = getDialog(); - if (dialog != null && getRetainInstance()) { - dialog.setDismissMessage(null); + @OnClick(R.id.grade_dialog_close_button) + void onClickClose() { + dismiss(); + } + + private String getDescriptionString() { + if ("".equals(grade.getDescription())) { + if (!"".equals(grade.getSymbol())) { + return grade.getSymbol(); + } else { + return getString(R.string.noDescription_text); + } + } else if (!"".equals(grade.getSymbol())) { + return String.format("%1$s - %2$s", grade.getSymbol(), grade.getDescription()); + } else { + return grade.getDescription(); + } + } + + private String getTeacherString() { + if (grade.getTeacher() != null && !"".equals(grade.getTeacher())) { + return grade.getTeacher(); + } else { + return getString(R.string.generic_app_no_data); } - super.onDestroyView(); } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesFragment.java b/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesFragment.java index 074837766..bd44f2983 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesFragment.java +++ b/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesFragment.java @@ -1,211 +1,133 @@ package io.github.wulkanowy.ui.main.grades; -import android.app.Activity; -import android.content.Context; -import android.os.AsyncTask; import android.os.Bundle; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.support.design.widget.Snackbar; -import android.support.v4.app.Fragment; import android.support.v4.widget.SwipeRefreshLayout; -import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; -import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.Toast; -import java.lang.ref.WeakReference; -import java.util.ArrayList; import java.util.List; +import javax.inject.Inject; + +import butterknife.BindView; +import butterknife.ButterKnife; +import eu.davidea.flexibleadapter.FlexibleAdapter; +import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager; import io.github.wulkanowy.R; -import io.github.wulkanowy.WulkanowyApp; -import io.github.wulkanowy.api.Vulcan; -import io.github.wulkanowy.api.login.VulcanOfflineException; -import io.github.wulkanowy.db.dao.DatabaseAccess; -import io.github.wulkanowy.db.dao.entities.Account; -import io.github.wulkanowy.db.dao.entities.AccountDao; -import io.github.wulkanowy.db.dao.entities.DaoSession; -import io.github.wulkanowy.db.dao.entities.Grade; -import io.github.wulkanowy.db.dao.entities.Subject; -import io.github.wulkanowy.services.jobs.VulcanJobHelper; -import io.github.wulkanowy.services.sync.LoginSession; -import io.github.wulkanowy.services.sync.VulcanSync; -import io.github.wulkanowy.utils.NetworkUtils; +import io.github.wulkanowy.di.component.FragmentComponent; +import io.github.wulkanowy.ui.base.BaseFragment; +import io.github.wulkanowy.ui.main.OnFragmentIsReadyListener; -public class GradesFragment extends Fragment { +public class GradesFragment extends BaseFragment implements GradesContract.View { - private static List subjectWithGradesList = new ArrayList<>(); + @BindView(R.id.grade_fragment_recycler) + RecyclerView recyclerView; - private static long userId; + @BindView(R.id.grade_fragment_no_item_container) + View noItemView; + + @BindView(R.id.grade_fragment_swipe_refresh) + SwipeRefreshLayout refreshLayout; + + @Inject + FlexibleAdapter adapter; + + @Inject + GradesContract.Presenter presenter; public GradesFragment() { - //empty constructor for fragments + // empty constructor for fragment } - private static void createExpList(View mainView, Activity activity) { + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_grades, container, false); - RecyclerView recyclerView = mainView.findViewById(R.id.subject_grade_recycler); - recyclerView.setLayoutManager(new LinearLayoutManager(activity)); - GradesAdapter gradesAdapter = new GradesAdapter(subjectWithGradesList, activity); - recyclerView.setAdapter(gradesAdapter); + FragmentComponent component = getFragmentComponent(); + if (component != null) { + component.inject(this); + setButterKnife(ButterKnife.bind(this, view)); + presenter.onStart(this, (OnFragmentIsReadyListener) getActivity()); + } + + return view; } - private static void downloadGradesFormDatabase(DaoSession daoSession) { + @Override + protected void setUpOnViewCreated(View fragmentView) { + noItemView.setVisibility(View.GONE); - subjectWithGradesList = new ArrayList<>(); + adapter.setAutoCollapseOnExpand(true); + adapter.setAutoScrollOnExpand(true); + adapter.expandItemsAtStartUp(); - AccountDao accountDao = daoSession.getAccountDao(); - Account account = accountDao.load(userId); + recyclerView.setLayoutManager(new SmoothScrollLinearLayoutManager(fragmentView.getContext())); + recyclerView.setAdapter(adapter); - for (Subject subject : account.getSubjectList()) { - List gradeList = subject.getGradeList(); - if (!gradeList.isEmpty()) { - SubjectWithGrades subjectWithGrades = new SubjectWithGrades(subject.getName(), gradeList); - subjectWithGradesList.add(subjectWithGrades); - } + refreshLayout.setColorSchemeResources(android.R.color.black); + refreshLayout.setOnRefreshListener(this); + } + + @Override + public void setMenuVisibility(boolean menuVisible) { + super.setMenuVisibility(menuVisible); + if (presenter != null) { + presenter.onFragmentVisible(menuVisible); } } @Override - public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.fragment_grades, container, false); - view.findViewById(R.id.fragment_no_grades).setVisibility(View.GONE); + public void setActivityTitle() { + setTitle(getString(R.string.grades_text)); + } + @Override + public void onRefresh() { + presenter.onRefresh(); + } + + @Override + public void showNoItem(boolean show) { + noItemView.setVisibility(show ? View.VISIBLE : View.INVISIBLE); + } + + @Override + public void hideRefreshingBar() { + refreshLayout.setRefreshing(false); + } + + @Override + public void updateAdapterList(List headerItems) { + adapter.updateDataSet(headerItems); + } + + @Override + public void onRefreshSuccessNoGrade() { + onError(R.string.snackbar_no_grades); + } + + @Override + public void onRefreshSuccess(int number) { + onError(getString(R.string.snackbar_new_grade, number)); + } + + @Override + public void onError(String message) { if (getActivity() != null) { - DaoSession daoSession = ((WulkanowyApp) getActivity().getApplication()).getDaoSession(); - userId = getActivity().getSharedPreferences("LoginData", Context.MODE_PRIVATE) - .getLong("userId", 0); - - prepareRefreshLayout(view, daoSession); - - if (subjectWithGradesList.equals(new ArrayList<>())) { - createExpList(view, getActivity()); - new GenerateListTask(getActivity(), view, daoSession).execute(); - } else { - createExpList(view, getActivity()); - view.findViewById(R.id.loadingPanel).setVisibility(View.INVISIBLE); - } - } - return view; - } - - private void prepareRefreshLayout(final View mainView, final DaoSession daoSession) { - - final SwipeRefreshLayout swipeRefreshLayout = mainView.findViewById(R.id.grade_swipe_refresh); - - swipeRefreshLayout.setColorSchemeResources(android.R.color.black, - android.R.color.holo_blue_bright, - android.R.color.holo_green_light, - android.R.color.holo_orange_light, - android.R.color.holo_red_light); - - swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { - @Override - public void onRefresh() { - if (NetworkUtils.isOnline(getContext())) { - new RefreshTask(getActivity(), mainView, daoSession).execute(); - } else { - Toast.makeText(mainView.getContext(), R.string.noInternet_text, Toast.LENGTH_SHORT).show(); - swipeRefreshLayout.setRefreshing(false); - } - } - }); - } - - private static class GenerateListTask extends AsyncTask { - - private WeakReference mainView; - - private WeakReference activity; - - private DaoSession daoSession; - - public GenerateListTask(Activity activity, View mainView, DaoSession daoSession) { - this.activity = new WeakReference<>(activity); - this.mainView = new WeakReference<>(mainView); - this.daoSession = daoSession; - } - - @Override - protected Void doInBackground(Void... params) { - downloadGradesFormDatabase(daoSession); - return null; - } - - @Override - protected void onPostExecute(Void result) { - super.onPostExecute(result); - createExpList(mainView.get(), activity.get()); - mainView.get().findViewById(R.id.loadingPanel).setVisibility(View.INVISIBLE); - if (subjectWithGradesList.isEmpty()) { - mainView.get().findViewById(R.id.fragment_no_grades).setVisibility(View.VISIBLE); - } + Snackbar.make(getActivity().findViewById(R.id.main_activity_view_pager), + message, Snackbar.LENGTH_LONG).show(); } } - private static class RefreshTask extends AsyncTask { - - private DaoSession daoSession; - - private WeakReference activity; - - private WeakReference mainView; - - public RefreshTask(Activity activity, View mainView, DaoSession daoSession) { - this.activity = new WeakReference<>(activity); - this.daoSession = daoSession; - this.mainView = new WeakReference<>(mainView); - } - - @Override - protected Integer doInBackground(Void... params) { - VulcanSync vulcanSync = new VulcanSync(new LoginSession()); - try { - vulcanSync.loginCurrentUser(activity.get(), daoSession, new Vulcan()); - vulcanSync.syncGrades(); - downloadGradesFormDatabase(daoSession); - return 1; - } catch (VulcanOfflineException e) { - Log.e(VulcanJobHelper.DEBUG_TAG, "There was a sync problem, because vulcan is offline", e); - return R.string.error_host_offline; - } catch (Exception e) { - Log.e(VulcanJobHelper.DEBUG_TAG, "There was a sync problem", e); - return R.string.refresh_error_text; - } - } - - @Override - protected void onPostExecute(Integer messageID) { - super.onPostExecute(messageID); - - if (1 == messageID) { - if (mainView.get() != null && activity.get() != null) { - createExpList(mainView.get(), activity.get()); - } - - int volumeGrades = new DatabaseAccess().getNewGrades(daoSession).size(); - - if (volumeGrades == 0) { - Snackbar.make(activity.get().findViewById(R.id.fragment_container), - R.string.snackbar_no_grades, - Snackbar.LENGTH_SHORT).show(); - } else { - Snackbar.make(activity.get().findViewById(R.id.fragment_container), - activity.get().getString(R.string.snackbar_new_grade, volumeGrades), - Snackbar.LENGTH_SHORT).show(); - } - } else { - Toast.makeText(activity.get(), messageID, Toast.LENGTH_SHORT).show(); - } - - if (mainView.get() != null) { - SwipeRefreshLayout swipeRefreshLayout = mainView.get().findViewById(R.id.grade_swipe_refresh); - swipeRefreshLayout.setRefreshing(false); - } - } + @Override + public void onDestroyView() { + presenter.onDestroy(); + super.onDestroyView(); } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesPresenter.java b/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesPresenter.java new file mode 100644 index 000000000..cae2fbcb6 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesPresenter.java @@ -0,0 +1,157 @@ +package io.github.wulkanowy.ui.main.grades; + +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; + +import io.github.wulkanowy.data.RepositoryContract; +import io.github.wulkanowy.data.db.dao.entities.Grade; +import io.github.wulkanowy.data.db.dao.entities.Subject; +import io.github.wulkanowy.ui.base.BasePresenter; +import io.github.wulkanowy.ui.main.OnFragmentIsReadyListener; +import io.github.wulkanowy.utils.async.AbstractTask; +import io.github.wulkanowy.utils.async.AsyncListeners; + +public class GradesPresenter extends BasePresenter + implements GradesContract.Presenter, AsyncListeners.OnRefreshListener, + AsyncListeners.OnFirstLoadingListener { + + private AbstractTask refreshTask; + + private AbstractTask loadingTask; + + private OnFragmentIsReadyListener listener; + + private List headerItems = new ArrayList<>(); + + private boolean isFirstSight = false; + + @Inject + GradesPresenter(RepositoryContract repository) { + super(repository); + } + + @Override + public void onStart(GradesContract.View view, OnFragmentIsReadyListener listener) { + super.onStart(view); + this.listener = listener; + + if (getView().isMenuVisible()) { + getView().setActivityTitle(); + } + + if (!isFirstSight) { + isFirstSight = true; + + loadingTask = new AbstractTask(); + loadingTask.setOnFirstLoadingListener(this); + loadingTask.execute(); + } + } + + @Override + public void onFragmentVisible(boolean isVisible) { + if (isVisible) { + getView().setActivityTitle(); + } + } + + @Override + public void onRefresh() { + if (getView().isNetworkConnected()) { + refreshTask = new AbstractTask(); + refreshTask.setOnRefreshListener(this); + refreshTask.execute(); + } else { + getView().onNoNetworkError(); + getView().hideRefreshingBar(); + } + } + + @Override + public void onDoInBackgroundRefresh() throws Exception { + getRepository().loginCurrentUser(); + getRepository().syncSubjects(); + getRepository().syncGrades(); + } + + @Override + public void onCanceledRefreshAsync() { + getView().hideRefreshingBar(); + } + + @Override + public void onEndRefreshAsync(boolean success, Exception exception) { + if (success) { + loadingTask = new AbstractTask(); + loadingTask.setOnFirstLoadingListener(this); + loadingTask.execute(); + + int numberOfNewGrades = getRepository().getNewGrades().size(); + + if (numberOfNewGrades <= 0) { + getView().onRefreshSuccessNoGrade(); + } else { + getView().onRefreshSuccess(numberOfNewGrades); + } + } else { + getView().onError(getRepository().getErrorLoginMessage(exception)); + } + getView().hideRefreshingBar(); + } + + @Override + public void onDoInBackgroundLoading() throws Exception { + List subjectList = getRepository().getCurrentUser().getSubjectList(); + + headerItems = new ArrayList<>(); + + for (Subject subject : subjectList) { + List gradeList = subject.getGradeList(); + + if (!gradeList.isEmpty()) { + GradeHeaderItem headerItem = new GradeHeaderItem(subject); + + List subItems = new ArrayList<>(); + + for (Grade grade : gradeList) { + subItems.add(new GradesSubItem(headerItem, grade)); + } + + headerItem.setSubItems(subItems); + headerItem.setExpanded(false); + headerItems.add(headerItem); + } + } + } + + @Override + public void onCanceledLoadingAsync() { + // do nothing + } + + @Override + public void onEndLoadingAsync(boolean result, Exception exception) { + if (headerItems.isEmpty()) { + getView().showNoItem(true); + } else { + getView().updateAdapterList(headerItems); + getView().showNoItem(false); + listener.onFragmentIsReady(); + } + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (refreshTask != null) { + refreshTask.cancel(true); + refreshTask = null; + } + if (loadingTask != null) { + loadingTask.cancel(true); + loadingTask = null; + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesSubItem.java b/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesSubItem.java new file mode 100644 index 000000000..c252bb2ad --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesSubItem.java @@ -0,0 +1,150 @@ +package io.github.wulkanowy.ui.main.grades; + +import android.app.Activity; +import android.content.Context; +import android.support.v4.app.DialogFragment; +import android.support.v4.app.FragmentActivity; +import android.view.View; +import android.widget.TextView; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; + +import java.util.List; + +import butterknife.BindView; +import butterknife.ButterKnife; +import eu.davidea.flexibleadapter.FlexibleAdapter; +import eu.davidea.flexibleadapter.items.AbstractSectionableItem; +import eu.davidea.viewholders.FlexibleViewHolder; +import io.github.wulkanowy.R; +import io.github.wulkanowy.data.db.dao.entities.Grade; + +public class GradesSubItem + extends AbstractSectionableItem { + + private Grade grade; + + private static int numberOfNotReadGrade; + + GradesSubItem(GradeHeaderItem header, Grade grade) { + super(header); + this.grade = grade; + } + + public Grade getGrade() { + return grade; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + GradesSubItem that = (GradesSubItem) o; + + return new EqualsBuilder() + .append(grade, that.grade) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(grade) + .toHashCode(); + } + + @Override + public int getLayoutRes() { + return R.layout.grade_subitem; + } + + @Override + public SubItemViewHolder createViewHolder(View view, FlexibleAdapter adapter) { + return new SubItemViewHolder(view, adapter); + } + + @Override + public void bindViewHolder(FlexibleAdapter adapter, SubItemViewHolder holder, int position, List payloads) { + holder.onBind(grade); + } + + static class SubItemViewHolder extends FlexibleViewHolder { + + @BindView(R.id.grade_subitem_value) + TextView value; + + @BindView(R.id.grade_subitem_description) + TextView description; + + @BindView(R.id.grade_subitem_date) + TextView date; + + @BindView(R.id.grade_subitem_alert_image) + View alert; + + private Context context; + + private Grade item; + + SubItemViewHolder(View view, FlexibleAdapter adapter) { + super(view, adapter); + ButterKnife.bind(this, view); + context = view.getContext(); + view.setOnClickListener(this); + } + + void onBind(Grade item) { + this.item = item; + + value.setText(item.getValue()); + value.setBackgroundResource(item.getValueColor()); + date.setText(item.getDate()); + description.setText(getDescriptionString()); + alert.setVisibility(item.getRead() ? View.INVISIBLE : View.VISIBLE); + + if (!item.getRead()) { + numberOfNotReadGrade++; + } + } + + @Override + public void onClick(View view) { + super.onClick(view); + showDialog(); + + if (!item.getRead()) { + numberOfNotReadGrade--; + + if (numberOfNotReadGrade == 0) { + ((Activity) context).findViewById(R.id.grade_fragment_container) + .findViewWithTag(item.getSubject()).setVisibility(View.INVISIBLE); + } + item.setIsNew(false); + item.setRead(true); + item.update(); + alert.setVisibility(View.INVISIBLE); + } + } + + private String getDescriptionString() { + if (item.getDescription() == null || "".equals(item.getDescription())) { + if (!"".equals(item.getSymbol())) { + return item.getSymbol(); + } else { + return context.getString(R.string.noDescription_text); + } + } else { + return item.getDescription(); + } + } + + private void showDialog() { + GradesDialogFragment dialogFragment = GradesDialogFragment.newInstance(item); + dialogFragment.setStyle(DialogFragment.STYLE_NO_TITLE, 0); + dialogFragment.show(((FragmentActivity) context).getSupportFragmentManager(), item.toString()); + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/grades/SubjectWithGrades.java b/app/src/main/java/io/github/wulkanowy/ui/main/grades/SubjectWithGrades.java deleted file mode 100644 index b343c2006..000000000 --- a/app/src/main/java/io/github/wulkanowy/ui/main/grades/SubjectWithGrades.java +++ /dev/null @@ -1,15 +0,0 @@ -package io.github.wulkanowy.ui.main.grades; - - -import com.thoughtbot.expandablerecyclerview.models.ExpandableGroup; - -import java.util.List; - -import io.github.wulkanowy.db.dao.entities.Grade; - -public class SubjectWithGrades extends ExpandableGroup { - - public SubjectWithGrades(String title, List items) { - super(title, items); - } -} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TabsData.java b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TabsData.java new file mode 100644 index 000000000..156d1759b --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TabsData.java @@ -0,0 +1,37 @@ +package io.github.wulkanowy.ui.main.timetable; + +import android.support.v4.app.Fragment; + +import java.util.ArrayList; +import java.util.List; + +class TabsData { + + private List fragments = new ArrayList<>(); + + private List titles = new ArrayList<>(); + + Fragment getFragment(int index) { + return fragments.get(index); + } + + void addFragment(Fragment fragment) { + if (fragment != null) { + fragments.add(fragment); + } + } + + int getFragmentsCount() { + return fragments.size(); + } + + String getTitle(int index) { + return titles.get(index); + } + + void addTitle(String title) { + if (title != null) { + titles.add(title); + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableContract.java b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableContract.java new file mode 100644 index 000000000..18fa33f96 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableContract.java @@ -0,0 +1,35 @@ +package io.github.wulkanowy.ui.main.timetable; + +import io.github.wulkanowy.di.annotations.PerActivity; +import io.github.wulkanowy.ui.base.BaseContract; +import io.github.wulkanowy.ui.main.OnFragmentIsReadyListener; + +public interface TimetableContract { + + interface View extends BaseContract.View { + + void setActivityTitle(); + + void scrollViewPagerToPosition(int position); + + void setTabDataToAdapter(TabsData tabsData); + + void setAdapterWithTabLayout(); + + void setChildFragmentSelected(int position, boolean selected); + + boolean isMenuVisible(); + } + + @PerActivity + interface Presenter extends BaseContract.Presenter { + + void onFragmentVisible(boolean isVisible); + + void onTabSelected(int position); + + void onTabUnselected(int position); + + void onStart(View view, OnFragmentIsReadyListener listener); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableDialogFragment.java b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableDialogFragment.java index 3845d26c0..cfc1e0739 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableDialogFragment.java +++ b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableDialogFragment.java @@ -1,6 +1,5 @@ package io.github.wulkanowy.ui.main.timetable; -import android.app.Dialog; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; @@ -8,93 +7,121 @@ import android.support.v4.app.DialogFragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.Button; import android.widget.TextView; import org.apache.commons.lang3.StringUtils; +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; import io.github.wulkanowy.R; -import io.github.wulkanowy.db.dao.entities.Lesson; +import io.github.wulkanowy.data.db.dao.entities.Lesson; public class TimetableDialogFragment extends DialogFragment { + private static final String ARGUMENT_KEY = "Item"; + private Lesson lesson; + @BindView(R.id.timetable_dialog_lesson_value) + TextView lessonName; + + @BindView(R.id.timetable_dialog_teacher_value) + TextView teacher; + + @BindView(R.id.timetable_dialog_group_value) + TextView group; + + @BindView(R.id.timetable_dialog_room_value) + TextView room; + + @BindView(R.id.timetable_dialog_time_value) + TextView time; + + @BindView(R.id.timetable_dialog_description_value) + TextView description; + + @BindView(R.id.timetable_dialog_description) + View descriptionLabel; + + @BindView(R.id.timetable_dialog_teacher) + View teacherLabel; + + @BindView(R.id.timetable_dialog_group) + View groupLabel; + public TimetableDialogFragment() { //empty constructor for fragment } public static TimetableDialogFragment newInstance(Lesson lesson) { - return new TimetableDialogFragment().setLesson(lesson); + TimetableDialogFragment dialogFragment = new TimetableDialogFragment(); + + Bundle bundle = new Bundle(); + bundle.putSerializable(ARGUMENT_KEY, lesson); + + dialogFragment.setArguments(bundle); + + return dialogFragment; } - private TimetableDialogFragment setLesson(Lesson lesson) { - this.lesson = lesson; - return this; + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (getArguments() != null) { + lesson = (Lesson) getArguments().getSerializable(ARGUMENT_KEY); + } } @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view = inflater.inflate(R.layout.timetable_dialog, container, false); - setRetainInstance(true); - TextView lessonText = view.findViewById(R.id.timetable_dialog_lesson_value); - TextView teacherText = view.findViewById(R.id.timetable_dialog_teacher_value); - TextView groupText = view.findViewById(R.id.timetable_dialog_group_value); - TextView roomText = view.findViewById(R.id.timetable_dialog_room_value); - TextView timeText = view.findViewById(R.id.timetable_dialog_time_value); - TextView descriptionText = view.findViewById(R.id.timetable_dialog_description_value); - Button closeButton = view.findViewById(R.id.timetable_dialog_close); + ButterKnife.bind(this, view); if (!lesson.getSubject().isEmpty()) { - lessonText.setText(lesson.getSubject()); + lessonName.setText(lesson.getSubject()); } if (!lesson.getTeacher().isEmpty()) { - teacherText.setText(lesson.getTeacher()); + teacher.setText(lesson.getTeacher()); } else { - teacherText.setVisibility(View.GONE); - view.findViewById(R.id.timetable_dialog_teacher).setVisibility(View.GONE); + teacher.setVisibility(View.GONE); + teacherLabel.setVisibility(View.GONE); } if (!lesson.getGroupName().isEmpty()) { - groupText.setText(lesson.getGroupName()); + group.setText(lesson.getGroupName()); } else { - groupText.setVisibility(View.GONE); - view.findViewById(R.id.timetable_dialog_group).setVisibility(View.GONE); + group.setVisibility(View.GONE); + groupLabel.setVisibility(View.GONE); } if (!lesson.getRoom().isEmpty()) { - roomText.setText(lesson.getRoom()); + room.setText(lesson.getRoom()); } if (!lesson.getEndTime().isEmpty() && !lesson.getStartTime().isEmpty()) { - timeText.setText(String.format("%1$s - %2$s", lesson.getStartTime(), lesson.getEndTime())); + time.setText(getTimeString()); } if (!lesson.getDescription().isEmpty()) { - descriptionText.setText(StringUtils.capitalize(lesson.getDescription())); + description.setText(StringUtils.capitalize(lesson.getDescription())); } else { - descriptionText.setVisibility(View.GONE); - view.findViewById(R.id.timetable_dialog_description).setVisibility(View.GONE); + description.setVisibility(View.GONE); + descriptionLabel.setVisibility(View.GONE); } - closeButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - dismiss(); - } - }); return view; } - @Override - public void onDestroyView() { - Dialog dialog = getDialog(); - if (dialog != null && getRetainInstance()) { - dialog.setDismissMessage(null); - } - super.onDestroyView(); + private String getTimeString() { + return String.format("%1$s - %2$s", lesson.getStartTime(), lesson.getEndTime()); + } + + @OnClick(R.id.timetable_dialog_close) + void onClickCloseButton() { + dismiss(); } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableFragment.java b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableFragment.java index ec25c7dd7..a9a833917 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableFragment.java +++ b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableFragment.java @@ -1,121 +1,114 @@ package io.github.wulkanowy.ui.main.timetable; -import android.os.AsyncTask; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.design.widget.Snackbar; import android.support.design.widget.TabLayout; -import android.support.v4.app.Fragment; import android.support.v4.view.ViewPager; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import org.joda.time.DateTime; -import org.joda.time.DateTimeConstants; - -import java.util.ArrayList; -import java.util.List; +import javax.inject.Inject; +import butterknife.BindView; +import butterknife.ButterKnife; import io.github.wulkanowy.R; -import io.github.wulkanowy.utils.TimeUtils; +import io.github.wulkanowy.di.component.FragmentComponent; +import io.github.wulkanowy.ui.base.BaseFragment; +import io.github.wulkanowy.ui.main.OnFragmentIsReadyListener; -public class TimetableFragment extends Fragment { +public class TimetableFragment extends BaseFragment implements TimetableContract.View, TabLayout.OnTabSelectedListener { - private final String DATE_PATTERN = "yyyy-MM-dd"; + @BindView(R.id.timetable_fragment_viewpager) + ViewPager viewPager; - private List dateStringList = new ArrayList<>(); + @BindView(R.id.timetable_fragment_tab_layout) + TabLayout tabLayout; - private TimetablePagerAdapter pagerAdapter; + @Inject + TimetablePagerAdapter pagerAdapter; - private ViewPager viewPager; - - private TabLayout tabLayout; - - public TimetableFragment() { - //empty constructor for fragment - } - - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setRetainInstance(true); - } + @Inject + TimetableContract.Presenter presenter; @Nullable @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, - @Nullable Bundle savedInstanceState) { + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_timetable, container, false); - viewPager = view.findViewById(R.id.timetable_fragment_viewpager); - tabLayout = view.findViewById(R.id.timetable_fragment_tab); - new CreateTabTask(this).execute(); + + FragmentComponent component = getFragmentComponent(); + if (component != null) { + component.inject(this); + setButterKnife(ButterKnife.bind(this, view)); + presenter.onStart(this, (OnFragmentIsReadyListener) getActivity()); + } return view; } - private TimetablePagerAdapter getPagerAdapter() { - TimetablePagerAdapter pagerAdapter = new TimetablePagerAdapter(getChildFragmentManager()); - for (String date : dateStringList) { - pagerAdapter.addFragment(TimetableFragmentTab.newInstance(date), date); + @Override + public void setMenuVisibility(boolean menuVisible) { + super.setMenuVisibility(menuVisible); + if (presenter != null) { + presenter.onFragmentVisible(menuVisible); } - return pagerAdapter; } - private String getDateOfCurrentMonday() { - DateTime currentDate = new DateTime(); - - if (currentDate.getDayOfWeek() == DateTimeConstants.SATURDAY) { - currentDate = currentDate.plusDays(2); - } else if (currentDate.getDayOfWeek() == DateTimeConstants.SUNDAY) { - currentDate = currentDate.plusDays(1); - } else { - currentDate = currentDate.withDayOfWeek(DateTimeConstants.MONDAY); - } - return currentDate.toString(DATE_PATTERN); + @Override + public void onTabSelected(TabLayout.Tab tab) { + presenter.onTabSelected(tab.getPosition()); } - private void setAdapterOnViewPager() { + @Override + public void onTabUnselected(TabLayout.Tab tab) { + presenter.onTabUnselected(tab.getPosition()); + } + + @Override + public void onTabReselected(TabLayout.Tab tab) { + //do nothing + } + + @Override + public void setTabDataToAdapter(TabsData tabsData) { + pagerAdapter.setTabsData(tabsData); + } + + @Override + public void setAdapterWithTabLayout() { viewPager.setAdapter(pagerAdapter); - viewPager.setCurrentItem(dateStringList.indexOf(getDateOfCurrentMonday())); - } - private void setDateStringList() { - if (dateStringList.isEmpty()) { - dateStringList = TimeUtils.getMondaysFromCurrentSchoolYear(DATE_PATTERN); - } - } - - private void setViewPagerOnTabLayout() { tabLayout.setupWithViewPager(viewPager); + tabLayout.addOnTabSelectedListener(this); } - protected final void setLoadingBarInvisible() { - if (getView() != null) { - getView().findViewById(R.id.timetable_tab_progress_bar).setVisibility(View.GONE); + @Override + public void setChildFragmentSelected(int position, boolean selected) { + ((TimetableTabFragment) pagerAdapter.getItem(position)).setSelected(selected); + } + + @Override + public void scrollViewPagerToPosition(int position) { + viewPager.setCurrentItem(position, false); + } + + @Override + public void setActivityTitle() { + setTitle(getString(R.string.lessonplan_text)); + } + + @Override + public void onError(String message) { + if (getActivity() != null) { + Snackbar.make(getActivity().findViewById(R.id.main_activity_view_pager), + message, Snackbar.LENGTH_LONG).show(); } } - private static class CreateTabTask extends AsyncTask { - - private TimetableFragment fragment; - - public CreateTabTask(TimetableFragment fragment) { - this.fragment = fragment; - } - - @Override - protected Void doInBackground(Void... voids) { - fragment.setDateStringList(); - fragment.pagerAdapter = fragment.getPagerAdapter(); - return null; - } - - @Override - protected void onPostExecute(Void aVoid) { - super.onPostExecute(aVoid); - fragment.setAdapterOnViewPager(); - fragment.setViewPagerOnTabLayout(); - fragment.setLoadingBarInvisible(); - } + @Override + public void onDestroyView() { + presenter.onDestroy(); + super.onDestroyView(); } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableFragmentTab.java b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableFragmentTab.java deleted file mode 100644 index 12a38daa3..000000000 --- a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableFragmentTab.java +++ /dev/null @@ -1,161 +0,0 @@ -package io.github.wulkanowy.ui.main.timetable; - -import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.design.widget.Snackbar; -import android.support.v7.widget.RecyclerView; - -import org.joda.time.DateTime; -import org.joda.time.DateTimeComparator; -import org.joda.time.DateTimeConstants; -import org.joda.time.LocalDate; -import org.joda.time.format.DateTimeFormat; -import org.joda.time.format.DateTimeFormatter; - -import java.util.ArrayList; -import java.util.List; - -import io.github.wulkanowy.R; -import io.github.wulkanowy.db.dao.entities.Day; -import io.github.wulkanowy.db.dao.entities.Lesson; -import io.github.wulkanowy.db.dao.entities.Week; -import io.github.wulkanowy.db.dao.entities.WeekDao; -import io.github.wulkanowy.services.sync.VulcanSync; -import io.github.wulkanowy.ui.main.AbstractFragment; - -public class TimetableFragmentTab extends AbstractFragment { - - private final String DATE_PATTERN = "yyyy-MM-dd"; - - private int positionToScroll; - - private String date; - - public static TimetableFragmentTab newInstance(String date) { - TimetableFragmentTab fragmentTab = new TimetableFragmentTab(); - - Bundle argument = new Bundle(); - argument.putString("date", date); - fragmentTab.setArguments(argument); - - return fragmentTab; - } - - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - if (getArguments() != null) { - date = getArguments().getString("date"); - } - } - - @Override - public int getLayoutId() { - return R.layout.fragment_timetable_tab; - } - - @Override - public int getRecyclerViewId() { - return R.id.timetable_recycler; - } - - @Override - public int getRefreshLayoutId() { - return R.id.timetable_refresh_layout; - } - - @Override - public int getLoadingBarId() { - return R.id.timetable_progress_bar; - } - - @NonNull - @Override - public List getItems() throws Exception { - Week week = getWeek(); - - if (week == null) { - onRefresh(); - return getItems(); - } - - List dayEntityList = week.getDayList(); - - List dayList = new ArrayList<>(); - - int iterator = -1; - - for (Day day : dayEntityList) { - List timetableSubItems = new ArrayList<>(); - - TimetableHeaderItem headerItem = new TimetableHeaderItem(day); - - for (Lesson lesson : day.getLessons()) { - TimetableSubItem subItem = new TimetableSubItem(headerItem, lesson, getFragmentManager()); - timetableSubItems.add(subItem); - } - - iterator++; - - boolean isExpanded = getExpanded(day.getDate()); - - if (isExpanded) { - positionToScroll = iterator; - } - - headerItem.setExpanded(isExpanded); - headerItem.setSubItems(timetableSubItems); - dayList.add(headerItem); - } - return dayList; - } - - @Override - protected void setAdapterOnRecyclerView(@NonNull RecyclerView recyclerView) { - super.setAdapterOnRecyclerView(recyclerView); - recyclerView.scrollToPosition(positionToScroll); - } - - @Override - public void onRefresh() throws Exception { - VulcanSync synchronization = new VulcanSync(); - synchronization.loginCurrentUser(getContext(), getDaoSession()); - synchronization.syncTimetable(date); - } - - @Override - public void onPostRefresh(int stringResult) { - if (stringResult == 0) { - stringResult = R.string.timetable_refresh_success; - } - Snackbar.make(getActivityWeakReference().findViewById(R.id.fragment_container), - stringResult, Snackbar.LENGTH_SHORT).show(); - } - - private boolean getExpanded(String dayDate) { - DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(DATE_PATTERN); - DateTime dayTime = dateTimeFormatter.parseDateTime(dayDate); - - DateTime currentDate = new DateTime(); - - if (currentDate.getDayOfWeek() == DateTimeConstants.SATURDAY) { - currentDate = currentDate.plusDays(2); - } else if (currentDate.getDayOfWeek() == DateTimeConstants.SUNDAY) { - currentDate = currentDate.plusDays(1); - } - - return DateTimeComparator.getDateOnlyInstance().compare(currentDate, dayTime) == 0; - } - - private Week getWeek() { - if (date == null) { - LocalDate currentMonday = new LocalDate().withDayOfWeek(DateTimeConstants.MONDAY); - date = currentMonday.toString(DATE_PATTERN); - } - return getDaoSession().getWeekDao().queryBuilder() - .where(WeekDao.Properties.StartDayDate.eq(date), - WeekDao.Properties.UserId.eq(getUserId())) - .unique(); - } -} \ No newline at end of file diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableHeaderItem.java b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableHeaderItem.java index e40c6689a..c418d9acd 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableHeaderItem.java +++ b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableHeaderItem.java @@ -5,6 +5,8 @@ import android.widget.ImageView; import android.widget.TextView; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; import java.util.List; @@ -14,20 +16,35 @@ import eu.davidea.flexibleadapter.FlexibleAdapter; import eu.davidea.flexibleadapter.items.AbstractExpandableHeaderItem; import eu.davidea.viewholders.ExpandableViewHolder; import io.github.wulkanowy.R; -import io.github.wulkanowy.db.dao.entities.Day; +import io.github.wulkanowy.data.db.dao.entities.Day; public class TimetableHeaderItem extends AbstractExpandableHeaderItem { private Day day; - public TimetableHeaderItem(Day day) { + TimetableHeaderItem(Day day) { this.day = day; } @Override public boolean equals(Object o) { - return this == o; + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + TimetableHeaderItem that = (TimetableHeaderItem) o; + + return new EqualsBuilder() + .append(day, that.day) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(day) + .toHashCode(); } @Override @@ -42,45 +59,42 @@ public class TimetableHeaderItem @Override public void bindViewHolder(FlexibleAdapter adapter, HeaderViewHolder holder, int position, List payloads) { - holder.dayName.setText(StringUtils.capitalize(day.getDayName())); - holder.date.setText(day.getDate()); - - boolean alertActive = false; - - for (TimetableSubItem subItem : getSubItems()) { - if (subItem.getLesson().getIsMovedOrCanceled() || - subItem.getLesson().getIsNewMovedInOrChanged()) { - alertActive = true; - } - } - - if (alertActive) { - holder.alert.setVisibility(View.VISIBLE); - } else { - holder.alert.setVisibility(View.GONE); - } + holder.onBind(day, getSubItems()); } - public static class HeaderViewHolder extends ExpandableViewHolder { + static class HeaderViewHolder extends ExpandableViewHolder { - @BindView(R.id.timetable_header_dayName_text) - public TextView dayName; + @BindView(R.id.timetable_header_day) + TextView dayName; - @BindView(R.id.timetable_header_date_text) - public TextView date; + @BindView(R.id.timetable_header_date) + TextView date; @BindView(R.id.timetable_header_alert_image) - public ImageView alert; + ImageView alert; - public HeaderViewHolder(View view, FlexibleAdapter adapter) { + HeaderViewHolder(View view, FlexibleAdapter adapter) { super(view, adapter); - view.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - toggleExpansion(); - } - }); + view.setOnClickListener(this); ButterKnife.bind(this, view); } + + void onBind(Day item, List subItems) { + dayName.setText(StringUtils.capitalize(item.getDayName())); + date.setText(item.getDate()); + alert.setVisibility(isSubItemNewMovedInOrChanged(subItems) ? View.VISIBLE : View.INVISIBLE); + } + + private boolean isSubItemNewMovedInOrChanged(List subItems) { + boolean isAlertActive = false; + + for (TimetableSubItem subItem : subItems) { + if (subItem.getLesson().getIsMovedOrCanceled() || + subItem.getLesson().getIsNewMovedInOrChanged()) { + isAlertActive = true; + } + } + return isAlertActive; + } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetablePagerAdapter.java b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetablePagerAdapter.java index 8e4044176..24e7582d3 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetablePagerAdapter.java +++ b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetablePagerAdapter.java @@ -1,48 +1,36 @@ package io.github.wulkanowy.ui.main.timetable; -import android.os.Parcelable; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentStatePagerAdapter; -import java.util.ArrayList; -import java.util.List; - public class TimetablePagerAdapter extends FragmentStatePagerAdapter { - private List fragmentList = new ArrayList<>(); + private TabsData tabsData; - private List titleList = new ArrayList<>(); - - public TimetablePagerAdapter(FragmentManager fm) { - super(fm); + public TimetablePagerAdapter(FragmentManager fragmentManager) { + super(fragmentManager); } - public void addFragment(Fragment fragment, String title) { - fragmentList.add(fragment); - titleList.add(title); + void setTabsData(TabsData tabsData) { + this.tabsData = tabsData; } @Override public Fragment getItem(int position) { - return fragmentList.get(position); + return tabsData.getFragment(position); } @Override public int getCount() { - return fragmentList.size(); + return tabsData.getFragmentsCount(); } @Nullable @Override public CharSequence getPageTitle(int position) { - return titleList.get(position); - } - - @Override - public void restoreState(Parcelable state, ClassLoader loader) { - // do nothing + return tabsData.getTitle(position); } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetablePresenter.java b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetablePresenter.java new file mode 100644 index 000000000..bee323ddb --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetablePresenter.java @@ -0,0 +1,107 @@ +package io.github.wulkanowy.ui.main.timetable; + +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; + +import io.github.wulkanowy.data.RepositoryContract; +import io.github.wulkanowy.ui.base.BasePresenter; +import io.github.wulkanowy.ui.main.OnFragmentIsReadyListener; +import io.github.wulkanowy.utils.TimeUtils; +import io.github.wulkanowy.utils.async.AbstractTask; +import io.github.wulkanowy.utils.async.AsyncListeners; + +public class TimetablePresenter extends BasePresenter + implements TimetableContract.Presenter, AsyncListeners.OnFirstLoadingListener { + + private AbstractTask loadingTask; + + private List dates = new ArrayList<>(); + + private TabsData tabsData = new TabsData(); + + private OnFragmentIsReadyListener listener; + + private int positionToScroll; + + private boolean isFirstSight = false; + + @Inject + TimetablePresenter(RepositoryContract repository) { + super(repository); + } + + @Override + public void onStart(TimetableContract.View view, OnFragmentIsReadyListener listener) { + super.onStart(view); + this.listener = listener; + + if (getView().isMenuVisible()) { + getView().setActivityTitle(); + } + + if (dates.isEmpty()) { + dates = TimeUtils.getMondaysFromCurrentSchoolYear(); + } + positionToScroll = dates.indexOf(TimeUtils.getDateOfCurrentMonday(true)); + + if (!isFirstSight) { + isFirstSight = true; + + loadingTask = new AbstractTask(); + loadingTask.setOnFirstLoadingListener(this); + loadingTask.execute(); + } + } + + @Override + public void onFragmentVisible(boolean isVisible) { + if (isVisible) { + getView().setActivityTitle(); + } + } + + @Override + public void onTabSelected(int position) { + getView().setChildFragmentSelected(position, true); + } + + @Override + public void onTabUnselected(int position) { + getView().setChildFragmentSelected(position, false); + } + + @Override + public void onDoInBackgroundLoading() throws Exception { + for (String date : dates) { + tabsData.addTitle(date); + tabsData.addFragment(TimetableTabFragment.newInstance(date)); + } + } + + @Override + public void onCanceledLoadingAsync() { + //do nothing + + } + + @Override + public void onEndLoadingAsync(boolean result, Exception exception) { + if (result) { + getView().setTabDataToAdapter(tabsData); + getView().setAdapterWithTabLayout(); + getView().scrollViewPagerToPosition(positionToScroll); + listener.onFragmentIsReady(); + } + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (loadingTask != null) { + loadingTask.cancel(true); + loadingTask = null; + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableSubItem.java b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableSubItem.java index 025f50317..b4212940f 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableSubItem.java +++ b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableSubItem.java @@ -1,8 +1,9 @@ package io.github.wulkanowy.ui.main.timetable; +import android.content.Context; import android.graphics.Paint; import android.support.v4.app.DialogFragment; -import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentActivity; import android.view.View; import android.widget.ImageView; import android.widget.TextView; @@ -15,19 +16,17 @@ import eu.davidea.flexibleadapter.FlexibleAdapter; import eu.davidea.flexibleadapter.items.AbstractSectionableItem; import eu.davidea.viewholders.FlexibleViewHolder; import io.github.wulkanowy.R; -import io.github.wulkanowy.db.dao.entities.Lesson; +import io.github.wulkanowy.data.db.dao.entities.Lesson; -public class TimetableSubItem extends AbstractSectionableItem { +public class TimetableSubItem + extends AbstractSectionableItem { private Lesson lesson; - private FragmentManager fragmentManager; - - public TimetableSubItem(TimetableHeaderItem header, Lesson lesson, FragmentManager fragmentManager) { + public TimetableSubItem(TimetableHeaderItem header, Lesson lesson) { super(header); this.lesson = lesson; - this.fragmentManager = fragmentManager; } public Lesson getLesson() { @@ -51,63 +50,74 @@ public class TimetableSubItem extends AbstractSectionableItem headerItems); + + void onRefreshSuccess(); + + void hideRefreshingBar(); + + void showProgressBar(boolean show); + } + + interface Presenter extends BaseContract.Presenter { + + void onFragmentSelected(boolean isSelected); + + void setArgumentDate(String date); + + void onStart(View view, boolean isPrimary); + + void onRefresh(); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableTabFragment.java b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableTabFragment.java new file mode 100644 index 000000000..10f3163af --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableTabFragment.java @@ -0,0 +1,145 @@ +package io.github.wulkanowy.ui.main.timetable; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.design.widget.Snackbar; +import android.support.v4.widget.SwipeRefreshLayout; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import java.util.List; + +import javax.inject.Inject; + +import butterknife.BindView; +import butterknife.ButterKnife; +import eu.davidea.flexibleadapter.FlexibleAdapter; +import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager; +import io.github.wulkanowy.R; +import io.github.wulkanowy.di.component.FragmentComponent; +import io.github.wulkanowy.ui.base.BaseFragment; + +public class TimetableTabFragment extends BaseFragment implements TimetableTabContract.View, + SwipeRefreshLayout.OnRefreshListener { + + private static final String ARGUMENT_KEY = "Date"; + + private boolean isPrimary = false; + + private boolean isSelected = false; + + @BindView(R.id.timetable_tab_fragment_recycler) + RecyclerView recyclerView; + + @BindView(R.id.timetable_tab_fragment_swipe_refresh) + SwipeRefreshLayout refreshLayout; + + @BindView(R.id.timetable_tab_fragment_progress_bar) + View progressBar; + + @Inject + TimetableTabContract.Presenter presenter; + + @Inject + FlexibleAdapter adapter; + + public static TimetableTabFragment newInstance(String date) { + TimetableTabFragment fragmentTab = new TimetableTabFragment(); + + Bundle bundle = new Bundle(); + bundle.putString(ARGUMENT_KEY, date); + fragmentTab.setArguments(bundle); + + return fragmentTab; + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_timetable_tab, container, false); + + FragmentComponent component = getFragmentComponent(); + if (component != null) { + component.inject(this); + setButterKnife(ButterKnife.bind(this, view)); + + if (getArguments() != null) { + presenter.setArgumentDate(getArguments().getString(ARGUMENT_KEY)); + } + + presenter.onStart(this, isPrimary); + } + return view; + } + + @Override + protected void setUpOnViewCreated(View fragmentView) { + adapter.setAutoCollapseOnExpand(true); + adapter.setAutoScrollOnExpand(true); + adapter.expandItemsAtStartUp(); + + recyclerView.setLayoutManager(new SmoothScrollLinearLayoutManager(fragmentView.getContext())); + recyclerView.setAdapter(adapter); + + refreshLayout.setColorSchemeResources(android.R.color.black); + refreshLayout.setOnRefreshListener(this); + + } + + @Override + public void updateAdapterList(List headerItems) { + adapter.updateDataSet(headerItems); + } + + @Override + public void setMenuVisibility(boolean menuVisible) { + super.setMenuVisibility(menuVisible); + if (presenter != null && getView() != null) { + presenter.onFragmentSelected(isSelected); + } else if (isSelected) { + isPrimary = true; + } + } + + @Override + public void onRefresh() { + presenter.onRefresh(); + } + + @Override + public void onRefreshSuccess() { + onError(R.string.timetable_refresh_success); + } + + @Override + public void hideRefreshingBar() { + refreshLayout.setRefreshing(false); + } + + @Override + public void showProgressBar(boolean show) { + progressBar.setVisibility(show ? View.VISIBLE : View.INVISIBLE); + } + + public void setSelected(boolean selected) { + isSelected = selected; + } + + @Override + public void onError(String message) { + if (getActivity() != null) { + Snackbar.make(getActivity().findViewById(R.id.main_activity_view_pager), + message, Snackbar.LENGTH_LONG).show(); + } + } + + @Override + public void onDestroyView() { + isPrimary = false; + presenter.onDestroy(); + super.onDestroyView(); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableTabPresenter.java b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableTabPresenter.java new file mode 100644 index 000000000..5872b4d05 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableTabPresenter.java @@ -0,0 +1,155 @@ +package io.github.wulkanowy.ui.main.timetable; + + +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; + +import io.github.wulkanowy.data.RepositoryContract; +import io.github.wulkanowy.data.db.dao.entities.Day; +import io.github.wulkanowy.data.db.dao.entities.Lesson; +import io.github.wulkanowy.data.db.dao.entities.Week; +import io.github.wulkanowy.ui.base.BasePresenter; +import io.github.wulkanowy.utils.async.AbstractTask; +import io.github.wulkanowy.utils.async.AsyncListeners; + +public class TimetableTabPresenter extends BasePresenter + implements TimetableTabContract.Presenter, AsyncListeners.OnRefreshListener, + AsyncListeners.OnFirstLoadingListener { + + private AbstractTask refreshTask; + + private AbstractTask loadingTask; + + private List headerItems = new ArrayList<>(); + + private String date; + + private boolean isFirstSight = false; + + @Inject + TimetableTabPresenter(RepositoryContract repository) { + super(repository); + } + + @Override + public void onStart(TimetableTabContract.View view, boolean isPrimary) { + super.onStart(view); + getView().showProgressBar(true); + onFragmentSelected(isPrimary); + } + + @Override + public void onFragmentSelected(boolean isSelected) { + if (!isFirstSight && isSelected) { + isFirstSight = true; + + loadingTask = new AbstractTask(); + loadingTask.setOnFirstLoadingListener(this); + loadingTask.execute(); + } + } + + @Override + public void onRefresh() { + if (getView().isNetworkConnected()) { + refreshTask = new AbstractTask(); + refreshTask.setOnRefreshListener(this); + refreshTask.execute(); + } else { + getView().onNoNetworkError(); + getView().hideRefreshingBar(); + } + } + + @Override + public void onDoInBackgroundRefresh() throws Exception { + syncData(); + } + + @Override + public void onCanceledRefreshAsync() { + // do nothing + } + + @Override + public void onEndRefreshAsync(boolean result, Exception exception) { + if (result) { + loadingTask = new AbstractTask(); + loadingTask.setOnFirstLoadingListener(this); + loadingTask.execute(); + + getView().onRefreshSuccess(); + } else { + getView().onError(getRepository().getErrorLoginMessage(exception)); + } + getView().hideRefreshingBar(); + } + + @Override + public void onDoInBackgroundLoading() throws Exception { + Week week = getRepository().getWeek(date); + + if (week == null) { + syncData(); + week = getRepository().getWeek(date); + } + + List dayList = week.getDayList(); + + headerItems = new ArrayList<>(); + + for (Day day : dayList) { + TimetableHeaderItem headerItem = new TimetableHeaderItem(day); + + List lessonList = day.getLessons(); + + List subItems = new ArrayList<>(); + + for (Lesson lesson : lessonList) { + subItems.add(new TimetableSubItem(headerItem, lesson)); + } + + headerItem.setSubItems(subItems); + headerItem.setExpanded(false); + headerItems.add(headerItem); + } + } + + @Override + public void onCanceledLoadingAsync() { + // do nothing + } + + @Override + public void onEndLoadingAsync(boolean result, Exception exception) { + getView().updateAdapterList(headerItems); + getView().showProgressBar(false); + } + + @Override + public void setArgumentDate(String date) { + this.date = date; + } + + private void syncData() throws Exception { + getRepository().loginCurrentUser(); + getRepository().syncTimetable(date); + } + + @Override + public void onDestroy() { + super.onDestroy(); + isFirstSight = false; + + if (refreshTask != null) { + refreshTask.cancel(true); + refreshTask = null; + } + if (loadingTask != null) { + loadingTask.cancel(true); + loadingTask = null; + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/splash/SplashActivity.java b/app/src/main/java/io/github/wulkanowy/ui/splash/SplashActivity.java index a4d39e06e..c9f98d5ee 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/splash/SplashActivity.java +++ b/app/src/main/java/io/github/wulkanowy/ui/splash/SplashActivity.java @@ -1,47 +1,50 @@ package io.github.wulkanowy.ui.splash; -import android.content.Context; -import android.content.Intent; import android.os.Bundle; -import android.os.Handler; -import android.support.v7.app.AppCompatActivity; -import android.widget.TextView; -import io.github.wulkanowy.BuildConfig; -import io.github.wulkanowy.R; -import io.github.wulkanowy.services.jobs.FullSyncJob; +import javax.inject.Inject; + +import butterknife.ButterKnife; +import io.github.wulkanowy.services.SyncJob; +import io.github.wulkanowy.ui.base.BaseActivity; import io.github.wulkanowy.ui.login.LoginActivity; -import io.github.wulkanowy.ui.main.DashboardActivity; +import io.github.wulkanowy.ui.main.MainActivity; -public class SplashActivity extends AppCompatActivity { +public class SplashActivity extends BaseActivity implements SplashContract.View { + + @Inject + SplashContract.Presenter presenter; @Override - protected void onCreate(Bundle savedInstanceState) { + public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.activity_splash); - TextView versionName = findViewById(R.id.rawText); - versionName.setText(getString(R.string.version_text, BuildConfig.VERSION_NAME)); + getActivityComponent().inject(this); + setButterKnife(ButterKnife.bind(this)); - new Handler().postDelayed(new Runnable() { - @Override - public void run() { - executeOnRunApp(); - } - }, 500); + presenter.onStart(this); } - private void executeOnRunApp() { - Intent intent; + @Override + protected void onDestroy() { + presenter.onDestroy(); + super.onDestroy(); + } - if (getSharedPreferences("LoginData", Context.MODE_PRIVATE).getLong("userId", 0) == 0) { - intent = new Intent(this, LoginActivity.class); - } else { - new FullSyncJob().scheduledJob(getApplicationContext()); + @Override + public void openLoginActivity() { + startActivity(LoginActivity.getStartIntent(this)); + finish(); + } - intent = new Intent(this, DashboardActivity.class); - } + @Override + public void openMainActivity() { + startActivity(MainActivity.getStartIntent(this)); + finish(); + } - startActivity(intent); + @Override + public void startSyncService() { + SyncJob.start(getApplicationContext()); } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/splash/SplashContract.java b/app/src/main/java/io/github/wulkanowy/ui/splash/SplashContract.java new file mode 100644 index 000000000..a69deb032 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/splash/SplashContract.java @@ -0,0 +1,21 @@ +package io.github.wulkanowy.ui.splash; + + +import io.github.wulkanowy.di.annotations.PerActivity; +import io.github.wulkanowy.ui.base.BaseContract; + +public interface SplashContract { + + interface View extends BaseContract.View { + + void openLoginActivity(); + + void openMainActivity(); + + void startSyncService(); + } + + @PerActivity + interface Presenter extends BaseContract.Presenter { + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/splash/SplashPresenter.java b/app/src/main/java/io/github/wulkanowy/ui/splash/SplashPresenter.java new file mode 100644 index 000000000..c3d81215c --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/splash/SplashPresenter.java @@ -0,0 +1,29 @@ +package io.github.wulkanowy.ui.splash; + +import android.support.annotation.NonNull; + +import javax.inject.Inject; + +import io.github.wulkanowy.data.RepositoryContract; +import io.github.wulkanowy.ui.base.BasePresenter; + +public class SplashPresenter extends BasePresenter + implements SplashContract.Presenter { + + @Inject + SplashPresenter(RepositoryContract repository) { + super(repository); + } + + @Override + public void onStart(@NonNull SplashContract.View activity) { + super.onStart(activity); + getView().startSyncService(); + + if (getRepository().getCurrentUserId() == 0) { + getView().openLoginActivity(); + } else { + getView().openMainActivity(); + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/utils/AppConstant.java b/app/src/main/java/io/github/wulkanowy/utils/AppConstant.java new file mode 100644 index 000000000..27d76e756 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/utils/AppConstant.java @@ -0,0 +1,25 @@ +package io.github.wulkanowy.utils; + +public final class AppConstant { + + public static final String APP_NAME = "Wulkanowy"; + + public static final String DATABASE_NAME = "wulkanowy_db"; + + public static final String SHARED_PREFERENCES_NAME = "user_data"; + + + public static final String VULCAN_CREATE_ACCOUNT_URL = + "https://cufs.vulcan.net.pl/Default/AccountManage/CreateAccount"; + + public static final String VULCAN_FORGOT_PASS_URL = + "https://cufs.vulcan.net.pl/Default/AccountManage/UnlockAccount"; + + public static final String DEFAULT_SYMBOL = "Default"; + + public static final String DATE_PATTERN = "yyyy-MM-dd"; + + private AppConstant() { + throw new IllegalStateException("Utility class"); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/utils/AverageCalculator.java b/app/src/main/java/io/github/wulkanowy/utils/AverageCalculator.java index 0d3326da1..cc33e65a9 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/AverageCalculator.java +++ b/app/src/main/java/io/github/wulkanowy/utils/AverageCalculator.java @@ -2,7 +2,7 @@ package io.github.wulkanowy.utils; import java.util.List; -import io.github.wulkanowy.db.dao.entities.Grade; +import io.github.wulkanowy.data.db.dao.entities.Grade; public final class AverageCalculator { diff --git a/app/src/main/java/io/github/wulkanowy/utils/CommonUtils.java b/app/src/main/java/io/github/wulkanowy/utils/CommonUtils.java new file mode 100644 index 000000000..9680817e7 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/utils/CommonUtils.java @@ -0,0 +1,40 @@ +package io.github.wulkanowy.utils; + +import android.content.Context; +import android.net.Uri; +import android.support.customtabs.CustomTabsIntent; + +import io.github.wulkanowy.R; + +public final class CommonUtils { + + private CommonUtils() { + throw new IllegalStateException("Utility class"); + } + + public static void openInternalBrowserViewer(Context context, String url) { + CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder(); + builder.setToolbarColor(context.getResources().getColor(R.color.colorPrimary)); + CustomTabsIntent customTabsIntent = builder.build(); + customTabsIntent.launchUrl(context, Uri.parse(url)); + } + + public static int colorHexToColorName(String hexColor) { + switch (hexColor) { + case "000000": + return R.string.color_black_text; + + case "F04C4C": + return R.string.color_red_text; + + case "20A4F7": + return R.string.color_blue_text; + + case "6ECD07": + return R.string.color_green_text; + + default: + return R.string.noColor_text; + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/utils/DataObjectConverter.java b/app/src/main/java/io/github/wulkanowy/utils/DataObjectConverter.java index fcede01d9..99006d1ae 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/DataObjectConverter.java +++ b/app/src/main/java/io/github/wulkanowy/utils/DataObjectConverter.java @@ -4,11 +4,11 @@ package io.github.wulkanowy.utils; import java.util.ArrayList; import java.util.List; -import io.github.wulkanowy.db.dao.entities.Day; -import io.github.wulkanowy.db.dao.entities.Grade; -import io.github.wulkanowy.db.dao.entities.Lesson; -import io.github.wulkanowy.db.dao.entities.Subject; -import io.github.wulkanowy.db.dao.entities.Week; +import io.github.wulkanowy.data.db.dao.entities.Day; +import io.github.wulkanowy.data.db.dao.entities.Grade; +import io.github.wulkanowy.data.db.dao.entities.Lesson; +import io.github.wulkanowy.data.db.dao.entities.Subject; +import io.github.wulkanowy.data.db.dao.entities.Week; public final class DataObjectConverter { @@ -52,50 +52,54 @@ public final class DataObjectConverter { return gradeEntityList; } - public static Week weekToWeekEntitie(io.github.wulkanowy.api.timetable.Week week) { + public static Week weekToWeekEntity(io.github.wulkanowy.api.timetable.Week week) { return new Week().setStartDayDate(week.getStartDayDate()); } + public static Day dayToDayEntity(io.github.wulkanowy.api.timetable.Day day) { + return new Day() + .setDate(day.getDate()) + .setDayName(day.getDayName()) + .setFreeDay(day.isFreeDay()) + .setFreeDayName(day.getFreeDayName()); + } + public static List daysToDaysEntities(List dayList) { List dayEntityList = new ArrayList<>(); for (io.github.wulkanowy.api.timetable.Day day : dayList) { - Day dayEntity = new Day() - .setDate(day.getDate()) - .setDayName(day.getDayName()) - .setFreeDay(day.isFreeDay()) - .setFreeDayName(day.getFreeDayName()); - - dayEntityList.add(dayEntity); + dayEntityList.add(dayToDayEntity(day)); } return dayEntityList; } + public static Lesson lessonToLessonEntity(io.github.wulkanowy.api.timetable.Lesson lesson) { + return new Lesson() + .setNumber(lesson.getNumber()) + .setSubject(lesson.getSubject()) + .setTeacher(lesson.getTeacher()) + .setRoom(lesson.getRoom()) + .setDescription(lesson.getDescription()) + .setGroupName(lesson.getGroupName()) + .setStartTime(lesson.getStartTime()) + .setEndTime(lesson.getEndTime()) + .setDate(lesson.getDate()) + .setEmpty(lesson.isEmpty()) + .setDivisionIntoGroups(lesson.isDivisionIntoGroups()) + .setPlanning(lesson.isPlanning()) + .setRealized(lesson.isRealized()) + .setMovedOrCanceled(lesson.isMovedOrCanceled()) + .setNewMovedInOrChanged(lesson.isNewMovedInOrChanged()); + } + public static List lessonsToLessonsEntities(List lessonList) { List lessonEntityList = new ArrayList<>(); for (io.github.wulkanowy.api.timetable.Lesson lesson : lessonList) { - Lesson lessonEntity = new Lesson() - .setNumber(lesson.getNumber()) - .setSubject(lesson.getSubject()) - .setTeacher(lesson.getTeacher()) - .setRoom(lesson.getRoom()) - .setDescription(lesson.getDescription()) - .setGroupName(lesson.getGroupName()) - .setStartTime(lesson.getStartTime()) - .setEndTime(lesson.getEndTime()) - .setDate(lesson.getDate()) - .setEmpty(lesson.isEmpty()) - .setDivisionIntoGroups(lesson.isDivisionIntoGroups()) - .setPlanning(lesson.isPlanning()) - .setRealized(lesson.isRealized()) - .setMovedOrCanceled(lesson.isMovedOrCanceled()) - .setNewMovedInOrChanged(lesson.isNewMovedInOrChanged()); - - lessonEntityList.add(lessonEntity); + lessonEntityList.add(lessonToLessonEntity(lesson)); } return lessonEntityList; } diff --git a/app/src/main/java/io/github/wulkanowy/utils/EntitiesCompare.java b/app/src/main/java/io/github/wulkanowy/utils/EntitiesCompare.java index 878db5c36..5681bad17 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/EntitiesCompare.java +++ b/app/src/main/java/io/github/wulkanowy/utils/EntitiesCompare.java @@ -5,7 +5,7 @@ import org.apache.commons.collections4.CollectionUtils; import java.util.ArrayList; import java.util.List; -import io.github.wulkanowy.db.dao.entities.Grade; +import io.github.wulkanowy.data.db.dao.entities.Grade; public final class EntitiesCompare { @@ -24,8 +24,8 @@ public final class EntitiesCompare { for (Grade grade : addedOrUpdatedGradeList) { if (!oldList.isEmpty()) { grade.setRead(false); - grade.setIsNew(true); } + grade.setIsNew(true); updatedList.add(grade); } diff --git a/app/src/main/java/io/github/wulkanowy/utils/LogUtils.java b/app/src/main/java/io/github/wulkanowy/utils/LogUtils.java new file mode 100644 index 000000000..f3b5b3ec2 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/utils/LogUtils.java @@ -0,0 +1,22 @@ +package io.github.wulkanowy.utils; + +import android.util.Log; + +public final class LogUtils { + + private LogUtils() { + throw new IllegalStateException("Utility class"); + } + + public static void debug(String message) { + Log.d(AppConstant.APP_NAME, message); + } + + public static void error(String message, Throwable throwable) { + Log.e(AppConstant.APP_NAME, message, throwable); + } + + public static void error(String message) { + Log.e(AppConstant.APP_NAME, message); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/utils/TimeUtils.java b/app/src/main/java/io/github/wulkanowy/utils/TimeUtils.java index 70a8ec5fa..717ed42a9 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/TimeUtils.java +++ b/app/src/main/java/io/github/wulkanowy/utils/TimeUtils.java @@ -13,6 +13,8 @@ import java.util.List; import java.util.Locale; import java.util.TimeZone; +import static io.github.wulkanowy.utils.AppConstant.DATE_PATTERN; + public final class TimeUtils { private static final long TICKS_AT_EPOCH = 621355968000000000L; @@ -31,7 +33,7 @@ public final class TimeUtils { } public static long getNetTicks(String dateString) throws ParseException { - return getNetTicks(dateString, "dd.MM.yyyy"); + return getNetTicks(dateString, DATE_PATTERN); } public static long getNetTicks(String dateString, String dateFormat) throws ParseException { @@ -46,7 +48,7 @@ public final class TimeUtils { return new Date((netTicks - TICKS_AT_EPOCH) / TICKS_PER_MILLISECOND); } - public static List getMondaysFromCurrentSchoolYear(String dateFormat) { + public static List getMondaysFromCurrentSchoolYear() { LocalDate startDate = new LocalDate(getCurrentSchoolYear(), 9, 1); LocalDate endDate = new LocalDate(getCurrentSchoolYear() + 1, 8, 31); @@ -61,7 +63,7 @@ public final class TimeUtils { } while (startDate.isBefore(endDate)) { - dateList.add(startDate.toString(dateFormat)); + dateList.add(startDate.toString(DATE_PATTERN)); startDate = startDate.plusWeeks(1); } return dateList; @@ -71,4 +73,17 @@ public final class TimeUtils { DateTime dateTime = new DateTime(); return dateTime.getMonthOfYear() <= 8 ? dateTime.getYear() - 1 : dateTime.getYear(); } + + public static String getDateOfCurrentMonday(boolean normalize) { + DateTime currentDate = new DateTime(); + + if (currentDate.getDayOfWeek() == DateTimeConstants.SATURDAY && normalize) { + currentDate = currentDate.plusDays(2); + } else if (currentDate.getDayOfWeek() == DateTimeConstants.SUNDAY && normalize) { + currentDate = currentDate.plusDays(1); + } else { + currentDate = currentDate.withDayOfWeek(DateTimeConstants.MONDAY); + } + return currentDate.toString(DATE_PATTERN); + } } diff --git a/app/src/main/java/io/github/wulkanowy/utils/async/AbstractTask.java b/app/src/main/java/io/github/wulkanowy/utils/async/AbstractTask.java new file mode 100644 index 000000000..5106d6417 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/utils/async/AbstractTask.java @@ -0,0 +1,63 @@ +package io.github.wulkanowy.utils.async; + +import android.os.AsyncTask; + +import io.github.wulkanowy.utils.LogUtils; + +public class AbstractTask extends AsyncTask { + + private Exception exception; + + private AsyncListeners.OnRefreshListener onRefreshListener; + + private AsyncListeners.OnFirstLoadingListener onFirstLoadingListener; + + public void setOnFirstLoadingListener(AsyncListeners.OnFirstLoadingListener onFirstLoadingListener) { + this.onFirstLoadingListener = onFirstLoadingListener; + } + + public void setOnRefreshListener(AsyncListeners.OnRefreshListener onRefreshListener) { + this.onRefreshListener = onRefreshListener; + } + + @Override + protected Boolean doInBackground(Void... voids) { + try { + if (onFirstLoadingListener != null) { + onFirstLoadingListener.onDoInBackgroundLoading(); + } else if (onRefreshListener != null) { + onRefreshListener.onDoInBackgroundRefresh(); + } else { + LogUtils.error("AbstractTask does not have a listener assigned"); + } + return true; + } catch (Exception e) { + exception = e; + return false; + } + } + + @Override + protected void onCancelled() { + super.onCancelled(); + if (onFirstLoadingListener != null) { + onFirstLoadingListener.onCanceledLoadingAsync(); + } else if (onRefreshListener != null) { + onRefreshListener.onCanceledRefreshAsync(); + } else { + LogUtils.error("AbstractTask does not have a listener assigned"); + } + } + + @Override + protected void onPostExecute(Boolean result) { + super.onPostExecute(result); + if (onFirstLoadingListener != null) { + onFirstLoadingListener.onEndLoadingAsync(result, exception); + } else if (onRefreshListener != null) { + onRefreshListener.onEndRefreshAsync(result, exception); + } else { + LogUtils.error("AbstractTask does not have a listener assigned"); + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/utils/async/AsyncListeners.java b/app/src/main/java/io/github/wulkanowy/utils/async/AsyncListeners.java new file mode 100644 index 000000000..0d33c57cf --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/utils/async/AsyncListeners.java @@ -0,0 +1,23 @@ +package io.github.wulkanowy.utils.async; + +public interface AsyncListeners { + + interface OnRefreshListener { + + void onDoInBackgroundRefresh() throws Exception; + + void onCanceledRefreshAsync(); + + void onEndRefreshAsync(boolean result, Exception exception); + + } + + interface OnFirstLoadingListener { + + void onDoInBackgroundLoading() throws Exception; + + void onCanceledLoadingAsync(); + + void onEndLoadingAsync(boolean result, Exception exception); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/utils/security/Safety.java b/app/src/main/java/io/github/wulkanowy/utils/security/Safety.java deleted file mode 100644 index ca7808753..000000000 --- a/app/src/main/java/io/github/wulkanowy/utils/security/Safety.java +++ /dev/null @@ -1,36 +0,0 @@ -package io.github.wulkanowy.utils.security; - -import android.content.Context; -import android.os.Build; -import android.util.Base64; - -import io.github.wulkanowy.utils.RootChecker; - -public class Safety extends Scrambler { - - public String encrypt(String email, String plainText, Context context) throws CryptoException { - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { - loadKeyStore(); - generateNewKey(email, context); - return encryptString(email, plainText); - } else { - if (RootChecker.isRooted()) { - return new String(Base64.encode(plainText.getBytes(), Base64.DEFAULT)); - } else { - throw new UnsupportedOperationException("Stored data in this devices isn't safe because android is rooted"); - } - } - } - - public String decrypt(String email, String encryptedText) throws CryptoException { - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { - loadKeyStore(); - return decryptString(email, encryptedText); - } else { - return new String(Base64.decode(encryptedText, Base64.DEFAULT)); - } - - } -} diff --git a/app/src/main/java/io/github/wulkanowy/utils/security/Scrambler.java b/app/src/main/java/io/github/wulkanowy/utils/security/Scrambler.java index 62ffbd725..93da1d0bd 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/security/Scrambler.java +++ b/app/src/main/java/io/github/wulkanowy/utils/security/Scrambler.java @@ -8,7 +8,6 @@ import android.security.KeyPairGeneratorSpec; import android.security.keystore.KeyGenParameterSpec; import android.security.keystore.KeyProperties; import android.util.Base64; -import android.util.Log; import org.apache.commons.lang3.ArrayUtils; @@ -27,29 +26,57 @@ import javax.crypto.CipherInputStream; import javax.crypto.CipherOutputStream; import javax.security.auth.x500.X500Principal; -public class Scrambler { +import io.github.wulkanowy.utils.LogUtils; +import io.github.wulkanowy.utils.RootChecker; - public static final String DEBUG_TAG = "WulkanowySecurity"; +public final class Scrambler { private static final String ANDROID_KEYSTORE = "AndroidKeyStore"; - private KeyStore keyStore; + private static KeyStore keyStore; - protected void loadKeyStore() throws CryptoException { + private Scrambler() { + throw new IllegalStateException("Utility class"); + } + public static String encrypt(String email, String plainText, Context context) + throws CryptoException { + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + loadKeyStore(); + generateNewKey(email, context); + return encryptString(email, plainText); + } else { + if (RootChecker.isRooted()) { + return new String(Base64.encode(plainText.getBytes(), Base64.DEFAULT)); + } else { + throw new UnsupportedOperationException("Stored data in this devices " + + "isn't safe because android is rooted"); + } + } + } + + public static String decrypt(String email, String encryptedText) throws CryptoException { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + loadKeyStore(); + return decryptString(email, encryptedText); + } else { + return new String(Base64.decode(encryptedText, Base64.DEFAULT)); + } + } + + private static void loadKeyStore() throws CryptoException { try { keyStore = KeyStore.getInstance(ANDROID_KEYSTORE); keyStore.load(null); } catch (Exception e) { - Log.e(DEBUG_TAG, e.getMessage()); throw new CryptoException(e.getMessage()); } - } @SuppressWarnings("deprecation") @TargetApi(18) - protected void generateNewKey(String alias, Context context) throws CryptoException { + private static void generateNewKey(String alias, Context context) throws CryptoException { Calendar start = Calendar.getInstance(); Calendar end = Calendar.getInstance(); @@ -61,7 +88,8 @@ public class Scrambler { try { if (!keyStore.containsAlias(alias)) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - spec = new KeyGenParameterSpec.Builder(alias, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) + spec = new KeyGenParameterSpec.Builder(alias, + KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1) .setDigests(KeyProperties.DIGEST_SHA256) .setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1) @@ -79,28 +107,23 @@ public class Scrambler { .build(); } - KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", ANDROID_KEYSTORE); + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", + ANDROID_KEYSTORE); keyPairGenerator.initialize(spec); keyPairGenerator.generateKeyPair(); - - } else { - Log.w(DEBUG_TAG, "GenerateNewKey - " + alias + " is exist"); } } catch (Exception e) { - Log.e(DEBUG_TAG, e.getMessage()); throw new CryptoException(e.getMessage()); } } else { - Log.e(DEBUG_TAG, "GenerateNewKey - String is empty"); throw new CryptoException("GenerateNewKey - String is empty"); } - - Log.d(DEBUG_TAG, "Key pair are create"); + LogUtils.debug("Key pair are create"); } - protected String encryptString(String alias, String text) throws CryptoException { + private static String encryptString(String alias, String text) throws CryptoException { if (!alias.isEmpty() && !text.isEmpty()) { try { @@ -121,16 +144,14 @@ public class Scrambler { return Base64.encodeToString(values, Base64.DEFAULT); } catch (Exception e) { - Log.e(DEBUG_TAG, e.getMessage()); throw new CryptoException(e.getMessage()); } } else { - Log.e(DEBUG_TAG, "EncryptString - String is empty"); throw new CryptoException("EncryptString - String is empty"); } } - protected String decryptString(String alias, String text) throws CryptoException { + private static String decryptString(String alias, String text) throws CryptoException { if (!alias.isEmpty() && !text.isEmpty()) { try { @@ -153,15 +174,11 @@ public class Scrambler { Byte[] bytes = values.toArray(new Byte[values.size()]); return new String(ArrayUtils.toPrimitive(bytes), 0, bytes.length, "UTF-8"); - } catch (Exception e) { - Log.e(DEBUG_TAG, e.getMessage()); throw new CryptoException(e.getMessage()); } } else { - Log.e(DEBUG_TAG, "EncryptString - String is empty"); throw new CryptoException("EncryptString - String is empty"); - } } } diff --git a/app/src/main/res/drawable/ic_arrow_down.xml b/app/src/main/res/drawable/ic_arrow_down.xml deleted file mode 100644 index 5a609d656..000000000 --- a/app/src/main/res/drawable/ic_arrow_down.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_logo_splash.png b/app/src/main/res/drawable/ic_logo_splash.png deleted file mode 100644 index cb62c0e56a85a5637b0aa3e4d16ba363d69fa780..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 33927 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 Yzp~e^{QYA^0ufLcRqac;7cB4pKTgo?o&W#< diff --git a/app/src/main/res/drawable/icon_grade_26dp.xml b/app/src/main/res/drawable/icon_grade_26dp.xml index 68964c33b..13f24206c 100644 --- a/app/src/main/res/drawable/icon_grade_26dp.xml +++ b/app/src/main/res/drawable/icon_grade_26dp.xml @@ -5,6 +5,6 @@ android:viewportWidth="26" android:viewportHeight="26"> \ No newline at end of file diff --git a/app/src/main/res/drawable/img_splash_512px.png b/app/src/main/res/drawable/img_splash_512px.png new file mode 100644 index 0000000000000000000000000000000000000000..9d8f403c2885d82a1d4c0c0f19d083c3880ce75a GIT binary patch literal 9691 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 iTbAcRv&PG + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_dashboard.xml b/app/src/main/res/layout/activity_dashboard.xml deleted file mode 100644 index 0bb788bd2..000000000 --- a/app/src/main/res/layout/activity_dashboard.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml index 86e67f94c..670959826 100644 --- a/app/src/main/res/layout/activity_login.xml +++ b/app/src/main/res/layout/activity_login.xml @@ -1,152 +1,137 @@ + tools:context="io.github.wulkanowy.ui.login.LoginActivity"> + android:layout_height="wrap_content"> + android:orientation="vertical"> + android:text="@string/login_heading" /> + android:layout_marginBottom="12dp"> + android:maxLines="1" /> + android:layout_height="wrap_content"> + android:inputType="textPassword" + android:maxLines="1" /> + android:visibility="gone"> + android:inputType="textAutoComplete" + android:maxLines="1" />