From 00e50cf11f622ecf66340d92727576b88027ccc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucas=20B=C3=BCrgy?= Date: Fri, 28 Aug 2020 23:39:01 +0200 Subject: [PATCH] initial commit --- .gitignore | 148 ++++ .idea/codeStyles/Project.xml | 113 +++ .idea/gradle.xml | 20 + .idea/inspectionProfiles/Project_Default.xml | 11 + .idea/jarRepositories.xml | 25 + .idea/runConfigurations.xml | 12 + LICENSE.md | 675 ++++++++++++++++++ README.md | 15 + app/.gitignore | 1 + app/build.gradle | 45 ++ app/proguard-rules.pro | 21 + .../ExampleInstrumentedTest.java | 27 + app/src/main/AndroidManifest.xml | 49 ++ app/src/main/ic_launcher-playstore.png | Bin 0 -> 30239 bytes .../activities/FindRoomActivity.java | 586 +++++++++++++++ .../activities/MainActivity.java | 604 ++++++++++++++++ .../activities/SettingsActivity.java | 263 +++++++ .../activities/WelcomeActivity.java | 445 ++++++++++++ .../heiafrschedule/database/ClassHEIAFR.java | 44 ++ .../database/ClassHEIAFRDao.java | 30 + .../heiafrschedule/database/Lesson.java | 83 +++ .../database/LessonClassJoin.java | 41 ++ .../database/LessonClassJoinDao.java | 33 + .../heiafrschedule/database/LessonDao.java | 20 + .../database/LessonOfClassComplete.java | 34 + .../database/LessonOfClassIncomplete.java | 27 + .../database/LessonOfRoomComplete.java | 28 + .../database/LessonOfRoomIncomplete.java | 27 + .../database/LessonRoomJoin.java | 41 ++ .../database/LessonRoomJoinDao.java | 21 + .../database/LessonTeacherJoin.java | 41 ++ .../database/LessonTeacherJoinDao.java | 17 + .../heiafrschedule/database/MyDatabase.java | 40 ++ .../heiafrschedule/database/RoomHEIAFR.java | 59 ++ .../database/RoomHEIAFRDao.java | 29 + .../heiafrschedule/database/Teacher.java | 77 ++ .../heiafrschedule/database/TeacherDao.java | 18 + .../http/BasicAuthInterceptor.java | 27 + .../heiafrschedule/http/HttpBasicClient.java | 352 +++++++++ .../preferences/Cryptography.java | 80 +++ .../preferences/CryptographyRSAAES.java | 116 +++ .../preferences/PrefManager.java | 178 +++++ .../selfupdate/ProgressResponseBody.java | 60 ++ .../heiafrschedule/selfupdate/SelfUpdate.java | 164 +++++ .../selfupdate/SelfUpdateHttpClient.java | 141 ++++ .../selfupdate/github/Asset.java | 126 ++++ .../selfupdate/github/Author.java | 171 +++++ .../selfupdate/github/Release.java | 173 +++++ .../selfupdate/github/Uploader.java | 171 +++++ .../type_converter/DateConverter.java | 18 + .../ui/BottomSheetDialogLessonsInfos.java | 100 +++ .../ui/BottomSheetDialogLessonsOfRoom.java | 117 +++ .../ui/RecyclerViewAdapter.java | 78 ++ .../heiafrschedule/ui/ViewPagerDisable.java | 40 ++ .../res/drawable-night/ic_arrow_drop_down.xml | 5 + .../res/drawable-night/ic_arrow_forward.xml | 5 + app/src/main/res/drawable-night/ic_search.xml | 5 + .../res/drawable/bg_upper_corners_rounded.xml | 9 + app/src/main/res/drawable/ic_access_time.xml | 5 + app/src/main/res/drawable/ic_action_next.xml | 5 + .../res/drawable/ic_action_next_disable.xml | 5 + app/src/main/res/drawable/ic_action_prev.xml | 5 + .../main/res/drawable/ic_action_refresh.xml | 5 + .../main/res/drawable/ic_action_settings.xml | 5 + .../main/res/drawable/ic_arrow_drop_down.xml | 5 + .../main/res/drawable/ic_arrow_forward.xml | 5 + app/src/main/res/drawable/ic_business.xml | 5 + app/src/main/res/drawable/ic_email.xml | 5 + .../res/drawable/ic_launcher_foreground.xml | 59 ++ app/src/main/res/drawable/ic_lock_outline.xml | 5 + app/src/main/res/drawable/ic_looks_1.xml | 5 + app/src/main/res/drawable/ic_looks_2.xml | 5 + app/src/main/res/drawable/ic_looks_3.xml | 5 + app/src/main/res/drawable/ic_looks_4.xml | 5 + app/src/main/res/drawable/ic_looks_5.xml | 5 + .../main/res/drawable/ic_person_outline.xml | 5 + app/src/main/res/drawable/ic_phone.xml | 5 + app/src/main/res/drawable/ic_search.xml | 5 + .../res/drawable/icons_bottom_navigation.xml | 5 + .../main/res/layout/activity_find_room.xml | 69 ++ app/src/main/res/layout/activity_main.xml | 26 + app/src/main/res/layout/activity_settings.xml | 10 + app/src/main/res/layout/activity_welcome.xml | 71 ++ app/src/main/res/layout/card_lesson.xml | 69 ++ app/src/main/res/layout/cell_room.xml | 14 + .../res/layout/content_dialog_download.xml | 15 + .../res/layout/content_dialog_show_update.xml | 28 + app/src/main/res/layout/main_slide.xml | 16 + app/src/main/res/layout/radio_button.xml | 7 + .../main/res/layout/sheet_lessons_infos.xml | 47 ++ .../layout/sheet_lessons_infos_teachers.xml | 50 ++ .../main/res/layout/sheet_lessons_of_room.xml | 27 + .../res/layout/sheet_lessons_of_room_day.xml | 21 + .../layout/sheet_lessons_of_room_lesson.xml | 34 + .../main/res/layout/spinner_dropdown_item.xml | 10 + app/src/main/res/layout/spinner_item.xml | 9 + .../main/res/layout/time_layout_content.xml | 26 + app/src/main/res/layout/welcome_slide_1.xml | 81 +++ app/src/main/res/layout/welcome_slide_2.xml | 43 ++ app/src/main/res/layout/welcome_slide_3.xml | 40 ++ app/src/main/res/menu/menu_find_room.xml | 12 + app/src/main/res/menu/menu_main.xml | 27 + app/src/main/res/menu/menu_navigation.xml | 29 + .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 + app/src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 2638 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 4635 bytes app/src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 1629 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 2829 bytes app/src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 3766 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 6748 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 5962 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 10703 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 8232 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 15142 bytes app/src/main/res/values-de/arrays.xml | 13 + app/src/main/res/values-de/strings.xml | 40 ++ app/src/main/res/values-fr/arrays.xml | 13 + app/src/main/res/values-fr/strings.xml | 40 ++ app/src/main/res/values-it/arrays.xml | 13 + app/src/main/res/values-it/strings.xml | 40 ++ app/src/main/res/values-night/colors.xml | 4 + app/src/main/res/values-v21/styles.xml | 11 + app/src/main/res/values/arrays.xml | 25 + app/src/main/res/values/colors.xml | 13 + app/src/main/res/values/dimens.xml | 51 ++ .../res/values/ic_launcher_background.xml | 4 + app/src/main/res/values/strings.xml | 70 ++ app/src/main/res/values/styles.xml | 24 + app/src/main/res/xml/file_provider_paths.xml | 6 + app/src/main/res/xml/root_preferences.xml | 55 ++ .../heiafrschedule/ExampleUnitTest.java | 17 + build.gradle | 24 + gradle.properties | 20 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 54329 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 172 +++++ gradlew.bat | 84 +++ settings.gradle | 2 + 139 files changed, 7603 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/codeStyles/Project.xml create mode 100644 .idea/gradle.xml create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/jarRepositories.xml create mode 100644 .idea/runConfigurations.xml create mode 100644 LICENSE.md create mode 100644 README.md create mode 100644 app/.gitignore create mode 100644 app/build.gradle create mode 100644 app/proguard-rules.pro create mode 100644 app/src/androidTest/java/ch/lburgy/heiafrschedule/ExampleInstrumentedTest.java create mode 100644 app/src/main/AndroidManifest.xml create mode 100644 app/src/main/ic_launcher-playstore.png create mode 100644 app/src/main/java/ch/lburgy/heiafrschedule/activities/FindRoomActivity.java create mode 100644 app/src/main/java/ch/lburgy/heiafrschedule/activities/MainActivity.java create mode 100644 app/src/main/java/ch/lburgy/heiafrschedule/activities/SettingsActivity.java create mode 100644 app/src/main/java/ch/lburgy/heiafrschedule/activities/WelcomeActivity.java create mode 100644 app/src/main/java/ch/lburgy/heiafrschedule/database/ClassHEIAFR.java create mode 100644 app/src/main/java/ch/lburgy/heiafrschedule/database/ClassHEIAFRDao.java create mode 100644 app/src/main/java/ch/lburgy/heiafrschedule/database/Lesson.java create mode 100644 app/src/main/java/ch/lburgy/heiafrschedule/database/LessonClassJoin.java create mode 100644 app/src/main/java/ch/lburgy/heiafrschedule/database/LessonClassJoinDao.java create mode 100644 app/src/main/java/ch/lburgy/heiafrschedule/database/LessonDao.java create mode 100644 app/src/main/java/ch/lburgy/heiafrschedule/database/LessonOfClassComplete.java create mode 100644 app/src/main/java/ch/lburgy/heiafrschedule/database/LessonOfClassIncomplete.java create mode 100644 app/src/main/java/ch/lburgy/heiafrschedule/database/LessonOfRoomComplete.java create mode 100644 app/src/main/java/ch/lburgy/heiafrschedule/database/LessonOfRoomIncomplete.java create mode 100644 app/src/main/java/ch/lburgy/heiafrschedule/database/LessonRoomJoin.java create mode 100644 app/src/main/java/ch/lburgy/heiafrschedule/database/LessonRoomJoinDao.java create mode 100644 app/src/main/java/ch/lburgy/heiafrschedule/database/LessonTeacherJoin.java create mode 100644 app/src/main/java/ch/lburgy/heiafrschedule/database/LessonTeacherJoinDao.java create mode 100644 app/src/main/java/ch/lburgy/heiafrschedule/database/MyDatabase.java create mode 100644 app/src/main/java/ch/lburgy/heiafrschedule/database/RoomHEIAFR.java create mode 100644 app/src/main/java/ch/lburgy/heiafrschedule/database/RoomHEIAFRDao.java create mode 100644 app/src/main/java/ch/lburgy/heiafrschedule/database/Teacher.java create mode 100644 app/src/main/java/ch/lburgy/heiafrschedule/database/TeacherDao.java create mode 100644 app/src/main/java/ch/lburgy/heiafrschedule/http/BasicAuthInterceptor.java create mode 100644 app/src/main/java/ch/lburgy/heiafrschedule/http/HttpBasicClient.java create mode 100644 app/src/main/java/ch/lburgy/heiafrschedule/preferences/Cryptography.java create mode 100644 app/src/main/java/ch/lburgy/heiafrschedule/preferences/CryptographyRSAAES.java create mode 100644 app/src/main/java/ch/lburgy/heiafrschedule/preferences/PrefManager.java create mode 100644 app/src/main/java/ch/lburgy/heiafrschedule/selfupdate/ProgressResponseBody.java create mode 100644 app/src/main/java/ch/lburgy/heiafrschedule/selfupdate/SelfUpdate.java create mode 100644 app/src/main/java/ch/lburgy/heiafrschedule/selfupdate/SelfUpdateHttpClient.java create mode 100644 app/src/main/java/ch/lburgy/heiafrschedule/selfupdate/github/Asset.java create mode 100644 app/src/main/java/ch/lburgy/heiafrschedule/selfupdate/github/Author.java create mode 100644 app/src/main/java/ch/lburgy/heiafrschedule/selfupdate/github/Release.java create mode 100644 app/src/main/java/ch/lburgy/heiafrschedule/selfupdate/github/Uploader.java create mode 100644 app/src/main/java/ch/lburgy/heiafrschedule/type_converter/DateConverter.java create mode 100644 app/src/main/java/ch/lburgy/heiafrschedule/ui/BottomSheetDialogLessonsInfos.java create mode 100644 app/src/main/java/ch/lburgy/heiafrschedule/ui/BottomSheetDialogLessonsOfRoom.java create mode 100644 app/src/main/java/ch/lburgy/heiafrschedule/ui/RecyclerViewAdapter.java create mode 100644 app/src/main/java/ch/lburgy/heiafrschedule/ui/ViewPagerDisable.java create mode 100644 app/src/main/res/drawable-night/ic_arrow_drop_down.xml create mode 100644 app/src/main/res/drawable-night/ic_arrow_forward.xml create mode 100644 app/src/main/res/drawable-night/ic_search.xml create mode 100644 app/src/main/res/drawable/bg_upper_corners_rounded.xml create mode 100644 app/src/main/res/drawable/ic_access_time.xml create mode 100644 app/src/main/res/drawable/ic_action_next.xml create mode 100644 app/src/main/res/drawable/ic_action_next_disable.xml create mode 100644 app/src/main/res/drawable/ic_action_prev.xml create mode 100644 app/src/main/res/drawable/ic_action_refresh.xml create mode 100644 app/src/main/res/drawable/ic_action_settings.xml create mode 100644 app/src/main/res/drawable/ic_arrow_drop_down.xml create mode 100644 app/src/main/res/drawable/ic_arrow_forward.xml create mode 100644 app/src/main/res/drawable/ic_business.xml create mode 100644 app/src/main/res/drawable/ic_email.xml create mode 100644 app/src/main/res/drawable/ic_launcher_foreground.xml create mode 100644 app/src/main/res/drawable/ic_lock_outline.xml create mode 100644 app/src/main/res/drawable/ic_looks_1.xml create mode 100644 app/src/main/res/drawable/ic_looks_2.xml create mode 100644 app/src/main/res/drawable/ic_looks_3.xml create mode 100644 app/src/main/res/drawable/ic_looks_4.xml create mode 100644 app/src/main/res/drawable/ic_looks_5.xml create mode 100644 app/src/main/res/drawable/ic_person_outline.xml create mode 100644 app/src/main/res/drawable/ic_phone.xml create mode 100644 app/src/main/res/drawable/ic_search.xml create mode 100644 app/src/main/res/drawable/icons_bottom_navigation.xml create mode 100644 app/src/main/res/layout/activity_find_room.xml create mode 100644 app/src/main/res/layout/activity_main.xml create mode 100644 app/src/main/res/layout/activity_settings.xml create mode 100644 app/src/main/res/layout/activity_welcome.xml create mode 100644 app/src/main/res/layout/card_lesson.xml create mode 100644 app/src/main/res/layout/cell_room.xml create mode 100644 app/src/main/res/layout/content_dialog_download.xml create mode 100644 app/src/main/res/layout/content_dialog_show_update.xml create mode 100644 app/src/main/res/layout/main_slide.xml create mode 100644 app/src/main/res/layout/radio_button.xml create mode 100644 app/src/main/res/layout/sheet_lessons_infos.xml create mode 100644 app/src/main/res/layout/sheet_lessons_infos_teachers.xml create mode 100644 app/src/main/res/layout/sheet_lessons_of_room.xml create mode 100644 app/src/main/res/layout/sheet_lessons_of_room_day.xml create mode 100644 app/src/main/res/layout/sheet_lessons_of_room_lesson.xml create mode 100644 app/src/main/res/layout/spinner_dropdown_item.xml create mode 100644 app/src/main/res/layout/spinner_item.xml create mode 100644 app/src/main/res/layout/time_layout_content.xml create mode 100644 app/src/main/res/layout/welcome_slide_1.xml create mode 100644 app/src/main/res/layout/welcome_slide_2.xml create mode 100644 app/src/main/res/layout/welcome_slide_3.xml create mode 100644 app/src/main/res/menu/menu_find_room.xml create mode 100644 app/src/main/res/menu/menu_main.xml create mode 100644 app/src/main/res/menu/menu_navigation.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/values-de/arrays.xml create mode 100644 app/src/main/res/values-de/strings.xml create mode 100644 app/src/main/res/values-fr/arrays.xml create mode 100644 app/src/main/res/values-fr/strings.xml create mode 100644 app/src/main/res/values-it/arrays.xml create mode 100644 app/src/main/res/values-it/strings.xml create mode 100644 app/src/main/res/values-night/colors.xml create mode 100644 app/src/main/res/values-v21/styles.xml create mode 100644 app/src/main/res/values/arrays.xml create mode 100644 app/src/main/res/values/colors.xml create mode 100644 app/src/main/res/values/dimens.xml create mode 100644 app/src/main/res/values/ic_launcher_background.xml create mode 100644 app/src/main/res/values/strings.xml create mode 100644 app/src/main/res/values/styles.xml create mode 100644 app/src/main/res/xml/file_provider_paths.xml create mode 100644 app/src/main/res/xml/root_preferences.xml create mode 100644 app/src/test/java/ch/lburgy/heiafrschedule/ExampleUnitTest.java create mode 100644 build.gradle create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..90335de --- /dev/null +++ b/.gitignore @@ -0,0 +1,148 @@ +# Created by https://www.toptal.com/developers/gitignore/api/androidstudio,visualstudiocode +# Edit at https://www.toptal.com/developers/gitignore?templates=androidstudio,visualstudiocode + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history + +### AndroidStudio ### +# Covers files to be ignored for android development using Android Studio. + +# Built application files +*.apk +*.ap_ + +# Files for the ART/Dalvik VM +*.dex + +# Java class files +*.class + +# Generated files +bin/ +gen/ +out/ + +# Gradle files +.gradle +.gradle/ +build/ + +# Signing files +.signing/ + +# Local configuration file (sdk path, etc) +local.properties + +# Proguard folder generated by Eclipse +proguard/ + +# Log Files +*.log + +# Android Studio +/*/build/ +/*/local.properties +/*/out +/*/*/build +/*/*/production +captures/ +.navigation/ +*.ipr +*~ +*.swp + +# Android Patch +gen-external-apklibs + +# External native build folder generated in Android Studio 2.2 and later +.externalNativeBuild + +# NDK +obj/ + +# IntelliJ IDEA +*.iml +*.iws +/out/ + +# User-specific configurations +.idea/caches/ +.idea/libraries/ +.idea/shelf/ +.idea/workspace.xml +.idea/tasks.xml +.idea/.name +.idea/compiler.xml +.idea/copyright/profiles_settings.xml +.idea/encodings.xml +.idea/misc.xml +.idea/modules.xml +.idea/scopes/scope_settings.xml +.idea/dictionaries +.idea/vcs.xml +.idea/jsLibraryMappings.xml +.idea/datasources.xml +.idea/dataSources.ids +.idea/sqlDataSources.xml +.idea/dynamic.xml +.idea/uiDesigner.xml +.idea/assetWizardSettings.xml + +# OS-specific files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Legacy Eclipse project files +.classpath +.project +.cproject +.settings/ + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.war +*.ear + +# virtual machine crash logs (Reference: http://www.java.com/en/download/help/error_hotspot.xml) +hs_err_pid* + +## Plugin-specific files: + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Mongo Explorer plugin +.idea/mongoSettings.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +### AndroidStudio Patch ### + +!/gradle/wrapper/gradle-wrapper.jar + +# End of https://www.toptal.com/developers/gitignore/api/androidstudio,visualstudiocode + +app/release/* diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..ae78c11 --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,113 @@ + + + + + +
+ + + + xmlns:android + + ^$ + + + +
+
+ + + + xmlns:.* + + ^$ + + + BY_NAME + +
+
+ + + + .*:id + + http://schemas.android.com/apk/res/android + + + +
+
+ + + + .*:name + + http://schemas.android.com/apk/res/android + + + +
+
+ + + + name + + ^$ + + + +
+
+ + + + style + + ^$ + + + +
+
+ + + + .* + + ^$ + + + BY_NAME + +
+
+ + + + .* + + http://schemas.android.com/apk/res/android + + + ANDROID_ATTRIBUTE_ORDER + +
+
+ + + + .* + + .* + + + BY_NAME + +
+
+
+
+
+
\ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..5cd135a --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,20 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..0385f24 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,11 @@ + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 0000000..a5f05cd --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..7f68460 --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..2fb2e74 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,675 @@ +### GNU GENERAL PUBLIC LICENSE + +Version 3, 29 June 2007 + +Copyright (C) 2007 Free Software Foundation, Inc. + + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + +### Preamble + +The GNU General Public License is a free, copyleft license for +software and other kinds of works. + +The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom +to share and change all versions of a program--to make sure it remains +free software for all its users. We, the Free Software Foundation, use +the GNU General Public License for most of our software; it applies +also to any other work released this way by its authors. You can apply +it to your programs, too. + +When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + +To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you +have certain responsibilities if you distribute copies of the +software, or if you modify it: responsibilities to respect the freedom +of others. + +For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + +Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + +For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + +Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the +manufacturer can do so. This is fundamentally incompatible with the +aim of protecting users' freedom to change the software. The +systematic pattern of such abuse occurs in the area of products for +individuals to use, which is precisely where it is most unacceptable. +Therefore, we have designed this version of the GPL to prohibit the +practice for those products. If such problems arise substantially in +other domains, we stand ready to extend this provision to those +domains in future versions of the GPL, as needed to protect the +freedom of users. + +Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish +to avoid the special danger that patents applied to a free program +could make it effectively proprietary. To prevent this, the GPL +assures that patents cannot be used to render the program non-free. + +The precise terms and conditions for copying, distribution and +modification follow. + +### TERMS AND CONDITIONS + +#### 0. Definitions. + +"This License" refers to version 3 of the GNU General Public License. + +"Copyright" also means copyright-like laws that apply to other kinds +of works, such as semiconductor masks. + +"The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + +To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of +an exact copy. The resulting work is called a "modified version" of +the earlier work or a work "based on" the earlier work. + +A "covered work" means either the unmodified Program or a work based +on the Program. + +To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + +To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user +through a computer network, with no transfer of a copy, is not +conveying. + +An interactive user interface displays "Appropriate Legal Notices" to +the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + +#### 1. Source Code. + +The "source code" for a work means the preferred form of the work for +making modifications to it. "Object code" means any non-source form of +a work. + +A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + +The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + +The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + +The Corresponding Source need not include anything that users can +regenerate automatically from other parts of the Corresponding Source. + +The Corresponding Source for a work in source code form is that same +work. + +#### 2. Basic Permissions. + +All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + +You may make, run and propagate covered works that you do not convey, +without conditions so long as your license otherwise remains in force. +You may convey covered works to others for the sole purpose of having +them make modifications exclusively for you, or provide you with +facilities for running those works, provided that you comply with the +terms of this License in conveying all material for which you do not +control copyright. Those thus making or running the covered works for +you must do so exclusively on your behalf, under your direction and +control, on terms that prohibit them from making any copies of your +copyrighted material outside their relationship with you. + +Conveying under any other circumstances is permitted solely under the +conditions stated below. Sublicensing is not allowed; section 10 makes +it unnecessary. + +#### 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + +No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + +When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such +circumvention is effected by exercising rights under this License with +respect to the covered work, and you disclaim any intention to limit +operation or modification of the work as a means of enforcing, against +the work's users, your or third parties' legal rights to forbid +circumvention of technological measures. + +#### 4. Conveying Verbatim Copies. + +You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + +You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + +#### 5. Conveying Modified Source Versions. + +You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these +conditions: + +- a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. +- b) The work must carry prominent notices stating that it is + released under this License and any conditions added under + section 7. This requirement modifies the requirement in section 4 + to "keep intact all notices". +- c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. +- d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + +A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + +#### 6. Conveying Non-Source Forms. + +You may convey a covered work in object code form under the terms of +sections 4 and 5, provided that you also convey the machine-readable +Corresponding Source under the terms of this License, in one of these +ways: + +- a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. +- b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the Corresponding + Source from a network server at no charge. +- c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. +- d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. +- e) Convey the object code using peer-to-peer transmission, + provided you inform other peers where the object code and + Corresponding Source of the work are being offered to the general + public at no charge under subsection 6d. + +A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + +A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, +family, or household purposes, or (2) anything designed or sold for +incorporation into a dwelling. In determining whether a product is a +consumer product, doubtful cases shall be resolved in favor of +coverage. For a particular product received by a particular user, +"normally used" refers to a typical or common use of that class of +product, regardless of the status of the particular user or of the way +in which the particular user actually uses, or expects or is expected +to use, the product. A product is a consumer product regardless of +whether the product has substantial commercial, industrial or +non-consumer uses, unless such uses represent the only significant +mode of use of the product. + +"Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to +install and execute modified versions of a covered work in that User +Product from a modified version of its Corresponding Source. The +information must suffice to ensure that the continued functioning of +the modified object code is in no case prevented or interfered with +solely because modification has been made. + +If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + +The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or +updates for a work that has been modified or installed by the +recipient, or for the User Product in which it has been modified or +installed. Access to a network may be denied when the modification +itself materially and adversely affects the operation of the network +or violates the rules and protocols for communication across the +network. + +Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + +#### 7. Additional Terms. + +"Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + +When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + +Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders +of that material) supplement the terms of this License with terms: + +- a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or +- b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or +- c) Prohibiting misrepresentation of the origin of that material, + or requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or +- d) Limiting the use for publicity purposes of names of licensors + or authors of the material; or +- e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or +- f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions + of it) with contractual assumptions of liability to the recipient, + for any liability that these contractual assumptions directly + impose on those licensors and authors. + +All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + +If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + +Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; the +above requirements apply either way. + +#### 8. Termination. + +You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + +However, if you cease all violation of this License, then your license +from a particular copyright holder is reinstated (a) provisionally, +unless and until the copyright holder explicitly and finally +terminates your license, and (b) permanently, if the copyright holder +fails to notify you of the violation by some reasonable means prior to +60 days after the cessation. + +Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + +Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + +#### 9. Acceptance Not Required for Having Copies. + +You are not required to accept this License in order to receive or run +a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + +#### 10. Automatic Licensing of Downstream Recipients. + +Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + +An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + +You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + +#### 11. Patents. + +A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + +A contributor's "essential patent claims" are all patent claims owned +or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + +Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + +In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + +If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + +If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + +A patent license is "discriminatory" if it does not include within the +scope of its coverage, prohibits the exercise of, or is conditioned on +the non-exercise of one or more of the rights that are specifically +granted under this License. You may not convey a covered work if you +are a party to an arrangement with a third party that is in the +business of distributing software, under which you make payment to the +third party based on the extent of your activity of conveying the +work, and under which the third party grants, to any of the parties +who would receive the covered work from you, a discriminatory patent +license (a) in connection with copies of the covered work conveyed by +you (or copies made from those copies), or (b) primarily for and in +connection with specific products or compilations that contain the +covered work, unless you entered into that arrangement, or that patent +license was granted, prior to 28 March 2007. + +Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + +#### 12. No Surrender of Others' Freedom. + +If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under +this License and any other pertinent obligations, then as a +consequence you may not convey it at all. For example, if you agree to +terms that obligate you to collect a royalty for further conveying +from those to whom you convey the Program, the only way you could +satisfy both those terms and this License would be to refrain entirely +from conveying the Program. + +#### 13. Use with the GNU Affero General Public License. + +Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + +#### 14. Revised Versions of this License. + +The Free Software Foundation may publish revised and/or new versions +of the GNU General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in +detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies that a certain numbered version of the GNU General Public +License "or any later version" applies to it, you have the option of +following the terms and conditions either of that numbered version or +of any later version published by the Free Software Foundation. If the +Program does not specify a version number of the GNU General Public +License, you may choose any version ever published by the Free +Software Foundation. + +If the Program specifies that a proxy can decide which future versions +of the GNU General Public License can be used, that proxy's public +statement of acceptance of a version permanently authorizes you to +choose that version for the Program. + +Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + +#### 15. Disclaimer of Warranty. + +THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT +WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND +PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE +DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR +CORRECTION. + +#### 16. Limitation of Liability. + +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR +CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES +ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT +NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR +LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM +TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER +PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +#### 17. Interpretation of Sections 15 and 16. + +If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + +END OF TERMS AND CONDITIONS + +### How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these +terms. + +To do so, attach the following notices to the program. It is safest to +attach them to the start of each source file to most effectively state +the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper +mail. + +If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands \`show w' and \`show c' should show the +appropriate parts of the General Public License. Of course, your +program's commands might be different; for a GUI interface, you would +use an "about box". + +You should also get your employer (if you work as a programmer) or +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. For more information on this, and how to apply and follow +the GNU GPL, see . + +The GNU General Public License does not permit incorporating your +program into proprietary programs. If your program is a subroutine +library, you may consider it more useful to permit linking proprietary +applications with the library. If this is what you want to do, use the +GNU Lesser General Public License instead of this License. But first, +please read . diff --git a/README.md b/README.md new file mode 100644 index 0000000..8c5d7c7 --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +# Self Updating App +An Android Application to allow students of the HEIA-FR easily access their schedule. + +Students can also check which rooms are available at a certain time. + +## Requirements +Android KitKat (4.4) and above. + +## Licenses & Credits +- [Apache HttpComponents](https://hc.apache.org/) by [The Apache Software Foundation](http://www.apache.org/), licensed under the Apache 2.0 license +- [jsoup](https://jsoup.org/) by [Jonathan Hedley](https://jhy.io/), licensed under the MIT license +- [Moshi](https://github.com/square/moshi) by [Square Open Source](https://square.github.io/), licensed under the Apache 2.0 license +- [OkHttp](https://github.com/square/okhttp) by [Square Open Source](https://square.github.io/), licensed under the Apache 2.0 license +- [Okio](https://github.com/square/okio) by [Square Open Source](https://square.github.io/), licensed under the Apache 2.0 license +- [Self Updating App](https://github.com/burgyl/SelfUpdatingApp) by [Lucas Bürgy](https://github.com/burgyl), licensed under the GNU General Public License v3.0 license diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..2e1e39e --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,45 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 29 + buildToolsVersion "29.0.3" + + useLibrary 'org.apache.http.legacy' + + defaultConfig { + applicationId "ch.lburgy.heiafrschedule" + minSdkVersion 18 + targetSdkVersion 29 + versionCode 1 + versionName "1.0.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + + implementation group: 'org.apache.httpcomponents', name: 'httpclient-android', version: '4.3.5.1' + implementation 'com.google.android.material:material:1.2.0' + implementation 'com.squareup.okhttp3:okhttp:4.6.0' + implementation 'com.squareup.moshi:moshi:1.9.3' + implementation 'com.squareup.okio:okio:2.8.0' + implementation 'org.jsoup:jsoup:1.12.2' + implementation 'androidx.appcompat:appcompat:1.2.0' + implementation 'androidx.constraintlayout:constraintlayout:2.0.1' + implementation 'androidx.preference:preference:1.1.1' + implementation 'androidx.room:room-runtime:2.2.5' + annotationProcessor 'androidx.room:room-compiler:2.2.5' + testImplementation 'junit:junit:4.12' + androidTestImplementation 'androidx.test.ext:junit:1.1.2' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/app/src/androidTest/java/ch/lburgy/heiafrschedule/ExampleInstrumentedTest.java b/app/src/androidTest/java/ch/lburgy/heiafrschedule/ExampleInstrumentedTest.java new file mode 100644 index 0000000..73bb917 --- /dev/null +++ b/app/src/androidTest/java/ch/lburgy/heiafrschedule/ExampleInstrumentedTest.java @@ -0,0 +1,27 @@ +package ch.lburgy.heiafrschedule; + +import android.content.Context; + +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + + assertEquals("ch.lburgy.heiafrschedule", appContext.getPackageName()); + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..38ea814 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/ic_launcher-playstore.png b/app/src/main/ic_launcher-playstore.png new file mode 100644 index 0000000000000000000000000000000000000000..3f8b391a435cbbe452d7ce9e63057cf149da427d GIT binary patch literal 30239 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�`9~^qO>O84#Lc`aT!yiRKzlasFGR>X(pciAZ|Z;%>}Q~~KWNMs^DhoA z?%97FGjH86O2Q?>r0lGgH65d^y0dZfyuKliN={C#<$C$Fiooy+57t*%&PRI6izu}UnxKyKWjAl@SwmSAZgg+(qq-xXR5=Ms4M=w^$ z9fv>Bvs>A%y8+rzE;_}|urnLy$xtte;)=p;BgE~#Uz(c8rW$eW%ijd4BUYW@*h43W zHANm`F*0U3vYJyjt(>B5nM8eH`{!`J_qB_JWHAg{*1t8R!#+a* zo_9uqUxU&jxTWS_Xrkk7b_!vJ?AC!EU{M+AWkr{2@u&{Z9Z@3IZJVmZL({o;28Wh% zTUJxi;V0}kuhIY98pvpu512(4nlI@+++4Vb;W0&^&Oz5Ew|UPf%vE&CYYifLf3y&l zu}kS<6^J^Y`nGvB(J`QDbm|f^|dL{Gp=^1p5KQhQEE3WH3Xq15Lo0NfMZ;LiP z<`$>baJ~zLd9+#(Kf}DH8dFfUw6^&P3|z?;R|KTThgB9Zp;wJq!Je=yO#H7Y8JX3P zsG@X6bVKvqv%UdeLe%*Q%)4l1dF!IFu~M-t!yH!wZ%|8EBzZ~zK!V8r|XyaTS%fIqka{~vb#kEi~h nkNbfKh>5U*x&sD}_=ylfeH0@ELdl literal 0 HcmV?d00001 diff --git a/app/src/main/java/ch/lburgy/heiafrschedule/activities/FindRoomActivity.java b/app/src/main/java/ch/lburgy/heiafrschedule/activities/FindRoomActivity.java new file mode 100644 index 0000000..99d6d8d --- /dev/null +++ b/app/src/main/java/ch/lburgy/heiafrschedule/activities/FindRoomActivity.java @@ -0,0 +1,586 @@ +package ch.lburgy.heiafrschedule.activities; + +import android.app.AlertDialog; +import android.app.ProgressDialog; +import android.app.TimePickerDialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.database.sqlite.SQLiteConstraintException; +import android.os.Bundle; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.ArrayAdapter; +import android.widget.ImageButton; +import android.widget.LinearLayout; +import android.widget.Spinner; +import android.widget.TextView; +import android.widget.TimePicker; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import org.jetbrains.annotations.NotNull; + +import java.io.InterruptedIOException; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.List; + +import ch.lburgy.heiafrschedule.R; +import ch.lburgy.heiafrschedule.database.ClassHEIAFR; +import ch.lburgy.heiafrschedule.database.ClassHEIAFRDao; +import ch.lburgy.heiafrschedule.database.Lesson; +import ch.lburgy.heiafrschedule.database.LessonClassJoin; +import ch.lburgy.heiafrschedule.database.LessonClassJoinDao; +import ch.lburgy.heiafrschedule.database.LessonDao; +import ch.lburgy.heiafrschedule.database.LessonOfRoomComplete; +import ch.lburgy.heiafrschedule.database.LessonOfRoomIncomplete; +import ch.lburgy.heiafrschedule.database.LessonRoomJoin; +import ch.lburgy.heiafrschedule.database.LessonRoomJoinDao; +import ch.lburgy.heiafrschedule.database.LessonTeacherJoin; +import ch.lburgy.heiafrschedule.database.LessonTeacherJoinDao; +import ch.lburgy.heiafrschedule.database.MyDatabase; +import ch.lburgy.heiafrschedule.database.RoomHEIAFR; +import ch.lburgy.heiafrschedule.database.RoomHEIAFRDao; +import ch.lburgy.heiafrschedule.database.Teacher; +import ch.lburgy.heiafrschedule.database.TeacherDao; +import ch.lburgy.heiafrschedule.http.HttpBasicClient; +import ch.lburgy.heiafrschedule.preferences.PrefManager; +import ch.lburgy.heiafrschedule.ui.BottomSheetDialogLessonsOfRoom; +import ch.lburgy.heiafrschedule.ui.RecyclerViewAdapter; + +public class FindRoomActivity extends AppCompatActivity { + + private static final int CODE_NO_PROBLEMS = -1; + private static final int CODE_NO_CONNECTION = -2; + + private static final int NB_DAYS = 5; + + private static final String KEY_SAVED_ROOMS = "rooms"; + private static final String KEY_SAVED_LESSONS_ROOMS = "lessons_rooms"; + private static final String KEY_SAVED_DAY_SELECTED = "day_selected"; + private static final String KEY_SAVED_HOUR_START = "hour_start"; + private static final String KEY_SAVED_MINUTES_START = "minutes_start"; + private static final String KEY_SAVED_HOUR_END = "hour_end"; + private static final String KEY_SAVED_MINUTES_END = "minutes_end"; + private static final String KEY_SAVED_THREAD_ALIVE = "thread_alive"; + private static final String KEY_SAVED_THREAD_TYPE = "thread_type"; + + private HttpBasicClient httpBasicClient; + private PrefManager prefManager; + private ArrayList freeRooms; + private HashMap>> lessonsByRoom; + private RecyclerViewAdapter adapter; + private ProgressDialog progressDialog; + private BottomSheetDialogLessonsOfRoom bottomSheetDialog; + + private LessonDao lessonDao; + private RoomHEIAFRDao roomHEIAFRDao; + private LessonRoomJoinDao lessonRoomJoinDao; + private TeacherDao teacherDao; + private LessonTeacherJoinDao lessonTeacherJoinDao; + private ClassHEIAFRDao classHEIAFRDao; + private LessonClassJoinDao lessonClassJoinDao; + + private Spinner spinnerDay; + private TextView txtStartHour; + private TextView txtStartMinutes; + private TextView txtEndHour; + private TextView txtEndMinutes; + private int startHour; + private int startMinutes; + private int endHour; + private int endMinutes; + private Thread currentThread; + private ThreadType threadType; + + public enum ThreadType { + refreshLessons, + searchRooms; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_find_room); + + prefManager = new PrefManager(this); + httpBasicClient = new HttpBasicClient(this, prefManager.getUsername(), prefManager.getPassword()); + MyDatabase myDatabase = MyDatabase.getInstance(this); + lessonDao = myDatabase.getLessonDao(); + roomHEIAFRDao = myDatabase.getRoomHEIAFRDao(); + lessonRoomJoinDao = myDatabase.getLessonRoomJoinDao(); + teacherDao = myDatabase.getTeacherDao(); + lessonTeacherJoinDao = myDatabase.getLessonTeacherJoinDao(); + classHEIAFRDao = myDatabase.getClassHEIAFRDao(); + lessonClassJoinDao = myDatabase.getLessonClassJoinDao(); + + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + + spinnerDay = findViewById(R.id.day); + + String[] days = {getResources().getString(R.string.day_0), getResources().getString(R.string.day_1), + getResources().getString(R.string.day_2), getResources().getString(R.string.day_3), + getResources().getString(R.string.day_4)}; + ArrayAdapter aa = new ArrayAdapter(this, R.layout.spinner_item, days); + aa.setDropDownViewResource(R.layout.spinner_dropdown_item); + spinnerDay.setAdapter(aa); + if (savedInstanceState != null) { + spinnerDay.setSelection(savedInstanceState.getInt(KEY_SAVED_DAY_SELECTED)); + } else { + switch (Calendar.getInstance().get(Calendar.DAY_OF_WEEK)) { + case Calendar.TUESDAY: + spinnerDay.setSelection(1); + break; + case Calendar.WEDNESDAY: + spinnerDay.setSelection(2); + break; + case Calendar.THURSDAY: + spinnerDay.setSelection(3); + break; + case Calendar.FRIDAY: + spinnerDay.setSelection(4); + break; + default: + spinnerDay.setSelection(0); + break; + } + } + + LinearLayout layoutTimeStart = findViewById(R.id.time_start); + LinearLayout layoutTimeEnd = findViewById(R.id.time_end); + + txtStartHour = layoutTimeStart.findViewById(R.id.hour); + txtStartMinutes = layoutTimeStart.findViewById(R.id.minutes); + txtEndHour = layoutTimeEnd.findViewById(R.id.hour); + txtEndMinutes = layoutTimeEnd.findViewById(R.id.minutes); + + if (savedInstanceState != null) { + startHour = savedInstanceState.getInt(KEY_SAVED_HOUR_START); + startMinutes = savedInstanceState.getInt(KEY_SAVED_HOUR_END); + endHour = savedInstanceState.getInt(KEY_SAVED_HOUR_END); + endMinutes = savedInstanceState.getInt(KEY_SAVED_MINUTES_END); + txtStartHour.setText(String.format("%02d", startHour)); + txtStartMinutes.setText(String.format("%02d", startMinutes)); + txtEndHour.setText(String.format("%02d", endHour)); + txtEndMinutes.setText(String.format("%02d", endMinutes)); + } else { + Calendar currentTime = Calendar.getInstance(); + int hour = currentTime.get(Calendar.HOUR_OF_DAY); + int minutes = currentTime.get(Calendar.MINUTE); + + startHour = hour; + startMinutes = minutes; + txtStartHour.setText(String.format("%02d", startHour)); + txtStartMinutes.setText(String.format("%02d", startMinutes)); + + hour++; + if (hour > 23) { + hour = 23; + minutes = 59; + } + + txtEndHour.setText(String.format("%02d", hour)); + txtEndMinutes.setText(String.format("%02d", minutes)); + endHour = hour; + endMinutes = minutes; + } + + layoutTimeStart.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + new TimePickerDialog(FindRoomActivity.this, new TimePickerDialog.OnTimeSetListener() { + @Override + public void onTimeSet(TimePicker timePicker, int selectedHour, int selectedMinute) { + txtStartHour.setText(String.format("%02d", selectedHour)); + txtStartMinutes.setText(String.format("%02d", selectedMinute)); + int diffHours = selectedHour - startHour; + int diffMinutes = selectedMinute - startMinutes; + startHour = selectedHour; + startMinutes = selectedMinute; + endHour += diffHours; + endMinutes += diffMinutes; + if (endMinutes > 59) { + endHour++; + endMinutes -= 60; + } else if (endMinutes < 0) { + endHour--; + endMinutes += 60; + } + if (endHour > 23) { + endHour = 23; + endMinutes = 59; + } else if (endHour < 0) { + endHour = 0; + endMinutes = 0; + } + txtEndHour.setText(String.format("%02d", endHour)); + txtEndMinutes.setText(String.format("%02d", endMinutes)); + } + }, startHour, startMinutes, true).show(); + } + }); + layoutTimeEnd.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + new TimePickerDialog(FindRoomActivity.this, new TimePickerDialog.OnTimeSetListener() { + @Override + public void onTimeSet(TimePicker timePicker, int selectedHour, int selectedMinute) { + if (startHour < selectedHour || (startHour == selectedHour && startMinutes <= selectedMinute)) { + txtEndHour.setText(String.format("%02d", selectedHour)); + txtEndMinutes.setText(String.format("%02d", selectedMinute)); + endHour = selectedHour; + endMinutes = selectedMinute; + } else { + Toast.makeText(FindRoomActivity.this, getResources().getString(R.string.error_end_time_before_start), Toast.LENGTH_SHORT).show(); + } + } + }, endHour, endMinutes, true).show(); + } + }); + + ImageButton btnSearch = findViewById(R.id.search_button); + btnSearch.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + searchRooms(); + } + }); + + if (savedInstanceState != null) { + freeRooms = (ArrayList) savedInstanceState.getSerializable(KEY_SAVED_ROOMS); + lessonsByRoom = (HashMap>>) savedInstanceState.getSerializable(KEY_SAVED_LESSONS_ROOMS); + + if (savedInstanceState.getBoolean(KEY_SAVED_THREAD_ALIVE, false)) { + threadType = (ThreadType) savedInstanceState.getSerializable(KEY_SAVED_THREAD_TYPE); + switch (threadType) { + case searchRooms: + searchRooms(); + break; + case refreshLessons: + refreshLessons(); + break; + } + } + } else { + freeRooms = new ArrayList<>(); + lessonsByRoom = new HashMap<>(); + } + RecyclerView recyclerView = findViewById(R.id.rooms); + recyclerView.setHasFixedSize(true); + LinearLayoutManager layoutManager = new LinearLayoutManager(this); + recyclerView.setLayoutManager(layoutManager); + bottomSheetDialog = new BottomSheetDialogLessonsOfRoom(); + adapter = new RecyclerViewAdapter(freeRooms, lessonsByRoom, bottomSheetDialog, getSupportFragmentManager()); + recyclerView.setAdapter(adapter); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.menu_find_room, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(@NonNull MenuItem item) { + // Handle item selection + switch (item.getItemId()) { + case R.id.action_refresh_lessons: + refreshLessons(); + return true; + case android.R.id.home: + finish(); + return true; + } + return super.onOptionsItemSelected(item); + } + + private void refreshLessons() { + String progressTitle = getResources().getString(R.string.progress_refresh_lessons_title); + progressDialog = ProgressDialog.show(this, progressTitle, null, true); + threadType = ThreadType.refreshLessons; + currentThread = new Thread(new Runnable() { + @Override + public void run() { + int httpCode = CODE_NO_PROBLEMS; + if (httpBasicClient.isConnectedToInternet()) { + try { + lessonClassJoinDao.deleteLessonsNoClassUpdate(); + getRoomsFromInternet(new ArrayList<>(roomHEIAFRDao.getRooms())); + } catch (HttpBasicClient.HttpException e) { + httpCode = e.getCode(); + } catch (UnknownHostException | HttpBasicClient.NoInternetConnectionException e) { + httpCode = CODE_NO_CONNECTION; + } catch (InterruptedIOException e) { + // do nothing + } + } else { + httpCode = CODE_NO_CONNECTION; + } + + final int finalHttpCode = httpCode; + runOnUiThread(new Runnable() { + @Override + public void run() { + if (!FindRoomActivity.this.isDestroyed() && progressDialog != null && progressDialog.isShowing()) + progressDialog.dismiss(); + if (finalHttpCode != CODE_NO_PROBLEMS) + showHttpErrorCode(finalHttpCode); + } + }); + } + }); + currentThread.start(); + } + + private void searchRooms() { + String progressTitle = getResources().getString(R.string.progress_search_rooms_title); + progressDialog = ProgressDialog.show(this, progressTitle, null, true); + searchRooms(spinnerDay.getSelectedItemPosition(), startHour, startMinutes, endHour, endMinutes); + } + + private void searchRooms(final int day, final int hourStart, final int minutesStart, final int hourEnd, final int minutesEnd) { + threadType = ThreadType.searchRooms; + currentThread = new Thread(new Runnable() { + @Override + public void run() { + try { + int httpCode = CODE_NO_PROBLEMS; + + Date lastUpdate = prefManager.getLastUpdateRoomsLessons(); + ArrayList rooms = new ArrayList<>(roomHEIAFRDao.getRooms()); + if (rooms.size() == 0) { + try { + rooms = new ArrayList<>(httpBasicClient.getRooms()); + roomHEIAFRDao.insertRooms(rooms); + } catch (HttpBasicClient.HttpException e) { + httpCode = e.getCode(); + } catch (UnknownHostException | HttpBasicClient.NoInternetConnectionException e) { + httpCode = CODE_NO_CONNECTION; + } + } + if (lessonsByRoom.size() == 0) { + if (lastUpdate == null) { + try { + getRoomsFromInternet(rooms); + } catch (HttpBasicClient.HttpException e) { + httpCode = e.getCode(); + } catch (UnknownHostException | HttpBasicClient.NoInternetConnectionException e) { + httpCode = CODE_NO_CONNECTION; + } + } else { + getRoomsFromDB(rooms); + } + } + + updateRoomFreeList(day, hourStart, minutesStart, hourEnd, minutesEnd); + + final int finalHttpCode = httpCode; + runOnUiThread(new Runnable() { + @Override + public void run() { + if (!FindRoomActivity.this.isDestroyed() && progressDialog != null && progressDialog.isShowing()) + progressDialog.dismiss(); + if (finalHttpCode == CODE_NO_PROBLEMS) { + adapter.notifyDataSetChanged(); + } else { + showHttpErrorCode(finalHttpCode); + } + } + }); + } catch (InterruptedIOException e) { + // do nothing + } + } + }); + currentThread.start(); + } + + private void getRoomsFromDB(ArrayList rooms) { + for (RoomHEIAFR room : rooms) { + HashMap> lessonsCompletes = new HashMap<>(); + for (int i = 0; i < NB_DAYS; i++) + lessonsCompletes.put(i, new ArrayList()); + + ArrayList lessons = new ArrayList<>(lessonRoomJoinDao.getLessonsForRoom(room.getId())); + for (Lesson lesson : lessons) { + ArrayList classes = new ArrayList<>(lessonClassJoinDao.getClassesForLesson(lesson.getId())); + ArrayList teachers = new ArrayList<>(lessonTeacherJoinDao.getTeachersForLesson(lesson.getId())); + lessonsCompletes.get(lesson.getDayOfTheWeek()).add(new LessonOfRoomComplete(lesson, classes, teachers)); + } + lessonsByRoom.put(room, lessonsCompletes); + } + } + + private void getRoomsFromInternet(ArrayList rooms) + throws HttpBasicClient.HttpException, InterruptedIOException, HttpBasicClient.NoInternetConnectionException, UnknownHostException { + ArrayList allLessons = new ArrayList<>(lessonDao.getLessons()); + for (RoomHEIAFR room : rooms) { + HashMap> lessonsCompletes = new HashMap<>(); + for (int i = 0; i < NB_DAYS; i++) + lessonsCompletes.put(i, new ArrayList()); + + List listReceived = httpBasicClient.getRoomLessons(room.getId()); + if (listReceived != null) { + ArrayList lessonsIncompletes = new ArrayList<>(listReceived); + for (LessonOfRoomIncomplete lessonIncomplete : lessonsIncompletes) { + Lesson lesson = lessonIncomplete.getLesson(); + if (allLessons.contains(lesson)) { + lesson = allLessons.get(allLessons.indexOf(lesson)); + } else { + long id = lessonDao.insertLesson(lesson); + lesson.setId(id); + allLessons.add(lesson); + } + try { + lessonRoomJoinDao.insertLessonRoomJoin(new LessonRoomJoin(lesson.getId(), room.getId())); + } catch (SQLiteConstraintException e) { + // do nothing, the association is allready in the DB + } + + ArrayList classes = new ArrayList<>(); + for (String className : lessonIncomplete.getClassIDs()) { + ClassHEIAFR classHEIAFR = classHEIAFRDao.getClass(className); + if (classHEIAFR == null) { + classHEIAFR = new ClassHEIAFR(className); + classHEIAFRDao.insertClass(classHEIAFR); + } + classes.add(classHEIAFR); + try { + lessonClassJoinDao.insertLessonClassJoin(new LessonClassJoin(lesson.getId(), className)); + } catch (SQLiteConstraintException e) { + // do nothing, the association is allready in the DB + } + } + + ArrayList teachers = new ArrayList<>(); + for (String teacherAbbr : lessonIncomplete.getTeachersAbbrs()) { + Teacher teacher = teacherDao.getTeacher(teacherAbbr); + if (teacher == null) { + teacher = httpBasicClient.getTeacherInfos(teacherAbbr); + teacherDao.insertTeacher(teacher); + } + teachers.add(teacher); + try { + lessonTeacherJoinDao.insertLessonTeacherJoin(new LessonTeacherJoin(lesson.getId(), teacherAbbr)); + } catch (SQLiteConstraintException e) { + // do nothing, the association is allready in the DB + } + } + + lessonsCompletes.get(lesson.getDayOfTheWeek()).add(new LessonOfRoomComplete(lesson, classes, teachers)); + } + } + lessonsByRoom.put(room, lessonsCompletes); + } + prefManager.setLastUpdateRoomsLessons(new Date()); + } + + private void updateRoomFreeList(int day, int hourStart, int minutesStart, int hourEnd, int minutesEnd) { + freeRooms.clear(); + for (RoomHEIAFR room : lessonsByRoom.keySet()) { + boolean roomFree = true; + for (LessonOfRoomComplete lessonOfRoomComplete : lessonsByRoom.get(room).get(day)) { + Lesson lesson = lessonOfRoomComplete.getLesson(); + + int thisHourStart = Integer.parseInt(lesson.getTimeStart().substring(0, 2)); + int thisMinutesStart = Integer.parseInt(lesson.getTimeStart().substring(3, 5)); + int thisHourEnd = Integer.parseInt(lesson.getTimeEnd().substring(0, 2)); + int thisMinutesEnd = Integer.parseInt(lesson.getTimeEnd().substring(3, 5)); + + if ((hourStart < thisHourEnd || (hourStart == thisHourEnd && minutesStart < thisMinutesEnd)) && + (hourEnd > thisHourStart || (hourEnd == thisHourStart && minutesEnd >= thisMinutesStart))) { + roomFree = false; + break; + } + } + if (roomFree) + freeRooms.add(room); + } + Collections.sort(freeRooms, RoomHEIAFR.ROOM_HEIAFR_COMPARATOR); + } + + private void showHttpErrorCode(int httpCode) { + String message; + switch (httpCode) { + case CODE_NO_PROBLEMS: + return; + case 401: + message = getResources().getString(R.string.login_failed); + break; + case CODE_NO_CONNECTION: + message = getResources().getString(R.string.unknown_host); + break; + default: + message = getResources().getString(R.string.http_error) + " " + httpCode; + break; + } + Toast.makeText(getApplicationContext(), message, Toast.LENGTH_SHORT).show(); + if (httpCode == 401) launchWelcomeScreen(); + } + + private void launchWelcomeScreen() { + prefManager.setUserDisconnected(true); + prefManager.setPassword(""); + startActivity(new Intent(FindRoomActivity.this, WelcomeActivity.class)); + finish(); + } + + @Override + public void onSaveInstanceState(@NotNull Bundle savedInstanceState) { + super.onSaveInstanceState(savedInstanceState); + savedInstanceState.putSerializable(KEY_SAVED_ROOMS, freeRooms); + savedInstanceState.putSerializable(KEY_SAVED_LESSONS_ROOMS, lessonsByRoom); + savedInstanceState.putInt(KEY_SAVED_DAY_SELECTED, spinnerDay.getSelectedItemPosition()); + savedInstanceState.putInt(KEY_SAVED_HOUR_START, startHour); + savedInstanceState.putInt(KEY_SAVED_MINUTES_START, startMinutes); + savedInstanceState.putInt(KEY_SAVED_HOUR_END, endHour); + savedInstanceState.putInt(KEY_SAVED_MINUTES_END, endMinutes); + if (currentThread != null && currentThread.isAlive()) { + currentThread.interrupt(); + savedInstanceState.putBoolean(KEY_SAVED_THREAD_ALIVE, true); + savedInstanceState.putSerializable(KEY_SAVED_THREAD_TYPE, threadType); + } else { + savedInstanceState.putBoolean(KEY_SAVED_THREAD_ALIVE, false); + } + } + + @Override + protected void onPause() { + super.onPause(); + if (bottomSheetDialog != null && bottomSheetDialog.isVisible()) + bottomSheetDialog.dismiss(); + } + + @Override + protected void onResume() { + super.onResume(); + + if (prefManager.getLastUpdateRoomsLessons() == null) { + new AlertDialog.Builder(this) + .setTitle(getString(R.string.action_find_room)) + .setMessage(getString(R.string.warning_first_search)) + .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + refreshLessons(); + } + }) + .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + finish(); + } + }) + .show(); + } + } +} diff --git a/app/src/main/java/ch/lburgy/heiafrschedule/activities/MainActivity.java b/app/src/main/java/ch/lburgy/heiafrschedule/activities/MainActivity.java new file mode 100644 index 0000000..afe47e3 --- /dev/null +++ b/app/src/main/java/ch/lburgy/heiafrschedule/activities/MainActivity.java @@ -0,0 +1,604 @@ +package ch.lburgy.heiafrschedule.activities; + +import android.app.ProgressDialog; +import android.content.Context; +import android.content.Intent; +import android.database.sqlite.SQLiteConstraintException; +import android.os.Build; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.app.AppCompatDelegate; +import androidx.fragment.app.DialogFragment; +import androidx.viewpager.widget.PagerAdapter; +import androidx.viewpager.widget.ViewPager; + +import com.google.android.material.bottomnavigation.BottomNavigationView; + +import org.jetbrains.annotations.NotNull; + +import java.io.InterruptedIOException; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.List; + +import ch.lburgy.heiafrschedule.R; +import ch.lburgy.heiafrschedule.database.ClassHEIAFR; +import ch.lburgy.heiafrschedule.database.ClassHEIAFRDao; +import ch.lburgy.heiafrschedule.database.Lesson; +import ch.lburgy.heiafrschedule.database.LessonClassJoin; +import ch.lburgy.heiafrschedule.database.LessonClassJoinDao; +import ch.lburgy.heiafrschedule.database.LessonOfClassComplete; +import ch.lburgy.heiafrschedule.database.LessonDao; +import ch.lburgy.heiafrschedule.database.LessonOfClassIncomplete; +import ch.lburgy.heiafrschedule.database.LessonRoomJoin; +import ch.lburgy.heiafrschedule.database.LessonRoomJoinDao; +import ch.lburgy.heiafrschedule.database.LessonTeacherJoin; +import ch.lburgy.heiafrschedule.database.LessonTeacherJoinDao; +import ch.lburgy.heiafrschedule.database.MyDatabase; +import ch.lburgy.heiafrschedule.database.RoomHEIAFR; +import ch.lburgy.heiafrschedule.database.RoomHEIAFRDao; +import ch.lburgy.heiafrschedule.database.Teacher; +import ch.lburgy.heiafrschedule.database.TeacherDao; +import ch.lburgy.heiafrschedule.http.HttpBasicClient; +import ch.lburgy.heiafrschedule.preferences.PrefManager; +import ch.lburgy.heiafrschedule.selfupdate.SelfUpdate; +import ch.lburgy.heiafrschedule.ui.BottomSheetDialogLessonsInfos; + +public class MainActivity extends AppCompatActivity implements BottomNavigationView.OnNavigationItemSelectedListener { + + private static final String KEY_SAVED_LESSONS = "lessons"; + private static final String KEY_SAVED_THREAD_ALIVE = "thread_alive"; + private static final String KEY_SAVED_THREAD_TYPE = "thread_type"; + + private static final int REQUEST_CODE_SETTINGS = 1; + private static final int REQUEST_CODE_FIND_ROOM = 2; + + private static final int CODE_NO_PROBLEMS = -1; + private static final int CODE_NO_CONNECTION = -2; + + private static final int NB_DAYS = 5; + + private LessonDao lessonDao; + private RoomHEIAFRDao roomHEIAFRDao; + private LessonRoomJoinDao lessonRoomJoinDao; + private TeacherDao teacherDao; + private LessonTeacherJoinDao lessonTeacherJoinDao; + private ClassHEIAFRDao classHEIAFRDao; + private LessonClassJoinDao lessonClassJoinDao; + private ClassHEIAFR classHEIAFR; + + private PrefManager prefManager; + private HttpBasicClient httpBasicClient; + + private ProgressDialog progressDialog; + private ViewPager viewPager; + private BottomNavigationView navigation; + private MyViewPagerAdapter myViewPagerAdapter; + private BottomSheetDialogLessonsInfos bottomSheetDialog; + + private HashMap> lessonsCompletesByDay; + private int dayDisplayed = -1; + + private Thread currentThread; + private ThreadType threadType; + + enum ThreadType { + getLessons, + getLessonsFromInternet; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + if (savedInstanceState == null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) + SelfUpdate.checkUpdate(this, "https://api.github.com/repos/burgyl/HEIA-FR-Horaires/releases/latest"); + + MyDatabase myDatabase = MyDatabase.getInstance(this); + lessonDao = myDatabase.getLessonDao(); + roomHEIAFRDao = myDatabase.getRoomHEIAFRDao(); + lessonRoomJoinDao = myDatabase.getLessonRoomJoinDao(); + teacherDao = myDatabase.getTeacherDao(); + lessonTeacherJoinDao = myDatabase.getLessonTeacherJoinDao(); + classHEIAFRDao = myDatabase.getClassHEIAFRDao(); + lessonClassJoinDao = myDatabase.getLessonClassJoinDao(); + prefManager = new PrefManager(this); + AppCompatDelegate.setDefaultNightMode(prefManager.getTheme()); + httpBasicClient = new HttpBasicClient(this, prefManager.getUsername(), prefManager.getPassword()); + + navigation = findViewById(R.id.bottom_navigation); + navigation.setOnNavigationItemSelectedListener(this); + + new Thread(new Runnable() { + @Override + public void run() { + classHEIAFR = classHEIAFRDao.getClass(prefManager.getClassID()); + runOnUiThread(new Runnable() { + @Override + public void run() { + setTitle(getResources().getString(R.string.app_name) + " " + classHEIAFR.getName()); + } + }); + } + }).start(); + + if (savedInstanceState == null) { + getLessonsAndShowThem(false); + } else { + lessonsCompletesByDay = (HashMap>) + savedInstanceState.getSerializable(KEY_SAVED_LESSONS); + if (savedInstanceState.getBoolean(KEY_SAVED_THREAD_ALIVE, false)) { + threadType = (ThreadType) savedInstanceState.getSerializable(KEY_SAVED_THREAD_TYPE); + switch (threadType) { + case getLessons: + getLessonsAndShowThem(false); + break; + case getLessonsFromInternet: + getLessonsAndShowThem(true); + break; + } + } else { + showLessonsStart(); + } + } + } + + private void getLessonsAndShowThem(final boolean forceFromInternet) { + if (MainActivity.this.isDestroyed()) return; + String progressTitle = getResources().getString(R.string.progress_refresh_lessons_title); + progressDialog = ProgressDialog.show(this, progressTitle, null, true); + threadType = ThreadType.getLessons; + currentThread = new Thread(new Runnable() { + @Override + public void run() { + try { + int httpCode = CODE_NO_PROBLEMS; + if (!forceFromInternet) { + lessonsCompletesByDay = new HashMap<>(); + for (int i = 0; i < NB_DAYS; i++) + lessonsCompletesByDay.put(i, new ArrayList()); + + if (classHEIAFR == null) + classHEIAFR = classHEIAFRDao.getClass(prefManager.getClassID()); + + if (classHEIAFR.getLastUpdate() != null) { + List lessons = lessonClassJoinDao.getLessonsForClass(classHEIAFR.getName()); + for (Lesson lesson : lessons) { + List rooms = lessonRoomJoinDao.getRoomsForLesson(lesson.getId()); + List teachers = lessonTeacherJoinDao.getTeachersForLesson(lesson.getId()); + lessonsCompletesByDay.get(lesson.getDayOfTheWeek()).add(new LessonOfClassComplete(lesson, rooms, teachers)); + } + } else { + try { + getLessonsFromInternet(false); + } catch (HttpBasicClient.HttpException e) { + httpCode = e.getCode(); + } catch (UnknownHostException | HttpBasicClient.NoInternetConnectionException e) { + httpCode = CODE_NO_CONNECTION; + } + } + } else { + try { + getLessonsFromInternet(false); + } catch (HttpBasicClient.HttpException e) { + httpCode = e.getCode(); + } catch (UnknownHostException | HttpBasicClient.NoInternetConnectionException e) { + httpCode = CODE_NO_CONNECTION; + } + } + + final int finalHttpCode = httpCode; + runOnUiThread(new Runnable() { + @Override + public void run() { + if (finalHttpCode == CODE_NO_PROBLEMS) { + if (dayDisplayed == -1) { + showLessonsStart(); + } else { + viewPager.setAdapter(myViewPagerAdapter); + viewPager.setCurrentItem(dayDisplayed); + } + } else { + showHttpErrorCode(finalHttpCode); + } + + if (!MainActivity.this.isDestroyed() && progressDialog != null && progressDialog.isShowing()) + progressDialog.dismiss(); + } + }); + } catch (InterruptedIOException e) { + // do nothing + } + } + }); + currentThread.start(); + } + + private void showLessonsStart() { + viewPager = findViewById(R.id.view_pager); + myViewPagerAdapter = new MyViewPagerAdapter(); + viewPager.setAdapter(myViewPagerAdapter); + viewPager.addOnPageChangeListener(viewPagerPageChangeListener); + + Calendar calendar = Calendar.getInstance(); + int day = calendar.get(Calendar.DAY_OF_WEEK); + int hour = calendar.get(Calendar.HOUR_OF_DAY); + int minutes = calendar.get(Calendar.MINUTE); + + int dayNumber = 0; + switch (day) { + case Calendar.TUESDAY: + dayNumber = 1; + break; + case Calendar.WEDNESDAY: + dayNumber = 2; + break; + case Calendar.THURSDAY: + dayNumber = 3; + break; + case Calendar.FRIDAY: + dayNumber = 4; + break; + case Calendar.SATURDAY: + dayNumber = 5; + break; + case Calendar.SUNDAY: + dayNumber = 6; + break; + } + + if (lessonsCompletesByDay.containsKey(dayNumber)) { + ArrayList lessonsOfTheDay = lessonsCompletesByDay.get(dayNumber); + if (lessonsOfTheDay.size() > 0) { + LessonOfClassComplete lastLesson = lessonsOfTheDay.get(lessonsOfTheDay.size() - 1); + String timeEnd = lastLesson.getLesson().getTimeEnd(); + int lastHour = Integer.parseInt(timeEnd.substring(0, 2)); + int minutesOfTheLastHour = Integer.parseInt(timeEnd.substring(3, 5)); + if (hour > lastHour || (hour == lastHour && minutes >= minutesOfTheLastHour)) + dayNumber++; + } + } + if (dayNumber > 4) + dayNumber = 0; + + dayDisplayed = dayNumber; + viewPager.setCurrentItem(dayDisplayed); + } + + private void refreshLessons() { + String progressTitle = getResources().getString(R.string.progress_refresh_lessons_title); + progressDialog = ProgressDialog.show(this, progressTitle, null, true); + threadType = ThreadType.getLessonsFromInternet; + currentThread = new Thread(new Runnable() { + @Override + public void run() { + try { + int httpCode = CODE_NO_PROBLEMS; + if (httpBasicClient.isConnectedToInternet()) { + try { + getLessonsFromInternet(true); + } catch (HttpBasicClient.HttpException e) { + httpCode = e.getCode(); + } catch (UnknownHostException | HttpBasicClient.NoInternetConnectionException e) { + httpCode = CODE_NO_CONNECTION; + } + } else { + httpCode = CODE_NO_CONNECTION; + } + + final int finalHttpCode = httpCode; + runOnUiThread(new Runnable() { + public void run() { + if (finalHttpCode == CODE_NO_PROBLEMS) { + viewPager.setAdapter(myViewPagerAdapter); + viewPager.setCurrentItem(dayDisplayed); + } else { + showHttpErrorCode(finalHttpCode); + } + if (!MainActivity.this.isDestroyed() && progressDialog != null && progressDialog.isShowing()) + progressDialog.dismiss(); + } + }); + } catch (InterruptedIOException e) { + // do nothing + } + } + }); + currentThread.start(); + } + + private void getLessonsFromInternet(boolean deleteExisting) throws HttpBasicClient.HttpException, UnknownHostException, HttpBasicClient.NoInternetConnectionException, InterruptedIOException { + ArrayList lessonsIncompletes = new ArrayList<>(httpBasicClient.getClassLessons(classHEIAFR.getName())); + if (deleteExisting) { + lessonClassJoinDao.deleteLessonsFromClass(classHEIAFR.getName()); + lessonClassJoinDao.deleteLessonsNoClass(); + } + + if (roomHEIAFRDao.getNbRoom() == 0) { + ArrayList rooms = new ArrayList<>(httpBasicClient.getRooms()); + roomHEIAFRDao.insertRooms(rooms); + } + + lessonsCompletesByDay = new HashMap<>(); + for (int i = 0; i < NB_DAYS; i++) + lessonsCompletesByDay.put(i, new ArrayList()); + + ArrayList allLessons = new ArrayList<>(lessonDao.getLessons()); + for (LessonOfClassIncomplete lessonIncomplete : lessonsIncompletes) { + Lesson lesson = lessonIncomplete.getLesson(); + if (allLessons.contains(lesson)) { + lesson = allLessons.get(allLessons.indexOf(lesson)); + } else { + long id = lessonDao.insertLesson(lesson); + lesson.setId(id); + allLessons.add(lesson); + } + try { + lessonClassJoinDao.insertLessonClassJoin(new LessonClassJoin(lesson.getId(), classHEIAFR.getName())); + } catch (SQLiteConstraintException e) { + // do nothing, the association is allready in the DB + } + + ArrayList rooms = new ArrayList<>(); + for (String roomID : lessonIncomplete.getRoomsIDs()) { + RoomHEIAFR room = roomHEIAFRDao.getRoom(roomID); + if (room == null) { + room = new RoomHEIAFR(roomID); + roomHEIAFRDao.insertRoom(room); + } + rooms.add(room); + try { + lessonRoomJoinDao.insertLessonRoomJoin(new LessonRoomJoin(lesson.getId(), roomID)); + } catch (SQLiteConstraintException e) { + // do nothing, the association is allready in the DB + } + } + + ArrayList teachers = new ArrayList<>(); + for (String teacherAbbr : lessonIncomplete.getTeachersAbbrs()) { + Teacher teacher = teacherDao.getTeacher(teacherAbbr); + if (teacher == null) { + teacher = httpBasicClient.getTeacherInfos(teacherAbbr); + teacherDao.insertTeacher(teacher); + } + teachers.add(teacher); + try { + lessonTeacherJoinDao.insertLessonTeacherJoin(new LessonTeacherJoin(lesson.getId(), teacherAbbr)); + } catch (SQLiteConstraintException e) { + // do nothing, the association is allready in the DB + } + } + + lessonsCompletesByDay.get(lesson.getDayOfTheWeek()).add(new LessonOfClassComplete(lesson, rooms, teachers)); + } + classHEIAFR.setLastUpdate(new Date()); + classHEIAFRDao.updateClassHEIAFR(classHEIAFR); + } + + private void showHttpErrorCode(int httpCode) { + String message; + switch (httpCode) { + case CODE_NO_PROBLEMS: + return; + case 401: + message = getResources().getString(R.string.login_failed); + break; + case CODE_NO_CONNECTION: + message = getResources().getString(R.string.unknown_host); + break; + default: + message = getResources().getString(R.string.http_error) + " " + httpCode; + break; + } + Toast.makeText(getApplicationContext(), message, Toast.LENGTH_SHORT).show(); + if (httpCode == 401) launchWelcomeScreen(); + } + + private void launchWelcomeScreen() { + prefManager.setUserDisconnected(true); + prefManager.setPassword(""); + startActivity(new Intent(MainActivity.this, WelcomeActivity.class)); + finish(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.menu_main, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // Handle item selection + switch (item.getItemId()) { + case R.id.action_settings: + startActivityForResult(new Intent(this, SettingsActivity.class), REQUEST_CODE_SETTINGS); + return true; + case R.id.action_refresh_lessons: + refreshLessons(); + return true; + case R.id.action_find_room: + startActivityForResult(new Intent(this, FindRoomActivity.class), REQUEST_CODE_FIND_ROOM); + return true; + } + return super.onOptionsItemSelected(item); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { + if (requestCode == REQUEST_CODE_SETTINGS) { + if (data == null) return; + String keyExtraClassChanged = getResources().getString(R.string.key_extra_class_changed); + boolean classChanged = data.getBooleanExtra(keyExtraClassChanged, false); + if (classChanged) { + new Thread(new Runnable() { + @Override + public void run() { + classHEIAFR = classHEIAFRDao.getClass(prefManager.getClassID()); + runOnUiThread(new Runnable() { + @Override + public void run() { + setTitle(getResources().getString(R.string.app_name) + " " + classHEIAFR.getName()); + } + }); + } + }).start(); + getLessonsAndShowThem(false); + } + } + if (prefManager.isLaunchWelcome()) { + finish(); + } + super.onActivityResult(requestCode, resultCode, data); + } + + @Override + public boolean onNavigationItemSelected(@NonNull MenuItem item) { + switch (item.getItemId()) { + case R.id.navigation_day_0: + dayDisplayed = 0; + break; + case R.id.navigation_day_1: + dayDisplayed = 1; + break; + case R.id.navigation_day_2: + dayDisplayed = 2; + break; + case R.id.navigation_day_3: + dayDisplayed = 3; + break; + case R.id.navigation_day_4: + dayDisplayed = 4; + break; + } + viewPager.setCurrentItem(dayDisplayed); + return true; + } + + private final ViewPager.OnPageChangeListener viewPagerPageChangeListener = new ViewPager.OnPageChangeListener() { + + @Override + public void onPageSelected(int position) { + dayDisplayed = position; + navigation.getMenu().getItem(position).setChecked(true); + } + + @Override + public void onPageScrolled(int arg0, float arg1, int arg2) { + } + + @Override + public void onPageScrollStateChanged(int arg0) { + } + }; + + public class MyViewPagerAdapter extends PagerAdapter { + + public MyViewPagerAdapter() { + } + + @NotNull + @Override + public Object instantiateItem(@NotNull ViewGroup container, int position) { + LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); + + View view = inflater.inflate(R.layout.main_slide, container, false); + container.addView(view); + + putLessonCards(view, inflater, position); + + return view; + } + + @Override + public int getCount() { + return NB_DAYS; + } + + @Override + public boolean isViewFromObject(@NotNull View view, @NotNull Object obj) { + return view == obj; + } + + @Override + public void destroyItem(ViewGroup container, int position, @NotNull Object object) { + View view = (View) object; + container.removeView(view); + } + } + + private void putLessonCards(View view, LayoutInflater inflater, int position) { + LinearLayout linearLayout = view.findViewById(R.id.contentLayout); + linearLayout.removeAllViews(); + for (final LessonOfClassComplete lessonOfClassComplete : lessonsCompletesByDay.get(position)) { + LinearLayout cardView = (LinearLayout) inflater.inflate(R.layout.card_lesson, null); + + cardView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + showLessonInfos(lessonOfClassComplete); + } + }); + + TextView txtTimeStart = cardView.findViewById(R.id.time_start); + TextView txtTimeEnd = cardView.findViewById(R.id.time_end); + TextView txtName = cardView.findViewById(R.id.name); + TextView txtRoom = cardView.findViewById(R.id.room); + + txtTimeStart.setText(lessonOfClassComplete.getLesson().getTimeStart()); + txtTimeEnd.setText(lessonOfClassComplete.getLesson().getTimeEnd()); + txtName.setText(lessonOfClassComplete.getLesson().getName()); + + StringBuilder roomText = new StringBuilder(); + for (int i = 0; i < lessonOfClassComplete.getRooms().size(); i++) { + if (i != 0) + roomText.append("\n"); + roomText.append(lessonOfClassComplete.getRooms().get(i).getId()); + } + txtRoom.setText(roomText); + + linearLayout.addView(cardView); + } + } + + private void showLessonInfos(LessonOfClassComplete lessonOfClassComplete) { + bottomSheetDialog = new BottomSheetDialogLessonsInfos(lessonOfClassComplete); + bottomSheetDialog.setStyle(DialogFragment.STYLE_NORMAL, R.style.AppBottomSheetDialogTheme); + bottomSheetDialog.show(getSupportFragmentManager(), bottomSheetDialog.getTag()); + } + + @Override + public void onSaveInstanceState(@NotNull Bundle savedInstanceState) { + super.onSaveInstanceState(savedInstanceState); + savedInstanceState.putSerializable(KEY_SAVED_LESSONS, lessonsCompletesByDay); + if (currentThread != null && currentThread.isAlive()) { + currentThread.interrupt(); + savedInstanceState.putBoolean(KEY_SAVED_THREAD_ALIVE, true); + savedInstanceState.putSerializable(KEY_SAVED_THREAD_TYPE, threadType); + } else { + savedInstanceState.putBoolean(KEY_SAVED_THREAD_ALIVE, false); + } + } + + @Override + protected void onPause() { + super.onPause(); + if (bottomSheetDialog != null && bottomSheetDialog.isVisible()) + bottomSheetDialog.dismiss(); + } +} diff --git a/app/src/main/java/ch/lburgy/heiafrschedule/activities/SettingsActivity.java b/app/src/main/java/ch/lburgy/heiafrschedule/activities/SettingsActivity.java new file mode 100644 index 0000000..bb97cd8 --- /dev/null +++ b/app/src/main/java/ch/lburgy/heiafrschedule/activities/SettingsActivity.java @@ -0,0 +1,263 @@ +package ch.lburgy.heiafrschedule.activities; + +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.Build; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.view.MenuItem; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.app.AppCompatDelegate; +import androidx.preference.ListPreference; +import androidx.preference.Preference; +import androidx.preference.PreferenceFragmentCompat; + +import java.io.InterruptedIOException; +import java.net.UnknownHostException; +import java.util.List; + +import ch.lburgy.heiafrschedule.R; +import ch.lburgy.heiafrschedule.database.ClassHEIAFR; +import ch.lburgy.heiafrschedule.database.ClassHEIAFRDao; +import ch.lburgy.heiafrschedule.database.LessonDao; +import ch.lburgy.heiafrschedule.database.MyDatabase; +import ch.lburgy.heiafrschedule.database.RoomHEIAFRDao; +import ch.lburgy.heiafrschedule.database.TeacherDao; +import ch.lburgy.heiafrschedule.http.HttpBasicClient; +import ch.lburgy.heiafrschedule.preferences.PrefManager; + +import static androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY; +import static androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM; +import static androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_NO; +import static androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_YES; + +public class SettingsActivity extends AppCompatActivity implements SharedPreferences.OnSharedPreferenceChangeListener { + + private String keyTheme9; + private String keyTheme10; + private String keyClass; + private String keyExtraClassChanged; + + private PrefManager prefManager; + private SharedPreferences prefs; + private static boolean classChanged; + + @Override + protected void onCreate(Bundle savedInstanceState) { + keyTheme9 = getResources().getString(R.string.settings_key_theme_9); + keyTheme10 = getResources().getString(R.string.settings_key_theme_10); + keyClass = getResources().getString(R.string.settings_key_class); + keyExtraClassChanged = getResources().getString(R.string.key_extra_class_changed); + + prefManager = new PrefManager(this); + + prefs = PreferenceManager.getDefaultSharedPreferences(this); + prefs.registerOnSharedPreferenceChangeListener(this); + + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_settings); + getSupportFragmentManager() + .beginTransaction() + .replace(R.id.settings, new SettingsFragment()) + .commit(); + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.setDisplayHomeAsUpEnabled(true); + } + } + + @Override + public boolean onOptionsItemSelected(@NonNull MenuItem item) { + if (item.getItemId() == android.R.id.home) { + finish(); + return true; + } + return super.onOptionsItemSelected(item); + } + + @Override + public void finish() { + Intent i = getIntent(); + i.putExtra(keyExtraClassChanged, classChanged); + setResult(RESULT_OK, i); + super.finish(); + } + + @Override + protected void onDestroy() { + prefs.unregisterOnSharedPreferenceChangeListener(this); + super.onDestroy(); + } + + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + if (keyTheme9.equals(key)) { + String themeString9 = sharedPreferences.getString(keyTheme9, "MODE_NIGHT_AUTO_BATTERY"); + changeTheme(themeString9); + } else if (keyTheme10.equals(key)) { + String themeString10 = sharedPreferences.getString(keyTheme10, "MODE_NIGHT_FOLLOW_SYSTEM"); + changeTheme(themeString10); + } else if (keyClass.equals(key)) { + String classChoosen = sharedPreferences.getString(keyClass, ""); + if (!classChoosen.equals(prefManager.getClassID())) { + prefManager.setClassID(classChoosen); + classChanged = true; + } + } + } + + private void changeTheme(String themeString) { + int theme = -1; + switch (themeString) { + case "MODE_NIGHT_FOLLOW_SYSTEM": + theme = MODE_NIGHT_FOLLOW_SYSTEM; + break; + case "MODE_NIGHT_AUTO_BATTERY": + theme = MODE_NIGHT_AUTO_BATTERY; + break; + case "MODE_NIGHT_NO": + theme = MODE_NIGHT_NO; + break; + case "MODE_NIGHT_YES": + theme = MODE_NIGHT_YES; + break; + } + AppCompatDelegate.setDefaultNightMode(theme); + prefManager.setTheme(theme); + } + + public static class SettingsFragment extends PreferenceFragmentCompat { + + private static final int CODE_NO_CONNECTION = -2; + + private PrefManager prefManager; + private HttpBasicClient httpBasicClient; + private LessonDao lessonDao; + private ClassHEIAFRDao classHEIAFRDao; + private RoomHEIAFRDao roomHEIAFRDao; + private TeacherDao teacherDao; + + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + setPreferencesFromResource(R.xml.root_preferences, rootKey); + + prefManager = new PrefManager(getContext()); + httpBasicClient = new HttpBasicClient(getContext(), prefManager.getUsername(), prefManager.getPassword()); + MyDatabase myDatabase = MyDatabase.getInstance(getContext()); + lessonDao = myDatabase.getLessonDao(); + classHEIAFRDao = myDatabase.getClassHEIAFRDao(); + roomHEIAFRDao = myDatabase.getRoomHEIAFRDao(); + teacherDao = myDatabase.getTeacherDao(); + + String keyTheme9 = getResources().getString(R.string.settings_key_theme_9); + String keyTheme10 = getResources().getString(R.string.settings_key_theme_10); + if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + findPreference(keyTheme10).setVisible(true); + } else { + findPreference(keyTheme9).setVisible(true); + } + + Preference btnDeleteDatas = findPreference(getString(R.string.settings_key_delete_datas)); + btnDeleteDatas.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + deleteDatas(); + return true; + } + }); + + new Thread(new Runnable() { + @Override + public void run() { + List classes = classHEIAFRDao.getClasses(); + updateClasses(classes); + } + }).start(); + } + + private void deleteDatas() { + new Thread(new Runnable() { + @Override + public void run() { + lessonDao.deleteLessons(); + roomHEIAFRDao.deleteRooms(); + teacherDao.deleteTeachers(); + prefManager.setLastUpdateRoomsLessons(null); + classChanged = true; + getActivity().runOnUiThread(new Runnable() { + public void run() { + String message = getResources().getString(R.string.datas_deleted); + Toast.makeText(getContext(), message, Toast.LENGTH_SHORT).show(); + } + }); + refreshClasses(); + } + }).start(); + } + + private void refreshClasses() { + try { + int httpCode = -1; + try { + List classes = httpBasicClient.getClasses(); + classHEIAFRDao.deleteClasses(); + classHEIAFRDao.insertClasses(classes); + updateClasses(classes); + } catch (HttpBasicClient.HttpException e) { + httpCode = e.getCode(); + } catch (UnknownHostException | HttpBasicClient.NoInternetConnectionException e) { + httpCode = CODE_NO_CONNECTION; + } + + final int finalHttpCode = httpCode; + getActivity().runOnUiThread(new Runnable() { + public void run() { + final String message; + switch (finalHttpCode) { + case -1: + message = getResources().getString(R.string.classes_refreshed); + break; + case 401: + message = getResources().getString(R.string.login_failed); + break; + case CODE_NO_CONNECTION: + message = getResources().getString(R.string.unknown_host); + break; + default: + message = getResources().getString(R.string.http_error) + " " + finalHttpCode; + break; + } + + Toast.makeText(getContext(), message, Toast.LENGTH_SHORT).show(); + if (finalHttpCode == 401) launchWelcomeScreen(); + } + }); + } catch (InterruptedIOException e) { + // do nothing + } + } + + private void launchWelcomeScreen() { + prefManager.setUserDisconnected(true); + prefManager.setPassword(""); + startActivity(new Intent(getActivity(), WelcomeActivity.class)); + getActivity().finish(); + } + + private void updateClasses(List classes) { + String[] classesString = new String[classes.size()]; + for (int i = 0; i < classes.size(); i++) + classesString[i] = classes.get(i).getName(); + + String keyClass = getResources().getString(R.string.settings_key_class); + ListPreference prefClass = findPreference(keyClass); + prefClass.setEntries(classesString); + prefClass.setEntryValues(classesString); + prefClass.setValue(prefManager.getClassID()); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ch/lburgy/heiafrschedule/activities/WelcomeActivity.java b/app/src/main/java/ch/lburgy/heiafrschedule/activities/WelcomeActivity.java new file mode 100644 index 0000000..9063b40 --- /dev/null +++ b/app/src/main/java/ch/lburgy/heiafrschedule/activities/WelcomeActivity.java @@ -0,0 +1,445 @@ +package ch.lburgy.heiafrschedule.activities; + +import android.app.Activity; +import android.app.ProgressDialog; +import android.content.Context; +import android.content.Intent; +import android.graphics.Color; +import android.os.Build; +import android.os.Bundle; +import android.text.Editable; +import android.text.Html; +import android.text.TextWatcher; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.view.WindowManager; +import android.view.autofill.AutofillManager; +import android.view.inputmethod.InputMethodManager; +import android.widget.EditText; +import android.widget.ImageButton; +import android.widget.LinearLayout; +import android.widget.RadioButton; +import android.widget.RadioGroup; +import android.widget.Space; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.app.AppCompatDelegate; +import androidx.constraintlayout.widget.ConstraintLayout; +import androidx.viewpager.widget.PagerAdapter; +import androidx.viewpager.widget.ViewPager; + +import com.google.android.material.textfield.TextInputEditText; + +import org.jetbrains.annotations.NotNull; + +import java.io.InterruptedIOException; +import java.net.UnknownHostException; +import java.util.List; + +import ch.lburgy.heiafrschedule.R; +import ch.lburgy.heiafrschedule.database.ClassHEIAFR; +import ch.lburgy.heiafrschedule.database.ClassHEIAFRDao; +import ch.lburgy.heiafrschedule.database.MyDatabase; +import ch.lburgy.heiafrschedule.http.HttpBasicClient; +import ch.lburgy.heiafrschedule.preferences.PrefManager; +import ch.lburgy.heiafrschedule.selfupdate.SelfUpdate; +import ch.lburgy.heiafrschedule.ui.ViewPagerDisable; + +public class WelcomeActivity extends AppCompatActivity { + + private static final int FIRST_INDEX_RADIO_GROUP = 1; + private static final int CODE_NO_CONNECTION = -2; + + private ViewPagerDisable viewPager; + private LinearLayout dotsLayout; + private int[] layouts; + private ImageButton btnPrev, btnNext; + private TextInputEditText txtUsername, txtPassword; + private PrefManager prefManager; + private boolean loggedIn; + private AutofillManager afm; + private HttpBasicClient httpBasicClient; + private MyDatabase myDatabase; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + prefManager = new PrefManager(this); + AppCompatDelegate.setDefaultNightMode(prefManager.getTheme()); + if (!prefManager.isLaunchWelcome() && !prefManager.isUserDisconnected()) + launchHomeScreen(); + + if (savedInstanceState == null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) + SelfUpdate.checkUpdate(this, "https://api.github.com/repos/burgyl/HEIA-FR-Horaires/releases/latest"); + + httpBasicClient = new HttpBasicClient(this); + myDatabase = MyDatabase.getInstance(this); + + // Making notification bar transparent + if (Build.VERSION.SDK_INT >= 21) { + getWindow().getDecorView().setSystemUiVisibility( + View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); + } + + setContentView(R.layout.activity_welcome); + + viewPager = findViewById(R.id.view_pager); + dotsLayout = findViewById(R.id.layoutDots); + btnPrev = findViewById(R.id.btn_prev); + btnNext = findViewById(R.id.btn_next); + + // layouts of all welcome sliders + // add few more layouts if you want + if (prefManager.isUserDisconnected()) { + layouts = new int[]{ + R.layout.welcome_slide_1, + }; + } else { + layouts = new int[]{ + R.layout.welcome_slide_1, + R.layout.welcome_slide_2, + R.layout.welcome_slide_3, + }; + } + + // adding bottom dots + addBottomDots(0); + + // making notification bar transparent + changeStatusBarColor(); + + Space statusBar = findViewById(R.id.status_bar); + statusBar.setLayoutParams(new ConstraintLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, getStatusBarHeight() + )); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + afm = getSystemService(AutofillManager.class); + } + + MyViewPagerAdapter myViewPagerAdapter = new MyViewPagerAdapter(); + viewPager.setEnable(false); + viewPager.setAdapter(myViewPagerAdapter); + viewPager.addOnPageChangeListener(viewPagerPageChangeListener); + + btnPrev.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + viewPager.setCurrentItem(viewPager.getCurrentItem() - 1); + } + }); + + btnNext.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (viewPager.getCurrentItem() == layouts.length - 1 && !prefManager.isUserDisconnected()) { + // only if it's the first time. If the user was disconnected, it's taken care of when the login is successful + prefManager.setLaunchWelcome(false); + launchHomeScreen(); + } else { + nextPage(); + } + } + }); + + setNextEnable(false); + + View root = findViewById(R.id.activity_welcome_root); + setupUI(root); + } + + private int getStatusBarHeight() { + int result = 0; + int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android"); + if (resourceId > 0) { + result = getResources().getDimensionPixelSize(resourceId); + } + return result; + } + + private void addBottomDots(int currentPage) { + TextView[] dots = new TextView[layouts.length]; + + int colorsActive = getResources().getColor(R.color.colorWelcomeAccent); + int colorsInactive = getResources().getColor(R.color.colorWelcomeAccentDark); + + dotsLayout.removeAllViews(); + for (int i = 0; i < dots.length; i++) { + dots[i] = new TextView(this); + dots[i].setText(Html.fromHtml("•")); + dots[i].setTextSize(35); + dots[i].setTextColor(colorsInactive); + dotsLayout.addView(dots[i]); + } + + if (dots.length > 0) + dots[currentPage].setTextColor(colorsActive); + } + + private void nextPage() { + int nextPage = viewPager.getCurrentItem() + 1; + switch (nextPage) { + case 1: + checkLoginGetClasses(txtUsername.getText().toString(), txtPassword.getText().toString(), nextPage); + break; + case 2: + RadioGroup radioGroupClassesID = findViewById(R.id.radio_group_classes_id); + int selectedIndex = radioGroupClassesID.getCheckedRadioButtonId() - FIRST_INDEX_RADIO_GROUP; + RadioButton radioButton = (RadioButton) radioGroupClassesID.getChildAt(selectedIndex); + String classID = radioButton.getText().toString(); + prefManager.setClassID(classID); + viewPager.setCurrentItem(nextPage); + break; + default: + viewPager.setCurrentItem(nextPage); + break; + } + } + + private void checkLoginGetClasses(final String username, final String password, final int nextPage) { + if (loggedIn) { + viewPager.setCurrentItem(nextPage); + } else if (username != null && !"".equals(username) && password != null && !"".equals(password)) { + String progressTitle = getResources().getString(R.string.progress_login_title); + final ProgressDialog progressDialog = ProgressDialog.show(this, progressTitle, null, true); + + new Thread(new Runnable() { + @Override + public void run() { + try { + httpBasicClient.setCredentials(username, password); + List classes = null; + int httpCode = -1; + try { + classes = httpBasicClient.getClasses(); + ClassHEIAFRDao classHEIAFRDao = myDatabase.getClassHEIAFRDao(); + classHEIAFRDao.deleteClasses(); + classHEIAFRDao.insertClasses(classes); + } catch (HttpBasicClient.HttpException e) { + httpCode = e.getCode(); + } catch (UnknownHostException | HttpBasicClient.NoInternetConnectionException e) { + httpCode = CODE_NO_CONNECTION; + } + + final List finalClasses = classes; + final int finalHttpCode = httpCode; + + runOnUiThread(new Runnable() { + @Override + public void run() { + progressDialog.dismiss(); + + loggedIn = finalHttpCode == -1; + afterCheckedLogin(username, password, nextPage, finalClasses, finalHttpCode); + } + }); + } catch (InterruptedIOException e) { + // do nothing + } + } + }).start(); + } + } + + private void afterCheckedLogin(String username, String password, int nextPage, List classes, int httpCode) { + if (loggedIn) { + prefManager.setUsername(username); + prefManager.setPassword(password); + + if (prefManager.isUserDisconnected()) { + prefManager.setUserDisconnected(false); + launchHomeScreen(); + } + + RadioGroup radioGroupClassesID = findViewById(R.id.radio_group_classes_id); + if (radioGroupClassesID.getChildCount() == 0) { + LayoutInflater inflater = LayoutInflater.from(this); + + for (ClassHEIAFR classHEIAFR : classes) { + RadioButton radioButton = (RadioButton) inflater.inflate(R.layout.radio_button, null); + radioButton.setText(classHEIAFR.getName()); + radioGroupClassesID.addView(radioButton); + } + String classID = prefManager.getClassID(); + if (classID != null && !"".equals(classID)) { + for (int i = 0; i < radioGroupClassesID.getChildCount(); i++) { + RadioButton radioButton = (RadioButton) radioGroupClassesID.getChildAt(i); + if (classID.equals(radioButton.getText().toString())) { + radioGroupClassesID.check(FIRST_INDEX_RADIO_GROUP + i); + break; + } + } + } else { + radioGroupClassesID.check(FIRST_INDEX_RADIO_GROUP); + } + } + + viewPager.setEnable(true); + viewPager.setCurrentItem(nextPage); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) + afm.commit(); + } else { + String message; + if (httpCode == 401) + message = getResources().getString(R.string.login_failed); + else if (httpCode == CODE_NO_CONNECTION) + message = getResources().getString(R.string.unknown_host); + else + message = getResources().getString(R.string.http_error) + " " + httpCode; + + Toast.makeText(getApplicationContext(), message, Toast.LENGTH_SHORT).show(); + } + } + + private void setNextEnable(boolean enable) { + btnNext.setEnabled(enable); + if (enable) { + btnNext.setImageDrawable(getResources().getDrawable(R.drawable.ic_action_next)); + } else { + btnNext.setImageDrawable(getResources().getDrawable(R.drawable.ic_action_next_disable)); + } + } + + private void launchHomeScreen() { + startActivity(new Intent(WelcomeActivity.this, MainActivity.class)); + finish(); + } + + // viewpager change listener + private final ViewPager.OnPageChangeListener viewPagerPageChangeListener = new ViewPager.OnPageChangeListener() { + + @Override + public void onPageSelected(int position) { + addBottomDots(position); + + if (position == 0) { + // first page + btnPrev.setVisibility(View.GONE); + btnNext.setVisibility(View.VISIBLE); + } else { + // other pages + btnPrev.setVisibility(View.VISIBLE); + btnNext.setVisibility(View.VISIBLE); + } + } + + @Override + public void onPageScrolled(int arg0, float arg1, int arg2) { + } + + @Override + public void onPageScrollStateChanged(int arg0) { + } + }; + + /** + * Making notification bar transparent + */ + private void changeStatusBarColor() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + Window window = getWindow(); + window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); + window.setStatusBarColor(Color.TRANSPARENT); + } + } + + private void setupUI(View view) { + // Set up touch listener for non-text box views to hide keyboard. + if (!(view instanceof EditText)) { + view.setOnTouchListener(new View.OnTouchListener() { + public boolean onTouch(View v, MotionEvent event) { + looseFocus(); + return false; + } + }); + } + + //If a layout container, iterate over children and seed recursion. + if (view instanceof ViewGroup) { + for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) { + View innerView = ((ViewGroup) view).getChildAt(i); + setupUI(innerView); + } + } + } + + private void looseFocus() { + View view = this.getCurrentFocus(); + if (view == null) return; + InputMethodManager inputMethodManager = (InputMethodManager) + this.getSystemService(Activity.INPUT_METHOD_SERVICE); + inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0); + this.getCurrentFocus().clearFocus(); + } + + public class LoginTextWatch implements TextWatcher { + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + loggedIn = false; + viewPager.setEnable(false); + if (!"".equals(txtUsername.getText().toString()) && !"".equals(txtPassword.getText().toString())) { + setNextEnable(true); + } else { + setNextEnable(false); + } + } + + @Override + public void afterTextChanged(Editable s) { + } + } + + public class MyViewPagerAdapter extends PagerAdapter { + + public MyViewPagerAdapter() { + } + + @NotNull + @Override + public Object instantiateItem(@NotNull ViewGroup container, int position) { + LayoutInflater layoutInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); + + View view = layoutInflater.inflate(layouts[position], container, false); + if (position == 0) { + txtUsername = view.findViewById(R.id.txt_username); + txtPassword = view.findViewById(R.id.txt_password); + LoginTextWatch loginTextWatch = new LoginTextWatch(); + txtUsername.addTextChangedListener(loginTextWatch); + txtPassword.addTextChangedListener(loginTextWatch); + txtUsername.setText(prefManager.getUsername()); + txtPassword.setText(prefManager.getPassword()); + } + container.addView(view); + + return view; + } + + @Override + public int getCount() { + return layouts.length; + } + + @Override + public boolean isViewFromObject(@NotNull View view, @NotNull Object obj) { + return view == obj; + } + + @Override + public void destroyItem(ViewGroup container, int position, @NotNull Object object) { + View view = (View) object; + container.removeView(view); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ch/lburgy/heiafrschedule/database/ClassHEIAFR.java b/app/src/main/java/ch/lburgy/heiafrschedule/database/ClassHEIAFR.java new file mode 100644 index 0000000..b8c4604 --- /dev/null +++ b/app/src/main/java/ch/lburgy/heiafrschedule/database/ClassHEIAFR.java @@ -0,0 +1,44 @@ +package ch.lburgy.heiafrschedule.database; + +import androidx.annotation.NonNull; +import androidx.room.Entity; +import androidx.room.PrimaryKey; +import androidx.room.TypeConverters; + +import org.jetbrains.annotations.NotNull; + +import java.util.Date; + +import ch.lburgy.heiafrschedule.type_converter.DateConverter; + +@Entity +@TypeConverters(DateConverter.class) +public class ClassHEIAFR { + @PrimaryKey + @NonNull + private final String name; + private Date lastUpdate; + + public ClassHEIAFR(@NotNull String name) { + this.name = name; + } + + @NotNull + public String getName() { + return name; + } + + public Date getLastUpdate() { + return lastUpdate; + } + + public void setLastUpdate(Date lastUpdate) { + this.lastUpdate = lastUpdate; + } + + @NonNull + @Override + public String toString() { + return name; + } +} diff --git a/app/src/main/java/ch/lburgy/heiafrschedule/database/ClassHEIAFRDao.java b/app/src/main/java/ch/lburgy/heiafrschedule/database/ClassHEIAFRDao.java new file mode 100644 index 0000000..01ddee4 --- /dev/null +++ b/app/src/main/java/ch/lburgy/heiafrschedule/database/ClassHEIAFRDao.java @@ -0,0 +1,30 @@ +package ch.lburgy.heiafrschedule.database; + +import androidx.room.Dao; +import androidx.room.Insert; +import androidx.room.Query; +import androidx.room.Update; + +import java.util.List; + +@Dao +public interface ClassHEIAFRDao { + + @Insert + void insertClass(ClassHEIAFR classHEIAFR); + + @Insert + void insertClasses(List classesHEIAFR); + + @Update + void updateClassHEIAFR(ClassHEIAFR classHEIAFR); + + @Query("DELETE FROM ClassHEIAFR") + void deleteClasses(); + + @Query("SELECT * FROM ClassHEIAFR") + List getClasses(); + + @Query("SELECT * FROM ClassHEIAFR WHERE ClassHEIAFR.name=:className") + ClassHEIAFR getClass(String className); +} diff --git a/app/src/main/java/ch/lburgy/heiafrschedule/database/Lesson.java b/app/src/main/java/ch/lburgy/heiafrschedule/database/Lesson.java new file mode 100644 index 0000000..2803c0d --- /dev/null +++ b/app/src/main/java/ch/lburgy/heiafrschedule/database/Lesson.java @@ -0,0 +1,83 @@ +package ch.lburgy.heiafrschedule.database; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.room.Entity; +import androidx.room.Ignore; +import androidx.room.PrimaryKey; + +import java.io.Serializable; + +@Entity() +public class Lesson implements Serializable { + @PrimaryKey(autoGenerate = true) + private long id; + private String name; + private String timeStart; + private String timeEnd; + private int dayOfTheWeek; + + @Ignore + public Lesson() { + } + + public Lesson(String name, String timeStart, String timeEnd, int dayOfTheWeek) { + this.name = name; + this.timeStart = timeStart; + this.timeEnd = timeEnd; + this.dayOfTheWeek = dayOfTheWeek; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getTimeStart() { + return timeStart; + } + + public void setTimeStart(String timeStart) { + this.timeStart = timeStart; + } + + public String getTimeEnd() { + return timeEnd; + } + + public void setTimeEnd(String timeEnd) { + this.timeEnd = timeEnd; + } + + public int getDayOfTheWeek() { + return dayOfTheWeek; + } + + public void setDayOfTheWeek(int dayOfTheWeek) { + this.dayOfTheWeek = dayOfTheWeek; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (getClass() != obj.getClass()) return false; + Lesson lesson = (Lesson) obj; + return name.equals(lesson.name) && timeStart.equals(lesson.timeStart) && timeEnd.equals(lesson.timeEnd) && dayOfTheWeek == lesson.dayOfTheWeek; + } + + @NonNull + @Override + public String toString() { + return dayOfTheWeek + " - " + timeStart + " - " + timeEnd + " - " + name; + } +} diff --git a/app/src/main/java/ch/lburgy/heiafrschedule/database/LessonClassJoin.java b/app/src/main/java/ch/lburgy/heiafrschedule/database/LessonClassJoin.java new file mode 100644 index 0000000..1b621af --- /dev/null +++ b/app/src/main/java/ch/lburgy/heiafrschedule/database/LessonClassJoin.java @@ -0,0 +1,41 @@ +package ch.lburgy.heiafrschedule.database; + +import androidx.annotation.NonNull; +import androidx.room.Entity; +import androidx.room.ForeignKey; +import androidx.room.Index; + +import org.jetbrains.annotations.NotNull; + +@Entity(primaryKeys = {"lessonID", "className"}, + indices = {@Index("className")}, + foreignKeys = { + @ForeignKey(entity = Lesson.class, + parentColumns = "id", + childColumns = "lessonID", + onDelete = ForeignKey.CASCADE), + @ForeignKey(entity = ClassHEIAFR.class, + parentColumns = "name", + childColumns = "className", + onDelete = ForeignKey.CASCADE) + }) +public class LessonClassJoin { + + private final long lessonID; + @NonNull + private final String className; + + public LessonClassJoin(final long lessonID, @NotNull final String className) { + this.lessonID = lessonID; + this.className = className; + } + + public long getLessonID() { + return lessonID; + } + + @NonNull + public String getClassName() { + return className; + } +} \ No newline at end of file diff --git a/app/src/main/java/ch/lburgy/heiafrschedule/database/LessonClassJoinDao.java b/app/src/main/java/ch/lburgy/heiafrschedule/database/LessonClassJoinDao.java new file mode 100644 index 0000000..753f9a3 --- /dev/null +++ b/app/src/main/java/ch/lburgy/heiafrschedule/database/LessonClassJoinDao.java @@ -0,0 +1,33 @@ +package ch.lburgy.heiafrschedule.database; + +import androidx.annotation.NonNull; +import androidx.room.Dao; +import androidx.room.Insert; +import androidx.room.Query; + +import java.util.List; + +@Dao +public interface LessonClassJoinDao { + + @Insert + void insertLessonClassJoin(LessonClassJoin lessonClassJoin); + + @Query("DELETE FROM LessonClassJoin WHERE className=:className") + void deleteLessonsFromClass(final @NonNull String className); + + @Query("DELETE FROM Lesson WHERE id NOT IN (" + + "SELECT id FROM Lesson INNER JOIN LessonClassJoin ON Lesson.id = LessonClassJoin.lessonID)") + void deleteLessonsNoClass(); + + @Query("DELETE FROM Lesson WHERE id NOT IN (" + + "SELECT id FROM Lesson INNER JOIN LessonClassJoin ON Lesson.id = LessonClassJoin.lessonID INNER JOIN ClassHEIAFR ON ClassHEIAFR.name = LessonClassJoin.className" + + " WHERE ClassHEIAFR.lastUpdate != -1)") + void deleteLessonsNoClassUpdate(); + + @Query("SELECT * FROM ClassHEIAFR INNER JOIN LessonClassJoin ON ClassHEIAFR.name = LessonClassJoin.className WHERE LessonClassJoin.lessonID =:lessonID") + List getClassesForLesson(final long lessonID); + + @Query("SELECT * FROM Lesson INNER JOIN LessonClassJoin ON Lesson.id = LessonClassJoin.lessonID WHERE LessonClassJoin.className =:className") + List getLessonsForClass(final @NonNull String className); +} diff --git a/app/src/main/java/ch/lburgy/heiafrschedule/database/LessonDao.java b/app/src/main/java/ch/lburgy/heiafrschedule/database/LessonDao.java new file mode 100644 index 0000000..8d9216b --- /dev/null +++ b/app/src/main/java/ch/lburgy/heiafrschedule/database/LessonDao.java @@ -0,0 +1,20 @@ +package ch.lburgy.heiafrschedule.database; + +import androidx.room.Dao; +import androidx.room.Insert; +import androidx.room.Query; + +import java.util.List; + +@Dao +public interface LessonDao { + + @Insert + long insertLesson(Lesson lesson); + + @Query("DELETE FROM Lesson") + void deleteLessons(); + + @Query("SELECT * FROM Lesson") + List getLessons(); +} diff --git a/app/src/main/java/ch/lburgy/heiafrschedule/database/LessonOfClassComplete.java b/app/src/main/java/ch/lburgy/heiafrschedule/database/LessonOfClassComplete.java new file mode 100644 index 0000000..890d33e --- /dev/null +++ b/app/src/main/java/ch/lburgy/heiafrschedule/database/LessonOfClassComplete.java @@ -0,0 +1,34 @@ +package ch.lburgy.heiafrschedule.database; + +import androidx.annotation.Nullable; + +import java.io.Serializable; +import java.util.List; + +public class LessonOfClassComplete implements Serializable { + private final Lesson lesson; + private final List rooms; + private final List teachers; + + public LessonOfClassComplete(Lesson lesson, List rooms, List teachers) { + this.lesson = lesson; + this.rooms = rooms; + this.teachers = teachers; + } + + public Lesson getLesson() { + return lesson; + } + + public List getRooms() { + return rooms; + } + + public List getTeachers() { + return teachers; + } + + public boolean equals(@Nullable LessonOfClassComplete lessonOfClassComplete) { + return lesson.equals(lessonOfClassComplete.lesson) && rooms.equals(lessonOfClassComplete.rooms) && teachers.equals(lessonOfClassComplete.teachers); + } +} diff --git a/app/src/main/java/ch/lburgy/heiafrschedule/database/LessonOfClassIncomplete.java b/app/src/main/java/ch/lburgy/heiafrschedule/database/LessonOfClassIncomplete.java new file mode 100644 index 0000000..8dfa6db --- /dev/null +++ b/app/src/main/java/ch/lburgy/heiafrschedule/database/LessonOfClassIncomplete.java @@ -0,0 +1,27 @@ +package ch.lburgy.heiafrschedule.database; + +import java.util.List; + +public class LessonOfClassIncomplete { + private final Lesson lesson; + private final List roomsIDs; + private final List teachersAbbrs; + + public LessonOfClassIncomplete(Lesson lesson, List roomsIDs, List teachersAbbrs) { + this.lesson = lesson; + this.roomsIDs = roomsIDs; + this.teachersAbbrs = teachersAbbrs; + } + + public Lesson getLesson() { + return lesson; + } + + public List getRoomsIDs() { + return roomsIDs; + } + + public List getTeachersAbbrs() { + return teachersAbbrs; + } +} diff --git a/app/src/main/java/ch/lburgy/heiafrschedule/database/LessonOfRoomComplete.java b/app/src/main/java/ch/lburgy/heiafrschedule/database/LessonOfRoomComplete.java new file mode 100644 index 0000000..df565b0 --- /dev/null +++ b/app/src/main/java/ch/lburgy/heiafrschedule/database/LessonOfRoomComplete.java @@ -0,0 +1,28 @@ +package ch.lburgy.heiafrschedule.database; + +import java.io.Serializable; +import java.util.List; + +public class LessonOfRoomComplete implements Serializable { + private final Lesson lesson; + private final List classes; + private final List teachers; + + public LessonOfRoomComplete(Lesson lesson, List classes, List teachers) { + this.lesson = lesson; + this.classes = classes; + this.teachers = teachers; + } + + public Lesson getLesson() { + return lesson; + } + + public List getClasses() { + return classes; + } + + public List getTeachers() { + return teachers; + } +} diff --git a/app/src/main/java/ch/lburgy/heiafrschedule/database/LessonOfRoomIncomplete.java b/app/src/main/java/ch/lburgy/heiafrschedule/database/LessonOfRoomIncomplete.java new file mode 100644 index 0000000..6bb0ac9 --- /dev/null +++ b/app/src/main/java/ch/lburgy/heiafrschedule/database/LessonOfRoomIncomplete.java @@ -0,0 +1,27 @@ +package ch.lburgy.heiafrschedule.database; + +import java.util.List; + +public class LessonOfRoomIncomplete { + private final Lesson lesson; + private final List classIDs; + private final List teachersAbbrs; + + public LessonOfRoomIncomplete(Lesson lesson, List classIDs, List teachersAbbrs) { + this.lesson = lesson; + this.classIDs = classIDs; + this.teachersAbbrs = teachersAbbrs; + } + + public Lesson getLesson() { + return lesson; + } + + public List getClassIDs() { + return classIDs; + } + + public List getTeachersAbbrs() { + return teachersAbbrs; + } +} diff --git a/app/src/main/java/ch/lburgy/heiafrschedule/database/LessonRoomJoin.java b/app/src/main/java/ch/lburgy/heiafrschedule/database/LessonRoomJoin.java new file mode 100644 index 0000000..465afce --- /dev/null +++ b/app/src/main/java/ch/lburgy/heiafrschedule/database/LessonRoomJoin.java @@ -0,0 +1,41 @@ +package ch.lburgy.heiafrschedule.database; + +import androidx.annotation.NonNull; +import androidx.room.Entity; +import androidx.room.ForeignKey; +import androidx.room.Index; + +import org.jetbrains.annotations.NotNull; + +@Entity(primaryKeys = {"lessonID", "roomID"}, + indices = {@Index("roomID")}, + foreignKeys = { + @ForeignKey(entity = Lesson.class, + parentColumns = "id", + childColumns = "lessonID", + onDelete = ForeignKey.CASCADE), + @ForeignKey(entity = RoomHEIAFR.class, + parentColumns = "id", + childColumns = "roomID", + onDelete = ForeignKey.CASCADE) + }) +public class LessonRoomJoin { + + private final long lessonID; + @NonNull + private final String roomID; + + public LessonRoomJoin(final long lessonID, @NotNull final String roomID) { + this.lessonID = lessonID; + this.roomID = roomID; + } + + public long getLessonID() { + return lessonID; + } + + @NonNull + public String getRoomID() { + return roomID; + } +} \ No newline at end of file diff --git a/app/src/main/java/ch/lburgy/heiafrschedule/database/LessonRoomJoinDao.java b/app/src/main/java/ch/lburgy/heiafrschedule/database/LessonRoomJoinDao.java new file mode 100644 index 0000000..2830cf3 --- /dev/null +++ b/app/src/main/java/ch/lburgy/heiafrschedule/database/LessonRoomJoinDao.java @@ -0,0 +1,21 @@ +package ch.lburgy.heiafrschedule.database; + +import androidx.annotation.NonNull; +import androidx.room.Dao; +import androidx.room.Insert; +import androidx.room.Query; + +import java.util.List; + +@Dao +public interface LessonRoomJoinDao { + + @Insert + void insertLessonRoomJoin(LessonRoomJoin lessonRoomJoin); + + @Query("SELECT * FROM RoomHEIAFR INNER JOIN LessonRoomJoin ON RoomHEIAFR.id = LessonRoomJoin.roomID WHERE LessonRoomJoin.lessonID =:lessonID") + List getRoomsForLesson(final long lessonID); + + @Query("SELECT * FROM Lesson INNER JOIN LessonRoomJoin ON Lesson.id = LessonRoomJoin.lessonID WHERE LessonRoomJoin.roomID =:roomID") + List getLessonsForRoom(final @NonNull String roomID); +} diff --git a/app/src/main/java/ch/lburgy/heiafrschedule/database/LessonTeacherJoin.java b/app/src/main/java/ch/lburgy/heiafrschedule/database/LessonTeacherJoin.java new file mode 100644 index 0000000..cc7b17d --- /dev/null +++ b/app/src/main/java/ch/lburgy/heiafrschedule/database/LessonTeacherJoin.java @@ -0,0 +1,41 @@ +package ch.lburgy.heiafrschedule.database; + +import androidx.annotation.NonNull; +import androidx.room.Entity; +import androidx.room.ForeignKey; +import androidx.room.Index; + +import org.jetbrains.annotations.NotNull; + +@Entity(primaryKeys = {"lessonId", "teacherAbbr"}, + indices = {@Index("teacherAbbr")}, + foreignKeys = { + @ForeignKey(entity = Lesson.class, + parentColumns = "id", + childColumns = "lessonId", + onDelete = ForeignKey.CASCADE), + @ForeignKey(entity = Teacher.class, + parentColumns = "abbr", + childColumns = "teacherAbbr", + onDelete = ForeignKey.CASCADE) + }) +public class LessonTeacherJoin { + + private final long lessonId; + @NonNull + private final String teacherAbbr; + + public LessonTeacherJoin(final long lessonId, @NotNull final String teacherAbbr) { + this.lessonId = lessonId; + this.teacherAbbr = teacherAbbr; + } + + public long getLessonId() { + return lessonId; + } + + @NonNull + public String getTeacherAbbr() { + return teacherAbbr; + } +} \ No newline at end of file diff --git a/app/src/main/java/ch/lburgy/heiafrschedule/database/LessonTeacherJoinDao.java b/app/src/main/java/ch/lburgy/heiafrschedule/database/LessonTeacherJoinDao.java new file mode 100644 index 0000000..bf4dc15 --- /dev/null +++ b/app/src/main/java/ch/lburgy/heiafrschedule/database/LessonTeacherJoinDao.java @@ -0,0 +1,17 @@ +package ch.lburgy.heiafrschedule.database; + +import androidx.room.Dao; +import androidx.room.Insert; +import androidx.room.Query; + +import java.util.List; + +@Dao +public interface LessonTeacherJoinDao { + + @Insert + void insertLessonTeacherJoin(LessonTeacherJoin LessonTeacherJoin); + + @Query("SELECT * FROM Teacher INNER JOIN LessonTeacherJoin ON Teacher.abbr = LessonTeacherJoin.teacherAbbr WHERE LessonTeacherJoin.lessonId =:lessonId") + List getTeachersForLesson(final long lessonId); +} diff --git a/app/src/main/java/ch/lburgy/heiafrschedule/database/MyDatabase.java b/app/src/main/java/ch/lburgy/heiafrschedule/database/MyDatabase.java new file mode 100644 index 0000000..5286e05 --- /dev/null +++ b/app/src/main/java/ch/lburgy/heiafrschedule/database/MyDatabase.java @@ -0,0 +1,40 @@ +package ch.lburgy.heiafrschedule.database; + +import android.content.Context; + +import androidx.room.Database; +import androidx.room.Room; +import androidx.room.RoomDatabase; + +@Database(entities = + {ClassHEIAFR.class, Lesson.class, LessonClassJoin.class, RoomHEIAFR.class, LessonRoomJoin.class, Teacher.class, LessonTeacherJoin.class}, + version = 1, exportSchema = false) +public abstract class MyDatabase extends RoomDatabase { + + private static final String DATABASE_NAME = "HEIAFRScheduleDatabase.db"; + private static volatile MyDatabase instance; + + public static synchronized MyDatabase getInstance(Context context) { + if (instance == null) + instance = create(context); + return instance; + } + + private static MyDatabase create(final Context context) { + return Room.databaseBuilder(context, MyDatabase.class, DATABASE_NAME).build(); + } + + public abstract ClassHEIAFRDao getClassHEIAFRDao(); + + public abstract LessonDao getLessonDao(); + + public abstract RoomHEIAFRDao getRoomHEIAFRDao(); + + public abstract LessonRoomJoinDao getLessonRoomJoinDao(); + + public abstract TeacherDao getTeacherDao(); + + public abstract LessonTeacherJoinDao getLessonTeacherJoinDao(); + + public abstract LessonClassJoinDao getLessonClassJoinDao(); +} diff --git a/app/src/main/java/ch/lburgy/heiafrschedule/database/RoomHEIAFR.java b/app/src/main/java/ch/lburgy/heiafrschedule/database/RoomHEIAFR.java new file mode 100644 index 0000000..583fc08 --- /dev/null +++ b/app/src/main/java/ch/lburgy/heiafrschedule/database/RoomHEIAFR.java @@ -0,0 +1,59 @@ +package ch.lburgy.heiafrschedule.database; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.room.Entity; +import androidx.room.Ignore; +import androidx.room.PrimaryKey; + +import org.jetbrains.annotations.NotNull; + +import java.io.Serializable; +import java.util.Comparator; + +@Entity +public class RoomHEIAFR implements Serializable { + @PrimaryKey + @NonNull + private final String id; + private String name; + + @Ignore + public RoomHEIAFR(@NotNull String id) { + this.id = id; + name = null; + } + + public RoomHEIAFR(@NotNull String id, @NotNull String name) { + this.id = id; + this.name = name; + } + + public void setName(String name) { + this.name = name; + } + + @NotNull + public String getName() { + return name; + } + + @NonNull + public String getId() { + return id; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (getClass() != obj.getClass()) return false; + RoomHEIAFR room = (RoomHEIAFR) obj; + return id.equals(room.id); + } + + public static final Comparator ROOM_HEIAFR_COMPARATOR = new Comparator() { + @Override + public int compare(RoomHEIAFR o1, RoomHEIAFR o2) { + return o1.id.compareTo(o2.id); + } + }; +} diff --git a/app/src/main/java/ch/lburgy/heiafrschedule/database/RoomHEIAFRDao.java b/app/src/main/java/ch/lburgy/heiafrschedule/database/RoomHEIAFRDao.java new file mode 100644 index 0000000..f4ece9b --- /dev/null +++ b/app/src/main/java/ch/lburgy/heiafrschedule/database/RoomHEIAFRDao.java @@ -0,0 +1,29 @@ +package ch.lburgy.heiafrschedule.database; + +import androidx.room.Dao; +import androidx.room.Insert; +import androidx.room.Query; + +import java.util.List; + +@Dao +public interface RoomHEIAFRDao { + + @Insert + void insertRoom(RoomHEIAFR room); + + @Query("SELECT * FROM RoomHEIAFR WHERE id=:id") + RoomHEIAFR getRoom(String id); + + @Query("SELECT count(*) FROM RoomHEIAFR") + int getNbRoom(); + + @Query("SELECT * FROM RoomHEIAFR") + List getRooms(); + + @Insert + void insertRooms(List roomsHEIAFR); + + @Query("DELETE FROM RoomHEIAFR") + void deleteRooms(); +} diff --git a/app/src/main/java/ch/lburgy/heiafrschedule/database/Teacher.java b/app/src/main/java/ch/lburgy/heiafrschedule/database/Teacher.java new file mode 100644 index 0000000..5df34a1 --- /dev/null +++ b/app/src/main/java/ch/lburgy/heiafrschedule/database/Teacher.java @@ -0,0 +1,77 @@ +package ch.lburgy.heiafrschedule.database; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.room.Entity; +import androidx.room.ForeignKey; +import androidx.room.PrimaryKey; + +import org.jetbrains.annotations.NotNull; + +import java.io.Serializable; + +@Entity +public class Teacher implements Serializable { + @PrimaryKey + @NonNull + private final String abbr; + @NonNull + private final String name; + @NonNull + private final String email; + private final String phone; + @ForeignKey(entity = RoomHEIAFR.class, + parentColumns = "name", + childColumns = "office", + onDelete = ForeignKey.CASCADE) + private final String office; + + public Teacher(@NonNull String abbr, @NonNull String name, @NonNull String email, String phone, String office) { + this.abbr = abbr; + this.name = name; + this.email = email; + this.phone = phone; + this.office = office; + } + + @NonNull + public String getAbbr() { + return abbr; + } + + @NotNull + public String getName() { + return name; + } + + @NonNull + public String getEmail() { + return email; + } + + public String getPhone() { + return phone; + } + + public String getOffice() { + return office; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (getClass() != obj.getClass()) return false; + Teacher teacher = (Teacher) obj; + return abbr.equals(teacher.abbr); + } + + @NonNull + @Override + public String toString() { + String out = abbr + ": " + name + " - " + email; + if (phone != null) + out += " - " + phone; + if (phone != null) + out += " - " + office; + return out; + } +} diff --git a/app/src/main/java/ch/lburgy/heiafrschedule/database/TeacherDao.java b/app/src/main/java/ch/lburgy/heiafrschedule/database/TeacherDao.java new file mode 100644 index 0000000..a3e3b9c --- /dev/null +++ b/app/src/main/java/ch/lburgy/heiafrschedule/database/TeacherDao.java @@ -0,0 +1,18 @@ +package ch.lburgy.heiafrschedule.database; + +import androidx.room.Dao; +import androidx.room.Insert; +import androidx.room.Query; + +@Dao +public interface TeacherDao { + + @Insert + void insertTeacher(Teacher teacher); + + @Query("SELECT * FROM Teacher WHERE abbr=:abbr") + Teacher getTeacher(String abbr); + + @Query("DELETE FROM Teacher") + void deleteTeachers(); +} diff --git a/app/src/main/java/ch/lburgy/heiafrschedule/http/BasicAuthInterceptor.java b/app/src/main/java/ch/lburgy/heiafrschedule/http/BasicAuthInterceptor.java new file mode 100644 index 0000000..e7a5600 --- /dev/null +++ b/app/src/main/java/ch/lburgy/heiafrschedule/http/BasicAuthInterceptor.java @@ -0,0 +1,27 @@ +package ch.lburgy.heiafrschedule.http; + +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; + +import okhttp3.Credentials; +import okhttp3.Interceptor; +import okhttp3.Request; +import okhttp3.Response; + +public class BasicAuthInterceptor implements Interceptor { + + private final String credentials; + + public BasicAuthInterceptor(String user, String password) { + credentials = Credentials.basic(user, password); + } + + @NotNull + @Override + public Response intercept(Chain chain) throws IOException { + Request request = chain.request(); + Request authenticatedRequest = request.newBuilder().header("Authorization", credentials).build(); + return chain.proceed(authenticatedRequest); + } +} diff --git a/app/src/main/java/ch/lburgy/heiafrschedule/http/HttpBasicClient.java b/app/src/main/java/ch/lburgy/heiafrschedule/http/HttpBasicClient.java new file mode 100644 index 0000000..e61105c --- /dev/null +++ b/app/src/main/java/ch/lburgy/heiafrschedule/http/HttpBasicClient.java @@ -0,0 +1,352 @@ +package ch.lburgy.heiafrschedule.http; + +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.os.Build; + +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.client.CredentialsProvider; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.impl.client.BasicCredentialsProvider; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.util.EntityUtils; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; + +import java.io.IOException; +import java.io.InterruptedIOException; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import ch.lburgy.heiafrschedule.database.ClassHEIAFR; +import ch.lburgy.heiafrschedule.database.Lesson; +import ch.lburgy.heiafrschedule.database.LessonOfClassIncomplete; +import ch.lburgy.heiafrschedule.database.LessonOfRoomIncomplete; +import ch.lburgy.heiafrschedule.database.RoomHEIAFR; +import ch.lburgy.heiafrschedule.database.Teacher; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; +import okhttp3.ResponseBody; + +public class HttpBasicClient { + + private static final String URL_CLASSES = "https://webapp.heia-fr.ch/horaire/bloc-horaire.jsp"; + private static final String URL_ROOMS = "https://webapp.heia-fr.ch/horaire/bloc-horaire-par-salle.jsp"; + private static final String URL_SCHEDULE_CLASS = "https://webapp.heia-fr.ch/horaire/bloc-jsp/horaire_par_classe.jsp?filiere="; + private static final String URL_SCHEDULE_ROOM = "https://webapp.heia-fr.ch/horaire/bloc-jsp/horaire_par_salle.jsp?salle="; + private static final String URL_TEACHER_INFOS = "https://webapp.heia-fr.ch/horaire/jsp/profParAbreviation.jsp?hefrAcronyme="; + + private OkHttpClient okHttpClient; + private HttpClient httpClient; + private final MediaType formDataType; + private final Context context; + + public HttpBasicClient(Context context) { + this.context = context; + formDataType = MediaType.parse("application/x-www-form-urlencoded; charset=utf-8"); + } + + public HttpBasicClient(Context context, String username, String password) { + this(context); + initCredentials(username, password); + } + + public void setCredentials(String username, String password) { + initCredentials(username, password); + } + + private void initCredentials(String username, String password) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + okHttpClient = new OkHttpClient.Builder().addInterceptor(new BasicAuthInterceptor(username, password)).build(); + } else { + CredentialsProvider provider = new BasicCredentialsProvider(); + UsernamePasswordCredentials credentials + = new UsernamePasswordCredentials(username, password); + provider.setCredentials(AuthScope.ANY, credentials); + + httpClient = HttpClientBuilder.create() + .setDefaultCredentialsProvider(provider) + .build(); + } + } + + public List getClasses() throws HttpException, UnknownHostException, NoInternetConnectionException, InterruptedIOException { + ArrayList classes = new ArrayList<>(); + Document doc = getDoc(URL_CLASSES, true); + if (doc == null) return null; + + Element select = doc.getElementsByTag("select").first(); + Elements options = select.getElementsByTag("option"); + for (Element option : options) { + classes.add(new ClassHEIAFR(option.attr("value"))); + } + return classes; + } + + public List getClassLessons(String classID) throws HttpException, UnknownHostException, NoInternetConnectionException, InterruptedIOException { + String url = URL_SCHEDULE_CLASS + classID; + Document doc = getDoc(url, true); + if (doc == null) return null; + ArrayList lessons = new ArrayList<>(); + + Elements raws = doc.getElementsByTag("tr"); + int dayOfTheWeek = -1; // [0,6] + + for (int i = 3; i < raws.size() - 3; i++) { + Elements cells = raws.get(i).getElementsByTag("td"); + String desc = cells.get(2).getElementsByTag("div").first().text(); + + String day = cells.first().getElementsByTag("div").first().text(); + if (!"".equals(day)) + dayOfTheWeek++; + + if (!"".equals(desc)) { + Lesson lesson = new Lesson(); + lesson.setName(desc); + lesson.setDayOfTheWeek(dayOfTheWeek); + + Elements hours = cells.get(1).getElementsByTag("div"); + lesson.setTimeStart(regexGetHour(hours.first().text(), true)); + lesson.setTimeEnd(regexGetHour(hours.last().text(), false)); + + Elements abbrCells = cells.get(3).child(0).children(); + ArrayList teacherAbbrs = new ArrayList<>(); + for (Element abbrCell : abbrCells) + teacherAbbrs.add(abbrCell.getElementsByTag("a").text()); + + Elements roomCells = cells.get(4).child(0).children(); + ArrayList rooms = new ArrayList<>(); + for (Element roomCell : roomCells) + rooms.add(roomCell.text()); + + LessonOfClassIncomplete lessonOfClassIncomplete = new LessonOfClassIncomplete(lesson, rooms, teacherAbbrs); + lessons.add(lessonOfClassIncomplete); + } + } + + return lessons; + } + + public Teacher getTeacherInfos(String teacherAbbr) throws HttpException, UnknownHostException, NoInternetConnectionException, InterruptedIOException { + String url = URL_TEACHER_INFOS + teacherAbbr; + Document doc = getDoc(url, false); + if (doc == null) return null; + + Element table = doc.body().child(0).child(0).child(0).child(1).child(0); + Elements raws = table.getElementsByTag("tr"); + + String name = raws.first().child(1).text(); + String office = regexFixOffice(raws.get(1).child(1).text()); + String phone = raws.get(2).child(1).text(); + String email = raws.get(3).child(1).text(); + + if ("".equals(office)) + office = null; + if ("".equals(phone)) + phone = null; + + return new Teacher(teacherAbbr, name, email, phone, office); + } + + public List getRooms() throws HttpException, UnknownHostException, NoInternetConnectionException, InterruptedIOException { + List rooms = new ArrayList<>(); + Document doc = getDoc(URL_ROOMS, true); + if (doc == null) return null; + + Element select = doc.getElementsByTag("select").first(); + Elements options = select.getElementsByTag("option"); + for (Element option : options) { + rooms.add(new RoomHEIAFR(option.attr("value"), option.text())); + } + return rooms; + } + + public List getRoomLessons(String roomID) throws HttpException, UnknownHostException, NoInternetConnectionException, InterruptedIOException { + String url = URL_SCHEDULE_ROOM + roomID; + Document doc = getDoc(url, true); + if (doc == null) return null; + ArrayList lessons = new ArrayList<>(); + + Element table = doc.getElementsByTag("table").get(1).child(0); + Element secondTable = table.getElementsByTag("table").first().child(0); + Elements raws = secondTable.getElementsByTag("tr"); + int dayOfTheWeek = -1; + + for (int i = 1; i < raws.size(); i++) { + Elements cells = raws.get(i).getElementsByTag("td"); + + String dayString = cells.first().getElementsByTag("div").first().text(); + switch (dayString) { + case "Lundi": + dayOfTheWeek = 0; + break; + case "Mardi": + dayOfTheWeek = 1; + break; + case "Mercredi": + dayOfTheWeek = 2; + break; + case "Jeudi": + dayOfTheWeek = 3; + break; + case "Vendredi": + dayOfTheWeek = 4; + break; + } + + Elements hours = cells.get(1).getElementsByTag("div").first().getElementsByTag("div"); + String timeStart = regexGetHour(hours.first().text(), true); + String timeEnd = regexGetHour(hours.last().text(), false); + String name = cells.get(2).getElementsByTag("div").first().getElementsByTag("div").first().text(); + + Elements classCells = cells.get(3).child(0).children(); + ArrayList classes = new ArrayList<>(); + for (Element classCell : classCells) + classes.add(classCell.getElementsByTag("a").text()); + + Elements abbrCells = cells.get(4).child(0).children(); + ArrayList teacherAbbrs = new ArrayList<>(); + for (Element abbrCell : abbrCells) + teacherAbbrs.add(abbrCell.getElementsByTag("a").text()); + + Lesson lesson = new Lesson(name, timeStart, timeEnd, dayOfTheWeek); + LessonOfRoomIncomplete lessonOfRoomIncomplete = new LessonOfRoomIncomplete(lesson, classes, teacherAbbrs); + lessons.add(lessonOfRoomIncomplete); + } + + return lessons; + } + + private String regexGetHour(String input, Boolean first) { + Pattern pattern = Pattern.compile("[0-9]{2}:[0-9]{2}"); + Matcher matcher = pattern.matcher(input); + + if (first) + if (matcher.find()) + return matcher.group(); + else + return null; + else { + String lastFind = ""; + while (matcher.find()) { + lastFind = matcher.group(); + } + return lastFind; + } + } + + private String regexFixOffice(String office) { + return office.replaceAll("([A-H][0-9]{2})\\.([0-9]{2})", "$1$2"); + } + + private Document getDoc(String url, boolean post) throws HttpException, UnknownHostException, NoInternetConnectionException, InterruptedIOException { + if (!isConnectedToInternet()) throw new NoInternetConnectionException(); + String result = null; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + Request request; + if (post) { + RequestBody body = RequestBody.create("", formDataType); + request = new Request.Builder().url(url).post(body).build(); + } else { + request = new Request.Builder().url(url).build(); + } + + Response response = null; + try { + response = okHttpClient.newCall(request).execute(); + } catch (IOException e) { + if (e.getClass() == UnknownHostException.class) + throw (UnknownHostException) e; + else if (e.getClass() == InterruptedIOException.class) + throw (InterruptedIOException) e; + e.printStackTrace(); + } + + if (!response.isSuccessful()) throw new HttpException(response.code()); + + ResponseBody responseBody = response.body(); + if (responseBody == null) return null; + + try { + result = responseBody.string(); + } catch (IOException e) { + return null; + } + } else { + HttpUriRequest request; + if (post) { + request = new HttpPost(url); + } else { + request = new HttpGet(url); + } + + HttpResponse response = null; + try { + response = httpClient.execute(request); + } catch (IOException e) { + if (e.getClass() == UnknownHostException.class) + throw (UnknownHostException) e; + e.printStackTrace(); + } + + int httpCode = response.getStatusLine().getStatusCode(); + if (httpCode != 200) throw new HttpException(httpCode); + + HttpEntity entity = response.getEntity(); + if (entity != null) { + try { + result = EntityUtils.toString(entity); + } catch (IOException e) { + return null; + } + } + } + if (result != null) + return Jsoup.parse(result); + else + return null; + } + + public boolean isConnectedToInternet() { + ConnectivityManager connectivity = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + if (connectivity != null) { + NetworkInfo[] info = connectivity.getAllNetworkInfo(); + for (NetworkInfo networkInfo : info) + if (networkInfo.getState() == NetworkInfo.State.CONNECTED) { + return true; + } + } + return false; + } + + public static class NoInternetConnectionException extends Exception { + } + + public static class HttpException extends Exception { + private final int code; + + public HttpException(int code) { + super("HTTP Exception " + code); + this.code = code; + } + + public int getCode() { + return code; + } + } +} diff --git a/app/src/main/java/ch/lburgy/heiafrschedule/preferences/Cryptography.java b/app/src/main/java/ch/lburgy/heiafrschedule/preferences/Cryptography.java new file mode 100644 index 0000000..bedeb90 --- /dev/null +++ b/app/src/main/java/ch/lburgy/heiafrschedule/preferences/Cryptography.java @@ -0,0 +1,80 @@ +package ch.lburgy.heiafrschedule.preferences; + +import android.os.Build; +import android.security.keystore.KeyGenParameterSpec; +import android.security.keystore.KeyProperties; + +import androidx.annotation.NonNull; +import androidx.annotation.RequiresApi; + +import java.nio.charset.StandardCharsets; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableEntryException; + +import javax.crypto.Cipher; +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; +import javax.crypto.spec.GCMParameterSpec; + +@RequiresApi(api = Build.VERSION_CODES.M) +class Cryptography { + + private static final String TRANSFORMATION = "AES/GCM/NoPadding"; + private static final String ANDROID_KEY_STORE = "AndroidKeyStore"; + + private byte[] iv; + private KeyStore keyStore; + + Cryptography() throws Exception { + initKeyStore(); + } + + private void initKeyStore() throws Exception { + keyStore = KeyStore.getInstance(ANDROID_KEY_STORE); + keyStore.load(null); + } + + byte[] encryptText(final String alias, final String textToEncrypt) throws Exception { + + final Cipher cipher = Cipher.getInstance(TRANSFORMATION); + cipher.init(Cipher.ENCRYPT_MODE, getSecretKeyEncrypted(alias)); + + iv = cipher.getIV(); + + return cipher.doFinal(textToEncrypt.getBytes(StandardCharsets.UTF_8)); + } + + String decryptData(final String alias, final byte[] encryptedData, final byte[] encryptionIv) throws Exception { + final Cipher cipher = Cipher.getInstance(TRANSFORMATION); + final GCMParameterSpec spec = new GCMParameterSpec(128, encryptionIv); + cipher.init(Cipher.DECRYPT_MODE, getSecretKeyDecrypted(alias), spec); + + return new String(cipher.doFinal(encryptedData), StandardCharsets.UTF_8); + } + + private SecretKey getSecretKeyDecrypted(final String alias) throws NoSuchAlgorithmException, + UnrecoverableEntryException, KeyStoreException { + return ((KeyStore.SecretKeyEntry) keyStore.getEntry(alias, null)).getSecretKey(); + } + + @NonNull + private SecretKey getSecretKeyEncrypted(final String alias) throws Exception { + + final KeyGenerator keyGenerator = KeyGenerator + .getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEY_STORE); + + keyGenerator.init(new KeyGenParameterSpec.Builder(alias, + KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) + .setBlockModes(KeyProperties.BLOCK_MODE_GCM) + .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) + .build()); + + return keyGenerator.generateKey(); + } + + byte[] getIv() { + return iv; + } +} diff --git a/app/src/main/java/ch/lburgy/heiafrschedule/preferences/CryptographyRSAAES.java b/app/src/main/java/ch/lburgy/heiafrschedule/preferences/CryptographyRSAAES.java new file mode 100644 index 0000000..28cea77 --- /dev/null +++ b/app/src/main/java/ch/lburgy/heiafrschedule/preferences/CryptographyRSAAES.java @@ -0,0 +1,116 @@ +package ch.lburgy.heiafrschedule.preferences; + +import android.content.Context; +import android.security.KeyPairGeneratorSpec; +import android.util.Base64; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.math.BigInteger; +import java.security.Key; +import java.security.KeyPairGenerator; +import java.security.KeyStore; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.Calendar; + +import javax.crypto.Cipher; +import javax.crypto.CipherInputStream; +import javax.crypto.CipherOutputStream; +import javax.crypto.spec.SecretKeySpec; +import javax.security.auth.x500.X500Principal; + +public class CryptographyRSAAES { + + private static final String AndroidKeyStore = "AndroidKeyStore"; + private static final String RSA_MODE = "RSA/ECB/PKCS1Padding"; + private static final String AES_MODE = "AES/ECB/PKCS7Padding"; + private static final String PROVIDER = "AndroidOpenSSL"; + + private static final String KEY_ALIAS = "key-alias"; + + private KeyStore keyStore; + + public CryptographyRSAAES(Context context) throws Exception { + keyStore = KeyStore.getInstance(AndroidKeyStore); + keyStore.load(null);// Generate the RSA key pairs + + if (!keyStore.containsAlias(KEY_ALIAS)) { + // Generate a key pair for encryption + Calendar start = Calendar.getInstance(); + Calendar end = Calendar.getInstance(); + end.add(Calendar.YEAR, 30); + KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(context) + .setAlias(KEY_ALIAS) + .setSubject(new X500Principal("CN=" + KEY_ALIAS)) + .setSerialNumber(BigInteger.TEN) + .setStartDate(start.getTime()) + .setEndDate(end.getTime()) + .build(); + KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA", AndroidKeyStore); + kpg.initialize(spec); + kpg.generateKeyPair(); + } + } + + private byte[] rsaEncrypt(byte[] secret) throws Exception { + KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry) keyStore.getEntry(KEY_ALIAS, null); + // Encrypt the text + Cipher inputCipher = Cipher.getInstance(RSA_MODE, PROVIDER); + inputCipher.init(Cipher.ENCRYPT_MODE, privateKeyEntry.getCertificate().getPublicKey()); + + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + CipherOutputStream cipherOutputStream = new CipherOutputStream(outputStream, inputCipher); + cipherOutputStream.write(secret); + cipherOutputStream.close(); + + return outputStream.toByteArray(); + } + + private byte[] rsaDecrypt(byte[] encrypted) throws Exception { + KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry) keyStore.getEntry(KEY_ALIAS, null); + Cipher output = Cipher.getInstance(RSA_MODE, PROVIDER); + output.init(Cipher.DECRYPT_MODE, privateKeyEntry.getPrivateKey()); + CipherInputStream cipherInputStream = new CipherInputStream( + new ByteArrayInputStream(encrypted), output); + ArrayList values = new ArrayList<>(); + int nextByte; + while ((nextByte = cipherInputStream.read()) != -1) { + values.add((byte) nextByte); + } + + byte[] bytes = new byte[values.size()]; + for (int i = 0; i < bytes.length; i++) { + bytes[i] = values.get(i); + } + return bytes; + } + + public String generateKey() throws Exception { + byte[] key = new byte[16]; + SecureRandom secureRandom = new SecureRandom(); + secureRandom.nextBytes(key); + byte[] encryptedKey = rsaEncrypt(key); + return Base64.encodeToString(encryptedKey, Base64.DEFAULT); + } + + private Key getSecretKey(String encryptedKeyB64) throws Exception { + byte[] encryptedKey = Base64.decode(encryptedKeyB64, Base64.DEFAULT); + byte[] key = rsaDecrypt(encryptedKey); + return new SecretKeySpec(key, "AES"); + } + + public String encrypt(String encryptedKeyB64, byte[] input) throws Exception { + Cipher c = Cipher.getInstance(AES_MODE); + c.init(Cipher.ENCRYPT_MODE, getSecretKey(encryptedKeyB64)); + byte[] encodedBytes = c.doFinal(input); + return Base64.encodeToString(encodedBytes, Base64.DEFAULT); + } + + + public byte[] decrypt(String encryptedKeyB64, byte[] encrypted) throws Exception { + Cipher c = Cipher.getInstance(AES_MODE); + c.init(Cipher.DECRYPT_MODE, getSecretKey(encryptedKeyB64)); + return c.doFinal(encrypted); + } +} diff --git a/app/src/main/java/ch/lburgy/heiafrschedule/preferences/PrefManager.java b/app/src/main/java/ch/lburgy/heiafrschedule/preferences/PrefManager.java new file mode 100644 index 0000000..0334378 --- /dev/null +++ b/app/src/main/java/ch/lburgy/heiafrschedule/preferences/PrefManager.java @@ -0,0 +1,178 @@ +package ch.lburgy.heiafrschedule.preferences; + +import android.content.Context; +import android.content.SharedPreferences; +import android.os.Build; +import android.util.Base64; + +import java.util.Date; + +import ch.lburgy.heiafrschedule.type_converter.DateConverter; + +import static androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY; +import static androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM; + +public class PrefManager { + private static final int PRIVATE_MODE = 0; + private static final String PREF_NAME = "ch.lburgy.heiafrschedule"; + + private static final String KEY_LAUNCH_WELCOME = "launch_welcome"; + private static final String KEY_USER_DISCONNECTED = "user_disconnected"; + private static final String KEY_USERNAME = "username"; + private static final String KEY_PASSWORD = "password"; + private static final String KEY_CLASS_ID = "class_id"; + private static final String KEY_THEME = "theme"; + private static final String KEY_LAST_UPDATE_ROOMS_LESSONS = "last_update_rooms_lessons"; + + private static final String KEY_IV = "-iv"; + private static final String KEY_ENCRYPTED = "encrypted_key"; + + private final SharedPreferences pref; + private final SharedPreferences.Editor editor; + + private Cryptography cryptography; + private CryptographyRSAAES cryptographyRSAAES; + + public PrefManager(Context context) { + pref = context.getSharedPreferences(PREF_NAME, PRIVATE_MODE); + editor = pref.edit(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + try { + cryptography = new Cryptography(); + } catch (Exception e) { + e.printStackTrace(); + } + } else { + try { + cryptographyRSAAES = new CryptographyRSAAES(context); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + public void setLaunchWelcome(boolean launchWelcome) { + editor.putBoolean(KEY_LAUNCH_WELCOME, launchWelcome); + editor.commit(); + } + + public boolean isLaunchWelcome() { + return pref.getBoolean(KEY_LAUNCH_WELCOME, true); + } + + public void setUserDisconnected(boolean userDisconnected) { + editor.putBoolean(KEY_USER_DISCONNECTED, userDisconnected); + editor.commit(); + } + + public boolean isUserDisconnected() { + return pref.getBoolean(KEY_USER_DISCONNECTED, false); + } + + public void setUsername(String username) { + try { + encrypt(KEY_USERNAME, username); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public String getUsername() { + try { + return getEncrypted(KEY_USERNAME); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + public void setPassword(String password) { + try { + encrypt(KEY_PASSWORD, password); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public String getPassword() { + try { + return getEncrypted(KEY_PASSWORD); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + public void setLastUpdateRoomsLessons(Date lastUpdate) { + editor.putLong(KEY_LAST_UPDATE_ROOMS_LESSONS, DateConverter.fromDate(lastUpdate)); + editor.commit(); + } + + public Date getLastUpdateRoomsLessons() { + return DateConverter.toDate(pref.getLong(KEY_LAST_UPDATE_ROOMS_LESSONS, -1)); + } + + public void setTheme(int theme) { + editor.putInt(KEY_THEME, theme); + editor.commit(); + } + + public int getTheme() { + if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + return pref.getInt(KEY_THEME, MODE_NIGHT_FOLLOW_SYSTEM); + } else { + return pref.getInt(KEY_THEME, MODE_NIGHT_AUTO_BATTERY); + } + } + + public void setClassID(String classID) { + editor.putString(KEY_CLASS_ID, classID); + editor.commit(); + } + + public String getClassID() { + return pref.getString(KEY_CLASS_ID, ""); + } + + private void encrypt(String key, String toEncrypt) throws Exception { + String encrypted; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + byte[] bytes = cryptography.encryptText(key, toEncrypt); + encrypted = Base64.encodeToString(bytes, Base64.DEFAULT); + String iv = Base64.encodeToString(cryptography.getIv(), Base64.DEFAULT); + editor.putString(key + KEY_IV, iv); + } else { + String encryptedKeyB64 = getKeyEncrypted(); + byte[] bytes = toEncrypt.getBytes("UTF-8"); + encrypted = cryptographyRSAAES.encrypt(encryptedKeyB64, bytes); + } + editor.putString(key, encrypted); + editor.commit(); + } + + private String getEncrypted(String key) throws Exception { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + String encrypte = pref.getString(key, null); + String ivString = pref.getString(key + KEY_IV, null); + if (encrypte == null || ivString == null) return null; + byte[] iv = Base64.decode(ivString, Base64.DEFAULT); + byte[] bytes = Base64.decode(encrypte, Base64.DEFAULT); + return cryptography.decryptData(key, bytes, iv); + } else { + String encryptedKeyB64 = getKeyEncrypted(); + String encrypted = pref.getString(key, null); + byte[] bytes = Base64.decode(encrypted, Base64.DEFAULT); + bytes = cryptographyRSAAES.decrypt(encryptedKeyB64, bytes); + return new String(bytes, "UTF-8"); + } + } + + private String getKeyEncrypted() throws Exception { + String encryptedKeyB64 = pref.getString(KEY_ENCRYPTED, null); + if (encryptedKeyB64 != null) return encryptedKeyB64; + encryptedKeyB64 = cryptographyRSAAES.generateKey(); + editor.putString(KEY_ENCRYPTED, encryptedKeyB64); + editor.commit(); + return encryptedKeyB64; + } +} \ No newline at end of file diff --git a/app/src/main/java/ch/lburgy/heiafrschedule/selfupdate/ProgressResponseBody.java b/app/src/main/java/ch/lburgy/heiafrschedule/selfupdate/ProgressResponseBody.java new file mode 100644 index 0000000..917ec6a --- /dev/null +++ b/app/src/main/java/ch/lburgy/heiafrschedule/selfupdate/ProgressResponseBody.java @@ -0,0 +1,60 @@ +package ch.lburgy.heiafrschedule.selfupdate; + +import java.io.IOException; + +import okhttp3.MediaType; +import okhttp3.ResponseBody; +import okio.Buffer; +import okio.BufferedSource; +import okio.ForwardingSource; +import okio.Okio; +import okio.Source; + +public class ProgressResponseBody extends ResponseBody { + + private final ResponseBody responseBody; + private final ProgressListener progressListener; + private BufferedSource bufferedSource; + + ProgressResponseBody(ResponseBody responseBody, ProgressListener progressListener) { + this.responseBody = responseBody; + this.progressListener = progressListener; + } + + @Override + public MediaType contentType() { + return responseBody.contentType(); + } + + @Override + public long contentLength() { + return responseBody.contentLength(); + } + + @Override + public BufferedSource source() { + if (bufferedSource == null) { + bufferedSource = Okio.buffer(source(responseBody.source())); + } + return bufferedSource; + } + + private Source source(Source source) { + return new ForwardingSource(source) { + long totalBytesRead = 0L; + + @Override + public long read(Buffer sink, long byteCount) throws IOException { + long bytesRead = super.read(sink, byteCount); + // read() returns the number of bytes read, or -1 if this source is exhausted. + totalBytesRead += bytesRead != -1 ? bytesRead : 0; + progressListener.update(totalBytesRead, responseBody.contentLength(), bytesRead == -1); + return bytesRead; + } + }; + } +} + +interface ProgressListener { + void update(long bytesRead, long contentLength, boolean done); +} diff --git a/app/src/main/java/ch/lburgy/heiafrschedule/selfupdate/SelfUpdate.java b/app/src/main/java/ch/lburgy/heiafrschedule/selfupdate/SelfUpdate.java new file mode 100644 index 0000000..41c3b27 --- /dev/null +++ b/app/src/main/java/ch/lburgy/heiafrschedule/selfupdate/SelfUpdate.java @@ -0,0 +1,164 @@ +package ch.lburgy.heiafrschedule.selfupdate; + +import android.content.DialogInterface; +import android.content.Intent; +import android.net.Uri; +import android.os.Build; +import android.view.View; +import android.widget.ProgressBar; +import android.widget.TextView; + +import androidx.annotation.RequiresApi; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.content.FileProvider; + +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.io.IOException; + +import ch.lburgy.heiafrschedule.BuildConfig; +import ch.lburgy.heiafrschedule.R; +import ch.lburgy.heiafrschedule.selfupdate.github.Asset; +import ch.lburgy.heiafrschedule.selfupdate.github.Release; +import okhttp3.Interceptor; +import okhttp3.Response; + +@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) +public class SelfUpdate { + + private static final String FILENAME_APK = "update.apk"; + private static final String CONTENT_TYPE_APK = "application/vnd.android.package-archive"; + + private static AppCompatActivity activity; + private static SelfUpdateHttpClient httpClient; + + public static void checkUpdate(AppCompatActivity activity, final String url) { + SelfUpdate.activity = activity; + httpClient = new SelfUpdateHttpClient(activity); + + if (!httpClient.isConnectedToInternet()) return; + new Thread(new Runnable() { + @Override + public void run() { + Release release = null; + try { + release = httpClient.getLastRelease(url); + } catch (Exception e) { + e.printStackTrace(); + } + if (release != null) { + try { + if (Integer.parseInt(release.getTag_name()) > BuildConfig.VERSION_CODE) + showUpdateAvailable(release); + } catch (NumberFormatException e) { + e.printStackTrace(); + } + } + } + }).start(); + } + + private static void showUpdateAvailable(final Release release) { + String downloadUrl = null; + for (Asset asset : release.getAssets()) { + if (asset.getContent_type().equals(CONTENT_TYPE_APK)) { + downloadUrl = asset.getBrowser_download_url(); + break; + } + } + if (downloadUrl == null) return; + + final String finalDownloadUrl = downloadUrl; + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + View content = activity.getLayoutInflater().inflate(R.layout.content_dialog_show_update, null); + TextView version = content.findViewById(R.id.version); + version.setText(String.format("%s :", release.getName())); + TextView changelog = content.findViewById(R.id.changelog); + changelog.setText(release.getBody()); + + new AlertDialog.Builder(activity) + .setTitle(activity.getResources().getString(R.string.dialog_update_title)) + .setView(content) + .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + updateApp(finalDownloadUrl); + } + }) + .setNegativeButton(android.R.string.no, null) + .show(); + } + }); + } + + private static void updateApp(String downloadUrl) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + downloadUpdate(downloadUrl); + } else { + Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(downloadUrl)); + activity.startActivity(browserIntent); + } + } + + @RequiresApi(api = Build.VERSION_CODES.N) + private static void downloadUpdate(final String url) { + View content = activity.getLayoutInflater().inflate(R.layout.content_dialog_download, null); + + final AlertDialog alertDialog = new AlertDialog.Builder(activity) + .setTitle(activity.getResources().getString(R.string.dialog_downloading_title)) + .setView(content) + .setCancelable(false) + .show(); + + final ProgressBar progressBar = content.findViewById(R.id.progressBar); + final ProgressListener progressListener = new ProgressListener() { + @Override + public void update(long bytesRead, long contentLength, boolean done) { + final int progress = Math.round(((float) bytesRead / contentLength) * 100); + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + if (!activity.isDestroyed()) + progressBar.setProgress(progress); + } + }); + if (done && !activity.isDestroyed()) alertDialog.dismiss(); + } + }; + + new Thread(new Runnable() { + @Override + public void run() { + Interceptor interceptor = new Interceptor() { + @NotNull + @Override + public Response intercept(@NotNull Chain chain) throws IOException { + Response originalResponse = chain.proceed(chain.request()); + return originalResponse.newBuilder() + .body(new ProgressResponseBody(originalResponse.body(), progressListener)) + .build(); + } + }; + + try { + File apkFile = httpClient.download(url, FILENAME_APK, interceptor); + if (apkFile != null) installAPK(apkFile); + } catch (Exception e) { + e.printStackTrace(); + } + } + }).start(); + } + + @RequiresApi(api = Build.VERSION_CODES.N) + private static void installAPK(File apkFile) { + Uri uri = FileProvider.getUriForFile(activity, activity.getPackageName() + ".provider", apkFile); + Intent i = new Intent(Intent.ACTION_VIEW); + i.setDataAndType(uri, CONTENT_TYPE_APK); + i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + activity.startActivity(i); + } +} diff --git a/app/src/main/java/ch/lburgy/heiafrschedule/selfupdate/SelfUpdateHttpClient.java b/app/src/main/java/ch/lburgy/heiafrschedule/selfupdate/SelfUpdateHttpClient.java new file mode 100644 index 0000000..f4562d5 --- /dev/null +++ b/app/src/main/java/ch/lburgy/heiafrschedule/selfupdate/SelfUpdateHttpClient.java @@ -0,0 +1,141 @@ +package ch.lburgy.heiafrschedule.selfupdate; + +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.os.Build; + +import androidx.annotation.RequiresApi; + +import com.squareup.moshi.JsonAdapter; +import com.squareup.moshi.Moshi; + +import java.io.File; +import java.io.IOException; +import java.io.InterruptedIOException; +import java.net.UnknownHostException; + +import ch.lburgy.heiafrschedule.selfupdate.github.Release; +import okhttp3.Interceptor; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.ResponseBody; +import okio.BufferedSink; +import okio.Okio; + +@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) +public class SelfUpdateHttpClient { + + private OkHttpClient simpleOkHttpClient; + private final Context context; + + public SelfUpdateHttpClient(Context context) { + this.context = context; + simpleOkHttpClient = new OkHttpClient(); + } + + public Release getLastRelease(String url) throws HttpException, IOException, NoInternetConnectionException { + String body = get(url); + Moshi moshi = new Moshi.Builder().build(); + JsonAdapter jsonAdapter = moshi.adapter(Release.class); + return jsonAdapter.fromJson(body); + } + + public File download(String url, String filename, Interceptor networkInterceptor) throws HttpException, UnknownHostException, NoInternetConnectionException, InterruptedIOException { + if (!isConnectedToInternet()) throw new NoInternetConnectionException(); + + OkHttpClient okHttpClient = simpleOkHttpClient; + if (networkInterceptor != null) + okHttpClient = new OkHttpClient.Builder().addNetworkInterceptor(networkInterceptor).build(); + + Request request = new Request.Builder().url(url).build(); + + Response response = null; + try { + response = okHttpClient.newCall(request).execute(); + } catch (IOException e) { + if (e.getClass() == UnknownHostException.class) + throw (UnknownHostException) e; + else if (e.getClass() == InterruptedIOException.class) + throw (InterruptedIOException) e; + e.printStackTrace(); + } + + if (!response.isSuccessful()) { + throw new HttpException(response.code()); + } + + ResponseBody responseBody = response.body(); + if (responseBody == null) return null; + + try { + File file = new File((context.getApplicationContext().getFileStreamPath(filename).getPath())); + BufferedSink sink = Okio.buffer(Okio.sink(file)); + sink.writeAll(response.body().source()); + sink.close(); + return file; + } catch (IOException e) { + e.printStackTrace(); + } + + return null; + } + + private String get(String url) throws HttpException, UnknownHostException, NoInternetConnectionException, InterruptedIOException { + if (!isConnectedToInternet()) throw new NoInternetConnectionException(); + + Request request = new Request.Builder().url(url).build(); + + Response response = null; + try { + response = simpleOkHttpClient.newCall(request).execute(); + } catch (IOException e) { + if (e.getClass() == UnknownHostException.class) + throw (UnknownHostException) e; + else if (e.getClass() == InterruptedIOException.class) + throw (InterruptedIOException) e; + e.printStackTrace(); + } + + if (!response.isSuccessful()) throw new HttpException(response.code()); + + ResponseBody responseBody = response.body(); + if (responseBody == null) return null; + + try { + return responseBody.string(); + } catch (IOException e) { + // nothing + } + return null; + } + + public boolean isConnectedToInternet() { + ConnectivityManager connectivity = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + if (connectivity != null) { + NetworkInfo[] info = connectivity.getAllNetworkInfo(); + for (NetworkInfo networkInfo : info) + if (networkInfo.getState() == NetworkInfo.State.CONNECTED) { + return true; + } + } + return false; + } + + public static class NoInternetConnectionException extends Exception { + } + + public static class HttpException extends Exception { + private final int code; + + public HttpException(int code) { + super("HTTP Exception " + code); + this.code = code; + } + + public int getCode() { + return code; + } + } +} diff --git a/app/src/main/java/ch/lburgy/heiafrschedule/selfupdate/github/Asset.java b/app/src/main/java/ch/lburgy/heiafrschedule/selfupdate/github/Asset.java new file mode 100644 index 0000000..946af05 --- /dev/null +++ b/app/src/main/java/ch/lburgy/heiafrschedule/selfupdate/github/Asset.java @@ -0,0 +1,126 @@ +package ch.lburgy.heiafrschedule.selfupdate.github; + +public class Asset { + private String url; + private String browser_download_url; + private float id; + private String node_id; + private String name; + private String label; + private String state; + private String content_type; + private float size; + private float download_count; + private String created_at; + private String updated_at; + Uploader UploaderObject; + + + // Getter Methods + + public String getUrl() { + return url; + } + + public String getBrowser_download_url() { + return browser_download_url; + } + + public float getId() { + return id; + } + + public String getNode_id() { + return node_id; + } + + public String getName() { + return name; + } + + public String getLabel() { + return label; + } + + public String getState() { + return state; + } + + public String getContent_type() { + return content_type; + } + + public float getSize() { + return size; + } + + public float getDownload_count() { + return download_count; + } + + public String getCreated_at() { + return created_at; + } + + public String getUpdated_at() { + return updated_at; + } + + public Uploader getUploader() { + return UploaderObject; + } + + // Setter Methods + + public void setUrl(String url) { + this.url = url; + } + + public void setBrowser_download_url(String browser_download_url) { + this.browser_download_url = browser_download_url; + } + + public void setId(float id) { + this.id = id; + } + + public void setNode_id(String node_id) { + this.node_id = node_id; + } + + public void setName(String name) { + this.name = name; + } + + public void setLabel(String label) { + this.label = label; + } + + public void setState(String state) { + this.state = state; + } + + public void setContent_type(String content_type) { + this.content_type = content_type; + } + + public void setSize(float size) { + this.size = size; + } + + public void setDownload_count(float download_count) { + this.download_count = download_count; + } + + public void setCreated_at(String created_at) { + this.created_at = created_at; + } + + public void setUpdated_at(String updated_at) { + this.updated_at = updated_at; + } + + public void setUploader(Uploader uploaderObject) { + this.UploaderObject = uploaderObject; + } +} diff --git a/app/src/main/java/ch/lburgy/heiafrschedule/selfupdate/github/Author.java b/app/src/main/java/ch/lburgy/heiafrschedule/selfupdate/github/Author.java new file mode 100644 index 0000000..9909ae8 --- /dev/null +++ b/app/src/main/java/ch/lburgy/heiafrschedule/selfupdate/github/Author.java @@ -0,0 +1,171 @@ +package ch.lburgy.heiafrschedule.selfupdate.github; + +public class Author { + private String login; + private float id; + private String node_id; + private String avatar_url; + private String gravatar_id; + private String url; + private String html_url; + private String followers_url; + private String following_url; + private String gists_url; + private String starred_url; + private String subscriptions_url; + private String organizations_url; + private String repos_url; + private String events_url; + private String received_events_url; + private String type; + private boolean site_admin; + + + // Getter Methods + + public String getLogin() { + return login; + } + + public float getId() { + return id; + } + + public String getNode_id() { + return node_id; + } + + public String getAvatar_url() { + return avatar_url; + } + + public String getGravatar_id() { + return gravatar_id; + } + + public String getUrl() { + return url; + } + + public String getHtml_url() { + return html_url; + } + + public String getFollowers_url() { + return followers_url; + } + + public String getFollowing_url() { + return following_url; + } + + public String getGists_url() { + return gists_url; + } + + public String getStarred_url() { + return starred_url; + } + + public String getSubscriptions_url() { + return subscriptions_url; + } + + public String getOrganizations_url() { + return organizations_url; + } + + public String getRepos_url() { + return repos_url; + } + + public String getEvents_url() { + return events_url; + } + + public String getReceived_events_url() { + return received_events_url; + } + + public String getType() { + return type; + } + + public boolean getSite_admin() { + return site_admin; + } + + // Setter Methods + + public void setLogin(String login) { + this.login = login; + } + + public void setId(float id) { + this.id = id; + } + + public void setNode_id(String node_id) { + this.node_id = node_id; + } + + public void setAvatar_url(String avatar_url) { + this.avatar_url = avatar_url; + } + + public void setGravatar_id(String gravatar_id) { + this.gravatar_id = gravatar_id; + } + + public void setUrl(String url) { + this.url = url; + } + + public void setHtml_url(String html_url) { + this.html_url = html_url; + } + + public void setFollowers_url(String followers_url) { + this.followers_url = followers_url; + } + + public void setFollowing_url(String following_url) { + this.following_url = following_url; + } + + public void setGists_url(String gists_url) { + this.gists_url = gists_url; + } + + public void setStarred_url(String starred_url) { + this.starred_url = starred_url; + } + + public void setSubscriptions_url(String subscriptions_url) { + this.subscriptions_url = subscriptions_url; + } + + public void setOrganizations_url(String organizations_url) { + this.organizations_url = organizations_url; + } + + public void setRepos_url(String repos_url) { + this.repos_url = repos_url; + } + + public void setEvents_url(String events_url) { + this.events_url = events_url; + } + + public void setReceived_events_url(String received_events_url) { + this.received_events_url = received_events_url; + } + + public void setType(String type) { + this.type = type; + } + + public void setSite_admin(boolean site_admin) { + this.site_admin = site_admin; + } +} diff --git a/app/src/main/java/ch/lburgy/heiafrschedule/selfupdate/github/Release.java b/app/src/main/java/ch/lburgy/heiafrschedule/selfupdate/github/Release.java new file mode 100644 index 0000000..ea6fa0c --- /dev/null +++ b/app/src/main/java/ch/lburgy/heiafrschedule/selfupdate/github/Release.java @@ -0,0 +1,173 @@ +package ch.lburgy.heiafrschedule.selfupdate.github; + +import java.util.List; + +public class Release { + private String url; + private String html_url; + private String assets_url; + private String upload_url; + private String tarball_url; + private String zipball_url; + private float id; + private String node_id; + private String tag_name; + private String target_commitish; + private String name; + private String body; + private boolean draft; + private boolean prerelease; + private String created_at; + private String published_at; + Author AuthorObject; + List assets; + + + // Getter Methods + + public String getUrl() { + return url; + } + + public String getHtml_url() { + return html_url; + } + + public String getAssets_url() { + return assets_url; + } + + public String getUpload_url() { + return upload_url; + } + + public String getTarball_url() { + return tarball_url; + } + + public String getZipball_url() { + return zipball_url; + } + + public float getId() { + return id; + } + + public String getNode_id() { + return node_id; + } + + public String getTag_name() { + return tag_name; + } + + public String getTarget_commitish() { + return target_commitish; + } + + public String getName() { + return name; + } + + public String getBody() { + return body; + } + + public boolean getDraft() { + return draft; + } + + public boolean getPrerelease() { + return prerelease; + } + + public String getCreated_at() { + return created_at; + } + + public String getPublished_at() { + return published_at; + } + + public Author getAuthor() { + return AuthorObject; + } + + public List getAssets() { + return assets; + } + + // Setter Methods + + public void setUrl(String url) { + this.url = url; + } + + public void setHtml_url(String html_url) { + this.html_url = html_url; + } + + public void setAssets_url(String assets_url) { + this.assets_url = assets_url; + } + + public void setUpload_url(String upload_url) { + this.upload_url = upload_url; + } + + public void setTarball_url(String tarball_url) { + this.tarball_url = tarball_url; + } + + public void setZipball_url(String zipball_url) { + this.zipball_url = zipball_url; + } + + public void setId(float id) { + this.id = id; + } + + public void setNode_id(String node_id) { + this.node_id = node_id; + } + + public void setTag_name(String tag_name) { + this.tag_name = tag_name; + } + + public void setTarget_commitish(String target_commitish) { + this.target_commitish = target_commitish; + } + + public void setName(String name) { + this.name = name; + } + + public void setBody(String body) { + this.body = body; + } + + public void setDraft(boolean draft) { + this.draft = draft; + } + + public void setPrerelease(boolean prerelease) { + this.prerelease = prerelease; + } + + public void setCreated_at(String created_at) { + this.created_at = created_at; + } + + public void setPublished_at(String published_at) { + this.published_at = published_at; + } + + public void setAuthor(Author authorObject) { + this.AuthorObject = authorObject; + } + + public void setAssets(List assets) { + this.assets = assets; + } +} \ No newline at end of file diff --git a/app/src/main/java/ch/lburgy/heiafrschedule/selfupdate/github/Uploader.java b/app/src/main/java/ch/lburgy/heiafrschedule/selfupdate/github/Uploader.java new file mode 100644 index 0000000..0f6dcd3 --- /dev/null +++ b/app/src/main/java/ch/lburgy/heiafrschedule/selfupdate/github/Uploader.java @@ -0,0 +1,171 @@ +package ch.lburgy.heiafrschedule.selfupdate.github; + +public class Uploader { + private String login; + private float id; + private String node_id; + private String avatar_url; + private String gravatar_id; + private String url; + private String html_url; + private String followers_url; + private String following_url; + private String gists_url; + private String starred_url; + private String subscriptions_url; + private String organizations_url; + private String repos_url; + private String events_url; + private String received_events_url; + private String type; + private boolean site_admin; + + + // Getter Methods + + public String getLogin() { + return login; + } + + public float getId() { + return id; + } + + public String getNode_id() { + return node_id; + } + + public String getAvatar_url() { + return avatar_url; + } + + public String getGravatar_id() { + return gravatar_id; + } + + public String getUrl() { + return url; + } + + public String getHtml_url() { + return html_url; + } + + public String getFollowers_url() { + return followers_url; + } + + public String getFollowing_url() { + return following_url; + } + + public String getGists_url() { + return gists_url; + } + + public String getStarred_url() { + return starred_url; + } + + public String getSubscriptions_url() { + return subscriptions_url; + } + + public String getOrganizations_url() { + return organizations_url; + } + + public String getRepos_url() { + return repos_url; + } + + public String getEvents_url() { + return events_url; + } + + public String getReceived_events_url() { + return received_events_url; + } + + public String getType() { + return type; + } + + public boolean getSite_admin() { + return site_admin; + } + + // Setter Methods + + public void setLogin(String login) { + this.login = login; + } + + public void setId(float id) { + this.id = id; + } + + public void setNode_id(String node_id) { + this.node_id = node_id; + } + + public void setAvatar_url(String avatar_url) { + this.avatar_url = avatar_url; + } + + public void setGravatar_id(String gravatar_id) { + this.gravatar_id = gravatar_id; + } + + public void setUrl(String url) { + this.url = url; + } + + public void setHtml_url(String html_url) { + this.html_url = html_url; + } + + public void setFollowers_url(String followers_url) { + this.followers_url = followers_url; + } + + public void setFollowing_url(String following_url) { + this.following_url = following_url; + } + + public void setGists_url(String gists_url) { + this.gists_url = gists_url; + } + + public void setStarred_url(String starred_url) { + this.starred_url = starred_url; + } + + public void setSubscriptions_url(String subscriptions_url) { + this.subscriptions_url = subscriptions_url; + } + + public void setOrganizations_url(String organizations_url) { + this.organizations_url = organizations_url; + } + + public void setRepos_url(String repos_url) { + this.repos_url = repos_url; + } + + public void setEvents_url(String events_url) { + this.events_url = events_url; + } + + public void setReceived_events_url(String received_events_url) { + this.received_events_url = received_events_url; + } + + public void setType(String type) { + this.type = type; + } + + public void setSite_admin(boolean site_admin) { + this.site_admin = site_admin; + } +} diff --git a/app/src/main/java/ch/lburgy/heiafrschedule/type_converter/DateConverter.java b/app/src/main/java/ch/lburgy/heiafrschedule/type_converter/DateConverter.java new file mode 100644 index 0000000..c05ea0a --- /dev/null +++ b/app/src/main/java/ch/lburgy/heiafrschedule/type_converter/DateConverter.java @@ -0,0 +1,18 @@ +package ch.lburgy.heiafrschedule.type_converter; + +import androidx.room.TypeConverter; + +import java.util.Date; + +public class DateConverter { + + @TypeConverter + public static Date toDate(long dateLong) { + return dateLong == -1 ? null : new Date(dateLong); + } + + @TypeConverter + public static long fromDate(Date date) { + return date == null ? -1 : date.getTime(); + } +} diff --git a/app/src/main/java/ch/lburgy/heiafrschedule/ui/BottomSheetDialogLessonsInfos.java b/app/src/main/java/ch/lburgy/heiafrschedule/ui/BottomSheetDialogLessonsInfos.java new file mode 100644 index 0000000..3f70eb4 --- /dev/null +++ b/app/src/main/java/ch/lburgy/heiafrschedule/ui/BottomSheetDialogLessonsInfos.java @@ -0,0 +1,100 @@ +package ch.lburgy.heiafrschedule.ui; + +import android.content.Context; +import android.content.DialogInterface; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.TextView; + +import androidx.annotation.Nullable; +import androidx.coordinatorlayout.widget.CoordinatorLayout; + +import com.google.android.material.bottomsheet.BottomSheetBehavior; +import com.google.android.material.bottomsheet.BottomSheetDialog; + +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +import ch.lburgy.heiafrschedule.R; +import ch.lburgy.heiafrschedule.database.LessonOfClassComplete; +import ch.lburgy.heiafrschedule.database.RoomHEIAFR; +import ch.lburgy.heiafrschedule.database.Teacher; + +public class BottomSheetDialogLessonsInfos extends com.google.android.material.bottomsheet.BottomSheetDialogFragment { + + private final LessonOfClassComplete lessonOfClassComplete; + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + View v = inflater.inflate(R.layout.sheet_lessons_infos, container, false); + + TextView txtName = v.findViewById(R.id.name); + TextView txtTime = v.findViewById(R.id.time); + TextView txtRooms = v.findViewById(R.id.rooms); + LinearLayout teachersLayout = v.findViewById(R.id.teachers_layout); + + txtName.setText(lessonOfClassComplete.getLesson().getName()); + txtTime.setText(String.format("%s - %s", lessonOfClassComplete.getLesson().getTimeStart(), lessonOfClassComplete.getLesson().getTimeEnd())); + StringBuilder rooms = new StringBuilder(); + List lessonCompleteRooms = lessonOfClassComplete.getRooms(); + for (int i = 0; i < lessonCompleteRooms.size(); i++) { + if (i != 0) + rooms.append(", "); + rooms.append(lessonCompleteRooms.get(i).getId()); + } + txtRooms.setText(rooms.toString()); + + for (Teacher teacher : lessonOfClassComplete.getTeachers()) { + LinearLayout teacherLayout = (LinearLayout) inflater.inflate(R.layout.sheet_lessons_infos_teachers, null); + + TextView txtNameTeacher = teacherLayout.findViewById(R.id.name); + TextView txtEmailTeacher = teacherLayout.findViewById(R.id.email); + TextView txtPhoneTeacher = teacherLayout.findViewById(R.id.phone); + TextView txtOfficeTeacher = teacherLayout.findViewById(R.id.office); + + txtNameTeacher.setText(teacher.getName()); + txtEmailTeacher.setText(teacher.getEmail()); + String phone = teacher.getPhone(); + if (phone != null) { + txtPhoneTeacher.setText(phone); + txtPhoneTeacher.setVisibility(View.VISIBLE); + } + String office = teacher.getOffice(); + if (office != null) { + txtOfficeTeacher.setText(office); + txtOfficeTeacher.setVisibility(View.VISIBLE); + } + + teachersLayout.addView(teacherLayout); + } + + getDialog().setOnShowListener(new DialogInterface.OnShowListener() { + @Override + public void onShow(DialogInterface dialog) { + BottomSheetDialog d = (BottomSheetDialog) dialog; + FrameLayout bottomSheet = d.findViewById(R.id.design_bottom_sheet); + CoordinatorLayout coordinatorLayout = (CoordinatorLayout) bottomSheet.getParent(); + BottomSheetBehavior bottomSheetBehavior = BottomSheetBehavior.from(bottomSheet); + bottomSheetBehavior.setPeekHeight(bottomSheet.getHeight()); + coordinatorLayout.getParent().requestLayout(); + } + }); + + return v; + } + + public BottomSheetDialogLessonsInfos(LessonOfClassComplete lessonOfClassComplete) { + this.lessonOfClassComplete = lessonOfClassComplete; + } + + @Override + public void onAttach(@NotNull Context context) { + super.onAttach(context); + } +} diff --git a/app/src/main/java/ch/lburgy/heiafrschedule/ui/BottomSheetDialogLessonsOfRoom.java b/app/src/main/java/ch/lburgy/heiafrschedule/ui/BottomSheetDialogLessonsOfRoom.java new file mode 100644 index 0000000..f748060 --- /dev/null +++ b/app/src/main/java/ch/lburgy/heiafrschedule/ui/BottomSheetDialogLessonsOfRoom.java @@ -0,0 +1,117 @@ +package ch.lburgy.heiafrschedule.ui; + +import android.content.Context; +import android.content.DialogInterface; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.TextView; + +import androidx.annotation.Nullable; +import androidx.coordinatorlayout.widget.CoordinatorLayout; + +import com.google.android.material.bottomsheet.BottomSheetBehavior; +import com.google.android.material.bottomsheet.BottomSheetDialog; + +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.HashMap; + +import ch.lburgy.heiafrschedule.R; +import ch.lburgy.heiafrschedule.database.Lesson; +import ch.lburgy.heiafrschedule.database.LessonOfRoomComplete; +import ch.lburgy.heiafrschedule.database.RoomHEIAFR; + +public class BottomSheetDialogLessonsOfRoom extends com.google.android.material.bottomsheet.BottomSheetDialogFragment { + + private RoomHEIAFR room; + private HashMap> lessonsByDay; + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + View v = inflater.inflate(R.layout.sheet_lessons_of_room, container, false); + + TextView txtRoom = v.findViewById(R.id.room); + LinearLayout daysLayout = v.findViewById(R.id.days_layout); + txtRoom.setText(room.getName()); + + for (int day : lessonsByDay.keySet()) { + if (lessonsByDay.get(day).size() == 0) continue; + LinearLayout thisDayLayout = (LinearLayout) inflater.inflate(R.layout.sheet_lessons_of_room_day, container, false); + TextView txtDay = thisDayLayout.findViewById(R.id.day); + switch (day) { + case 0: + txtDay.setText(getString(R.string.day_0)); + break; + case 1: + txtDay.setText(getString(R.string.day_1)); + break; + case 2: + txtDay.setText(getString(R.string.day_2)); + break; + case 3: + txtDay.setText(getString(R.string.day_3)); + break; + case 4: + txtDay.setText(getString(R.string.day_4)); + break; + } + LinearLayout lessonsLayout = thisDayLayout.findViewById(R.id.lessons_layout); + for (LessonOfRoomComplete lessonComplete : lessonsByDay.get(day)) { + LinearLayout thisLessonLayout = (LinearLayout) inflater.inflate(R.layout.sheet_lessons_of_room_lesson, container, false); + TextView txtName = thisLessonLayout.findViewById(R.id.name); + TextView txtTime = thisLessonLayout.findViewById(R.id.time); + TextView txtClasses = thisLessonLayout.findViewById(R.id.classes); + + Lesson lesson = lessonComplete.getLesson(); + txtName.setText(lesson.getName()); + txtTime.setText(String.format("%s - %s", lesson.getTimeStart(), lesson.getTimeEnd())); + StringBuilder classes = new StringBuilder(); + for (int i = 0; i < lessonComplete.getClasses().size(); i++) { + if (i != 0) + classes.append(", "); + classes.append(lessonComplete.getClasses().get(i).getName()); + } + txtClasses.setText(classes.toString()); + + lessonsLayout.addView(thisLessonLayout); + } + daysLayout.addView(thisDayLayout); + } + + getDialog().setOnShowListener(new DialogInterface.OnShowListener() { + @Override + public void onShow(DialogInterface dialog) { + BottomSheetDialog d = (BottomSheetDialog) dialog; + FrameLayout bottomSheet = d.findViewById(R.id.design_bottom_sheet); + CoordinatorLayout coordinatorLayout = (CoordinatorLayout) bottomSheet.getParent(); + BottomSheetBehavior bottomSheetBehavior = BottomSheetBehavior.from(bottomSheet); + bottomSheetBehavior.setPeekHeight(bottomSheet.getHeight()); + coordinatorLayout.getParent().requestLayout(); + } + }); + + return v; + } + + public BottomSheetDialogLessonsOfRoom() { + } + + public void setRoom(RoomHEIAFR room) { + this.room = room; + } + + public void setLessonsByDay(HashMap> lessonsByDay) { + this.lessonsByDay = lessonsByDay; + } + + @Override + public void onAttach(@NotNull Context context) { + super.onAttach(context); + } +} diff --git a/app/src/main/java/ch/lburgy/heiafrschedule/ui/RecyclerViewAdapter.java b/app/src/main/java/ch/lburgy/heiafrschedule/ui/RecyclerViewAdapter.java new file mode 100644 index 0000000..7ac892b --- /dev/null +++ b/app/src/main/java/ch/lburgy/heiafrschedule/ui/RecyclerViewAdapter.java @@ -0,0 +1,78 @@ +package ch.lburgy.heiafrschedule.ui; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.fragment.app.DialogFragment; +import androidx.fragment.app.FragmentManager; +import androidx.recyclerview.widget.RecyclerView; + +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.HashMap; + +import ch.lburgy.heiafrschedule.R; +import ch.lburgy.heiafrschedule.database.LessonOfRoomComplete; +import ch.lburgy.heiafrschedule.database.RoomHEIAFR; + +public class RecyclerViewAdapter extends RecyclerView.Adapter { + + private final ArrayList rooms; + private final HashMap>> lessonsByRoom; + private final FragmentManager fragmentManager; + private final BottomSheetDialogLessonsOfRoom bottomSheetDialog; + + public static class ViewHolder extends RecyclerView.ViewHolder { + public final TextView txtRoomName; + + public ViewHolder(View v) { + super(v); + txtRoomName = v.findViewById(R.id.room_name); + } + + public void bind(final ArrayList rooms, final HashMap>> lessonsByRoom, + final BottomSheetDialogLessonsOfRoom bottomSheetDialog, final FragmentManager fragmentManager, final int position) { + RoomHEIAFR room = rooms.get(position); + txtRoomName.setText(room.getName()); + txtRoomName.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + RoomHEIAFR room = rooms.get(position); + bottomSheetDialog.setRoom(room); + bottomSheetDialog.setLessonsByDay(lessonsByRoom.get(room)); + bottomSheetDialog.setStyle(DialogFragment.STYLE_NORMAL, R.style.AppBottomSheetDialogTheme); + bottomSheetDialog.show(fragmentManager, bottomSheetDialog.getTag()); + } + }); + } + } + + public RecyclerViewAdapter(ArrayList rooms, HashMap>> lessonsByRoom, + BottomSheetDialogLessonsOfRoom bottomSheetDialog, FragmentManager fragmentManager) { + this.rooms = rooms; + this.lessonsByRoom = lessonsByRoom; + this.fragmentManager = fragmentManager; + this.bottomSheetDialog = bottomSheetDialog; + } + + @NotNull + @Override + public RecyclerViewAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View v = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.cell_room, parent, false); + return new ViewHolder(v); + } + + @Override + public void onBindViewHolder(ViewHolder holder, int position) { + holder.bind(rooms, lessonsByRoom, bottomSheetDialog, fragmentManager, position); + } + + @Override + public int getItemCount() { + return rooms.size(); + } +} diff --git a/app/src/main/java/ch/lburgy/heiafrschedule/ui/ViewPagerDisable.java b/app/src/main/java/ch/lburgy/heiafrschedule/ui/ViewPagerDisable.java new file mode 100644 index 0000000..f86a05c --- /dev/null +++ b/app/src/main/java/ch/lburgy/heiafrschedule/ui/ViewPagerDisable.java @@ -0,0 +1,40 @@ +package ch.lburgy.heiafrschedule.ui; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.KeyEvent; +import android.view.MotionEvent; + +import androidx.annotation.NonNull; +import androidx.viewpager.widget.ViewPager; + +public class ViewPagerDisable extends ViewPager { + private boolean enable = true; + + public ViewPagerDisable(Context context) { + super(context); + } + + public ViewPagerDisable(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent event) { + return enable && super.onInterceptTouchEvent(event); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + return enable && super.onTouchEvent(event); + } + + @Override + public boolean executeKeyEvent(@NonNull KeyEvent event) { + return enable && super.executeKeyEvent(event); + } + + public void setEnable(boolean enable) { + this.enable = enable; + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable-night/ic_arrow_drop_down.xml b/app/src/main/res/drawable-night/ic_arrow_drop_down.xml new file mode 100644 index 0000000..0bb54f5 --- /dev/null +++ b/app/src/main/res/drawable-night/ic_arrow_drop_down.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable-night/ic_arrow_forward.xml b/app/src/main/res/drawable-night/ic_arrow_forward.xml new file mode 100644 index 0000000..7a033e2 --- /dev/null +++ b/app/src/main/res/drawable-night/ic_arrow_forward.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable-night/ic_search.xml b/app/src/main/res/drawable-night/ic_search.xml new file mode 100644 index 0000000..be5ad99 --- /dev/null +++ b/app/src/main/res/drawable-night/ic_search.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/bg_upper_corners_rounded.xml b/app/src/main/res/drawable/bg_upper_corners_rounded.xml new file mode 100644 index 0000000..3aea284 --- /dev/null +++ b/app/src/main/res/drawable/bg_upper_corners_rounded.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_access_time.xml b/app/src/main/res/drawable/ic_access_time.xml new file mode 100644 index 0000000..87e8fda --- /dev/null +++ b/app/src/main/res/drawable/ic_access_time.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_action_next.xml b/app/src/main/res/drawable/ic_action_next.xml new file mode 100644 index 0000000..a6d7349 --- /dev/null +++ b/app/src/main/res/drawable/ic_action_next.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_action_next_disable.xml b/app/src/main/res/drawable/ic_action_next_disable.xml new file mode 100644 index 0000000..30bcd82 --- /dev/null +++ b/app/src/main/res/drawable/ic_action_next_disable.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_action_prev.xml b/app/src/main/res/drawable/ic_action_prev.xml new file mode 100644 index 0000000..ee3ff4b --- /dev/null +++ b/app/src/main/res/drawable/ic_action_prev.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_action_refresh.xml b/app/src/main/res/drawable/ic_action_refresh.xml new file mode 100644 index 0000000..cc2d1e0 --- /dev/null +++ b/app/src/main/res/drawable/ic_action_refresh.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_action_settings.xml b/app/src/main/res/drawable/ic_action_settings.xml new file mode 100644 index 0000000..1397d37 --- /dev/null +++ b/app/src/main/res/drawable/ic_action_settings.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_arrow_drop_down.xml b/app/src/main/res/drawable/ic_arrow_drop_down.xml new file mode 100644 index 0000000..70fb505 --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_drop_down.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_arrow_forward.xml b/app/src/main/res/drawable/ic_arrow_forward.xml new file mode 100644 index 0000000..6dd4a6d --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_forward.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_business.xml b/app/src/main/res/drawable/ic_business.xml new file mode 100644 index 0000000..b7e6b25 --- /dev/null +++ b/app/src/main/res/drawable/ic_business.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_email.xml b/app/src/main/res/drawable/ic_email.xml new file mode 100644 index 0000000..79199e3 --- /dev/null +++ b/app/src/main/res/drawable/ic_email.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..fe31341 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_lock_outline.xml b/app/src/main/res/drawable/ic_lock_outline.xml new file mode 100644 index 0000000..3d9f543 --- /dev/null +++ b/app/src/main/res/drawable/ic_lock_outline.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_looks_1.xml b/app/src/main/res/drawable/ic_looks_1.xml new file mode 100644 index 0000000..774825a --- /dev/null +++ b/app/src/main/res/drawable/ic_looks_1.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_looks_2.xml b/app/src/main/res/drawable/ic_looks_2.xml new file mode 100644 index 0000000..ee5c681 --- /dev/null +++ b/app/src/main/res/drawable/ic_looks_2.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_looks_3.xml b/app/src/main/res/drawable/ic_looks_3.xml new file mode 100644 index 0000000..3cebd3b --- /dev/null +++ b/app/src/main/res/drawable/ic_looks_3.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_looks_4.xml b/app/src/main/res/drawable/ic_looks_4.xml new file mode 100644 index 0000000..c92cdc5 --- /dev/null +++ b/app/src/main/res/drawable/ic_looks_4.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_looks_5.xml b/app/src/main/res/drawable/ic_looks_5.xml new file mode 100644 index 0000000..69a3a7d --- /dev/null +++ b/app/src/main/res/drawable/ic_looks_5.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_person_outline.xml b/app/src/main/res/drawable/ic_person_outline.xml new file mode 100644 index 0000000..9b1420c --- /dev/null +++ b/app/src/main/res/drawable/ic_person_outline.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_phone.xml b/app/src/main/res/drawable/ic_phone.xml new file mode 100644 index 0000000..7e6f97c --- /dev/null +++ b/app/src/main/res/drawable/ic_phone.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_search.xml b/app/src/main/res/drawable/ic_search.xml new file mode 100644 index 0000000..f2a523e --- /dev/null +++ b/app/src/main/res/drawable/ic_search.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/icons_bottom_navigation.xml b/app/src/main/res/drawable/icons_bottom_navigation.xml new file mode 100644 index 0000000..1e6f526 --- /dev/null +++ b/app/src/main/res/drawable/icons_bottom_navigation.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_find_room.xml b/app/src/main/res/layout/activity_find_room.xml new file mode 100644 index 0000000..c1473c3 --- /dev/null +++ b/app/src/main/res/layout/activity_find_room.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..95f41a4 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,26 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_settings.xml b/app/src/main/res/layout/activity_settings.xml new file mode 100644 index 0000000..667e20a --- /dev/null +++ b/app/src/main/res/layout/activity_settings.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_welcome.xml b/app/src/main/res/layout/activity_welcome.xml new file mode 100644 index 0000000..1ef8007 --- /dev/null +++ b/app/src/main/res/layout/activity_welcome.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/card_lesson.xml b/app/src/main/res/layout/card_lesson.xml new file mode 100644 index 0000000..5cc1de5 --- /dev/null +++ b/app/src/main/res/layout/card_lesson.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/cell_room.xml b/app/src/main/res/layout/cell_room.xml new file mode 100644 index 0000000..28c6025 --- /dev/null +++ b/app/src/main/res/layout/cell_room.xml @@ -0,0 +1,14 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/content_dialog_download.xml b/app/src/main/res/layout/content_dialog_download.xml new file mode 100644 index 0000000..7b81724 --- /dev/null +++ b/app/src/main/res/layout/content_dialog_download.xml @@ -0,0 +1,15 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/content_dialog_show_update.xml b/app/src/main/res/layout/content_dialog_show_update.xml new file mode 100644 index 0000000..7e77878 --- /dev/null +++ b/app/src/main/res/layout/content_dialog_show_update.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/main_slide.xml b/app/src/main/res/layout/main_slide.xml new file mode 100644 index 0000000..72e6c2f --- /dev/null +++ b/app/src/main/res/layout/main_slide.xml @@ -0,0 +1,16 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/radio_button.xml b/app/src/main/res/layout/radio_button.xml new file mode 100644 index 0000000..6cca55e --- /dev/null +++ b/app/src/main/res/layout/radio_button.xml @@ -0,0 +1,7 @@ + + \ No newline at end of file diff --git a/app/src/main/res/layout/sheet_lessons_infos.xml b/app/src/main/res/layout/sheet_lessons_infos.xml new file mode 100644 index 0000000..aa8eee8 --- /dev/null +++ b/app/src/main/res/layout/sheet_lessons_infos.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/sheet_lessons_infos_teachers.xml b/app/src/main/res/layout/sheet_lessons_infos_teachers.xml new file mode 100644 index 0000000..5476964 --- /dev/null +++ b/app/src/main/res/layout/sheet_lessons_infos_teachers.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/sheet_lessons_of_room.xml b/app/src/main/res/layout/sheet_lessons_of_room.xml new file mode 100644 index 0000000..377124e --- /dev/null +++ b/app/src/main/res/layout/sheet_lessons_of_room.xml @@ -0,0 +1,27 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/sheet_lessons_of_room_day.xml b/app/src/main/res/layout/sheet_lessons_of_room_day.xml new file mode 100644 index 0000000..f90a6af --- /dev/null +++ b/app/src/main/res/layout/sheet_lessons_of_room_day.xml @@ -0,0 +1,21 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/sheet_lessons_of_room_lesson.xml b/app/src/main/res/layout/sheet_lessons_of_room_lesson.xml new file mode 100644 index 0000000..9b5c1c9 --- /dev/null +++ b/app/src/main/res/layout/sheet_lessons_of_room_lesson.xml @@ -0,0 +1,34 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/spinner_dropdown_item.xml b/app/src/main/res/layout/spinner_dropdown_item.xml new file mode 100644 index 0000000..89a81b1 --- /dev/null +++ b/app/src/main/res/layout/spinner_dropdown_item.xml @@ -0,0 +1,10 @@ + + \ No newline at end of file diff --git a/app/src/main/res/layout/spinner_item.xml b/app/src/main/res/layout/spinner_item.xml new file mode 100644 index 0000000..a971daf --- /dev/null +++ b/app/src/main/res/layout/spinner_item.xml @@ -0,0 +1,9 @@ + + \ No newline at end of file diff --git a/app/src/main/res/layout/time_layout_content.xml b/app/src/main/res/layout/time_layout_content.xml new file mode 100644 index 0000000..179480a --- /dev/null +++ b/app/src/main/res/layout/time_layout_content.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/welcome_slide_1.xml b/app/src/main/res/layout/welcome_slide_1.xml new file mode 100644 index 0000000..1ff1c36 --- /dev/null +++ b/app/src/main/res/layout/welcome_slide_1.xml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/welcome_slide_2.xml b/app/src/main/res/layout/welcome_slide_2.xml new file mode 100644 index 0000000..a4b04ec --- /dev/null +++ b/app/src/main/res/layout/welcome_slide_2.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/welcome_slide_3.xml b/app/src/main/res/layout/welcome_slide_3.xml new file mode 100644 index 0000000..425143d --- /dev/null +++ b/app/src/main/res/layout/welcome_slide_3.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_find_room.xml b/app/src/main/res/menu/menu_find_room.xml new file mode 100644 index 0000000..c624bd8 --- /dev/null +++ b/app/src/main/res/menu/menu_find_room.xml @@ -0,0 +1,12 @@ +

+ + + diff --git a/app/src/main/res/menu/menu_main.xml b/app/src/main/res/menu/menu_main.xml new file mode 100644 index 0000000..a0d776f --- /dev/null +++ b/app/src/main/res/menu/menu_main.xml @@ -0,0 +1,27 @@ + + + + + + + + + diff --git a/app/src/main/res/menu/menu_navigation.xml b/app/src/main/res/menu/menu_navigation.xml new file mode 100644 index 0000000..ca6a82f --- /dev/null +++ b/app/src/main/res/menu/menu_navigation.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..7353dbd --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..7353dbd --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..a942a430c8d0aacc9d8861b1e95d54965cbd75d1 GIT binary patch literal 2638 zcmV-U3bFNxP)eQB6^>2ZQs}g4n*s$&DFbb3(oCiq3h8u6N@+Vu3DB0(3qx!PB>W3G%Fe(9UY~bnwqDk>g*3dQ4WV=XSf2yGVXS} z$zU+N3ta;NDB5PT?Zaf@2oQAQ0e}uc*FXSTYOz@Mhbus^AONJ(=?+8JKmj^%9RPGB zV1Rmidk=;?Kw7Q#Xat~e1*mIil#Zx7$m(!L0Gdh4+6L%b|1Baz-*5yVuee<^fYWA3U7c$9K%$n(@^f&WRm5cqgt(+2CRJyn1fs{Cn?+d zFS%H+UF_qyZNN$mwK^)y&!U|4B&x1blHE2KUI6iQ)Y;ZTvg~xBXlGk9jg5`^#X)d2 zb61D3x;(b5=0*c??GX9^x!o>dDR864IvqL3#;%B@VOanNR;Vd2CZ}^ObOAzfc)qff zWRi3y+-^SRS+FQ(we(Vfd!KbffViHC&sntMckWEt72R4mITQ)Asn zjzOt?Yz0qnZUgy8P91d*T_)Qj>UMg&u!*+G8tJ861O1ETr91<@Av01uTUUang8}wX zVY5|SYa4aVc@L=i5zT@QAeYNUvYfNj-O+lP6C6(ADmT2EO`q7BLw`D_7m;YH72F@M zn9P4>k_|^Ih4q}$c8Sn$nWx(*+&t^Fr~!ivASc!9&vTMEAo3Nt$x2_1lL;kP?=2Q# zyqzQ03z@ZaqPlbPx?d%or{AVki)aNadP~tnZ_17IPqvmnrt2<6F+Tw6;ffzTI71N| z1bGlEVjpwUc2*Rj8uD^pJvH|`guC3u;XHbagg&flo4h8rpnRcNIz#cg0)O{v3ug=bCNU?Nq;N|VP2=M z2o8W$orARQgEG2fr;JI}AZn%u*oD5y8Sryw^@3y{c&~(Rd@V;<(f#p-^y?%IRdx(a z&hZLc55haR3ZWeYy_Ky8Asr!qS5cD)^8g5L+cq#F$_|HXe8%o2GWo8V>#=JG5TXjW z<(_xs0w5@X9OvQpm7=GEjQDOQWB|7@p^{!>BHmMC5`>JfpJ}*UiadKpE27*t*gfy% z`Md&mUA?!60X;uyL2%#o{Qm${0rT7R`|2}y3xLogT2z3L4TDG0xbohdp%+{O`QAY# zJ;H7V9s#HPIirRiWf!@1hg9@<;4;x$vgwejeR8e`S=pG0JZUCDhtV|w&{94RbKJ4B zfNFa`-Yr0T7v05K8fVwu#p?U)^g4%h@C+;if}V2B)@)HOz$#!>Pce9#IY~sGv%jL1 zj8@0w9CDLIL_4@E+8a})s$-BNo>k;kQvpID>vVf&YXR%2Niq( zp@3DD6^YsiA$XoA>{n|SWQ=*yfAaAabg;g9c9Wi;aL(BzYUDCtu2;`Kl{H+Y8|!N* z_gpg7RIAu>++nb~gP%ASEMU=rB0p1Px>y(F?>Zu^Awn|@ZPl*X-(H07@)xY=sMDz_ zD=krQN1a&yVj&OTxNtwO3-Z<@WSsh~ec~FNk37eq*92;Xp|gMr+|<@=6blbBQW7Zr z)L~&2g;EKX%JU|zB>m)JvA{}MC>7_s{It4|UxEiKf$Ja|b+xxrv(Z3J%rDqtA9)S_ zzCh$2Sq2;dC<0LM19adz04VV9YFI3mu(crX*P--!ec%>kHk;$a{R?&F<>dzh21uz? zKI5Hiq3Wg}u^#c2OuOAqiHV8NL06SZ731%hdT(I)QcFvV2BwG!0aQ2$bb(H4wOY$M z-U(g({-J^7vTGuc5rY|WUiPvlm%{Ov)QPss!F$E!-hwoV{B~f(j|e6 z_3PK)$eY_)R>#N3zbuhR5)}$XT0ucUid-&FohmY!>?5D2@B4br)tGZ)n}U0X4$uWU zLASMQ*M90setaQn&6+hg@%A>B`&d@6tPF8kF`>(?t5&VL89H9^Z(J})F>FoCc)Oe@ wy(N?dI!x#iv*ap_s4$Vph>XaHjF1@r2ieli@^}3yCjbBd07*qoM6N<$g5VkQk^lez literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..77c2d93bf480957c0a73b1841e9d521fac978a5d GIT binary patch literal 4635 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 REoT4#002ovPDHLkV1fd)>V5zK literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..1f6f5ee5b695e9aa491617c587041b63e84b0ec0 GIT binary patch literal 1629 zcmV-j2BP_iP)xh)O9R?>D-%4rg_n5)H@0wmTk+k4x<2km6eqX5_L@qKrWZh zCjg#Y1W;C1ws5ila&iYsN=hEf8UXqy42PqlpOOFuf)Q+znBk{>$^fAC2!})PxSg=} z_TkNc^*G;c7uTUsknTrw696mT@32EtCxxu$I2tu|Fk8C>fVOiT;#w{_fqJ~8 z1;9Vx6N>9pG7NYSJ)&yR!$t+1D#9pURR9;ProW=nDi>?wmq$l0eup4`6680F6Qlug8`2zJrv0ySx#1 zY^=iC{f)4ChLSTCx!hQH$_Rj#CM|5%jxm;tLEYuS*F>EkR2cBmpBlVcs>92FX#|)r zPPU=O;vDy1){b@<8`KzyMl%C|t);E2MK~0?Vmug$qC{_lq{E4g1Y%vK5o;(0KBj&{ zjTzOJe(W>$jQhVxBm%Xp241%-GXN}Y&FJd1q`Y5FNQrNR^2WmvRLhQ-uBOV{#Fw{Wv$ zsfE|$7Q4y}m39Ei+Um>hV;>mC`s%az^uJ~(tZp$T>vZA015J2`{9XtJanV5oG&V0J z=bQv&eBkrx&4vjM52Y7CI2=Y}y#h2JQu6f$KcB(lWZ@&GUetD6Qf#}j6ZdYZ5#x)0 zk&AxL76}fw_M%v2#U5Sqlb9(v;2%i;4h)9E_=bY?p6_aiSxhKv>c-Q*DukQFcc5^8 z6PA}~P_U;Sul%jW`xQ+v*as#ApjFlq0AG3mSeyYYCt7@tf|D)1s;mLC2?#5@Rep(9 zD_ZP$VYeE~{!k0$8N>>@&n}h<0PiTDE5k#B=>fn2T1hVO zC9@9mJXypodfOU`)l^;v1)Gv))F;x|5mwPSoU2K4%!VA*y5xPMFeP0;`(U!SbF_IF&+o- z`LWi7q|ku|wOpLY*9`za{9CC7UsC?UvhI)(^U11Ahj(qN#>@>>G0#3C%CY+Z_3_V`xskrI1oJB05 zmdIE=z>Lvq(1{9bY+`pcVRzxw!E7>)tCKTYk#PW8JIquEpFoezI<6{9Hs4P3xEky3 zV%8eTjiR1;oO61GqzY{N9RI-Twy z={93B-^lXv^Ya&Kwc5i@r_eaW>&zU55W?5#kF4y$oor8sZ@0M&z%7DGm ba-{taE>Rb`X2NO>00000NkvXXu0mjf88{=< literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..50764df7ce6b6ed48f396f14d0b448102fe9a4c2 GIT binary patch literal 2829 zcmV+o3-a`dP)e256~@61C825agHSiiumlQm7pEynnl22}Wzr-=-G!7i<90eU6DA=o6Ix2r$rLB; zcpxbxq#@7*FfGOoff6vtvB9!s$+B$An`}##Wm(>2ZMJsFvMlNJoXdOSX|W|46Et(? zy(it}JNJBNx%X+og6sSMKo-hElpMOI(LUOi`YyOxADsLal|eyD8EBF9En8g!r`zE#e(+^61u1F=r?M^wO#X>D?+A^TjfFoRfXa~0R)-0 zaDA$143yiG%`uIX8EgueK!pgEGwJs;qAAn=f2^3WVWClzk2Fh?!A-*Mea2s`3{hxk?JRxL7L z;NvJb%|8c&!NYS_LKqEjuu={@&tJHaa?KcFn3|e8g%GkbQbOoFIN19HUO4DnB`_TV zwZ+Y@Y5E77&*SkN5h#(Bjsn@-%KwpH`RfHwVs!c8N5`AM1IVy_ zv#b_8ZfDZyL{vgtfL7R^luEotc)bk=)e2&Bx{(7{(F_f>)u7XK!KB}xkq`KNKBQF- zNNXy=Y93?b#pelHRJ1~$NF=%=!3w-aUaz-Mu=;7Qyv;HO^)(`}jE!EG6ORlHFj`qm zqseNgF!}-v3=C|Iv4Su(1pk2;O8W`7-&w2t8TGmmFA z8$n$82{;}0q`qLWSgr_sAuF07tXAu4Y~@cORY0J?E6IQ>&e>8S5hvvC7~7;A+QE9C``OeV{VCO1ATA-}eA^hw)s zjp(QpMS{UZAk8BVc>WU=JaFb9XrxxFUxp>=7bNMhv3%;_WK_+LjJ#` zumP{XDsO|bj#20`xj==ItiQ=P%%~~M6I?kC27{r5va_(f$nyDo(o7VvkK3S8as?*{ z&f4yg_hoSZKWp*Y02@ARg!PAHuo9mOPAX^h1)@&_Dj>`eJT{ZbBuC8GLTPPn?UKpK zNnIui^kOp7VMa~W?k`muc)X;EDMH}s`s9C_;mvw|%D$SecIfTyNXX1Kn@wL_TzqF# zfsT%jyD?FiGf_Z^nN}F??7k_e)>)xKV`kI{az)$EN|mtjr~)?OivJX4(|=pw2gek! z?R*#bF*?bI?BJeEZbh?k1bwV7N+VtYm&;{2e*F0Ir~*o*auqhRr>g+(WKPEfdmeTA z8P6B*se;E(G(!h+f0|21zc|$j`R~@lY7C(yYYx=GH{X-OZnTUSqZH)^4OXei=Al}O zvNSoaPyzH;$C)!{)QGn9=$eZL%#Lk!Go)!3hds!`%b^#pSBYd$2Xm9ft7F7!h4MdcwO8F_n{*9?i0gv2&_n~ z1)KNkq%WWap22v(8I}CanGP00e~%V9AR9mhre{^aF)WdX|Y-R&*W;*Q5QJUpyG%(n>(xonKB$7fT(gp{ESO3CPzY7NVxKRnjN zR+N$x(d6sstiQ!oCaX|XqJG77{jlRw59}1FnY=9OVSaF=X^2H1ib$tbM!29h$K(Y9 z-QFa_a(f^lk#%)-okYwz3_Dj!WT{lD?R@KCr&(wTyxD-%u!sg>9;XHZ?EWLT&IvpPD&t%Mnng)$Sc*o zQ6IR}Yi7COa-TIu#QJe?p#^vaww+fcB(a{()|l0&Vh*Y9-n)12qg(>c7M9aXup$-n zr7K@e(j>f`?=ito5Hw{}iseM7hcUB0cBFv;q3Dx`5$1dUmP4=A9n&|NSUi*PC@P+V zRVvYroI1osAKG8I1SK_JPHVMV31a<1%vON+gZldVmpF%s-~2ILld=>jpt@J1Ku?e?_)mW5g1mGixF@og|oI7`p=#w3@t>m|7>({Tp z7fsa1Q6@wyMDaY8^qVK3O{)ik&6QUBnZ#&!L3>LhbhS6f6p8$n5~GhUKRnrWGYB5lzSnR37{NKK4w9Uhyj)2R=*-h_Sw$un?xS-`a5gK&D#J($aE)vjj8g zjXqxIX+v|$DI2WSqO>xTRdfs4UJsV^;Ey7a-LM~}YR+1V+v z*=$Z)LH_iBvrf#1e@RGfD61k>oLIt3Xbc*Q#!M^D&xbX*Nfx6lL;1>6 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..54e24f0b70f35b654124d02fe0270eeea8bf8fd4 GIT binary patch literal 3766 zcmV;n4oUHeP)My@ucFpcyPVeAgH)@P((Rm@!Iw%OsP`OqN;U@BiJ*#33XwnGE2}+s;JlK@|{ryY5Iue|08VrVO_y)BO;4OB$-Nui0X{YQCEG)o=38>4?&c5DjBf)tQ zo6WWvJ)du8<(r+Bz5Xl%CRVfAOifKqZ+nFRXLyy6kT8=kUJ8amw*-4^b7Irp-d?eG z?b=zc7*4A9E0xNHeBmC)O-@O$$0gttRHCb^OBWm*ykyd&!13m(xw*MZ`3C)x0QU&! z?(QB43JSUp*m;A1X{o8HcS-`>CqS#!_V3uS<9=Z04Faae#l`KR*{Z@w%M%XN|4<_T2oyOjMT2+l+8xej>KwobU z9b>Q~4(y_&m@raSog~xHkjFj%($!d3Ek;oIr$LmS7)?FePD#L7ABcNyErT5!wx1HB zLa4r`LTI?RoUp}WrsjrPN<4CyC2BWSmK`VSBxK322msJU>_Cr%?BT}@G8*)5Nrfj- z&_KVDPUK|?X*;G!7S98kfT6(wq4lw$`?=RwlGS3l;9)2na$R*fMIYWv8Od>E;D{+8 z2^bn2WapYlap4C@)1sQNgPcqrfP1M}l48R?WfEe=s0%0pEM~Kin;7o-9hw%8uF=5+ z+{FZZe}56_hb$gl1AZYUbf0kiLqmgsK>&JRO+_h1AKFXJ^-7PfQKHe)&%#UTORuHS zY_@OhDWJj@kEBF6fB2uW<9QDGp4?kV+wz*Js?$goo88UmsGm~e!YTD=1er`j{!2h(ZMBF# zG%d~UX>{)(t>WJPg;$d3hAnCIoqdJ$``8NFtf-^y`OUQVM2oNw^HubEMm?>ItEBI- zPq=YQI!$LXAWan=2Dk2oIuptmHRUD#NdPk1)T5Ci{jsA-oRG3M`uc}C^yUAg(v_Q%>GD^T#K?f*_{xpR zboJ&`TE>x4OjU>iKO2+Bx_^DhFBZ6e`9$mi)b*0T1)AH0-QBlP~QtOnY8tckYfHi=`LTc1=- z&+uzDv5&BO*ilXv7l?Y%xylqiN5HWB>?d7!m&e!mw4{}O$$I|H-34_2j$?H9_8eOJ zeiq%iEn6Jp+zNKOzhk1}YC0zsCZmLn|JiQ{C@#pMGxC)Fs z3l;`Ni9+Nl{z8BvDTZ=0QpTuX*JE9KswFTh1F|65`; z{hj3q2I+xBAx}W)`P-NZ5j9ykB0AC5M`i8q*Ltj0i>MD}rNxi?|N0aGsJkDH2&VGl z!n3nb{(u1d_N|#Cy%$l=tLe2P(T~%TIjr2Is1;%5j}M*@BjO2`BN*yI@DBj~ot#Di zIDCM?WTE>$Vrkl2Fd;usn4_Si=+JRX6Fx=2;6T49J)i`9#!$D~>CLQq5kfk_Umlb< zQq!@G>V1pf%XC|U);OXdPY8gWJW(J*X1^c+(L{b$nwaNxo~!Hb7bOGNUjH6PG((&~ zbq|>7!vAS@fIe@<|a; zb+VX_gzO%ffA)DFfS!b*jVv|vq=x3dtr#w%ZAcKMgQ_kg4VbN>tN@Tv-}y&!%^BBF zYK@e_GIWR&vfam9Xh(rcgrM)U1r@2qdm}2^Mb@k_4EqmH2|%?LWeL9_0H*CX(_iAN zXclL&0RQH<6rzTMY&E^1TZET0UF2vq0S|thD@xCJ4T^3c$@$vpe2jCDGnA+Dhs5-5yI2Ku5@|;84`3!_aoakjPxo z z3B#cxDgh|y`N&LjntSNa?0j+V2G(xO<$l1RuRB^vUwt=YBpO1`p7UlpJso_4-sNPf zeAGk=M$Vx!4MkUV2*Z2;q9Ryab*DHNRoTuG)J6(+>aGM}^bB!kjQ_8f6FmEQCT6g1 zsJExvw+K-84$@B!mk4=S$a)^aKwxSRI)5tz{Sr$GGFfDrE`5z9 z4s*-n>3>0XIwc-Cbk1cmpF0B3vjFy=xTkMSucdX^i5}r> z_g}2hAPBu0QiG%nBg9qLNBe+@6yuOv446M7!1Zq+eB50(MuRBCRg@O`-h6=FVTfLk zl_H`+?2hsR3T1G#Z)Z0OGVbDD3a!Q{7z;r7jt!}2E?M~zOA|uPrTku87t@U>KZ86N zGUSK&Jk|<5&ki5clA{U0burq;&49~Jh&gNQH}KgB8DyfEk^1$Ka!!aU1vmiCDHXS{ z963h^hypp7KnW^TQ%rRSWwXz~x*v_<%+(2fLl#k>!y+ttcJJ4V#k(lP`Sheb93n!_7uf-?i7XScgx-v}e@kXPy~2{So;CUpag|~; zJj;ILyZZ}8k46Xz|AA2Qjg0!q&%}));98cszA;wOVUYk9W&SY(L&B%E^+Ocv1ju#8X6p7I^I56<}Um1t0C1k|c4J+rc z{wFUpmEt1~lD0z=Sj*F~LR!S$Bb8VgVLj& zlYX~Jucf(BEZl-&i>d7bND^kZu;dVugIa%Qdz;*(=W6;so!Bpisld#XII(TX-3=4x zTmO$WpI91$Ys2Ycp@7_`I;LZ^h+Q*SpNU;70Ifk;Nd|r2gryB%S5Q}7F)TN%_2~&m z#0Cs+IDEhVssikugcMQPZ9hK7R_WUg571;e@{oWuIlr0By!%CNElWd)1H zav_3;!27xy?50Q7+N13f`2%`8oGMJs$L+0Z0XSx~F4VWA$nOi1Us)zi5+DhXCJB%P zNCKQ+-tAQa;^X7*mIO>}1Z>~F{UNUr5Ed4;gfFg>1h_|lMx*K7vSrIX-XH)!zcGFJ z^5xf?Or}ct6DH^X2@_zXR4QwiEn7C%(RnBLJ2E?W?!=FjT~%FO{Sm5=d;=@)BLP2a zBIz`+F&d4OkdUwsm^rYU;tc|3tz5bC-WG1C8sbKLQyT^Y-#DNR7~p5DfC;cEEiG+Z zv0?=r@a&BnH-2G?7uwWSt5)F`f#$AXzy4Vs32l1)@C=n~!ws@yFc_$~xLC7p-MSZm z)l*MBH5=GX@lu`qAAfvBR8&+5Gu@z4sq}4aZG&tBj>kYU@tM_XwESqJd@Y#U@ zWAO+u0X7bdW_q*tI~yXR%NH(O_!Vvo`S>=rJJ^;4xB-LPfC;bxMh>hlNCT^I%2&>v zJNMcJ3l`isfByWN=FOWoClC#v9T)%$VB)}L>XeJnaFgi{ox3c+ZH5DjivqlCvdJc! gY_iEFn}4+b1Jk~(^b literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..6313dc51983094cc538abbbbc07a17e5c7dc0aa7 GIT binary patch literal 6748 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 BXwCot literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..3d5ce05148c44c31fb08ca3f0380959ef5fdd2e5 GIT binary patch literal 8232 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 x9i<) + + Systemeinstellung + Hell + Dunkle + + + + Batterieschoner + Hell + Dunkle + + \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml new file mode 100644 index 0000000..54909ee --- /dev/null +++ b/app/src/main/res/values-de/strings.xml @@ -0,0 +1,40 @@ + + + HEIA-FR Zeitplan + Anmeldung + Benutzername + Kennwort + Klasse + Sie können jetzt Ihren Zeitplan sehen ! + Sie sind startklar ! + Anmeldung … + Montag + Dienstag + Mittwoch + Donnerstag + Freitag + Einstellungen + Unterricht aktualisieren + Anmeldung fehlgeschlagen + Unterricht abrufen … + Einstellungen + Die Liste der Klassen wurde aktualisiert + Informationen + Display + Daten + Design + Wählen Sie Ihre Klasse + Design auswählen + Klasse + HTTP Fehler + Keine Internetverbindung + Suchen + Einen Raum finden + Die Endzeit muss später als die Startzeit liegen + Räume für Sie suchen … + Lokale Daten löschen + Lokale Daten wurden gelöscht + Da dies das erste Mal ist, dass Sie einen Raum finden wollen, muss die Anwendung alle Unterrichte aller Räume aus dem Internet abrufen, was bis zu 2 oder 3 Minuten dauern kann. + Das Update herunterladen … + Möchten Sie die Anwendung aktualisieren ? + \ No newline at end of file diff --git a/app/src/main/res/values-fr/arrays.xml b/app/src/main/res/values-fr/arrays.xml new file mode 100644 index 0000000..57f7f89 --- /dev/null +++ b/app/src/main/res/values-fr/arrays.xml @@ -0,0 +1,13 @@ + + + Thème par défaut du système + Clair + Sombre + + + + Économie de batterie + Clair + Sombre + + \ No newline at end of file diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml new file mode 100644 index 0000000..e418ea6 --- /dev/null +++ b/app/src/main/res/values-fr/strings.xml @@ -0,0 +1,40 @@ + + + HEIA-FR Horaires + Connexion + Nom d\'utilisateur + Mot de passe + Classe + Vous pouvez y aller ! + Vous pouvez consulter vos horaires ! + Paramètres + Actualiser les cours + Récupération des cours … + Lundi + Mardi + Mercredi + Jeudi + Vendredi + Connexion … + Échec de la connexion + Erreur HTTP + Pas de connexion internet + Paramètres + Affichage + Informations + Données + Thème + Choisissez votre classe + Classe + Choisissez un thème + La liste des classes a été actualisée + Rechercher + Trouver une salle + L\'heure de fin doit être postérieure à l\'heure de début + Recherche de salles pour vous … + Supprimer les données locales + Les données locales ont été supprimées + Etant donné que c\'est la première fois que vous voulez trouver une salle, l\'application doit obtenir tous les cours de toutes les salles à partir d\'Internet, ce qui peut prendre jusqu\'à 2 ou 3 minutes. + Voulez-vous mettre à jour l\'application ? + Téléchargement de la mise à jour … + \ No newline at end of file diff --git a/app/src/main/res/values-it/arrays.xml b/app/src/main/res/values-it/arrays.xml new file mode 100644 index 0000000..317561e --- /dev/null +++ b/app/src/main/res/values-it/arrays.xml @@ -0,0 +1,13 @@ + + + Predefinito + Chiaro + Scuro + + + + Risparmio batteria + Chiaro + Scuro + + \ No newline at end of file diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml new file mode 100644 index 0000000..6928e6d --- /dev/null +++ b/app/src/main/res/values-it/strings.xml @@ -0,0 +1,40 @@ + + + HEIA-FR Orari + Login + Nome utente + Password + Classe + È pronto ad iniziare ! + Ora lei può consultare i suoi orari ! + Login … + Display + Classe + Recuperare le lezioni … + Aggiornare le lezioni + Nessuna connessione internet + Login non riuscito + L\'elenco delle classi è stato aggiornato + Impostazioni + Errore HTTP + Informazioni + Dati + Tema + Scelga la vostra classe + Scelga tema + Impostazioni + Lunedì + Martedì + Venerdì + Giovedì + Mercoledì + Cercare + Trovare una stanza + L\'ora della fine deve essere successiva all\'ora del inizio + Ricerca stanze per lei … + Cancellare i dati locali + I dati locali sono stati cancellati + Poiché è la prima volta che si desidera trovare una stanza, l\'applicazione deve recuperare tutte le lezioni di tutte le stanze da Internet, che può richiedere fino a 2 o 3 minuti. + Vuole aggiornare l\'applicazione ? + Il download del aggiornamento … + \ No newline at end of file diff --git a/app/src/main/res/values-night/colors.xml b/app/src/main/res/values-night/colors.xml new file mode 100644 index 0000000..19d0928 --- /dev/null +++ b/app/src/main/res/values-night/colors.xml @@ -0,0 +1,4 @@ + + + #C1C1C1 + diff --git a/app/src/main/res/values-v21/styles.xml b/app/src/main/res/values-v21/styles.xml new file mode 100644 index 0000000..2e4096a --- /dev/null +++ b/app/src/main/res/values-v21/styles.xml @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml new file mode 100644 index 0000000..1fead7e --- /dev/null +++ b/app/src/main/res/values/arrays.xml @@ -0,0 +1,25 @@ + + + System default + Light + Dark + + + + MODE_NIGHT_FOLLOW_SYSTEM + MODE_NIGHT_NO + MODE_NIGHT_YES + + + + Set by Battery Saver + Light + Dark + + + + MODE_NIGHT_AUTO_BATTERY + MODE_NIGHT_NO + MODE_NIGHT_YES + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..310fdc9 --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,13 @@ + + + #3F51B5 + #303F9F + #FF4081 + + #737373 + + #ffffff + #888888 + + @android:color/white + diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml new file mode 100644 index 0000000..2e33e56 --- /dev/null +++ b/app/src/main/res/values/dimens.xml @@ -0,0 +1,51 @@ + + + 8dp + 8dp + 8dp + 16dp + 24sp + 18sp + 21sp + 18sp + + + 16dp + 16dp + 16dp + 4dp + 16dp + 28sp + 20sp + 24sp + 18sp + 8dp + + + 16dp + 16dp + 8dp + 18sp + 16sp + + + 16dp + 32dp + 30dp + 20dp + 16sp + 20dp + 10dp + 20dp + 30sp + 16sp + + + 16sp + 50dp + 16dp + + + 24dp + 8dp + diff --git a/app/src/main/res/values/ic_launcher_background.xml b/app/src/main/res/values/ic_launcher_background.xml new file mode 100644 index 0000000..c5d5899 --- /dev/null +++ b/app/src/main/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #FFFFFF + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..722df0e --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,70 @@ + + HEIA-FR Schedule + Settings + Refresh lessons + Find a room + + + Retrieving lessons … + + + Monday + Tuesday + Wednesday + Thursday + Friday + + + Login … + Login failed + HTTP Error + No internet connection + + Login + Username + Password + + Class + + You\'re good to go ! + You can now see your schedule ! + + + Search + : + Searching rooms for you … + The end time must be later than the start time + Since this is the first time you want to find a room, the application has to retrieve all lessons of all rooms from the Internet, which can take up to 2 or 3 minutes. + + + class_changed + + + Settings + Display + Informations + Datas + + + theme_9 + theme_10 + class + delete_datas + + + Theme + Choose theme + + + Class + Choose your class + + + Delete the local datas + The local datas have been deleted + The class list has been refreshed + + + Do you want to update the app ? + Downloading the update … + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..f53ef34 --- /dev/null +++ b/app/src/main/res/values/styles.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/xml/file_provider_paths.xml b/app/src/main/res/xml/file_provider_paths.xml new file mode 100644 index 0000000..f547538 --- /dev/null +++ b/app/src/main/res/xml/file_provider_paths.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/root_preferences.xml b/app/src/main/res/xml/root_preferences.xml new file mode 100644 index 0000000..841cb7e --- /dev/null +++ b/app/src/main/res/xml/root_preferences.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/test/java/ch/lburgy/heiafrschedule/ExampleUnitTest.java b/app/src/test/java/ch/lburgy/heiafrschedule/ExampleUnitTest.java new file mode 100644 index 0000000..8712130 --- /dev/null +++ b/app/src/test/java/ch/lburgy/heiafrschedule/ExampleUnitTest.java @@ -0,0 +1,17 @@ +package ch.lburgy.heiafrschedule; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Example local unit test, which will execute on the development machine (host). + * + * @see Testing documentation + */ +public class ExampleUnitTest { + @Test + public void addition_isCorrect() { + assertEquals(4, 2 + 2); + } +} \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..b58dc04 --- /dev/null +++ b/build.gradle @@ -0,0 +1,24 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +buildscript { + repositories { + google() + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:4.0.1' + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + google() + jcenter() + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..199d16e --- /dev/null +++ b/gradle.properties @@ -0,0 +1,20 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx1536m +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Automatically convert third-party libraries to use AndroidX +android.enableJetifier=true + diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..f6b961fd5a86aa5fbfe90f707c3138408be7c718 GIT binary patch literal 54329 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

<5G2M1WZP5 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 F{|7XC?P&l2 literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..15f5748 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Sat Jun 13 16:10:29 CEST 2020 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..cccdd3d --- /dev/null +++ b/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..f955316 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..618942b --- /dev/null +++ b/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name='HEIA-FR Horaires' +include ':app'