1
0

Compare commits

..

127 Commits
0.7.1 ... 0.9.2

Author SHA1 Message Date
0bccbc6011 Version 0.9.2 2019-06-08 11:38:06 +02:00
ed6a0f8cd0 Add StackOverflowError to RxJava's error handler (#408) 2019-06-07 21:38:53 +02:00
58d5e4da0e Fix showing empty view in mobile device view (#407) 2019-06-07 19:03:26 +02:00
ba6fb1a4b9 Fix update of grades in GradeDetailsFragment (#406) 2019-06-07 14:46:11 +02:00
b6b862d4c3 Organize AndroidX libraries (#404) 2019-06-07 14:12:52 +02:00
9b044a19fe Fix typo in service interval (#405) 2019-06-06 22:47:45 +02:00
7485cb2a39 Fix change of worker specs after app update (#402) 2019-06-06 22:32:43 +02:00
75122d0dcd Fix crash TabLayout on Android 4.x in new version of material components (#403) 2019-06-06 19:16:00 +02:00
39af56484b Bump threetenabp from 1.2.0 to 1.2.1 (#401) 2019-06-04 11:23:50 +00:00
05f7e1d115 Merge tag '0.9.1' into develop
Version 0.9.1
2019-06-04 02:44:04 +02:00
9cd5377438 Merge branch 'release/0.9.1' 2019-06-04 02:43:54 +02:00
7a61b233f0 Version 0.9.1 2019-06-04 02:43:50 +02:00
83dbd9874e Add option to force calc average by app (#400) 2019-06-04 02:27:15 +02:00
1d9a49d552 Add githook plugin (#398) 2019-06-04 01:53:20 +02:00
6175081b88 Fix tab layout crash on prelolipop (#399) 2019-06-03 22:47:35 +02:00
116e551186 Merge tag '0.9.0' into develop
0.9.0 0.9.0
2019-06-03 18:15:42 +02:00
713500d892 Merge branch 'release/0.9.0' 2019-06-03 18:15:41 +02:00
dfdbe374c2 Version 0.9.0 2019-06-03 18:15:11 +02:00
b1e3bab5e7 Add F-Droid flavor (#349) 2019-06-03 14:12:48 +02:00
0b2ef367da Change timeout string (#397) 2019-06-03 13:15:21 +02:00
28f27db2b5 Add mobile access managment (#344) 2019-06-03 00:43:54 +02:00
5c70cd8b8c Add session expired dialog after password change (#389) 2019-05-31 15:12:10 +02:00
0f75ff3206 Show grades average from register if exist instead of calculating (#374) 2019-05-31 14:40:53 +02:00
383cab4dae Fix only wifi setting (#394) 2019-05-31 13:46:53 +02:00
e1b0db76c2 Bump material from 1.1.0-alpha06 to 1.1.0-alpha07 (#393) 2019-05-31 11:46:29 +02:00
f10684097c Bump dagger-compiler from 2.23 to 2.23.1 (#391) 2019-05-31 02:02:09 +00:00
35d4342bec Bump dagger-android-processor from 2.23 to 2.23.1 (#392) 2019-05-31 01:42:40 +00:00
068aee215a Bump dagger-android-support from 2.23 to 2.23.1 (#390) 2019-05-31 01:19:26 +00:00
033074c66b Bump room-testing from 2.1.0-beta01 to 2.1.0-rc01 (#377) 2019-05-30 16:46:54 +02:00
0de534cb66 Bump room-rxjava2 from 2.1.0-beta01 to 2.1.0-rc01 (#385) 2019-05-30 16:46:28 +02:00
26e4619dde Bump room-runtime from 2.1.0-beta01 to 2.1.0-rc01 (#388) 2019-05-30 14:06:06 +00:00
cf08d1ff24 Bump mockito-core from 2.28.1 to 2.28.2 (#387) 2019-05-30 13:58:42 +00:00
96c400a0bd Bump runner from 1.1.1 to 1.2.0 (#386) 2019-05-30 13:50:28 +00:00
e238f65dde Bump rxjava from 2.2.8 to 2.2.9 (#381) 2019-05-30 15:22:15 +02:00
ba7966125b Bump dagger-compiler from 2.22.1 to 2.23 (#380) 2019-05-30 12:01:15 +00:00
1536208e45 Bump core from 1.1.0 to 1.2.0 (#383) 2019-05-30 11:46:56 +00:00
24cc07264a Bump dagger-android-support from 2.22.1 to 2.23 (#384) 2019-05-30 11:31:22 +00:00
1ad5232520 Bump mockito-inline from 2.28.1 to 2.28.2 (#382) 2019-05-30 11:29:13 +00:00
07f3333029 Bump mockito-android from 2.28.1 to 2.28.2 (#379) 2019-05-30 11:12:44 +00:00
214afb82a6 Bump junit from 1.1.0 to 1.1.1 (#378) 2019-05-30 11:05:05 +00:00
7a69710261 Bump dagger-android-processor from 2.22.1 to 2.23 (#376) 2019-05-30 10:54:26 +00:00
64d3afbdb3 Bump room-compiler from 2.1.0-beta01 to 2.1.0-rc01 (#375) 2019-05-30 10:53:35 +00:00
387e46f72d Bump mockito-inline from 2.27.5 to 2.28.1 (#372) 2019-05-29 00:16:10 +00:00
89acc2f384 Bump mockito-android from 2.27.5 to 2.28.1 (#373) 2019-05-28 23:59:19 +00:00
4ef1439878 Bump mockito-core from 2.27.2 to 2.28.1 (#371) 2019-05-29 01:57:19 +02:00
6efd170e03 Bump mockito-inline from 2.27.0 to 2.27.5 (#367) 2019-05-28 14:57:39 +00:00
82cd39329a Bump mockito-android from 2.27.0 to 2.27.5 (#354) 2019-05-28 14:22:24 +00:00
b38e0d04e7 Bump threetenbp from 1.3.8 to 1.4.0 (#366) 2019-05-28 14:41:48 +02:00
90b2ffe250 Bump logging-interceptor from 3.12.1 to 3.12.3 (#369)
Bumps logging-interceptor from 3.12.1 to 3.12.3.
2019-05-28 14:41:27 +02:00
33951dff54 Bump crashlytics from 2.9.9 to 2.10.1 (#365) 2019-05-28 11:30:42 +00:00
9ced00c4d7 Bump room-runtime from 2.1.0-alpha07 to 2.1.0-beta01 (#364) 2019-05-28 11:06:56 +00:00
2ac1638911 Bump room-testing from 2.1.0-alpha07 to 2.1.0-beta01 (#363) 2019-05-28 10:48:37 +00:00
463a7e2744 Bump gradle from 1.28.1 to 1.29.0 (#355)
Bumps gradle from 1.28.1 to 1.29.0.
2019-05-28 02:24:36 +02:00
d14382f3b4 Bump firebase-core from 16.0.8 to 16.0.9 (#358)
Bumps firebase-core from 16.0.8 to 16.0.9.
2019-05-28 00:58:09 +02:00
efa0b19cc1 Bump room-rxjava2 from 2.1.0-alpha07 to 2.1.0-beta01 (#357)
Bumps room-rxjava2 from 2.1.0-alpha07 to 2.1.0-beta01.
2019-05-28 00:39:12 +02:00
7cb893a254 Bump play-publisher from 2.2.0 to 2.2.1 (#356)
Bumps play-publisher from 2.2.0 to 2.2.1.
2019-05-28 00:37:06 +02:00
f7371f7b73 Bump room-compiler from 2.1.0-alpha07 to 2.1.0-beta01 (#350)
Bumps room-compiler from 2.1.0-alpha07 to 2.1.0-beta01.
2019-05-27 23:04:12 +02:00
9c626ad517 Bump reactivenetwork-rx2 from 3.0.2 to 3.0.3 (#351)
Bumps [reactivenetwork-rx2](https://github.com/pwittchen/ReactiveNetwork) from 3.0.2 to 3.0.3.
- [Release notes](https://github.com/pwittchen/ReactiveNetwork/releases)
- [Changelog](https://github.com/pwittchen/ReactiveNetwork/blob/RxJava2.x/CHANGELOG.md)
- [Commits](https://github.com/pwittchen/ReactiveNetwork/commits)
2019-05-27 22:58:17 +02:00
9c4c6b0192 Bump material from 1.1.0-alpha05 to 1.1.0-alpha06 (#353)
Bumps [material](https://github.com/material-components/material-components-android) from 1.1.0-alpha05 to 1.1.0-alpha06.
- [Release notes](https://github.com/material-components/material-components-android/releases)
- [Commits](https://github.com/material-components/material-components-android/compare/1.1.0-alpha05...1.1.0-alpha06)
2019-05-27 22:49:16 +02:00
8efc4d750d Bump sonarqube-gradle-plugin from 2.7 to 2.7.1 (#352)
Bumps sonarqube-gradle-plugin from 2.7 to 2.7.1.
2019-05-27 22:41:16 +02:00
e4a6caa13e Version 0.8.4 2019-05-27 18:13:20 +02:00
209e75160a Fix network constraint in background sync (#348) 2019-05-25 14:34:17 +02:00
153e026a8d Version 0.8.3 2019-05-20 00:21:30 +02:00
8731c2e1f2 Change help text when entering the symbol to a more precise one (#345) 2019-05-19 22:47:57 +02:00
0977282a4b Fix no current student in services after logout all accounts (#342) 2019-05-18 23:42:47 +02:00
667c4b6af7 Fix empty maybe when loading message content (#343) 2019-05-18 23:32:37 +02:00
1f5088cfc9 Don't copy teacher from previous lesson to completed (#341) 2019-05-18 13:03:38 +02:00
bf6b857a3e Fix null returns in widgets (#340) 2019-05-18 00:22:07 +02:00
80cb94c434 Version 0.8.2 2019-05-15 19:26:25 +02:00
0cb4eda32b Fix crash on reselecting fragment (#339) 2019-05-15 15:11:28 +02:00
d169f964f2 Fix crash when no webview activity (#338) 2019-05-14 11:45:27 +02:00
103ab95c80 Fix not attached fragment (#337)
* Add condition for error dialog
* Add safe call of forEach in LuckyNumberWidgetProvider
2019-05-09 22:06:11 +02:00
a191f03cdf Version 0.8.1 2019-04-30 23:56:22 +02:00
63404b8576 Fix entity list comparing (#335) 2019-04-30 19:04:05 +02:00
1b7760ff88 Fix dark theme background with custom theme engine (#334) 2019-04-30 17:45:37 +02:00
24f58835e7 Fix menu view initialization and restoration (#333) 2019-04-30 17:28:09 +02:00
b032c459d1 Fix theme on release build (#332) 2019-04-30 11:16:23 +02:00
df0a1e59cc Version 0.8.0 2019-04-29 20:39:35 +02:00
dbbc8069b1 Add showing proper fragment in notifications (#331) 2019-04-29 19:56:23 +02:00
f84040109c Add lucky number widget (#292) 2019-04-29 14:33:33 +02:00
baf1420193 Improve login student selection layout (#329) 2019-04-29 00:55:30 +02:00
4a36d78709 Add activity and fragment lifecycle logging (#327) 2019-04-26 23:53:02 +02:00
4464812651 Fix configure activity's theme (#325) 2019-04-22 09:27:25 +02:00
72a35481e5 Fix showing last message after update (#326) 2019-04-22 09:13:26 +02:00
017c200115 Fix grade summary final grade string (#324) 2019-04-20 22:19:06 +02:00
2bf7755157 Add AMOLED mode (#279) 2019-04-19 23:52:34 +02:00
269af4b7ba Update dependencies (#323) 2019-04-18 16:38:49 +02:00
7431738366 Add a selection of multiple students to login (#318) 2019-04-18 12:18:58 +02:00
034b99c7ab Add counting of the full-year average to the summary of grades (#322) 2019-04-18 00:32:43 +02:00
74e98e4430 Change style of privacy policy link (#321) 2019-04-09 23:33:53 +02:00
cbf3215dd1 Merge branch '0.7.x' 2019-04-08 13:51:02 +02:00
c18877466f Add account picker for timetable widget (#314)
Close #281
2019-04-08 00:18:45 +02:00
6cd6cae1e0 Version 0.7.6 2019-04-07 11:08:46 +02:00
333f7bfa16 Add privacy policy link (#320) 2019-04-07 10:59:27 +02:00
aa6dcaff94 Merge branch '0.7.x' 2019-04-06 01:18:03 +02:00
f2fa04105d Version 0.7.5 2019-04-06 01:06:11 +02:00
7d97d71066 Fix message loading if student and parent are logged in (#319)
Fixes #316
2019-04-05 19:35:21 +02:00
8daea5c900 Add class name to student (#315) 2019-04-04 13:00:07 +02:00
2bff468e56 Add deleting messages (#290) 2019-03-31 22:01:04 +02:00
f2855d598d Merge branch '0.7.x' 2019-03-30 20:19:34 +01:00
297a2909ba Version 0.7.4 2019-03-30 19:45:57 +01:00
935bec3f5b Add 0,75 grade modifier (#313) 2019-03-30 19:26:19 +01:00
e71dd55066 Fix more than one current semester (#307) 2019-03-30 18:28:37 +01:00
4e3864f26f Add opening login view on no current student (#312) 2019-03-30 09:31:30 +01:00
8601093725 Again fix rejected execution in sync worker (#310) 2019-03-28 23:30:30 +01:00
b97b94ae29 Fix more than one current student (#311)
Fix #309
2019-03-28 23:07:59 +01:00
c6c7357623 Fix black spinner in login form (#308) 2019-03-28 18:27:17 +01:00
1ebc296bfe Merge branch '0.7.x' 2019-03-26 21:22:17 +01:00
c2ab53cfeb Version 0.7.3 2019-03-26 19:46:59 +01:00
87268b3ef6 Fix rejected execution in sync worker (#305) 2019-03-26 16:47:14 +01:00
b3cd7e8ac1 Fix undeliverable network exceptions (#306)
* Remove unnecessary this
2019-03-26 14:32:23 +01:00
a2a18e5652 Merge branch '0.7.x' 2019-03-24 23:22:44 +01:00
5a997dacb7 Version 0.7.2 2019-03-24 22:42:09 +01:00
ed9458d9a5 Fix more than one current semester in database (#304) 2019-03-24 20:21:05 +01:00
3656d3161f Fix crash on duplicate items (#303) 2019-03-24 17:31:39 +01:00
d178c15d2f Update dependencies (#302) 2019-03-24 16:03:51 +01:00
1f65b8465e Add logging to sync worker (#300) 2019-03-23 18:35:56 +01:00
6bb03b3be8 Fix day navigation unevenition (#301) 2019-03-23 17:29:34 +01:00
68b9847927 Fix reselecting root fragments (#299) 2019-03-23 16:35:33 +01:00
e1a83927c4 Fix reset button in timetable widget (#298) 2019-03-23 14:44:52 +01:00
fc9981aa5d Fix issues when loading lucky number (#297) 2019-03-23 13:01:01 +01:00
2d6610e05c Set max concurrency in sync worker (#296) 2019-03-23 01:12:17 +01:00
316cd2f7f9 Add checking current student in background services (#295) 2019-03-23 00:37:13 +01:00
36785f019a Fix restoring the grade fragment (#293) 2019-03-22 23:54:58 +01:00
4b78862486 Remove retry sync work when completed lessons is disabled (#294) 2019-03-22 23:41:41 +01:00
cd1ceea860 Add messages forwarding (#288) 2019-03-21 22:55:47 +01:00
20d0abba29 Fix empty container id in grade fragemnt adapter (#289) 2019-03-21 22:34:41 +01:00
283 changed files with 11782 additions and 1321 deletions

View File

@ -7,11 +7,11 @@ references:
container_config: &container_config container_config: &container_config
docker: docker:
- image: circleci/android:api-28-alpha - image: circleci/android@sha256:5cdc8626cc6f13efe5ed982cdcdb432b0472f8740fed8743a6461e025ad6cdfc
working_directory: *workspace_root working_directory: *workspace_root
environment: environment:
environment: environment:
JVM_OPTS: -Xmx3200m _JAVA_OPTS: -Xmx3072m
attach_workspace: &attach_workspace attach_workspace: &attach_workspace
attach_workspace: attach_workspace:
@ -35,7 +35,7 @@ jobs:
command: ./gradlew dependencies --no-daemon --stacktrace --console=plain -PdisablePreDex || true command: ./gradlew dependencies --no-daemon --stacktrace --console=plain -PdisablePreDex || true
- run: - run:
name: Initial build name: Initial build
command: ./gradlew build -x test -x lint -x fabricGenerateResourcesRelease -x packageRelease --no-daemon --stacktrace --console=plain -PdisablePreDex command: ./gradlew build -x test -x lint -x fabricGenerateResourcesFdroidRelease -x fabricGenerateResourcesPlayRelease -x packageRelease --no-daemon --stacktrace --console=plain -PdisablePreDex
- run: - run:
name: Run FOSSA name: Run FOSSA
command: fossa --no-ansi || true command: fossa --no-ansi || true
@ -56,7 +56,7 @@ jobs:
<<: *general_cache_key <<: *general_cache_key
- run: - run:
name: Run lint name: Run lint
command: ./gradlew lint -x fabricGenerateResourcesRelease --no-daemon --stacktrace --console=plain -PdisablePreDex command: ./gradlew lint -x fabricGenerateResourcesFdroidRelease -x fabricGenerateResourcesPlayRelease --no-daemon --stacktrace --console=plain -PdisablePreDex
- store_artifacts: - store_artifacts:
path: ./app/build/reports/ path: ./app/build/reports/
destination: lint_reports/app/ destination: lint_reports/app/
@ -75,7 +75,7 @@ jobs:
<<: *general_cache_key <<: *general_cache_key
- run: - run:
name: Run app tests name: Run app tests
command: ./gradlew :app:test :app:jacocoTestReport -x fabricGenerateResourcesRelease --no-daemon --stacktrace --console=plain -PdisablePreDex command: ./gradlew :app:test :app:jacocoTestReport -x fabricGenerateResourcesFdroidRelease -x fabricGenerateResourcesPlayRelease --no-daemon --stacktrace --console=plain -PdisablePreDex
- run: - run:
name: Upload unit code coverage to codecov name: Upload unit code coverage to codecov
command: bash <(curl -s https://codecov.io/bash) -F app command: bash <(curl -s https://codecov.io/bash) -F app
@ -93,6 +93,9 @@ jobs:
<<: *container_config <<: *container_config
steps: steps:
- *attach_workspace - *attach_workspace
- run:
name: Accept licenses
command: yes | sdkmanager --licenses && yes | sdkmanager --update
- run: - run:
name: Setup emulator name: Setup emulator
command: sdkmanager "system-images;android-19;default;armeabi-v7a" && echo "no" | avdmanager create avd -n test -k "system-images;android-19;default;armeabi-v7a" command: sdkmanager "system-images;android-19;default;armeabi-v7a" && echo "no" | avdmanager create avd -n test -k "system-images;android-19;default;armeabi-v7a"
@ -113,7 +116,7 @@ jobs:
adb shell input keyevent 82 adb shell input keyevent 82
- run: - run:
name: Run instrumented tests name: Run instrumented tests
command: ./gradlew clean createDebugCoverageReport jacocoTestReport --no-daemon --stacktrace --console=plain -PdisablePreDex command: ./gradlew clean createPlayDebugCoverageReport jacocoTestReport --no-daemon --stacktrace --console=plain -PdisablePreDex
- run: - run:
name: Collect logs from emulator name: Collect logs from emulator
command: adb logcat -d > ./app/build/reports/logcat_emulator.txt command: adb logcat -d > ./app/build/reports/logcat_emulator.txt
@ -159,7 +162,7 @@ jobs:
openssl aes-256-cbc -d -in ./app/upload-key-encrypted.jks -k $ENCRYPT_KEY >> ./app/upload-key.jks openssl aes-256-cbc -d -in ./app/upload-key-encrypted.jks -k $ENCRYPT_KEY >> ./app/upload-key.jks
- run: - run:
name: Publish release name: Publish release
command: ./gradlew publish --no-daemon --stacktrace --console=plain -PenableCrashlytics -PdisablePreDex command: ./gradlew publishPlayRelease --no-daemon --stacktrace --console=plain -PenableCrashlytics -PdisablePreDex
workflows: workflows:
version: 2 version: 2

1
.gitignore vendored
View File

@ -48,3 +48,4 @@ app/key.p12
app/upload-key.jks app/upload-key.jks
*.log *.log
.idea/assetWizardSettings.xml .idea/assetWizardSettings.xml
.idea/uiDesigner.xml

View File

@ -12,7 +12,7 @@ build:
script: script:
- ./gradlew --no-daemon --stacktrace dependencies || true - ./gradlew --no-daemon --stacktrace dependencies || true
- ./gradlew --no-daemon --stacktrace assembleDebug - ./gradlew --no-daemon --stacktrace assembleDebug
- mv app/build/outputs/apk/debug/app-debug.apk . - mv app/build/outputs/apk/fdroid/debug/app-fdroid-debug.apk .
artifacts: artifacts:
name: "${CI_PROJECT_NAME}_${CI_BUILD_REF_NAME}-${CI_BUILD_ID}" name: "${CI_PROJECT_NAME}_${CI_BUILD_REF_NAME}-${CI_BUILD_ID}"
paths: paths:
@ -26,7 +26,7 @@ tests:
- .gradle - .gradle
policy: pull policy: pull
script: script:
- ./gradlew --no-daemon --stacktrace -x fabricGenerateResourcesRelease test - ./gradlew --no-daemon --stacktrace -x fabricGenerateResourcesFdroidRelease -x fabricGenerateResourcesPlayRelease test
artifacts: artifacts:
paths: paths:
- app/build/reports/tests - app/build/reports/tests
@ -39,7 +39,7 @@ lint:
- .gradle - .gradle
policy: pull policy: pull
script: script:
- ./gradlew --no-daemon --stacktrace -x fabricGenerateResourcesRelease lint - ./gradlew --no-daemon --stacktrace -x fabricGenerateResourcesFdroidRelease -x fabricGenerateResourcesPlayRelease lint
artifacts: artifacts:
paths: paths:
- app/build/reports - app/build/reports

View File

@ -21,31 +21,6 @@
<option name="CONTINUATION_INDENT_IN_ELVIS" value="false" /> <option name="CONTINUATION_INDENT_IN_ELVIS" value="false" />
<option name="WRAP_ELVIS_EXPRESSIONS" value="0" /> <option name="WRAP_ELVIS_EXPRESSIONS" value="0" />
</JetCodeStyleSettings> </JetCodeStyleSettings>
<Objective-C-extensions>
<file>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Import" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Macro" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Typedef" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Enum" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Constant" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Global" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Struct" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="FunctionPredecl" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Function" />
</file>
<class>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Property" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Synthesize" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InitMethod" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="StaticMethod" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InstanceMethod" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="DeallocMethod" />
</class>
<extensions>
<pair source="cpp" header="h" fileNamingConvention="NONE" />
<pair source="c" header="h" fileNamingConvention="NONE" />
</extensions>
</Objective-C-extensions>
<XML> <XML>
<option name="XML_KEEP_LINE_BREAKS" value="false" /> <option name="XML_KEEP_LINE_BREAKS" value="false" />
<option name="XML_ALIGN_ATTRIBUTES" value="false" /> <option name="XML_ALIGN_ATTRIBUTES" value="false" />

View File

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
</set>
</option>
</component>
</project>

View File

@ -11,10 +11,10 @@ cache:
- $HOME/.gradle/caches/ - $HOME/.gradle/caches/
- $HOME/.gradle/wrapper/ - $HOME/.gradle/wrapper/
#branches: branches:
# only: only:
# - master - develop
# - 0.7.x - 0.9.2
android: android:
licenses: licenses:
@ -48,20 +48,20 @@ before_script:
script: script:
- ./gradlew dependencies --stacktrace --daemon - ./gradlew dependencies --stacktrace --daemon
- fossa --no-ansi || true - fossa --no-ansi || true
- ./gradlew lint -x fabricGenerateResourcesRelease --stacktrace --daemon #- ./gradlew lintPlayRelease -x fabricGenerateResourcesPlayRelease --stacktrace --daemon
- ./gradlew test -x fabricGenerateResourcesRelease --stacktrace --daemon - ./gradlew testPlayDebugUnitTest -x fabricGenerateResourcesPlay --stacktrace --daemon
- ./gradlew createDebugCoverageReport --stacktrace --daemon - ./gradlew createPlayDebugCoverageReport --stacktrace --daemon
- ./gradlew jacocoTestReport --stacktrace --daemon - ./gradlew jacocoTestReport --stacktrace --daemon
- if [ -z ${SONAR_HOST+x} ]; then echo "sonar scan skipped"; else - if [ -z ${SONAR_HOST+x} ]; then echo "sonar scan skipped"; else
git fetch --unshallow; git fetch --unshallow;
./gradlew sonarqube -x test -x lint -x fabricGenerateResourcesRelease -Dsonar.host.url=$SONAR_HOST -Dsonar.organization=$SONAR_ORG -Dsonar.login=$SONAR_KEY -Dsonar.branch.name=${TRAVIS_PULL_REQUEST_BRANCH:-$TRAVIS_BRANCH} --stacktrace --daemon; ./gradlew sonarqube -x test -x lint -x fabricGenerateResourcesPlayRelease -x fabricGenerateResourcesFdroidRelease -Dsonar.host.url=$SONAR_HOST -Dsonar.organization=$SONAR_ORG -Dsonar.login=$SONAR_KEY -Dsonar.branch.name=${TRAVIS_PULL_REQUEST_BRANCH:-$TRAVIS_BRANCH} --stacktrace --daemon;
fi fi
- | - |
if [ $TRAVIS_TAG ]; then if [ $TRAVIS_TAG ]; then
gpg --yes --batch --passphrase=$SERVICES_ENCRYPT_KEY ./app/src/release/google-services.json.gpg; gpg --yes --batch --passphrase=$SERVICES_ENCRYPT_KEY ./app/src/release/google-services.json.gpg;
gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/key.p12.gpg; gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/key.p12.gpg;
gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/upload-key.jks.gpg; gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/upload-key.jks.gpg;
./gradlew publish -PenableCrashlytics --stacktrace; ./gradlew publishPlayRelease -PenableCrashlytics --stacktrace;
fi fi
after_success: after_success:

View File

@ -1,6 +1,5 @@
# Wulkanowy # Wulkanowy
[![CircleCI](https://img.shields.io/circleci/project/github/wulkanowy/wulkanowy/master.svg?style=flat-square)](https://circleci.com/gh/wulkanowy/wulkanowy)
[![Travis](https://img.shields.io/travis/com/wulkanowy/wulkanowy/master.svg?style=flat-square)](https://travis-ci.com/wulkanowy/wulkanowy) [![Travis](https://img.shields.io/travis/com/wulkanowy/wulkanowy/master.svg?style=flat-square)](https://travis-ci.com/wulkanowy/wulkanowy)
[![Bitrise](https://img.shields.io/bitrise/daeff1893f3c8128/master.svg?token=Hjm1ACamk86JDeVVJHOeqQ&style=flat-square)](https://www.bitrise.io/app/daeff1893f3c8128) [![Bitrise](https://img.shields.io/bitrise/daeff1893f3c8128/master.svg?token=Hjm1ACamk86JDeVVJHOeqQ&style=flat-square)](https://www.bitrise.io/app/daeff1893f3c8128)
[![Codecov](https://img.shields.io/codecov/c/github/wulkanowy/wulkanowy/master.svg?style=flat-square)](https://codecov.io/gh/wulkanowy/wulkanowy) [![Codecov](https://img.shields.io/codecov/c/github/wulkanowy/wulkanowy/master.svg?style=flat-square)](https://codecov.io/gh/wulkanowy/wulkanowy)
@ -8,6 +7,8 @@
[![Sonarcloud](https://sonarcloud.io/api/project_badges/measure?project=io.github.wulkanowy%3Aapp&metric=sqale_rating)](https://sonarcloud.io/dashboard?id=io.github.wulkanowy%3Aapp) [![Sonarcloud](https://sonarcloud.io/api/project_badges/measure?project=io.github.wulkanowy%3Aapp&metric=sqale_rating)](https://sonarcloud.io/dashboard?id=io.github.wulkanowy%3Aapp)
[![FOSSA Status](https://app.fossa.com/api/projects/custom%2B5644%2Fgithub.com%2Fwulkanowy%2Fwulkanowy.svg?type=shield)](https://app.fossa.com/projects/custom%2B5644%2Fgithub.com%2Fwulkanowy%2Fwulkanowy?ref=badge_shield) [![FOSSA Status](https://app.fossa.com/api/projects/custom%2B5644%2Fgithub.com%2Fwulkanowy%2Fwulkanowy.svg?type=shield)](https://app.fossa.com/projects/custom%2B5644%2Fgithub.com%2Fwulkanowy%2Fwulkanowy?ref=badge_shield)
[![Discord](https://img.shields.io/discord/390889354199040011.svg?style=flat-square)](https://discord.gg/vccAQBr) [![Discord](https://img.shields.io/discord/390889354199040011.svg?style=flat-square)](https://discord.gg/vccAQBr)
[![F-Droid](https://img.shields.io/f-droid/v/io.github.wulkanowy.svg)](https://f-droid.org/packages/io.github.wulkanowy/)
[![Last release](https://img.shields.io/github/release/wulkanowy/wulkanowy.svg?logo=github)](https://github.com/wulkanowy/wulkanowy/releases)
[Pobierz wersję beta z Google Play](https://play.google.com/store/apps/details?id=io.github.wulkanowy&amp;utm_source=vcs) [Pobierz wersję beta z Google Play](https://play.google.com/store/apps/details?id=io.github.wulkanowy&amp;utm_source=vcs)

View File

@ -6,6 +6,7 @@ apply plugin: 'io.fabric'
apply plugin: 'com.github.triplet.play' apply plugin: 'com.github.triplet.play'
apply from: 'jacoco.gradle' apply from: 'jacoco.gradle'
apply from: 'sonarqube.gradle' apply from: 'sonarqube.gradle'
apply from: 'hooks.gradle'
android { android {
compileSdkVersion 28 compileSdkVersion 28
@ -16,15 +17,24 @@ android {
testApplicationId "io.github.tests.wulkanowy" testApplicationId "io.github.tests.wulkanowy"
minSdkVersion 15 minSdkVersion 15
targetSdkVersion 28 targetSdkVersion 28
versionCode 27 versionCode 40
versionName "0.7.1" versionName "0.9.2"
multiDexEnabled true multiDexEnabled true
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true
manifestPlaceholders = [ manifestPlaceholders = [
fabric_api_key: System.getenv("FABRIC_API_KEY") ?: "null", fabric_api_key : System.getenv("FABRIC_API_KEY") ?: "null",
crashlytics_enabled: project.hasProperty("enableCrashlytics") crashlytics_enabled: project.hasProperty("enableCrashlytics")
] ]
javaCompileOptions {
annotationProcessorOptions {
arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]
}
}
}
sourceSets {
androidTest.assets.srcDirs += files("$projectDir/schemas".toString())
} }
signingConfigs { signingConfigs {
@ -54,6 +64,18 @@ android {
} }
} }
flavorDimensions "platform"
productFlavors {
play {
dimension "platform"
}
fdroid {
buildConfigField "boolean", "CRASHLYTICS_ENABLED", "false"
dimension "platform"
}
}
lintOptions { lintOptions {
disable 'HardwareIds' disable 'HardwareIds'
} }
@ -71,73 +93,86 @@ androidExtensions {
play { play {
serviceAccountEmail = System.getenv("PLAY_SERVICE_ACCOUNT_EMAIL") ?: "jan@fakelog.cf" serviceAccountEmail = System.getenv("PLAY_SERVICE_ACCOUNT_EMAIL") ?: "jan@fakelog.cf"
serviceAccountCredentials = file('key.p12') serviceAccountCredentials = file('key.p12')
defaultToAppBundles = true defaultToAppBundles = false
track = 'alpha' track = 'alpha'
} }
dependencies { dependencies {
implementation "io.github.wulkanowy:api:0.9.2"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation('io.github.wulkanowy:api:0.7.1') { exclude module: "threetenbp" } implementation "androidx.core:core:1.0.2"
implementation "androidx.legacy:legacy-support-v4:1.0.0"
implementation "androidx.appcompat:appcompat:1.0.2" implementation "androidx.appcompat:appcompat:1.0.2"
implementation "androidx.cardview:cardview:1.0.0" implementation "androidx.fragment:fragment:1.0.0"
implementation "com.google.android.material:material:1.0.0" implementation "androidx.annotation:annotation:1.1.0"
implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation "androidx.multidex:multidex:2.0.1"
implementation 'androidx.multidex:multidex:2.0.1'
implementation 'com.takisoft.preferencex:preferencex:1.0.0' implementation "androidx.recyclerview:recyclerview:1.0.0"
implementation "androidx.viewpager:viewpager:1.0.0"
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"
implementation "androidx.constraintlayout:constraintlayout:1.1.3"
implementation "androidx.coordinatorlayout:coordinatorlayout:1.0.0"
implementation "com.google.android.material:material:1.1.0-alpha07"
implementation "com.github.wulkanowy:MaterialChipsInput:b72fd0ee6f"
implementation "com.github.PhilJay:MPAndroidChart:v3.1.0"
implementation "android.arch.work:work-rxjava2:1.0.0" implementation "androidx.work:work-runtime:2.0.1"
implementation "android.arch.work:work-runtime:1.0.0" implementation "androidx.work:work-rxjava2:2.0.1"
implementation 'com.squareup.inject:assisted-inject-annotations-dagger2:0.3.3' implementation "androidx.room:room-runtime:2.1.0-rc01"
kapt 'com.squareup.inject:assisted-inject-processor-dagger2:0.3.3' implementation "androidx.room:room-rxjava2:2.1.0-rc01"
kapt "androidx.room:room-compiler:2.1.0-rc01"
implementation "com.google.dagger:dagger-android-support:2.21" implementation "com.google.dagger:dagger-android-support:2.23.1"
kapt "com.google.dagger:dagger-compiler:2.21" kapt "com.google.dagger:dagger-compiler:2.23.1"
kapt "com.google.dagger:dagger-android-processor:2.21" kapt "com.google.dagger:dagger-android-processor:2.23.1"
implementation "com.squareup.inject:assisted-inject-annotations-dagger2:0.4.0"
implementation "androidx.room:room-runtime:2.1.0-alpha05" kapt "com.squareup.inject:assisted-inject-processor-dagger2:0.4.0"
implementation "androidx.room:room-rxjava2:2.1.0-alpha05"
kapt "androidx.room:room-compiler:2.1.0-alpha05"
implementation "eu.davidea:flexible-adapter:5.1.0" implementation "eu.davidea:flexible-adapter:5.1.0"
implementation "eu.davidea:flexible-adapter-ui:1.0.0" implementation "eu.davidea:flexible-adapter-ui:1.0.0"
implementation "com.aurelhubert:ahbottomnavigation:2.3.4" implementation "com.aurelhubert:ahbottomnavigation:2.3.4"
implementation 'com.ncapdevi:frag-nav:3.1.0' implementation "com.ncapdevi:frag-nav:3.2.0"
implementation 'com.github.wulkanowy:MaterialChipsInput:b72fd0ee6f' implementation "com.github.pwittchen:reactivenetwork-rx2:3.0.3"
implementation 'com.github.PhilJay:MPAndroidChart:971640b29d' implementation "io.reactivex.rxjava2:rxandroid:2.1.1"
implementation "io.reactivex.rxjava2:rxjava:2.2.9"
implementation 'com.github.pwittchen:reactivenetwork-rx2:3.0.2' implementation "com.google.code.gson:gson:2.8.5"
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' implementation "com.jakewharton.threetenabp:threetenabp:1.2.1"
implementation "io.reactivex.rxjava2:rxjava:2.2.7"
implementation "com.jakewharton.threetenabp:threetenabp:1.2.0"
implementation "com.jakewharton.timber:timber:4.7.1" implementation "com.jakewharton.timber:timber:4.7.1"
implementation "at.favre.lib:slf4j-timber:1.0.1" implementation "at.favre.lib:slf4j-timber:1.0.1"
implementation "com.squareup.okhttp3:logging-interceptor:3.12.3"
implementation "com.mikepenz:aboutlibraries:6.2.3" implementation "com.mikepenz:aboutlibraries:6.2.3"
implementation "com.takisoft.preferencex:preferencex:1.0.0"
implementation 'com.google.firebase:firebase-core:16.0.8' playImplementation "com.google.firebase:firebase-core:16.0.9"
implementation 'com.crashlytics.sdk.android:crashlytics:2.9.9' playImplementation "com.crashlytics.sdk.android:crashlytics:2.10.1"
releaseImplementation 'fr.o80.chucker:library-no-op:2.0.4' releaseImplementation "fr.o80.chucker:library-no-op:2.0.4"
debugImplementation 'fr.o80.chucker:library:2.0.4' debugImplementation "fr.o80.chucker:library:2.0.4"
debugImplementation "com.amitshekhar.android:debug-db:1.0.6" debugImplementation "com.amitshekhar.android:debug-db:1.0.6"
testImplementation "junit:junit:4.12" testImplementation "junit:junit:4.12"
testImplementation "io.mockk:mockk:1.9.1" testImplementation "io.mockk:mockk:1.9.2"
testImplementation "org.mockito:mockito-inline:2.25.0" testImplementation "org.threeten:threetenbp:1.4.0"
testImplementation 'org.threeten:threetenbp:1.3.8' testImplementation "org.mockito:mockito-core:2.28.2"
testImplementation("org.mockito:mockito-inline:2.28.2") {
exclude group: "org.mockito", module: "mockito-core"
}
androidTestImplementation "io.mockk:mockk-android:1.9.1" androidTestImplementation "androidx.test:core:1.2.0"
androidTestImplementation 'androidx.test:core:1.1.0' androidTestImplementation "androidx.test:runner:1.2.0"
androidTestImplementation 'androidx.test:runner:1.1.1' androidTestImplementation "androidx.test.ext:junit:1.1.1"
androidTestImplementation 'androidx.test.ext:junit:1.1.0' androidTestImplementation "io.mockk:mockk-android:1.9.2"
androidTestImplementation 'org.mockito:mockito-android:2.25.0' androidTestImplementation "androidx.room:room-testing:2.1.0-rc01"
androidTestImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version" androidTestImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
androidTestImplementation "org.mockito:mockito-core:2.28.2"
androidTestImplementation("org.mockito:mockito-android:2.28.2") {
exclude group: 'org.mockito', module: 'mockito-core'
}
} }
apply plugin: 'com.google.gms.google-services' apply plugin: 'com.google.gms.google-services'

10
app/hooks.gradle Normal file
View File

@ -0,0 +1,10 @@
apply plugin: "com.star-zero.gradle.githook"
githook {
failOnMissingHooksDir = false
hooks {
"pre-push" {
shell = "./app/play-publish-lint.sh"
}
}
}

View File

@ -31,17 +31,20 @@ task jacocoTestReport(type: JacocoReport) {
'**/*_Provide*Factory*.*', '**/*_Provide*Factory*.*',
'**/*_Factory.*'] '**/*_Factory.*']
classDirectories = fileTree( classDirectories.setFrom(fileTree(
dir: "$buildDir/intermediates/classes/debug", dir: "$buildDir/intermediates/classes/debug",
excludes: excludes excludes: excludes
) + fileTree( ) + fileTree(
dir: "$buildDir/tmp/kotlin-classes/debug", dir: "$buildDir/tmp/kotlin-classes/playDebug",
excludes: excludes excludes: excludes
) ))
sourceDirectories = files("$project.projectDir/src/main/java") sourceDirectories.setFrom(files([
executionData = fileTree( "src/main/java",
"src/play/java"
]))
executionData.setFrom(fileTree(
dir: project.projectDir, dir: project.projectDir,
includes: ["**/*.exec", "**/*.ec"] includes: ["**/*.exec", "**/*.ec"]
) ))
} }

7
app/play-publish-lint.sh Executable file
View File

@ -0,0 +1,7 @@
#!/bin/bash -
content=$(cat < "app/src/main/play/release-notes/pl-PL/default.txt") || exit
if [[ "${#content}" -gt 500 ]]; then
echo >&2 "Release notes content has reached the limit of 500 characters"
exit 1
fi

View File

@ -37,3 +37,6 @@
#Config for API #Config for API
-keep class io.github.wulkanowy.api.** {*;} -keep class io.github.wulkanowy.api.** {*;}
#Config for Material Components
-keep class com.google.android.material.tabs.** {*;}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -29,5 +29,6 @@ sonarqube {
property "sonar.java.coveragePlugin", "jacoco" property "sonar.java.coveragePlugin", "jacoco"
property "sonar.android.lint.report", "build/reports/lint-results.xml" property "sonar.android.lint.report", "build/reports/lint-results.xml"
property "sonar.jacoco.reportPaths", fileTree(dir: project.projectDir, includes: ['**/*.exec', '**/*.ec']) property "sonar.jacoco.reportPaths", fileTree(dir: project.projectDir, includes: ['**/*.exec', '**/*.ec'])
property "sonar.coverage.jacoco.xmlReportPaths", "build/reports/jacocoTestReport/jacocoTestReport.xml"
} }
} }

View File

@ -0,0 +1,36 @@
package io.github.wulkanowy.data.db.migrations
import androidx.room.Room
import androidx.room.testing.MigrationTestHelper
import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory
import androidx.test.core.app.ApplicationProvider
import androidx.test.platform.app.InstrumentationRegistry
import io.github.wulkanowy.data.db.AppDatabase
import org.junit.Rule
abstract class AbstractMigrationTest {
val dbName = "migration-test"
@get:Rule
val helper: MigrationTestHelper = MigrationTestHelper(
InstrumentationRegistry.getInstrumentation(),
AppDatabase::class.java.canonicalName,
FrameworkSQLiteOpenHelperFactory()
)
fun getMigratedRoomDatabase(): AppDatabase {
val database = Room.databaseBuilder(ApplicationProvider.getApplicationContext(),
AppDatabase::class.java, dbName)
.addMigrations(
Migration12(),
Migration13(),
Migration14(),
Migration15()
)
.build()
// close the database and release any stream resources when the test finishes
helper.closeWhenFinished(database)
return database
}
}

View File

@ -0,0 +1,133 @@
package io.github.wulkanowy.data.db.migrations
import android.content.ContentValues
import android.database.sqlite.SQLiteDatabase.CONFLICT_FAIL
import androidx.sqlite.db.SupportSQLiteDatabase
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import kotlin.test.assertEquals
@RunWith(AndroidJUnit4::class)
class Migration12Test : AbstractMigrationTest() {
@Test
fun twoNotRelatedStudents() {
helper.createDatabase(dbName, 11).apply {
// user 1
createStudent(this, 1, true)
createSemester(this, 1, false, 5, 1)
createSemester(this, 1, true, 5, 2)
// user 2
createStudent(this, 2, true)
createSemester(this, 2, false, 6, 1)
createSemester(this, 2, true, 6, 2)
close()
}
helper.runMigrationsAndValidate(dbName, 12, true, Migration12())
val db = getMigratedRoomDatabase()
val students = db.studentDao.loadAll().blockingGet()
assertEquals(2, students.size)
students[0].run {
assertEquals(1, studentId)
assertEquals(5, classId)
}
students[1].run {
assertEquals(2, studentId)
assertEquals(6, classId)
}
}
@Test
fun removeStudentsWithoutClassId() {
helper.createDatabase(dbName, 11).apply {
// user 1
createStudent(this, 1, true)
createSemester(this, 1, false, 0, 2)
createStudent(this, 2, true)
createSemester(this, 2, true, 1, 2)
close()
}
helper.runMigrationsAndValidate(dbName, 12, true, Migration12())
val db = getMigratedRoomDatabase()
val students = db.studentDao.loadAll().blockingGet()
assertEquals(1, students.size)
students[0].run {
assertEquals(2, studentId)
assertEquals(1, classId)
}
}
@Test
fun ensureThereIsOnlyOneCurrentStudent() {
helper.createDatabase(dbName, 11).apply {
// user 1
createStudent(this, 1, true)
createSemester(this, 1, true, 5, 2)
createStudent(this, 2, true)
createSemester(this, 2, true, 6, 2)
createStudent(this, 3, true)
createSemester(this, 3, false, 7, 2)
close()
}
helper.runMigrationsAndValidate(dbName, 12, true, Migration12())
val db = getMigratedRoomDatabase()
val students = db.studentDao.loadAll().blockingGet()
assertEquals(3, students.size)
students[0].run {
assertEquals(studentId, 1)
assertEquals(false, isCurrent)
}
students[1].run {
assertEquals(studentId, 2)
assertEquals(false, isCurrent)
}
students[2].run {
assertEquals(studentId, 3)
assertEquals(true, isCurrent)
}
}
private fun createStudent(db: SupportSQLiteDatabase, studentId: Int, isCurrent: Boolean) {
db.insert("Students", CONFLICT_FAIL, ContentValues().apply {
put("endpoint", "https://fakelog.cf")
put("loginType", "STANDARD")
put("email", "jan@fakelog.cf")
put("password", "******")
put("symbol", "Default")
put("student_id", studentId)
put("student_name", "Jan Kowalski")
put("school_id", "000123")
put("school_name", "")
put("is_current", isCurrent)
put("registration_date", "0")
})
}
private fun createSemester(db: SupportSQLiteDatabase, studentId: Int, isCurrent: Boolean, classId: Int, diaryId: Int) {
db.insert("Semesters", CONFLICT_FAIL, ContentValues().apply {
put("student_id", studentId)
put("diary_id", diaryId)
put("diary_name", "IA")
put("semester_id", diaryId * 5)
put("semester_name", "1")
put("is_current", isCurrent)
put("class_id", classId)
put("unit_id", "99")
})
}
}

View File

@ -0,0 +1,171 @@
package io.github.wulkanowy.data.db.migrations
import android.content.ContentValues
import android.database.sqlite.SQLiteDatabase
import androidx.sqlite.db.SupportSQLiteDatabase
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Test
import org.threeten.bp.LocalDate.of
import kotlin.test.assertTrue
class Migration13Test : AbstractMigrationTest() {
@Test
fun studentsWithSchoolNameWithClassName() {
helper.createDatabase(dbName, 12).apply {
createStudent(this, 1, "Klasa A - Publiczna szkoła Wulkanowego nr 1 w fakelog.cf", 1, 1)
createStudent(this, 2, "Klasa B - Publiczna szkoła Wulkanowego-fejka nr 1 w fakelog.cf", 2, 1)
createStudent(this, 2, "Klasa C - Publiczna szkoła Wulkanowego-fejka nr 2 w fakelog.cf", 1, 2)
close()
}
helper.runMigrationsAndValidate(dbName, 13, true, Migration13())
val db = getMigratedRoomDatabase()
val students = db.studentDao.loadAll().blockingGet()
assertEquals(3, students.size)
students[0].run {
assertEquals(1, studentId)
assertEquals("A", className)
assertEquals("Publiczna szkoła Wulkanowego nr 1 w fakelog.cf", schoolName)
}
students[1].run {
assertEquals(2, studentId)
assertEquals("B", className)
assertEquals("Publiczna szkoła Wulkanowego-fejka nr 1 w fakelog.cf", schoolName)
}
students[2].run {
assertEquals(2, studentId)
assertEquals("C", className)
assertEquals("Publiczna szkoła Wulkanowego-fejka nr 2 w fakelog.cf", schoolName)
}
}
@Test
fun studentsWithSchoolNameWithoutClassName() {
helper.createDatabase(dbName, 12).apply {
createStudent(this, 1, "Publiczna szkoła Wulkanowego nr 1 w fakelog.cf", 1)
createStudent(this, 2, "Publiczna szkoła Wulkanowego-fejka nr 1 w fakelog.cf", 1)
close()
}
helper.runMigrationsAndValidate(dbName, 13, true, Migration13())
val db = getMigratedRoomDatabase()
val students = db.studentDao.loadAll().blockingGet()
assertEquals(2, students.size)
students[0].run {
assertEquals(1, studentId)
assertEquals("", className)
assertEquals("Publiczna szkoła Wulkanowego nr 1 w fakelog.cf", schoolName)
}
students[1].run {
assertEquals(2, studentId)
assertEquals("", className)
assertEquals("Publiczna szkoła Wulkanowego-fejka nr 1 w fakelog.cf", schoolName)
}
}
@Test
fun markAtLeastAndOnlyOneSemesterAtCurrent() {
helper.createDatabase(dbName, 12).apply {
createStudent(this, 1, "", 5)
createSemester(this, 1, 5, 1, 1, false)
createSemester(this, 1, 5, 2, 1, false)
createSemester(this, 1, 5, 3, 2, false)
createSemester(this, 1, 5, 4, 2, false)
createStudent(this, 2, "", 5)
createSemester(this, 2, 5, 5, 5, true)
createSemester(this, 2, 5, 6, 5, true)
createSemester(this, 2, 5, 7, 55, true)
createSemester(this, 2, 5, 8, 55, true)
createStudent(this, 3, "", 5)
createSemester(this, 3, 5, 11, 99, false)
createSemester(this, 3, 5, 12, 99, false)
createSemester(this, 3, 5, 13, 100, false)
createSemester(this, 3, 5, 14, 100, true)
close()
}
helper.runMigrationsAndValidate(dbName, 13, true, Migration13())
val db = getMigratedRoomDatabase()
val semesters1 = db.semesterDao.loadAll(1, 5).blockingGet()
assertTrue { semesters1.single { it.isCurrent }.isCurrent }
semesters1[0].run {
assertFalse(isCurrent)
assertEquals(1, semesterId)
assertEquals(1, diaryId)
}
semesters1[2].run {
assertFalse(isCurrent)
assertEquals(3, semesterId)
assertEquals(2, diaryId)
}
semesters1[3].run {
assertTrue(isCurrent)
assertEquals(4, semesterId)
assertEquals(2, diaryId)
}
db.semesterDao.loadAll(2, 5).blockingGet().let {
assertTrue { it.single { it.isCurrent }.isCurrent }
assertEquals(1970, it[0].schoolYear)
assertEquals(of(1970, 1, 1), it[0].end)
assertEquals(of(1970, 1, 1), it[0].start)
assertFalse(it[0].isCurrent)
assertFalse(it[1].isCurrent)
assertFalse(it[2].isCurrent)
assertTrue(it[3].isCurrent)
}
db.semesterDao.loadAll(2, 5).blockingGet().let {
assertTrue { it.single { it.isCurrent }.isCurrent }
assertFalse(it[0].isCurrent)
assertFalse(it[1].isCurrent)
assertFalse(it[2].isCurrent)
assertTrue(it[3].isCurrent)
}
}
private fun createStudent(db: SupportSQLiteDatabase, studentId: Int, schoolName: String = "", classId: Int = -1, schoolId: Int = 123) {
db.insert("Students", SQLiteDatabase.CONFLICT_FAIL, ContentValues().apply {
put("endpoint", "https://fakelog.cf")
put("loginType", "STANDARD")
put("email", "jan@fakelog.cf")
put("password", "******")
put("symbol", "Default")
put("student_id", studentId)
put("class_id", classId)
put("student_name", "Jan Kowalski")
put("school_id", schoolId)
put("school_name", schoolName)
put("is_current", false)
put("registration_date", "0")
})
}
private fun createSemester(db: SupportSQLiteDatabase, studentId: Int, classId: Int, semesterId: Int, diaryId: Int, isCurrent: Boolean = false) {
db.insert("Semesters", SQLiteDatabase.CONFLICT_FAIL, ContentValues().apply {
put("student_id", studentId)
put("diary_id", diaryId)
put("diary_name", "IA")
put("semester_id", semesterId)
put("semester_name", "1")
put("is_current", isCurrent)
put("class_id", classId)
put("unit_id", "99")
})
}
}

View File

@ -11,6 +11,7 @@ import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.threeten.bp.LocalDate import org.threeten.bp.LocalDate
import org.threeten.bp.LocalDate.now
import kotlin.test.assertEquals import kotlin.test.assertEquals
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
@ -40,7 +41,7 @@ class AttendanceLocalTest {
)) ))
val attendance = attendanceLocal val attendance = attendanceLocal
.getAttendance(Semester(1, 2, "", 1, 3, true, 1, 1), .getAttendance(Semester(1, 2, "", 1, 3, 2019, true, now(), now(), 1, 1),
LocalDate.of(2018, 9, 10), LocalDate.of(2018, 9, 10),
LocalDate.of(2018, 9, 14) LocalDate.of(2018, 9, 14)
) )

View File

@ -41,7 +41,7 @@ class CompletedLessonsLocalTest {
)) ))
val completed = completedLessonsLocal val completed = completedLessonsLocal
.getCompletedLessons(Semester(1, 2, "", 1, 3, true, 1, 1), .getCompletedLessons(Semester(1, 2, "", 1, 3, 2019, true, LocalDate.now(), LocalDate.now(), 1, 1),
LocalDate.of(2018, 9, 10), LocalDate.of(2018, 9, 10),
LocalDate.of(2018, 9, 14) LocalDate.of(2018, 9, 14)
) )

View File

@ -40,7 +40,7 @@ class ExamLocalTest {
)) ))
val exams = examLocal val exams = examLocal
.getExams(Semester(1, 2, "", 1, 3, true, 1, 1), .getExams(Semester(1, 2, "", 1, 3, 2019, true, LocalDate.now(), LocalDate.now(), 1, 1),
LocalDate.of(2018, 9, 10), LocalDate.of(2018, 9, 10),
LocalDate.of(2018, 9, 14) LocalDate.of(2018, 9, 14)
) )

View File

@ -10,6 +10,7 @@ import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.threeten.bp.LocalDate import org.threeten.bp.LocalDate
import org.threeten.bp.LocalDate.now
import kotlin.test.assertEquals import kotlin.test.assertEquals
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
@ -39,8 +40,10 @@ class GradeLocalTest {
createGradeLocal(3, 5.0, LocalDate.of(2019, 2, 28), "", 2) createGradeLocal(3, 5.0, LocalDate.of(2019, 2, 28), "", 2)
)) ))
val semester = Semester(1, 2, "", 2019, 2, 1, true, now(), now(), 1, 1)
val grades = gradeLocal val grades = gradeLocal
.getGrades(Semester(1, 2, "", 2, 3, true, 1, 1)) .getGrades(semester)
.blockingGet() .blockingGet()
assertEquals(2, grades.size) assertEquals(2, grades.size)

View File

@ -22,6 +22,7 @@ import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.threeten.bp.LocalDate.of import org.threeten.bp.LocalDate.of
import org.threeten.bp.LocalDateTime import org.threeten.bp.LocalDateTime
import kotlin.test.assertEquals
import kotlin.test.assertFalse import kotlin.test.assertFalse
import kotlin.test.assertTrue import kotlin.test.assertTrue
import io.github.wulkanowy.api.grades.Grade as GradeApi import io.github.wulkanowy.api.grades.Grade as GradeApi
@ -109,4 +110,73 @@ class GradeRepositoryTest {
assertTrue { grades[2].isRead } assertTrue { grades[2].isRead }
assertTrue { grades[3].isRead } assertTrue { grades[3].isRead }
} }
@Test
fun subtractLocaleDuplicateGrades() {
gradeLocal.saveGrades(listOf(
createGradeLocal(5, 3.0, of(2019, 2, 25), "Taka sama ocena"),
createGradeLocal(5, 3.0, of(2019, 2, 25), "Taka sama ocena"),
createGradeLocal(3, 5.0, of(2019, 2, 26), "Jakaś inna ocena")
))
every { mockApi.getGrades(1) } returns Single.just(listOf(
createGradeApi(5, 3.0, of(2019, 2, 25), "Taka sama ocena"),
createGradeApi(3, 5.0, of(2019, 2, 26), "Jakaś inna ocena")
))
val grades = GradeRepository(settings, gradeLocal, gradeRemote)
.getGrades(studentMock, semesterMock, true).blockingGet()
assertEquals(2, grades.size)
}
@Test
fun subtractRemoteDuplicateGrades() {
gradeLocal.saveGrades(listOf(
createGradeLocal(5, 3.0, of(2019, 2, 25), "Taka sama ocena"),
createGradeLocal(3, 5.0, of(2019, 2, 26), "Jakaś inna ocena")
))
every { mockApi.getGrades(1) } returns Single.just(listOf(
createGradeApi(5, 3.0, of(2019, 2, 25), "Taka sama ocena"),
createGradeApi(5, 3.0, of(2019, 2, 25), "Taka sama ocena"),
createGradeApi(3, 5.0, of(2019, 2, 26), "Jakaś inna ocena")
))
val grades = GradeRepository(settings, gradeLocal, gradeRemote)
.getGrades(studentMock, semesterMock, true).blockingGet()
assertEquals(3, grades.size)
}
@Test
fun emptyLocal() {
gradeLocal.saveGrades(listOf())
every { mockApi.getGrades(1) } returns Single.just(listOf(
createGradeApi(5, 3.0, of(2019, 2, 25), "Taka sama ocena"),
createGradeApi(5, 3.0, of(2019, 2, 25), "Taka sama ocena"),
createGradeApi(3, 5.0, of(2019, 2, 26), "Jakaś inna ocena")
))
val grades = GradeRepository(settings, gradeLocal, gradeRemote)
.getGrades(studentMock, semesterMock, true).blockingGet()
assertEquals(3, grades.size)
}
@Test
fun emptyRemote() {
gradeLocal.saveGrades(listOf(
createGradeLocal(5, 3.0, of(2019, 2, 25), "Taka sama ocena"),
createGradeLocal(3, 5.0, of(2019, 2, 26), "Jakaś inna ocena")
))
every { mockApi.getGrades(1) } returns Single.just(listOf())
val grades = GradeRepository(settings, gradeLocal, gradeRemote)
.getGrades(studentMock, semesterMock, true).blockingGet()
assertEquals(0, grades.size)
}
} }

View File

@ -10,6 +10,7 @@ import org.junit.After
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.threeten.bp.LocalDate
import kotlin.test.assertEquals import kotlin.test.assertEquals
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
@ -39,7 +40,7 @@ class GradeStatisticsLocalTest {
)) ))
val stats = gradeStatisticsLocal.getGradesStatistics( val stats = gradeStatisticsLocal.getGradesStatistics(
Semester(2, 2, "", 1, 2, true, 1, 1), false, Semester(2, 2, "", 2019, 1, 2, true, LocalDate.now(), LocalDate.now(), 1, 1), false,
"Matematyka" "Matematyka"
).blockingGet() ).blockingGet()
assertEquals(1, stats.size) assertEquals(1, stats.size)
@ -55,7 +56,7 @@ class GradeStatisticsLocalTest {
)) ))
val stats = gradeStatisticsLocal.getGradesStatistics( val stats = gradeStatisticsLocal.getGradesStatistics(
Semester(2, 2, "", 1, 2, true, 1, 1), false, Semester(2, 2, "", 2019, 1, 2, true, LocalDate.now(), LocalDate.now(), 1, 1), false,
"Wszystkie" "Wszystkie"
).blockingGet() ).blockingGet()
assertEquals(1, stats.size) assertEquals(1, stats.size)

View File

@ -36,7 +36,7 @@ class LuckyNumberLocalTest {
fun saveAndReadTest() { fun saveAndReadTest() {
luckyNumberLocal.saveLuckyNumber(LuckyNumber(1, LocalDate.of(2019, 1, 20), 14)) luckyNumberLocal.saveLuckyNumber(LuckyNumber(1, LocalDate.of(2019, 1, 20), 14))
val luckyNumber = luckyNumberLocal.getLuckyNumber(Semester(1, 1, "", 1, 3, true, 1, 1), val luckyNumber = luckyNumberLocal.getLuckyNumber(Semester(1, 1, "", 1, 3, 2019, true, LocalDate.now(), LocalDate.now(), 1, 1),
LocalDate.of(2019, 1, 20) LocalDate.of(2019, 1, 20)
).blockingGet() ).blockingGet()

View File

@ -42,7 +42,7 @@ class RecipientLocalTest {
)) ))
val recipients = recipientLocal.getRecipients( val recipients = recipientLocal.getRecipients(
Student("fakelog.cf", "AUTO", "", "", "", 1, "", "", "", true, LocalDateTime.now()), Student("fakelog.cf", "AUTO", "", "", "", 1, "", "", "", "", 1, true, LocalDateTime.now()),
2, 2,
ReportingUnit(1, 4, "", 0, "", emptyList()) ReportingUnit(1, 4, "", 0, "", emptyList())
).blockingGet() ).blockingGet()

View File

@ -39,7 +39,7 @@ class StudentLocalTest {
@Test @Test
fun saveAndReadTest() { fun saveAndReadTest() {
studentLocal.saveStudent(Student(email = "test", password = "test123", schoolSymbol = "23", endpoint = "fakelog.cf", loginType = "AUTO", isCurrent = true, studentName = "", schoolName = "", studentId = 0, symbol = "", registrationDate = now())) studentLocal.saveStudents(listOf(Student(email = "test", password = "test123", schoolSymbol = "23", endpoint = "fakelog.cf", loginType = "AUTO", isCurrent = true, studentName = "", schoolName = "", studentId = 0, classId = 1, symbol = "", registrationDate = now(), className = "")))
.blockingGet() .blockingGet()
val student = studentLocal.getCurrentStudent(true).blockingGet() val student = studentLocal.getCurrentStudent(true).blockingGet()

View File

@ -7,7 +7,7 @@ import org.threeten.bp.LocalDateTime.now
import io.github.wulkanowy.api.timetable.Timetable as TimetableRemote import io.github.wulkanowy.api.timetable.Timetable as TimetableRemote
import io.github.wulkanowy.data.db.entities.Timetable as TimetableLocal import io.github.wulkanowy.data.db.entities.Timetable as TimetableLocal
fun createTimetableLocal(number: Int, start: LocalDateTime, room: String = "", subject: String = ""): TimetableLocal { fun createTimetableLocal(number: Int, start: LocalDateTime, room: String = "", subject: String = "", teacher: String = ""): TimetableLocal {
return TimetableLocal( return TimetableLocal(
studentId = 1, studentId = 1,
diaryId = 2, diaryId = 2,
@ -20,7 +20,7 @@ fun createTimetableLocal(number: Int, start: LocalDateTime, room: String = "", s
group = "", group = "",
room = room, room = room,
roomOld = "", roomOld = "",
teacher = "", teacher = teacher,
teacherOld = "", teacherOld = "",
info = "", info = "",
changes = false, changes = false,
@ -28,7 +28,7 @@ fun createTimetableLocal(number: Int, start: LocalDateTime, room: String = "", s
) )
} }
fun createTimetableRemote(number: Int, start: LocalDateTime, room: String, subject: String = ""): TimetableRemote { fun createTimetableRemote(number: Int, start: LocalDateTime, room: String, subject: String = "", teacher: String = ""): TimetableRemote {
return TimetableRemote( return TimetableRemote(
number = number, number = number,
start = start.toDate(), start = start.toDate(),
@ -37,7 +37,7 @@ fun createTimetableRemote(number: Int, start: LocalDateTime, room: String, subje
subject = subject, subject = subject,
group = "", group = "",
room = room, room = room,
teacher = "", teacher = teacher,
info = "", info = "",
changes = false, changes = false,
canceled = false canceled = false

View File

@ -41,7 +41,7 @@ class TimetableLocalTest {
)) ))
val exams = timetableDb.getTimetable( val exams = timetableDb.getTimetable(
Semester(1, 2, "", 1, 1, true, 1, 1), Semester(1, 2, "", 1, 1, 2019, true, LocalDate.now(), LocalDate.now(), 1, 1),
LocalDate.of(2018, 9, 10), LocalDate.of(2018, 9, 10),
LocalDate.of(2018, 9, 14) LocalDate.of(2018, 9, 14)
).blockingGet() ).blockingGet()

View File

@ -63,23 +63,27 @@ class TimetableRepositoryTest {
fun copyDetailsToCompletedFromPrevious() { fun copyDetailsToCompletedFromPrevious() {
timetableLocal.saveTimetable(listOf( timetableLocal.saveTimetable(listOf(
createTimetableLocal(1, of(2019, 3, 5, 8, 0), "123", "Przyroda"), createTimetableLocal(1, of(2019, 3, 5, 8, 0), "123", "Przyroda"),
createTimetableLocal(1, of(2019, 3, 5, 8, 50), "321", "Religia"), createTimetableLocal(2, of(2019, 3, 5, 8, 50), "321", "Religia"),
createTimetableLocal(1, of(2019, 3, 5, 9, 40), "213", "W-F") createTimetableLocal(3, of(2019, 3, 5, 9, 40), "213", "W-F"),
createTimetableLocal(4, of(2019, 3, 5, 10, 30), "213", "W-F", "Jan Kowalski")
)) ))
every { mockApi.getTimetable(any(), any()) } returns Single.just(listOf( every { mockApi.getTimetable(any(), any()) } returns Single.just(listOf(
createTimetableRemote(1, of(2019, 3, 5, 8, 0), "", "Przyroda"), createTimetableRemote(1, of(2019, 3, 5, 8, 0), "", "Przyroda"),
createTimetableRemote(1, of(2019, 3, 5, 8, 50), "", "Religia"), createTimetableRemote(2, of(2019, 3, 5, 8, 50), "", "Religia"),
createTimetableRemote(1, of(2019, 3, 5, 9, 40), "", "W-F") createTimetableRemote(3, of(2019, 3, 5, 9, 40), "", "W-F"),
createTimetableRemote(4, of(2019, 3, 5, 10, 30), "", "W-F")
)) ))
val lessons = TimetableRepository(settings, timetableLocal, timetableRemote) val lessons = TimetableRepository(settings, timetableLocal, timetableRemote)
.getTimetable(semesterMock, LocalDate.of(2019, 3, 5), LocalDate.of(2019, 3, 5), true) .getTimetable(semesterMock, LocalDate.of(2019, 3, 5), LocalDate.of(2019, 3, 5), true)
.blockingGet() .blockingGet()
assertEquals(3, lessons.size) assertEquals(4, lessons.size)
assertEquals("123", lessons[0].room) assertEquals("123", lessons[0].room)
assertEquals("321", lessons[1].room) assertEquals("321", lessons[1].room)
assertEquals("213", lessons[2].room) assertEquals("213", lessons[2].room)
assertEquals("", lessons[3].teacher)
} }
} }

View File

@ -0,0 +1,17 @@
@file:Suppress("UNUSED_PARAMETER")
package io.github.wulkanowy.utils
import android.content.Context
import timber.log.Timber
fun initCrashlytics(context: Context, appInfo: AppInfo) {
// do nothing
}
class CrashlyticsTree : Timber.Tree() {
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
// do nothing
}
}

View File

@ -0,0 +1,13 @@
package io.github.wulkanowy.utils
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class FirebaseAnalyticsHelper @Inject constructor() {
@Suppress("UNUSED_PARAMETER")
fun logEvent(name: String, vararg params: Pair<String, Any?>) {
// do nothing
}
}

View File

@ -34,6 +34,7 @@
android:name=".ui.modules.login.LoginActivity" android:name=".ui.modules.login.LoginActivity"
android:configChanges="orientation|screenSize" android:configChanges="orientation|screenSize"
android:label="@string/login_title" android:label="@string/login_title"
android:theme="@style/WulkanowyTheme.NoActionBar"
android:windowSoftInputMode="adjustResize" /> android:windowSoftInputMode="adjustResize" />
<activity <activity
android:name=".ui.modules.main.MainActivity" android:name=".ui.modules.main.MainActivity"
@ -45,13 +46,32 @@
android:configChanges="orientation|screenSize" android:configChanges="orientation|screenSize"
android:label="@string/send_message_title" android:label="@string/send_message_title"
android:theme="@style/WulkanowyTheme.NoActionBar" /> android:theme="@style/WulkanowyTheme.NoActionBar" />
<activity
android:name=".ui.modules.timetablewidget.TimetableWidgetConfigureActivity"
android:excludeFromRecents="true"
android:noHistory="true"
android:theme="@style/WulkanowyTheme.WidgetAccountSwitcher">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
</intent-filter>
</activity>
<activity
android:name=".ui.modules.luckynumberwidget.LuckyNumberWidgetConfigureActivity"
android:excludeFromRecents="true"
android:noHistory="true"
android:theme="@style/WulkanowyTheme.WidgetAccountSwitcher">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
</intent-filter>
</activity>
<service <service
android:name=".services.widgets.TimetableWidgetService" android:name=".services.widgets.TimetableWidgetService"
android:permission="android.permission.BIND_REMOTEVIEWS" /> android:permission="android.permission.BIND_REMOTEVIEWS" />
<receiver <receiver
android:name=".ui.widgets.timetable.TimetableWidgetProvider" android:name=".ui.modules.timetablewidget.TimetableWidgetProvider"
android:exported="true"
android:label="@string/timetable_title"> android:label="@string/timetable_title">
<intent-filter> <intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
@ -60,6 +80,16 @@
android:name="android.appwidget.provider" android:name="android.appwidget.provider"
android:resource="@xml/provider_widget_timetable" /> android:resource="@xml/provider_widget_timetable" />
</receiver> </receiver>
<receiver
android:name=".ui.modules.luckynumberwidget.LuckyNumberWidgetProvider"
android:label="@string/lucky_number_title">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/provider_widget_lucky_number" />
</receiver>
<provider <provider
android:name="androidx.work.impl.WorkManagerInitializer" android:name="androidx.work.impl.WorkManagerInitializer"

View File

@ -1,34 +1,36 @@
package io.github.wulkanowy package io.github.wulkanowy
import android.content.Context import android.content.Context
import androidx.appcompat.app.AppCompatDelegate import android.util.Log.INFO
import android.util.Log.VERBOSE
import androidx.multidex.MultiDex import androidx.multidex.MultiDex
import androidx.work.Configuration import androidx.work.Configuration
import androidx.work.WorkManager import androidx.work.WorkManager
import com.crashlytics.android.Crashlytics
import com.crashlytics.android.core.CrashlyticsCore
import com.jakewharton.threetenabp.AndroidThreeTen import com.jakewharton.threetenabp.AndroidThreeTen
import dagger.android.AndroidInjector import dagger.android.AndroidInjector
import dagger.android.support.DaggerApplication import dagger.android.support.DaggerApplication
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.utils.Log import eu.davidea.flexibleadapter.utils.Log
import io.fabric.sdk.android.Fabric
import io.github.wulkanowy.BuildConfig.DEBUG
import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository
import io.github.wulkanowy.di.DaggerAppComponent import io.github.wulkanowy.di.DaggerAppComponent
import io.github.wulkanowy.services.sync.SyncWorkerFactory import io.github.wulkanowy.services.sync.SyncWorkerFactory
import io.github.wulkanowy.utils.ActivityLifecycleLogger
import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.CrashlyticsTree import io.github.wulkanowy.utils.CrashlyticsTree
import io.github.wulkanowy.utils.DebugLogTree import io.github.wulkanowy.utils.DebugLogTree
import io.github.wulkanowy.utils.initCrashlytics
import io.reactivex.exceptions.UndeliverableException
import io.reactivex.plugins.RxJavaPlugins
import timber.log.Timber import timber.log.Timber
import java.io.IOException
import javax.inject.Inject import javax.inject.Inject
class WulkanowyApp : DaggerApplication() { class WulkanowyApp : DaggerApplication() {
@Inject @Inject
lateinit var prefRepository: PreferencesRepository lateinit var workerFactory: SyncWorkerFactory
@Inject @Inject
lateinit var workerFactory: SyncWorkerFactory lateinit var appInfo: AppInfo
override fun attachBaseContext(base: Context?) { override fun attachBaseContext(base: Context?) {
super.attachBaseContext(base) super.attachBaseContext(base)
@ -38,25 +40,40 @@ class WulkanowyApp : DaggerApplication() {
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
AndroidThreeTen.init(this) AndroidThreeTen.init(this)
initializeFabric() RxJavaPlugins.setErrorHandler(::onError)
if (DEBUG) enableDebugLog()
AppCompatDelegate.setDefaultNightMode(prefRepository.currentTheme) initWorkManager()
WorkManager.initialize(this, Configuration.Builder().setWorkerFactory(workerFactory).build()) initLogging()
initCrashlytics(this, appInfo)
} }
private fun enableDebugLog() { private fun initWorkManager() {
Timber.plant(DebugLogTree()) WorkManager.initialize(this,
FlexibleAdapter.enableLogs(Log.Level.DEBUG) Configuration.Builder()
.setWorkerFactory(workerFactory)
.setMinimumLoggingLevel(if (appInfo.isDebug) VERBOSE else INFO)
.build())
} }
private fun initializeFabric() { private fun initLogging() {
Fabric.with(Fabric.Builder(this).kits( if (appInfo.isDebug) {
Crashlytics.Builder().core(CrashlyticsCore.Builder().disabled(!BuildConfig.CRASHLYTICS_ENABLED).build()).build() Timber.plant(DebugLogTree())
).debuggable(BuildConfig.DEBUG).build()) FlexibleAdapter.enableLogs(Log.Level.DEBUG)
Timber.plant(CrashlyticsTree()) } else {
Timber.plant(CrashlyticsTree())
}
registerActivityLifecycleCallbacks(ActivityLifecycleLogger())
}
private fun onError(error: Throwable) {
//RxJava's too deep stack traces may cause SOE on older android devices
val cause = error.cause
if (error is UndeliverableException && cause is IOException || cause is InterruptedException || cause is StackOverflowError) {
Timber.e(cause, "An undeliverable error occurred")
} else throw error
} }
override fun applicationInjector(): AndroidInjector<out DaggerApplication> { override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
return DaggerAppComponent.builder().create(this) return DaggerAppComponent.factory().create(this)
} }
} }

View File

@ -14,6 +14,7 @@ class ApiHelper @Inject constructor(private val api: Api) {
symbol = student.symbol symbol = student.symbol
schoolSymbol = student.schoolSymbol schoolSymbol = student.schoolSymbol
studentId = student.studentId studentId = student.studentId
classId = student.classId
host = URL(student.endpoint).run { host + ":$port".removeSuffix(":-1") } host = URL(student.endpoint).run { host + ":$port".removeSuffix(":-1") }
ssl = student.endpoint.startsWith("https") ssl = student.endpoint.startsWith("https")
loginType = Api.LoginType.valueOf(student.loginType) loginType = Api.LoginType.valueOf(student.loginType)
@ -28,6 +29,7 @@ class ApiHelper @Inject constructor(private val api: Api) {
this.symbol = symbol this.symbol = symbol
host = URL(endpoint).run { host + ":$port".removeSuffix(":-1") } host = URL(endpoint).run { host + ":$port".removeSuffix(":-1") }
ssl = endpoint.startsWith("https") ssl = endpoint.startsWith("https")
useNewStudent = true
} }
} }
} }

View File

@ -132,4 +132,8 @@ internal class RepositoryModule {
@Singleton @Singleton
@Provides @Provides
fun provideRecipientDao(database: AppDatabase) = database.recipientDao fun provideRecipientDao(database: AppDatabase) = database.recipientDao
@Singleton
@Provides
fun provideMobileDevicesDao(database: AppDatabase) = database.mobileDeviceDao
} }

View File

@ -16,6 +16,7 @@ import io.github.wulkanowy.data.db.dao.GradeSummaryDao
import io.github.wulkanowy.data.db.dao.HomeworkDao import io.github.wulkanowy.data.db.dao.HomeworkDao
import io.github.wulkanowy.data.db.dao.LuckyNumberDao import io.github.wulkanowy.data.db.dao.LuckyNumberDao
import io.github.wulkanowy.data.db.dao.MessagesDao import io.github.wulkanowy.data.db.dao.MessagesDao
import io.github.wulkanowy.data.db.dao.MobileDeviceDao
import io.github.wulkanowy.data.db.dao.NoteDao import io.github.wulkanowy.data.db.dao.NoteDao
import io.github.wulkanowy.data.db.dao.RecipientDao import io.github.wulkanowy.data.db.dao.RecipientDao
import io.github.wulkanowy.data.db.dao.ReportingUnitDao import io.github.wulkanowy.data.db.dao.ReportingUnitDao
@ -33,6 +34,7 @@ import io.github.wulkanowy.data.db.entities.GradeSummary
import io.github.wulkanowy.data.db.entities.Homework import io.github.wulkanowy.data.db.entities.Homework
import io.github.wulkanowy.data.db.entities.LuckyNumber import io.github.wulkanowy.data.db.entities.LuckyNumber
import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.Message
import io.github.wulkanowy.data.db.entities.MobileDevice
import io.github.wulkanowy.data.db.entities.Note import io.github.wulkanowy.data.db.entities.Note
import io.github.wulkanowy.data.db.entities.Recipient import io.github.wulkanowy.data.db.entities.Recipient
import io.github.wulkanowy.data.db.entities.ReportingUnit import io.github.wulkanowy.data.db.entities.ReportingUnit
@ -42,6 +44,10 @@ import io.github.wulkanowy.data.db.entities.Subject
import io.github.wulkanowy.data.db.entities.Timetable import io.github.wulkanowy.data.db.entities.Timetable
import io.github.wulkanowy.data.db.migrations.Migration10 import io.github.wulkanowy.data.db.migrations.Migration10
import io.github.wulkanowy.data.db.migrations.Migration11 import io.github.wulkanowy.data.db.migrations.Migration11
import io.github.wulkanowy.data.db.migrations.Migration12
import io.github.wulkanowy.data.db.migrations.Migration13
import io.github.wulkanowy.data.db.migrations.Migration14
import io.github.wulkanowy.data.db.migrations.Migration15
import io.github.wulkanowy.data.db.migrations.Migration2 import io.github.wulkanowy.data.db.migrations.Migration2
import io.github.wulkanowy.data.db.migrations.Migration3 import io.github.wulkanowy.data.db.migrations.Migration3
import io.github.wulkanowy.data.db.migrations.Migration4 import io.github.wulkanowy.data.db.migrations.Migration4
@ -71,16 +77,17 @@ import javax.inject.Singleton
LuckyNumber::class, LuckyNumber::class,
CompletedLesson::class, CompletedLesson::class,
ReportingUnit::class, ReportingUnit::class,
Recipient::class Recipient::class,
MobileDevice::class
], ],
version = AppDatabase.VERSION_SCHEMA, version = AppDatabase.VERSION_SCHEMA,
exportSchema = false exportSchema = true
) )
@TypeConverters(Converters::class) @TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() { abstract class AppDatabase : RoomDatabase() {
companion object { companion object {
const val VERSION_SCHEMA = 11 const val VERSION_SCHEMA = 15
fun newInstance(context: Context): AppDatabase { fun newInstance(context: Context): AppDatabase {
return Room.databaseBuilder(context, AppDatabase::class.java, "wulkanowy_database") return Room.databaseBuilder(context, AppDatabase::class.java, "wulkanowy_database")
@ -97,7 +104,11 @@ abstract class AppDatabase : RoomDatabase() {
Migration8(), Migration8(),
Migration9(), Migration9(),
Migration10(), Migration10(),
Migration11() Migration11(),
Migration12(),
Migration13(),
Migration14(),
Migration15()
) )
.build() .build()
} }
@ -136,4 +147,6 @@ abstract class AppDatabase : RoomDatabase() {
abstract val reportingUnitDao: ReportingUnitDao abstract val reportingUnitDao: ReportingUnitDao
abstract val recipientDao: RecipientDao abstract val recipientDao: RecipientDao
abstract val mobileDeviceDao: MobileDeviceDao
} }

View File

@ -6,18 +6,16 @@ import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@Singleton @Singleton
@SuppressLint("ApplySharedPref")
class SharedPrefHelper @Inject constructor(private val sharedPref: SharedPreferences) { class SharedPrefHelper @Inject constructor(private val sharedPref: SharedPreferences) {
@SuppressLint("ApplySharedPref")
fun putLong(key: String, value: Long, sync: Boolean = false) { fun putLong(key: String, value: Long, sync: Boolean = false) {
sharedPref.edit().putLong(key, value).apply { sharedPref.edit().putLong(key, value).apply {
if (sync) commit() else apply() if (sync) commit() else apply()
} }
} }
fun getLong(key: String, defaultValue: Long): Long { fun getLong(key: String, defaultValue: Long) = sharedPref.getLong(key, defaultValue)
return sharedPref.getLong(key, defaultValue)
}
fun delete(key: String) { fun delete(key: String) {
sharedPref.edit().remove(key).apply() sharedPref.edit().remove(key).apply()

View File

@ -23,8 +23,8 @@ interface MessagesDao {
@Query("SELECT * FROM Messages WHERE student_id = :studentId AND folder_id = :folder AND removed = 0 ORDER BY date DESC") @Query("SELECT * FROM Messages WHERE student_id = :studentId AND folder_id = :folder AND removed = 0 ORDER BY date DESC")
fun loadAll(studentId: Int, folder: Int): Maybe<List<Message>> fun loadAll(studentId: Int, folder: Int): Maybe<List<Message>>
@Query("SELECT * FROM Messages WHERE student_id = :studentId AND real_id = :id") @Query("SELECT * FROM Messages WHERE id = :id")
fun load(studentId: Int, id: Int): Maybe<Message> fun load(id: Long): Maybe<Message>
@Query("SELECT * FROM Messages WHERE student_id = :studentId AND removed = 1 ORDER BY date DESC") @Query("SELECT * FROM Messages WHERE student_id = :studentId AND removed = 1 ORDER BY date DESC")
fun loadDeleted(studentId: Int): Maybe<List<Message>> fun loadDeleted(studentId: Int): Maybe<List<Message>>

View File

@ -0,0 +1,21 @@
package io.github.wulkanowy.data.db.dao
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import io.github.wulkanowy.data.db.entities.MobileDevice
import io.reactivex.Maybe
@Dao
interface MobileDeviceDao {
@Insert
fun insertAll(devices: List<MobileDevice>)
@Delete
fun deleteAll(devices: List<MobileDevice>)
@Query("SELECT * FROM MobileDevices WHERE student_id = :studentId ORDER BY date DESC")
fun loadAll(studentId: Int): Maybe<List<MobileDevice>>
}

View File

@ -1,8 +1,8 @@
package io.github.wulkanowy.data.db.dao package io.github.wulkanowy.data.db.dao
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert import androidx.room.Insert
import androidx.room.OnConflictStrategy.IGNORE
import androidx.room.Query import androidx.room.Query
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.reactivex.Maybe import io.reactivex.Maybe
@ -12,15 +12,12 @@ import javax.inject.Singleton
@Dao @Dao
interface SemesterDao { interface SemesterDao {
@Insert(onConflict = IGNORE) @Insert
fun insertAll(semester: List<Semester>) fun insertAll(semester: List<Semester>)
@Query("SELECT * FROM Semesters WHERE student_id = :studentId") @Delete
fun loadAll(studentId: Int): Maybe<List<Semester>> fun deleteAll(semester: List<Semester>)
@Query("UPDATE Semesters SET is_current = 1 WHERE semester_id = :semesterId AND diary_id = :diaryId") @Query("SELECT * FROM Semesters WHERE student_id = :studentId AND class_id = :classId")
fun updateCurrent(semesterId: Int, diaryId: Int) fun loadAll(studentId: Int, classId: Int): Maybe<List<Semester>>
@Query("UPDATE Semesters SET is_current = 0 WHERE student_id = :studentId")
fun resetCurrent(studentId: Int)
} }

View File

@ -14,7 +14,7 @@ import javax.inject.Singleton
interface StudentDao { interface StudentDao {
@Insert(onConflict = ABORT) @Insert(onConflict = ABORT)
fun insert(student: Student): Long fun insertAll(student: List<Student>): List<Long>
@Delete @Delete
fun delete(student: Student) fun delete(student: Student)
@ -25,8 +25,8 @@ interface StudentDao {
@Query("SELECT * FROM Students") @Query("SELECT * FROM Students")
fun loadAll(): Maybe<List<Student>> fun loadAll(): Maybe<List<Student>>
@Query("UPDATE Students SET is_current = 1 WHERE student_id = :studentId") @Query("UPDATE Students SET is_current = 1 WHERE id = :id")
fun updateCurrent(studentId: Int) fun updateCurrent(id: Long)
@Query("UPDATE Students SET is_current = 0") @Query("UPDATE Students SET is_current = 0")
fun resetCurrent() fun resetCurrent()

View File

@ -13,11 +13,26 @@ data class GradeSummary(
@ColumnInfo(name = "student_id") @ColumnInfo(name = "student_id")
val studentId: Int, val studentId: Int,
val position: Int,
val subject: String, val subject: String,
@ColumnInfo(name = "predicted_grade")
val predictedGrade: String, val predictedGrade: String,
val finalGrade: String @ColumnInfo(name = "final_grade")
val finalGrade: String,
@ColumnInfo(name = "proposed_points")
val proposedPoints: String,
@ColumnInfo(name = "final_points")
val finalPoints: String,
@ColumnInfo(name = "points_sum")
val pointsSum: String,
val average: Double
) { ) {
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
var id: Long = 0 var id: Long = 0

View File

@ -0,0 +1,25 @@
package io.github.wulkanowy.data.db.entities
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import org.threeten.bp.LocalDateTime
import java.io.Serializable
@Entity(tableName = "MobileDevices")
data class MobileDevice(
@ColumnInfo(name = "student_id")
val studentId: Int,
@ColumnInfo(name = "device_id")
val deviceId: Int,
val name: String,
val date: LocalDateTime
) : Serializable {
@PrimaryKey(autoGenerate = true)
var id: Long = 0
}

View File

@ -4,6 +4,7 @@ import androidx.room.ColumnInfo
import androidx.room.Entity import androidx.room.Entity
import androidx.room.Index import androidx.room.Index
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import org.threeten.bp.LocalDate
@Entity(tableName = "Semesters", indices = [Index(value = ["student_id", "diary_id", "semester_id"], unique = true)]) @Entity(tableName = "Semesters", indices = [Index(value = ["student_id", "diary_id", "semester_id"], unique = true)])
data class Semester( data class Semester(
@ -17,6 +18,9 @@ data class Semester(
@ColumnInfo(name = "diary_name") @ColumnInfo(name = "diary_name")
val diaryName: String, val diaryName: String,
@ColumnInfo(name = "school_year")
val schoolYear: Int,
@ColumnInfo(name = "semester_id") @ColumnInfo(name = "semester_id")
val semesterId: Int, val semesterId: Int,
@ -26,6 +30,10 @@ data class Semester(
@ColumnInfo(name = "is_current") @ColumnInfo(name = "is_current")
val isCurrent: Boolean, val isCurrent: Boolean,
val start: LocalDate,
val end: LocalDate,
@ColumnInfo(name = "class_id") @ColumnInfo(name = "class_id")
val classId: Int, val classId: Int,

View File

@ -7,7 +7,7 @@ import androidx.room.PrimaryKey
import org.threeten.bp.LocalDateTime import org.threeten.bp.LocalDateTime
import java.io.Serializable import java.io.Serializable
@Entity(tableName = "Students", indices = [Index(value = ["email", "symbol", "student_id", "school_id"], unique = true)]) @Entity(tableName = "Students", indices = [Index(value = ["email", "symbol", "student_id", "school_id", "class_id"], unique = true)])
data class Student( data class Student(
val endpoint: String, val endpoint: String,
@ -32,6 +32,12 @@ data class Student(
@ColumnInfo(name = "school_name") @ColumnInfo(name = "school_name")
val schoolName: String, val schoolName: String,
@ColumnInfo(name = "class_name")
val className: String,
@ColumnInfo(name = "class_id")
val classId: Int,
@ColumnInfo(name = "is_current") @ColumnInfo(name = "is_current")
val isCurrent: Boolean, val isCurrent: Boolean,

View File

@ -8,7 +8,7 @@ class Migration11 : Migration(10, 11) {
override fun migrate(database: SupportSQLiteDatabase) { override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL(""" database.execSQL("""
CREATE TABLE IF NOT EXISTS Grades_temp ( CREATE TABLE IF NOT EXISTS Grades_temp (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id INTEGER PRIMARY KEY NOT NULL,
is_read INTEGER NOT NULL, is_read INTEGER NOT NULL,
is_notified INTEGER NOT NULL, is_notified INTEGER NOT NULL,
semester_id INTEGER NOT NULL, semester_id INTEGER NOT NULL,

View File

@ -0,0 +1,69 @@
package io.github.wulkanowy.data.db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
class Migration12 : Migration(11, 12) {
override fun migrate(database: SupportSQLiteDatabase) {
createTempStudentsTable(database)
replaceStudentTable(database)
updateStudentsWithClassId(database, getStudentsIds(database))
removeStudentsWithNoClassId(database)
ensureThereIsOnlyOneCurrentStudent(database)
}
private fun createTempStudentsTable(database: SupportSQLiteDatabase) {
database.execSQL("""
CREATE TABLE IF NOT EXISTS Students_tmp (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
endpoint TEXT NOT NULL,
loginType TEXT NOT NULL,
email TEXT NOT NULL,
password TEXT NOT NULL,
symbol TEXT NOT NULL,
student_id INTEGER NOT NULL,
student_name TEXT NOT NULL,
school_id TEXT NOT NULL,
school_name TEXT NOT NULL,
is_current INTEGER NOT NULL,
registration_date INTEGER NOT NULL,
class_id INTEGER NOT NULL
)
""")
database.execSQL("CREATE UNIQUE INDEX index_Students_email_symbol_student_id_school_id_class_id ON Students_tmp (email, symbol, student_id, school_id, class_id)")
}
private fun replaceStudentTable(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE Students ADD COLUMN class_id INTEGER DEFAULT 0 NOT NULL")
database.execSQL("INSERT INTO Students_tmp SELECT * FROM Students")
database.execSQL("DROP TABLE Students")
database.execSQL("ALTER TABLE Students_tmp RENAME TO Students")
}
private fun getStudentsIds(database: SupportSQLiteDatabase): List<Int> {
val students = mutableListOf<Int>()
val studentsCursor = database.query("SELECT student_id FROM Students")
if (studentsCursor.moveToFirst()) {
do {
students.add(studentsCursor.getInt(0))
} while (studentsCursor.moveToNext())
}
return students
}
private fun updateStudentsWithClassId(database: SupportSQLiteDatabase, students: List<Int>) {
students.forEach {
database.execSQL("UPDATE Students SET class_id = IFNULL((SELECT class_id FROM Semesters WHERE student_id = $it), 0) WHERE student_id = $it")
}
}
private fun removeStudentsWithNoClassId(database: SupportSQLiteDatabase) {
database.execSQL("DELETE FROM Students WHERE class_id = 0")
}
private fun ensureThereIsOnlyOneCurrentStudent(database: SupportSQLiteDatabase) {
database.execSQL("UPDATE Students SET is_current = 0")
database.execSQL("UPDATE Students SET is_current = 1 WHERE id = (SELECT MAX(id) FROM Students)")
}
}

View File

@ -0,0 +1,64 @@
package io.github.wulkanowy.data.db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
class Migration13 : Migration(12, 13) {
override fun migrate(database: SupportSQLiteDatabase) {
addClassNameToStudents(database, getStudentsIds(database))
updateSemestersTable(database)
markAtLeastAndOnlyOneSemesterAtCurrent(database, getStudentsAndClassIds(database))
clearMessagesTable(database)
}
private fun addClassNameToStudents(database: SupportSQLiteDatabase, students: List<Pair<Int, String>>) {
database.execSQL("ALTER TABLE Students ADD COLUMN class_name TEXT DEFAULT \"\" NOT NULL")
students.forEach { (id, name) ->
val schoolName = name.substringAfter(" - ")
val className = name.substringBefore(" - ", "").replace("Klasa ", "")
database.execSQL("UPDATE Students SET class_name = '$className' WHERE id = '$id'")
database.execSQL("UPDATE Students SET school_name = '$schoolName' WHERE id = '$id'")
}
}
private fun getStudentsIds(database: SupportSQLiteDatabase): MutableList<Pair<Int, String>> {
val students = mutableListOf<Pair<Int, String>>()
val studentsCursor = database.query("SELECT id, school_name FROM Students")
if (studentsCursor.moveToFirst()) {
do {
students.add(studentsCursor.getInt(0) to studentsCursor.getString(1))
} while (studentsCursor.moveToNext())
}
return students
}
private fun updateSemestersTable(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE Semesters ADD COLUMN school_year INTEGER DEFAULT 1970 NOT NULL")
database.execSQL("ALTER TABLE Semesters ADD COLUMN start INTEGER DEFAULT 0 NOT NULL")
database.execSQL("ALTER TABLE Semesters ADD COLUMN `end` INTEGER DEFAULT 0 NOT NULL")
}
private fun getStudentsAndClassIds(database: SupportSQLiteDatabase): List<Pair<Int, Int>> {
val students = mutableListOf<Pair<Int, Int>>()
val studentsCursor = database.query("SELECT student_id, class_id FROM Students")
if (studentsCursor.moveToFirst()) {
do {
students.add(studentsCursor.getInt(0) to studentsCursor.getInt(1))
} while (studentsCursor.moveToNext())
}
return students
}
private fun markAtLeastAndOnlyOneSemesterAtCurrent(database: SupportSQLiteDatabase, students: List<Pair<Int, Int>>) {
students.forEach { (studentId, classId) ->
database.execSQL("UPDATE Semesters SET is_current = 0 WHERE student_id = '$studentId' AND class_id = '$classId'")
database.execSQL("UPDATE Semesters SET is_current = 1 WHERE id = (SELECT id FROM Semesters WHERE student_id = '$studentId' AND class_id = '$classId' ORDER BY semester_id DESC)")
}
}
private fun clearMessagesTable(database: SupportSQLiteDatabase) {
database.execSQL("DELETE FROM Messages")
}
}

View File

@ -0,0 +1,26 @@
package io.github.wulkanowy.data.db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
class Migration14 : Migration(13, 14) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("DROP TABLE IF EXISTS GradesSummary")
database.execSQL("""
CREATE TABLE IF NOT EXISTS GradesSummary (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
semester_id INTEGER NOT NULL,
student_id INTEGER NOT NULL,
position INTEGER NOT NULL,
subject TEXT NOT NULL,
predicted_grade TEXT NOT NULL,
final_grade TEXT NOT NULL,
proposed_points TEXT NOT NULL,
final_points TEXT NOT NULL,
points_sum TEXT NOT NULL,
average REAL NOT NULL
)
""")
}
}

View File

@ -0,0 +1,19 @@
package io.github.wulkanowy.data.db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
class Migration15 : Migration(14, 15) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("""
CREATE TABLE IF NOT EXISTS MobileDevices (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
student_id INTEGER NOT NULL,
device_id INTEGER NOT NULL,
name TEXT NOT NULL,
date INTEGER NOT NULL
)
""")
}
}

View File

@ -0,0 +1,3 @@
package io.github.wulkanowy.data.exceptions
class NoCurrentStudentException : Exception("There no set current student in database")

View File

@ -0,0 +1,12 @@
package io.github.wulkanowy.data.pojos
data class MobileDeviceToken(
val token: String,
val symbol: String,
val pin: String,
val qr: String
)

View File

@ -6,6 +6,7 @@ import io.github.wulkanowy.data.db.entities.Attendance
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.utils.friday import io.github.wulkanowy.utils.friday
import io.github.wulkanowy.utils.monday import io.github.wulkanowy.utils.monday
import io.github.wulkanowy.utils.uniqueSubtract
import io.reactivex.Single import io.reactivex.Single
import org.threeten.bp.LocalDate import org.threeten.bp.LocalDate
import java.net.UnknownHostException import java.net.UnknownHostException
@ -31,8 +32,8 @@ class AttendanceRepository @Inject constructor(
local.getAttendance(semester, dates.first, dates.second) local.getAttendance(semester, dates.first, dates.second)
.toSingle(emptyList()) .toSingle(emptyList())
.doOnSuccess { oldAttendance -> .doOnSuccess { oldAttendance ->
local.deleteAttendance(oldAttendance - newAttendance) local.deleteAttendance(oldAttendance.uniqueSubtract(newAttendance))
local.saveAttendance(newAttendance - oldAttendance) local.saveAttendance(newAttendance.uniqueSubtract(oldAttendance))
} }
}.flatMap { }.flatMap {
local.getAttendance(semester, dates.first, dates.second) local.getAttendance(semester, dates.first, dates.second)

View File

@ -4,6 +4,7 @@ import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork
import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings
import io.github.wulkanowy.data.db.entities.AttendanceSummary import io.github.wulkanowy.data.db.entities.AttendanceSummary
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.utils.uniqueSubtract
import io.reactivex.Single import io.reactivex.Single
import java.net.UnknownHostException import java.net.UnknownHostException
import javax.inject.Inject import javax.inject.Inject
@ -25,8 +26,8 @@ class AttendanceSummaryRepository @Inject constructor(
}.flatMap { new -> }.flatMap { new ->
local.getAttendanceSummary(semester, subjectId).toSingle(emptyList()) local.getAttendanceSummary(semester, subjectId).toSingle(emptyList())
.doOnSuccess { old -> .doOnSuccess { old ->
local.deleteAttendanceSummary(old - new) local.deleteAttendanceSummary(old.uniqueSubtract(new))
local.saveAttendanceSummary(new - old) local.saveAttendanceSummary(new.uniqueSubtract(old))
} }
}.flatMap { local.getAttendanceSummary(semester, subjectId).toSingle(emptyList()) }) }.flatMap { local.getAttendanceSummary(semester, subjectId).toSingle(emptyList()) })
} }

View File

@ -6,6 +6,7 @@ import io.github.wulkanowy.data.db.entities.CompletedLesson
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.utils.friday import io.github.wulkanowy.utils.friday
import io.github.wulkanowy.utils.monday import io.github.wulkanowy.utils.monday
import io.github.wulkanowy.utils.uniqueSubtract
import io.reactivex.Single import io.reactivex.Single
import org.threeten.bp.LocalDate import org.threeten.bp.LocalDate
import java.net.UnknownHostException import java.net.UnknownHostException
@ -31,8 +32,8 @@ class CompletedLessonsRepository @Inject constructor(
local.getCompletedLessons(semester, dates.first, dates.second) local.getCompletedLessons(semester, dates.first, dates.second)
.toSingle(emptyList()) .toSingle(emptyList())
.doOnSuccess { old -> .doOnSuccess { old ->
local.deleteCompleteLessons(old - new) local.deleteCompleteLessons(old.uniqueSubtract(new))
local.saveCompletedLessons(new - old) local.saveCompletedLessons(new.uniqueSubtract(old))
} }
}.flatMap { }.flatMap {
local.getCompletedLessons(semester, dates.first, dates.second) local.getCompletedLessons(semester, dates.first, dates.second)

View File

@ -6,6 +6,7 @@ import io.github.wulkanowy.data.db.entities.Exam
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.utils.friday import io.github.wulkanowy.utils.friday
import io.github.wulkanowy.utils.monday import io.github.wulkanowy.utils.monday
import io.github.wulkanowy.utils.uniqueSubtract
import io.reactivex.Single import io.reactivex.Single
import org.threeten.bp.LocalDate import org.threeten.bp.LocalDate
import java.net.UnknownHostException import java.net.UnknownHostException
@ -27,12 +28,12 @@ class ExamRepository @Inject constructor(
.flatMap { .flatMap {
if (it) remote.getExams(semester, dates.first, dates.second) if (it) remote.getExams(semester, dates.first, dates.second)
else Single.error(UnknownHostException()) else Single.error(UnknownHostException())
}.flatMap { newExams -> }.flatMap { new ->
local.getExams(semester, dates.first, dates.second) local.getExams(semester, dates.first, dates.second)
.toSingle(emptyList()) .toSingle(emptyList())
.doOnSuccess { oldExams -> .doOnSuccess { old ->
local.deleteExams(oldExams - newExams) local.deleteExams(old.uniqueSubtract(new))
local.saveExams(newExams - oldExams) local.saveExams(new.uniqueSubtract(old))
} }
}.flatMap { }.flatMap {
local.getExams(semester, dates.first, dates.second) local.getExams(semester, dates.first, dates.second)

View File

@ -5,6 +5,7 @@ import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.Inter
import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.utils.uniqueSubtract
import io.reactivex.Completable import io.reactivex.Completable
import io.reactivex.Single import io.reactivex.Single
import java.net.UnknownHostException import java.net.UnknownHostException
@ -24,13 +25,12 @@ class GradeRepository @Inject constructor(
.flatMap { .flatMap {
if (it) remote.getGrades(semester) if (it) remote.getGrades(semester)
else Single.error(UnknownHostException()) else Single.error(UnknownHostException())
}.flatMap { newGrades -> }.flatMap { new ->
local.getGrades(semester).toSingle(emptyList()) local.getGrades(semester).toSingle(emptyList())
.doOnSuccess { oldGrades -> .doOnSuccess { old ->
val notifyBreakDate = oldGrades.maxBy { it.date }?.date val notifyBreakDate = old.maxBy { it.date }?.date ?: student.registrationDate.toLocalDate()
?: student.registrationDate.toLocalDate() local.deleteGrades(old.uniqueSubtract(new))
local.deleteGrades(oldGrades - newGrades) local.saveGrades(new.uniqueSubtract(old)
local.saveGrades((newGrades - oldGrades)
.onEach { .onEach {
if (it.date >= notifyBreakDate) it.apply { if (it.date >= notifyBreakDate) it.apply {
isRead = false isRead = false

View File

@ -18,9 +18,14 @@ class GradeSummaryRemote @Inject constructor(private val api: Api) {
GradeSummary( GradeSummary(
semesterId = semester.semesterId, semesterId = semester.semesterId,
studentId = semester.studentId, studentId = semester.studentId,
position = it.order,
subject = it.name, subject = it.name,
predictedGrade = it.predicted, predictedGrade = it.predicted,
finalGrade = it.final finalGrade = it.final,
pointsSum = it.pointsSum,
proposedPoints = it.proposedPoints,
finalPoints = it.finalPoints,
average = it.average
) )
} }
} }

View File

@ -4,6 +4,7 @@ import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork
import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings
import io.github.wulkanowy.data.db.entities.GradeSummary import io.github.wulkanowy.data.db.entities.GradeSummary
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.utils.uniqueSubtract
import io.reactivex.Single import io.reactivex.Single
import java.net.UnknownHostException import java.net.UnknownHostException
import javax.inject.Inject import javax.inject.Inject
@ -22,11 +23,11 @@ class GradeSummaryRepository @Inject constructor(
.flatMap { .flatMap {
if (it) remote.getGradeSummary(semester) if (it) remote.getGradeSummary(semester)
else Single.error(UnknownHostException()) else Single.error(UnknownHostException())
}.flatMap { newGradesSummary -> }.flatMap { new ->
local.getGradesSummary(semester).toSingle(emptyList()) local.getGradesSummary(semester).toSingle(emptyList())
.doOnSuccess { oldGradesSummary -> .doOnSuccess { old ->
local.deleteGradesSummary(oldGradesSummary - newGradesSummary) local.deleteGradesSummary(old.uniqueSubtract(new))
local.saveGradesSummary(newGradesSummary - oldGradesSummary) local.saveGradesSummary(new.uniqueSubtract(old))
} }
}.flatMap { local.getGradesSummary(semester).toSingle(emptyList()) }) }.flatMap { local.getGradesSummary(semester).toSingle(emptyList()) })
} }

View File

@ -4,6 +4,7 @@ import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork
import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings
import io.github.wulkanowy.data.db.entities.GradeStatistics import io.github.wulkanowy.data.db.entities.GradeStatistics
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.utils.uniqueSubtract
import io.reactivex.Single import io.reactivex.Single
import java.net.UnknownHostException import java.net.UnknownHostException
import javax.inject.Inject import javax.inject.Inject
@ -22,11 +23,11 @@ class GradeStatisticsRepository @Inject constructor(
.flatMap { .flatMap {
if (it) remote.getGradeStatistics(semester, isSemester) if (it) remote.getGradeStatistics(semester, isSemester)
else Single.error(UnknownHostException()) else Single.error(UnknownHostException())
}.flatMap { newGradesStats -> }.flatMap { new ->
local.getGradesStatistics(semester, isSemester).toSingle(emptyList()) local.getGradesStatistics(semester, isSemester).toSingle(emptyList())
.doOnSuccess { oldGradesStats -> .doOnSuccess { old ->
local.deleteGradesStatistics(oldGradesStats - newGradesStats) local.deleteGradesStatistics(old.uniqueSubtract(new))
local.saveGradesStatistics(newGradesStats - oldGradesStats) local.saveGradesStatistics(new.uniqueSubtract(old))
} }
}.flatMap { local.getGradesStatistics(semester, isSemester, subjectName).toSingle(emptyList()) }) }.flatMap { local.getGradesStatistics(semester, isSemester, subjectName).toSingle(emptyList()) })
} }

View File

@ -6,6 +6,7 @@ import io.github.wulkanowy.data.db.entities.Homework
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.utils.friday import io.github.wulkanowy.utils.friday
import io.github.wulkanowy.utils.monday import io.github.wulkanowy.utils.monday
import io.github.wulkanowy.utils.uniqueSubtract
import io.reactivex.Single import io.reactivex.Single
import org.threeten.bp.LocalDate import org.threeten.bp.LocalDate
import java.net.UnknownHostException import java.net.UnknownHostException
@ -26,11 +27,11 @@ class HomeworkRepository @Inject constructor(
.flatMap { .flatMap {
if (it) remote.getHomework(semester, monday, friday) if (it) remote.getHomework(semester, monday, friday)
else Single.error(UnknownHostException()) else Single.error(UnknownHostException())
}.flatMap { newGrades -> }.flatMap { new ->
local.getHomework(semester, monday, friday).toSingle(emptyList()) local.getHomework(semester, monday, friday).toSingle(emptyList())
.doOnSuccess { oldGrades -> .doOnSuccess { old ->
local.deleteHomework(oldGrades - newGrades) local.deleteHomework(old.uniqueSubtract(new))
local.saveHomework(newGrades - oldGrades) local.saveHomework(new.uniqueSubtract(old))
} }
}.flatMap { local.getHomework(semester, monday, friday).toSingle(emptyList()) }) }.flatMap { local.getHomework(semester, monday, friday).toSingle(emptyList()) })
} }

View File

@ -23,14 +23,14 @@ class MessageLocal @Inject constructor(private val messagesDb: MessagesDao) {
messagesDb.deleteAll(messages) messagesDb.deleteAll(messages)
} }
fun getMessage(student: Student, id: Int): Maybe<Message> { fun getMessage(id: Long): Maybe<Message> {
return messagesDb.load(student.studentId, id) return messagesDb.load(id)
} }
fun getMessages(student: Student, folder: MessageFolder): Maybe<List<Message>> { fun getMessages(student: Student, folder: MessageFolder): Maybe<List<Message>> {
return when (folder) { return when (folder) {
TRASHED -> messagesDb.loadDeleted(student.studentId) TRASHED -> messagesDb.loadDeleted(student.id.toInt())
else -> messagesDb.loadAll(student.studentId, folder.id) else -> messagesDb.loadAll(student.id.toInt(), folder.id)
}.filter { it.isNotEmpty() } }.filter { it.isNotEmpty() }
} }
} }

View File

@ -5,6 +5,7 @@ import io.github.wulkanowy.api.messages.Folder
import io.github.wulkanowy.api.messages.SentMessage import io.github.wulkanowy.api.messages.SentMessage
import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.Message
import io.github.wulkanowy.data.db.entities.Recipient import io.github.wulkanowy.data.db.entities.Recipient
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.utils.toLocalDateTime import io.github.wulkanowy.utils.toLocalDateTime
import io.reactivex.Single import io.reactivex.Single
import org.threeten.bp.LocalDateTime.now import org.threeten.bp.LocalDateTime.now
@ -16,11 +17,11 @@ import io.github.wulkanowy.api.messages.Recipient as ApiRecipient
@Singleton @Singleton
class MessageRemote @Inject constructor(private val api: Api) { class MessageRemote @Inject constructor(private val api: Api) {
fun getMessages(studentId: Int, folder: MessageFolder): Single<List<Message>> { fun getMessages(student: Student, folder: MessageFolder): Single<List<Message>> {
return api.getMessages(Folder.valueOf(folder.name)).map { messages -> return api.getMessages(Folder.valueOf(folder.name)).map { messages ->
messages.map { messages.map {
Message( Message(
studentId = studentId, studentId = student.id.toInt(),
realId = it.id ?: 0, realId = it.id ?: 0,
messageId = it.messageId ?: 0, messageId = it.messageId ?: 0,
sender = it.sender.orEmpty(), sender = it.sender.orEmpty(),
@ -58,4 +59,8 @@ class MessageRemote @Inject constructor(private val api: Api) {
} }
) )
} }
fun deleteMessage(message: Message): Single<Boolean> {
return api.deleteMessages(listOf(Pair(message.realId, message.folderId)))
}
} }

View File

@ -8,7 +8,9 @@ import io.github.wulkanowy.data.db.entities.Message
import io.github.wulkanowy.data.db.entities.Recipient import io.github.wulkanowy.data.db.entities.Recipient
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.message.MessageFolder.RECEIVED import io.github.wulkanowy.data.repositories.message.MessageFolder.RECEIVED
import io.github.wulkanowy.utils.uniqueSubtract
import io.reactivex.Completable import io.reactivex.Completable
import io.reactivex.Maybe
import io.reactivex.Single import io.reactivex.Single
import java.net.UnknownHostException import java.net.UnknownHostException
import javax.inject.Inject import javax.inject.Inject
@ -28,13 +30,13 @@ class MessageRepository @Inject constructor(
local.getMessages(student, folder).filter { !forceRefresh } local.getMessages(student, folder).filter { !forceRefresh }
.switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings) .switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings)
.flatMap { .flatMap {
if (it) remote.getMessages(student.studentId, folder) if (it) remote.getMessages(student, folder)
else Single.error(UnknownHostException()) else Single.error(UnknownHostException())
}.flatMap { new -> }.flatMap { new ->
local.getMessages(student, folder).toSingle(emptyList()) local.getMessages(student, folder).toSingle(emptyList())
.doOnSuccess { old -> .doOnSuccess { old ->
local.deleteMessages(old - new) local.deleteMessages(old.uniqueSubtract(new))
local.saveMessages((new - old) local.saveMessages(new.uniqueSubtract(old)
.onEach { .onEach {
it.isNotified = !notify it.isNotified = !notify
}) })
@ -44,14 +46,14 @@ class MessageRepository @Inject constructor(
} }
} }
fun getMessage(student: Student, messageId: Int, markAsRead: Boolean = false): Single<Message> { fun getMessage(student: Student, messageDbId: Long, markAsRead: Boolean = false): Single<Message> {
return Single.just(apiHelper.initApi(student)) return Single.just(apiHelper.initApi(student))
.flatMap { _ -> .flatMap { _ ->
local.getMessage(student, messageId) local.getMessage(messageDbId)
.filter { !it.content.isNullOrEmpty() } .filter { !it.content.isNullOrEmpty() }
.switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings) .switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings)
.flatMap { .flatMap {
if (it) local.getMessage(student, messageId).toSingle() if (it) local.getMessage(messageDbId).toSingle()
else Single.error(UnknownHostException()) else Single.error(UnknownHostException())
} }
.flatMap { dbMessage -> .flatMap { dbMessage ->
@ -62,7 +64,7 @@ class MessageRepository @Inject constructor(
})) }))
} }
}.flatMap { }.flatMap {
local.getMessage(student, messageId).toSingle() local.getMessage(messageDbId).toSingle()
} }
) )
} }
@ -89,4 +91,20 @@ class MessageRepository @Inject constructor(
else Single.error(UnknownHostException()) else Single.error(UnknownHostException())
} }
} }
fun deleteMessage(message: Message): Maybe<Boolean> {
return ReactiveNetwork.checkInternetConnectivity(settings)
.flatMap {
if (it) remote.deleteMessage(message)
else Single.error(UnknownHostException())
}
.filter { it }
.doOnSuccess {
if (!message.removed) local.updateMessages(listOf(message.copy(removed = true).apply {
id = message.id
content = message.content
}))
else local.deleteMessages(listOf(message))
}
}
} }

View File

@ -0,0 +1,25 @@
package io.github.wulkanowy.data.repositories.mobiledevice
import io.github.wulkanowy.data.db.dao.MobileDeviceDao
import io.github.wulkanowy.data.db.entities.MobileDevice
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.reactivex.Maybe
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class MobileDeviceLocal @Inject constructor(private val mobileDb: MobileDeviceDao) {
fun saveDevices(devices: List<MobileDevice>) {
mobileDb.insertAll(devices)
}
fun deleteDevices(devices: List<MobileDevice>) {
mobileDb.deleteAll(devices)
}
fun getDevices(semester: Semester): Maybe<List<MobileDevice>> {
return mobileDb.loadAll(semester.studentId).filter { it.isNotEmpty() }
}
}

View File

@ -0,0 +1,47 @@
package io.github.wulkanowy.data.repositories.mobiledevice
import io.github.wulkanowy.api.Api
import io.github.wulkanowy.data.db.entities.MobileDevice
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.pojos.MobileDeviceToken
import io.github.wulkanowy.utils.toLocalDateTime
import io.reactivex.Single
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class MobileDeviceRemote @Inject constructor(private val api: Api) {
fun getDevices(semester: Semester): Single<List<MobileDevice>> {
return Single.just(api.apply { diaryId = semester.diaryId })
.flatMap { api.getRegisteredDevices() }
.map { devices ->
devices.map {
MobileDevice(
studentId = semester.studentId,
date = it.date.toLocalDateTime(),
deviceId = it.id,
name = it.name
)
}
}
}
fun unregisterDevice(semester: Semester, device: MobileDevice): Single<Boolean> {
return Single.just(api.apply { diaryId = semester.diaryId })
.flatMap { api.unregisterDevice(device.deviceId) }
}
fun getToken(semester: Semester): Single<MobileDeviceToken> {
return Single.just(api.apply { diaryId = semester.diaryId })
.flatMap { api.getToken() }
.map {
MobileDeviceToken(
token = it.token,
symbol = it.symbol,
pin = it.pin,
qr = it.qrCodeImage
)
}
}
}

View File

@ -0,0 +1,52 @@
package io.github.wulkanowy.data.repositories.mobiledevice
import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork
import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings
import io.github.wulkanowy.data.db.entities.MobileDevice
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.pojos.MobileDeviceToken
import io.github.wulkanowy.utils.uniqueSubtract
import io.reactivex.Single
import java.net.UnknownHostException
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class MobileDeviceRepository @Inject constructor(
private val settings: InternetObservingSettings,
private val local: MobileDeviceLocal,
private val remote: MobileDeviceRemote
) {
fun getDevices(semester: Semester, forceRefresh: Boolean = false): Single<List<MobileDevice>> {
return local.getDevices(semester).filter { !forceRefresh }
.switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings)
.flatMap {
if (it) remote.getDevices(semester)
else Single.error(UnknownHostException())
}.flatMap { new ->
local.getDevices(semester).toSingle(emptyList())
.doOnSuccess { old ->
local.deleteDevices(old uniqueSubtract new)
local.saveDevices(new uniqueSubtract old)
}
}
).flatMap { local.getDevices(semester).toSingle(emptyList()) }
}
fun unregisterDevice(semester: Semester, device: MobileDevice): Single<Boolean> {
return ReactiveNetwork.checkInternetConnectivity(settings)
.flatMap {
if (it) remote.unregisterDevice(semester, device)
else Single.error(UnknownHostException())
}
}
fun getToken(semester: Semester): Single<MobileDeviceToken> {
return ReactiveNetwork.checkInternetConnectivity(settings)
.flatMap {
if (it) remote.getToken(semester)
else Single.error(UnknownHostException())
}
}
}

View File

@ -5,6 +5,7 @@ import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.Inter
import io.github.wulkanowy.data.db.entities.Note import io.github.wulkanowy.data.db.entities.Note
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.utils.uniqueSubtract
import io.reactivex.Completable import io.reactivex.Completable
import io.reactivex.Single import io.reactivex.Single
import java.net.UnknownHostException import java.net.UnknownHostException
@ -27,8 +28,8 @@ class NoteRepository @Inject constructor(
}.flatMap { new -> }.flatMap { new ->
local.getNotes(student).toSingle(emptyList()) local.getNotes(student).toSingle(emptyList())
.doOnSuccess { old -> .doOnSuccess { old ->
local.deleteNotes(old - new) local.deleteNotes(old.uniqueSubtract(new))
local.saveNotes((new - old) local.saveNotes(new.uniqueSubtract(old)
.onEach { .onEach {
if (it.date >= student.registrationDate.toLocalDate()) it.apply { if (it.date >= student.registrationDate.toLocalDate()) it.apply {
isRead = false isRead = false

View File

@ -17,12 +17,18 @@ class PreferencesRepository @Inject constructor(
val isShowPresent: Boolean val isShowPresent: Boolean
get() = sharedPref.getBoolean(context.getString(R.string.pref_key_attendance_present), true) get() = sharedPref.getBoolean(context.getString(R.string.pref_key_attendance_present), true)
val gradeAverageMode: String
get() = sharedPref.getString(context.getString(R.string.pref_key_grade_average_mode), "only_one_semester") ?: "only_one_semester"
val gradeAverageForceCalc: Boolean
get() = sharedPref.getBoolean(context.getString(R.string.pref_key_grade_average_force_calc), false)
val isGradeExpandable: Boolean val isGradeExpandable: Boolean
get() = !sharedPref.getBoolean(context.getString(R.string.pref_key_expand_grade), false) get() = !sharedPref.getBoolean(context.getString(R.string.pref_key_expand_grade), false)
val currentThemeKey: String = context.getString(R.string.pref_key_theme) val appThemeKey: String = context.getString(R.string.pref_key_app_theme)
val currentTheme: Int val appTheme: String
get() = sharedPref.getString(currentThemeKey, "1")?.toIntOrNull() ?: 1 get() = sharedPref.getString(appThemeKey, "light") ?: "light"
val gradeColorTheme: String val gradeColorTheme: String
get() = sharedPref.getString(context.getString(R.string.pref_key_grade_color_scheme), "vulcan") ?: "vulcan" get() = sharedPref.getString(context.getString(R.string.pref_key_grade_color_scheme), "vulcan") ?: "vulcan"
@ -37,7 +43,7 @@ class PreferencesRepository @Inject constructor(
val servicesOnlyWifiKey: String = context.getString(R.string.pref_key_services_wifi_only) val servicesOnlyWifiKey: String = context.getString(R.string.pref_key_services_wifi_only)
val isServicesOnlyWifi: Boolean val isServicesOnlyWifi: Boolean
get() = sharedPref.getBoolean(servicesOnlyWifiKey, true) get() = sharedPref.getBoolean(servicesOnlyWifiKey, false)
val isNotificationsEnable: Boolean val isNotificationsEnable: Boolean
get() = sharedPref.getBoolean(context.getString(R.string.pref_key_notifications_enable), true) get() = sharedPref.getBoolean(context.getString(R.string.pref_key_notifications_enable), true)
@ -50,8 +56,7 @@ class PreferencesRepository @Inject constructor(
get() = sharedPref.getString(context.getString(R.string.pref_key_grade_modifier_plus), "0.0")?.toDouble() ?: 0.0 get() = sharedPref.getString(context.getString(R.string.pref_key_grade_modifier_plus), "0.0")?.toDouble() ?: 0.0
val gradeMinusModifier: Double val gradeMinusModifier: Double
get() = sharedPref.getString(context.getString(R.string.pref_key_grade_modifier_minus), "0.0")?.toDouble() get() = sharedPref.getString(context.getString(R.string.pref_key_grade_modifier_minus), "0.0")?.toDouble() ?: 0.0
?: 0.0
val fillMessageContent: Boolean val fillMessageContent: Boolean
get() = sharedPref.getBoolean(context.getString(R.string.pref_key_fill_message_content), true) get() = sharedPref.getBoolean(context.getString(R.string.pref_key_fill_message_content), true)

View File

@ -7,6 +7,7 @@ import io.github.wulkanowy.data.db.entities.Message
import io.github.wulkanowy.data.db.entities.Recipient import io.github.wulkanowy.data.db.entities.Recipient
import io.github.wulkanowy.data.db.entities.ReportingUnit import io.github.wulkanowy.data.db.entities.ReportingUnit
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.utils.uniqueSubtract
import io.reactivex.Single import io.reactivex.Single
import java.net.UnknownHostException import java.net.UnknownHostException
import javax.inject.Inject import javax.inject.Inject
@ -31,8 +32,8 @@ class RecipientRepository @Inject constructor(
}.flatMap { new -> }.flatMap { new ->
local.getRecipients(student, role, unit).toSingle(emptyList()) local.getRecipients(student, role, unit).toSingle(emptyList())
.doOnSuccess { old -> .doOnSuccess { old ->
local.deleteRecipients(old - new) local.deleteRecipients(old.uniqueSubtract(new))
local.saveRecipients(new - old) local.saveRecipients(new.uniqueSubtract(old))
} }
}.flatMap { }.flatMap {
local.getRecipients(student, role, unit).toSingle(emptyList()) local.getRecipients(student, role, unit).toSingle(emptyList())

View File

@ -5,6 +5,7 @@ import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.Inter
import io.github.wulkanowy.data.ApiHelper import io.github.wulkanowy.data.ApiHelper
import io.github.wulkanowy.data.db.entities.ReportingUnit import io.github.wulkanowy.data.db.entities.ReportingUnit
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.utils.uniqueSubtract
import io.reactivex.Maybe import io.reactivex.Maybe
import io.reactivex.Single import io.reactivex.Single
import java.net.UnknownHostException import java.net.UnknownHostException
@ -30,8 +31,8 @@ class ReportingUnitRepository @Inject constructor(
}.flatMap { new -> }.flatMap { new ->
local.getReportingUnits(student).toSingle(emptyList()) local.getReportingUnits(student).toSingle(emptyList())
.doOnSuccess { old -> .doOnSuccess { old ->
local.deleteReportingUnits(old - new) local.deleteReportingUnits(old.uniqueSubtract(new))
local.saveReportingUnits(new - old) local.saveReportingUnits(new.uniqueSubtract(old))
} }
}.flatMap { local.getReportingUnits(student).toSingle(emptyList()) } }.flatMap { local.getReportingUnits(student).toSingle(emptyList()) }
) )

View File

@ -11,17 +11,14 @@ import javax.inject.Singleton
class SemesterLocal @Inject constructor(private val semesterDb: SemesterDao) { class SemesterLocal @Inject constructor(private val semesterDb: SemesterDao) {
fun saveSemesters(semesters: List<Semester>) { fun saveSemesters(semesters: List<Semester>) {
return semesterDb.insertAll(semesters) semesterDb.insertAll(semesters)
}
fun deleteSemesters(semesters: List<Semester>) {
semesterDb.deleteAll(semesters)
} }
fun getSemesters(student: Student): Maybe<List<Semester>> { fun getSemesters(student: Student): Maybe<List<Semester>> {
return semesterDb.loadAll(student.studentId).filter { !it.isEmpty() } return semesterDb.loadAll(student.studentId, student.classId).filter { !it.isEmpty() }
}
fun setCurrentSemester(semester: Semester) {
semesterDb.run {
resetCurrent(semester.studentId)
updateCurrent(semester.semesterId, semester.diaryId)
}
} }
} }

View File

@ -17,9 +17,12 @@ class SemesterRemote @Inject constructor(private val api: Api) {
studentId = student.studentId, studentId = student.studentId,
diaryId = semester.diaryId, diaryId = semester.diaryId,
diaryName = semester.diaryName, diaryName = semester.diaryName,
schoolYear = semester.schoolYear,
semesterId = semester.semesterId, semesterId = semester.semesterId,
semesterName = semester.semesterNumber, semesterName = semester.semesterNumber,
isCurrent = semester.current, isCurrent = semester.current,
start = semester.start,
end = semester.end,
classId = semester.classId, classId = semester.classId,
unitId = semester.unitId unitId = semester.unitId
) )

View File

@ -5,8 +5,10 @@ import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.Inter
import io.github.wulkanowy.data.ApiHelper import io.github.wulkanowy.data.ApiHelper
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.utils.uniqueSubtract
import io.reactivex.Maybe import io.reactivex.Maybe
import io.reactivex.Single import io.reactivex.Single
import timber.log.Timber
import java.net.UnknownHostException import java.net.UnknownHostException
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -25,10 +27,17 @@ class SemesterRepository @Inject constructor(
.switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings) .switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings)
.flatMap { .flatMap {
if (it) remote.getSemesters(student) else Single.error(UnknownHostException()) if (it) remote.getSemesters(student) else Single.error(UnknownHostException())
}.map { newSemesters -> }.flatMap { new ->
local.apply { val currentSemesters = new.filter { it.isCurrent }
saveSemesters(newSemesters) if (currentSemesters.size == 1) {
setCurrentSemester(newSemesters.single { it.isCurrent }) local.getSemesters(student).toSingle(emptyList())
.doOnSuccess { old ->
local.deleteSemesters(old.uniqueSubtract(new))
local.saveSemesters(new.uniqueSubtract(old))
}
} else {
Timber.i("Current semesters list:\n${currentSemesters.joinToString(separator = "\n")}")
throw IllegalArgumentException("Current semester can be only one.")
} }
}.flatMap { local.getSemesters(student).toSingle(emptyList()) }) }.flatMap { local.getSemesters(student).toSingle(emptyList()) })
} }

View File

@ -17,8 +17,8 @@ class StudentLocal @Inject constructor(
private val context: Context private val context: Context
) { ) {
fun saveStudent(student: Student): Single<Long> { fun saveStudents(students: List<Student>): Single<List<Long>> {
return Single.fromCallable { studentDb.insert(student.copy(password = encrypt(student.password, context))) } return Single.fromCallable { studentDb.insertAll(students.map { it.copy(password = encrypt(it.password, context)) }) }
} }
fun getStudents(decryptPass: Boolean): Maybe<List<Student>> { fun getStudents(decryptPass: Boolean): Maybe<List<Student>> {
@ -35,7 +35,7 @@ class StudentLocal @Inject constructor(
return Completable.fromCallable { return Completable.fromCallable {
studentDb.run { studentDb.run {
resetCurrent() resetCurrent()
updateCurrent(student.studentId) updateCurrent(student.id)
} }
} }
} }

View File

@ -21,6 +21,8 @@ class StudentRemote @Inject constructor(private val api: Api) {
studentName = student.studentName, studentName = student.studentName,
schoolSymbol = student.schoolSymbol, schoolSymbol = student.schoolSymbol,
schoolName = student.schoolName, schoolName = student.schoolName,
className = student.className,
classId = student.classId,
endpoint = endpoint, endpoint = endpoint,
loginType = student.loginType.name, loginType = student.loginType.name,
isCurrent = false, isCurrent = false,

View File

@ -4,6 +4,7 @@ import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork
import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings
import io.github.wulkanowy.data.ApiHelper import io.github.wulkanowy.data.ApiHelper
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.exceptions.NoCurrentStudentException
import io.reactivex.Completable import io.reactivex.Completable
import io.reactivex.Maybe import io.reactivex.Maybe
import io.reactivex.Single import io.reactivex.Single
@ -21,6 +22,8 @@ class StudentRepository @Inject constructor(
fun isStudentSaved(): Single<Boolean> = local.getStudents(false).isEmpty.map { !it } fun isStudentSaved(): Single<Boolean> = local.getStudents(false).isEmpty.map { !it }
fun isCurrentStudentSet(): Single<Boolean> = local.getCurrentStudent(false).isEmpty.map { !it }
fun getStudents(email: String, password: String, endpoint: String, symbol: String = ""): Single<List<Student>> { fun getStudents(email: String, password: String, endpoint: String, symbol: String = ""): Single<List<Student>> {
return ReactiveNetwork.checkInternetConnectivity(settings) return ReactiveNetwork.checkInternetConnectivity(settings)
.flatMap { .flatMap {
@ -36,12 +39,12 @@ class StudentRepository @Inject constructor(
fun getCurrentStudent(decryptPass: Boolean = true): Single<Student> { fun getCurrentStudent(decryptPass: Boolean = true): Single<Student> {
return local.getCurrentStudent(decryptPass) return local.getCurrentStudent(decryptPass)
.switchIfEmpty(Maybe.error(NoSuchElementException("No current student"))) .switchIfEmpty(Maybe.error(NoCurrentStudentException()))
.toSingle() .toSingle()
} }
fun saveStudent(student: Student): Single<Long> { fun saveStudents(students: List<Student>): Single<List<Long>> {
return local.saveStudent(student) return local.saveStudents(students)
} }
fun switchStudent(student: Student): Completable { fun switchStudent(student: Student): Completable {

View File

@ -4,6 +4,7 @@ import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork
import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Subject import io.github.wulkanowy.data.db.entities.Subject
import io.github.wulkanowy.utils.uniqueSubtract
import io.reactivex.Single import io.reactivex.Single
import java.net.UnknownHostException import java.net.UnknownHostException
import javax.inject.Inject import javax.inject.Inject
@ -26,8 +27,8 @@ class SubjectRepository @Inject constructor(
local.getSubjects(semester) local.getSubjects(semester)
.toSingle(emptyList()) .toSingle(emptyList())
.doOnSuccess { old -> .doOnSuccess { old ->
local.deleteSubjects(old - new) local.deleteSubjects(old.uniqueSubtract(new))
local.saveSubjects(new - old) local.saveSubjects(new.uniqueSubtract(old))
} }
}.flatMap { }.flatMap {
local.getSubjects(semester).toSingle(emptyList()) local.getSubjects(semester).toSingle(emptyList())

View File

@ -6,6 +6,7 @@ import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Timetable import io.github.wulkanowy.data.db.entities.Timetable
import io.github.wulkanowy.utils.friday import io.github.wulkanowy.utils.friday
import io.github.wulkanowy.utils.monday import io.github.wulkanowy.utils.monday
import io.github.wulkanowy.utils.uniqueSubtract
import io.reactivex.Single import io.reactivex.Single
import org.threeten.bp.LocalDate import org.threeten.bp.LocalDate
import java.net.UnknownHostException import java.net.UnknownHostException
@ -25,17 +26,16 @@ class TimetableRepository @Inject constructor(
.switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings).flatMap { .switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings).flatMap {
if (it) remote.getTimetable(semester, monday, friday) if (it) remote.getTimetable(semester, monday, friday)
else Single.error(UnknownHostException()) else Single.error(UnknownHostException())
}.flatMap { newTimetable -> }.flatMap { new ->
local.getTimetable(semester, monday, friday) local.getTimetable(semester, monday, friday)
.toSingle(emptyList()) .toSingle(emptyList())
.doOnSuccess { oldTimetable -> .doOnSuccess { old ->
local.deleteTimetable(oldTimetable - newTimetable) local.deleteTimetable(old.uniqueSubtract(new))
local.saveTimetable((newTimetable - oldTimetable).map { item -> local.saveTimetable(new.uniqueSubtract(old).map { item ->
item.apply { item.apply {
oldTimetable.singleOrNull { this.start == it.start }?.let { old.singleOrNull { this.start == it.start }?.let {
return@map copy( return@map copy(
room = if (room.isEmpty()) it.room else room, room = if (room.isEmpty()) it.room else room
teacher = if (teacher.isEmpty()) it.teacher else teacher
) )
} }
} }

View File

@ -17,6 +17,6 @@ import javax.inject.Singleton
BuilderModule::class]) BuilderModule::class])
interface AppComponent : AndroidInjector<WulkanowyApp> { interface AppComponent : AndroidInjector<WulkanowyApp> {
@Component.Builder @Component.Factory
abstract class Builder : AndroidInjector.Builder<WulkanowyApp>() interface Factory : AndroidInjector.Factory<WulkanowyApp>
} }

View File

@ -2,16 +2,13 @@ package io.github.wulkanowy.di
import android.appwidget.AppWidgetManager import android.appwidget.AppWidgetManager
import android.content.Context import android.content.Context
import com.google.firebase.analytics.FirebaseAnalytics
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.BuildConfig.DEBUG
import io.github.wulkanowy.WulkanowyApp import io.github.wulkanowy.WulkanowyApp
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.SchedulersProvider import io.github.wulkanowy.utils.SchedulersProvider
import javax.inject.Named
import javax.inject.Singleton import javax.inject.Singleton
@Module @Module
@ -30,14 +27,9 @@ internal class AppModule {
@Singleton @Singleton
@Provides @Provides
fun provideFirebaseAnalyticsHelper(context: Context) = FirebaseAnalyticsHelper(FirebaseAnalytics.getInstance(context)) fun provideAppWidgetManager(context: Context): AppWidgetManager = AppWidgetManager.getInstance(context)
@Singleton @Singleton
@Provides @Provides
fun provideAppWidgetManager(context: Context) = AppWidgetManager.getInstance(context) fun provideAppInfo() = AppInfo()
@Singleton
@Named("isDebug")
@Provides
fun provideIsDebug() = DEBUG
} }

View File

@ -3,14 +3,16 @@ package io.github.wulkanowy.di
import dagger.Module import dagger.Module
import dagger.android.ContributesAndroidInjector import dagger.android.ContributesAndroidInjector
import io.github.wulkanowy.di.scopes.PerActivity import io.github.wulkanowy.di.scopes.PerActivity
import io.github.wulkanowy.services.widgets.TimetableWidgetService
import io.github.wulkanowy.ui.modules.login.LoginActivity import io.github.wulkanowy.ui.modules.login.LoginActivity
import io.github.wulkanowy.ui.modules.login.LoginModule import io.github.wulkanowy.ui.modules.login.LoginModule
import io.github.wulkanowy.ui.modules.luckynumberwidget.LuckyNumberWidgetConfigureActivity
import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.main.MainModule import io.github.wulkanowy.ui.modules.main.MainModule
import io.github.wulkanowy.ui.modules.message.send.SendMessageActivity import io.github.wulkanowy.ui.modules.message.send.SendMessageActivity
import io.github.wulkanowy.ui.modules.splash.SplashActivity import io.github.wulkanowy.ui.modules.splash.SplashActivity
import io.github.wulkanowy.ui.widgets.timetable.TimetableWidgetProvider import io.github.wulkanowy.ui.modules.luckynumberwidget.LuckyNumberWidgetProvider
import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetConfigureActivity
import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider
@Module @Module
internal abstract class BuilderModule { internal abstract class BuilderModule {
@ -31,8 +33,14 @@ internal abstract class BuilderModule {
abstract fun bindMessageSendActivity(): SendMessageActivity abstract fun bindMessageSendActivity(): SendMessageActivity
@ContributesAndroidInjector @ContributesAndroidInjector
abstract fun bindTimetableWidgetService(): TimetableWidgetService abstract fun bindTimetableWidgetAccountActivity(): TimetableWidgetConfigureActivity
@ContributesAndroidInjector @ContributesAndroidInjector
abstract fun bindTimetableWidgetProvider(): TimetableWidgetProvider abstract fun bindTimetableWidgetProvider(): TimetableWidgetProvider
@ContributesAndroidInjector
abstract fun bindLuckyNumberWidgetAccountActivity(): LuckyNumberWidgetConfigureActivity
@ContributesAndroidInjector
abstract fun bindLuckyNumberWidgetProvider(): LuckyNumberWidgetProvider
} }

View File

@ -9,6 +9,7 @@ import com.squareup.inject.assisted.dagger2.AssistedModule
import dagger.Binds import dagger.Binds
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import dagger.android.ContributesAndroidInjector
import dagger.multibindings.IntoSet import dagger.multibindings.IntoSet
import io.github.wulkanowy.services.sync.works.AttendanceSummaryWork import io.github.wulkanowy.services.sync.works.AttendanceSummaryWork
import io.github.wulkanowy.services.sync.works.AttendanceWork import io.github.wulkanowy.services.sync.works.AttendanceWork
@ -24,6 +25,7 @@ import io.github.wulkanowy.services.sync.works.NoteWork
import io.github.wulkanowy.services.sync.works.RecipientWork import io.github.wulkanowy.services.sync.works.RecipientWork
import io.github.wulkanowy.services.sync.works.TimetableWork import io.github.wulkanowy.services.sync.works.TimetableWork
import io.github.wulkanowy.services.sync.works.Work import io.github.wulkanowy.services.sync.works.Work
import io.github.wulkanowy.services.widgets.TimetableWidgetService
import javax.inject.Singleton import javax.inject.Singleton
@AssistedModule @AssistedModule
@ -48,6 +50,9 @@ abstract class ServicesModule {
fun provideNotificationManager(context: Context) = context.getSystemService(NOTIFICATION_SERVICE) as NotificationManager fun provideNotificationManager(context: Context) = context.getSystemService(NOTIFICATION_SERVICE) as NotificationManager
} }
@ContributesAndroidInjector
abstract fun bindTimetableWidgetService(): TimetableWidgetService
@Binds @Binds
@IntoSet @IntoSet
abstract fun provideGradeWork(work: GradeWork): Work abstract fun provideGradeWork(work: GradeWork): Work

View File

@ -6,44 +6,60 @@ import androidx.work.BackoffPolicy.EXPONENTIAL
import androidx.work.Constraints import androidx.work.Constraints
import androidx.work.ExistingPeriodicWorkPolicy.KEEP import androidx.work.ExistingPeriodicWorkPolicy.KEEP
import androidx.work.ExistingPeriodicWorkPolicy.REPLACE import androidx.work.ExistingPeriodicWorkPolicy.REPLACE
import androidx.work.NetworkType.METERED import androidx.work.NetworkType.CONNECTED
import androidx.work.NetworkType.UNMETERED import androidx.work.NetworkType.UNMETERED
import androidx.work.PeriodicWorkRequest import androidx.work.PeriodicWorkRequest
import androidx.work.WorkManager import androidx.work.WorkManager
import io.github.wulkanowy.data.db.SharedPrefHelper
import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository
import io.github.wulkanowy.services.sync.channels.DebugChannel import io.github.wulkanowy.services.sync.channels.DebugChannel
import io.github.wulkanowy.services.sync.channels.NewEntriesChannel import io.github.wulkanowy.services.sync.channels.NewEntriesChannel
import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.isHolidays import io.github.wulkanowy.utils.isHolidays
import org.threeten.bp.LocalDate.now import org.threeten.bp.LocalDate.now
import timber.log.Timber import timber.log.Timber
import java.util.concurrent.TimeUnit.MINUTES import java.util.concurrent.TimeUnit.MINUTES
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Named
import javax.inject.Singleton import javax.inject.Singleton
@Singleton @Singleton
class SyncManager @Inject constructor( class SyncManager @Inject constructor(
private val workManager: WorkManager, private val workManager: WorkManager,
private val preferencesRepository: PreferencesRepository, private val preferencesRepository: PreferencesRepository,
sharedPrefHelper: SharedPrefHelper,
newEntriesChannel: NewEntriesChannel, newEntriesChannel: NewEntriesChannel,
debugChannel: DebugChannel, debugChannel: DebugChannel,
@Named("isDebug") isDebug: Boolean appInfo: AppInfo
) { ) {
companion object {
private const val APP_VERSION_CODE_KEY = "app_version_code"
}
init { init {
if (SDK_INT >= O) newEntriesChannel.create()
if (SDK_INT >= O && isDebug) debugChannel.create()
if (now().isHolidays) stopSyncWorker() if (now().isHolidays) stopSyncWorker()
if (SDK_INT > O) {
newEntriesChannel.create()
if (appInfo.isDebug) debugChannel.create()
}
if (sharedPrefHelper.getLong(APP_VERSION_CODE_KEY, -1L) != appInfo.versionCode.toLong()) {
startSyncWorker(true)
sharedPrefHelper.putLong(APP_VERSION_CODE_KEY, appInfo.versionCode.toLong(), true)
}
Timber.i("SyncManager was initialized") Timber.i("SyncManager was initialized")
} }
fun startSyncWorker(restart: Boolean = false) { fun startSyncWorker(restart: Boolean = false) {
if (preferencesRepository.isServiceEnabled && !now().isHolidays) { if (preferencesRepository.isServiceEnabled && !now().isHolidays) {
workManager.enqueueUniquePeriodicWork(SyncWorker::class.java.simpleName, if (restart) REPLACE else KEEP, workManager.enqueueUniquePeriodicWork(SyncWorker::class.java.simpleName, if (restart) REPLACE else KEEP,
PeriodicWorkRequest.Builder(SyncWorker::class.java, preferencesRepository.servicesInterval, MINUTES) PeriodicWorkRequest.Builder(SyncWorker::class.java, preferencesRepository.servicesInterval, MINUTES, 10, MINUTES)
.setBackoffCriteria(EXPONENTIAL, 30, MINUTES) .setBackoffCriteria(EXPONENTIAL, 30, MINUTES)
.setConstraints(Constraints.Builder() .setConstraints(Constraints.Builder()
.setRequiredNetworkType(if (preferencesRepository.isServicesOnlyWifi) METERED else UNMETERED) .setRequiredNetworkType(if (preferencesRepository.isServicesOnlyWifi) UNMETERED else CONNECTED)
.build()) .build())
.build()) .build())
} }

View File

@ -11,6 +11,7 @@ import androidx.work.WorkerParameters
import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject import com.squareup.inject.assisted.AssistedInject
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.api.interceptor.FeatureDisabledException
import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository
import io.github.wulkanowy.data.repositories.semester.SemesterRepository import io.github.wulkanowy.data.repositories.semester.SemesterRepository
import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.data.repositories.student.StudentRepository
@ -33,19 +34,31 @@ class SyncWorker @AssistedInject constructor(
) : RxWorker(appContext, workerParameters) { ) : RxWorker(appContext, workerParameters) {
override fun createWork(): Single<Result> { override fun createWork(): Single<Result> {
return studentRepository.getCurrentStudent() Timber.i("SyncWorker is starting")
return studentRepository.isCurrentStudentSet()
.filter { true }
.flatMap { studentRepository.getCurrentStudent().toMaybe() }
.flatMapCompletable { student -> .flatMapCompletable { student ->
semesterRepository.getCurrentSemester(student, true) semesterRepository.getCurrentSemester(student, true)
.flatMapCompletable { semester -> .flatMapCompletable { semester ->
Completable.mergeDelayError(works.map { it.create(student, semester) }) Completable.mergeDelayError(works.map { work ->
work.create(student, semester)
.doOnSubscribe { Timber.i("${work::class.java.simpleName} is starting") }
.doOnError { Timber.i("${work::class.java.simpleName} result: An exception occurred") }
.doOnComplete { Timber.i("${work::class.java.simpleName} result: Success") }
})
} }
} }
.toSingleDefault(Result.success()) .toSingleDefault(Result.success())
.onErrorReturn { .onErrorReturn {
Timber.e(it, "There was an error during synchronization") Timber.e(it, "There was an error during synchronization")
Result.retry() if (it is FeatureDisabledException) Result.success()
else Result.retry()
}
.doOnSuccess {
if (preferencesRepository.isDebugNotificationEnable) notify(it)
Timber.i("SyncWorker result: $it")
} }
.doOnSuccess { if (preferencesRepository.isDebugNotificationEnable) notify(it) }
} }
private fun notify(result: Result) { private fun notify(result: Result) {

View File

@ -15,7 +15,7 @@ import io.github.wulkanowy.data.repositories.grade.GradeRepository
import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository
import io.github.wulkanowy.services.sync.channels.NewEntriesChannel import io.github.wulkanowy.services.sync.channels.NewEntriesChannel
import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.main.MainActivity.Companion.EXTRA_START_MENU_INDEX import io.github.wulkanowy.ui.modules.main.MainView.MenuView
import io.github.wulkanowy.utils.getCompatColor import io.github.wulkanowy.utils.getCompatColor
import io.reactivex.Completable import io.reactivex.Completable
import javax.inject.Inject import javax.inject.Inject
@ -47,8 +47,8 @@ class GradeWork @Inject constructor(
.setDefaults(DEFAULT_ALL) .setDefaults(DEFAULT_ALL)
.setColor(context.getCompatColor(R.color.colorPrimary)) .setColor(context.getCompatColor(R.color.colorPrimary))
.setContentIntent( .setContentIntent(
PendingIntent.getActivity(context, 0, PendingIntent.getActivity(context, MenuView.GRADE.id,
MainActivity.getStartIntent(context).putExtra(EXTRA_START_MENU_INDEX, 0), FLAG_UPDATE_CURRENT)) MainActivity.getStartIntent(context, MenuView.GRADE, true), FLAG_UPDATE_CURRENT))
.setStyle(NotificationCompat.InboxStyle().run { .setStyle(NotificationCompat.InboxStyle().run {
setSummaryText(context.resources.getQuantityString(R.plurals.grade_number_item, grades.size, grades.size)) setSummaryText(context.resources.getQuantityString(R.plurals.grade_number_item, grades.size, grades.size))
grades.forEach { addLine("${it.subject}: ${it.entry}") } grades.forEach { addLine("${it.subject}: ${it.entry}") }

View File

@ -15,7 +15,7 @@ import io.github.wulkanowy.data.repositories.luckynumber.LuckyNumberRepository
import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository
import io.github.wulkanowy.services.sync.channels.NewEntriesChannel import io.github.wulkanowy.services.sync.channels.NewEntriesChannel
import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.main.MainActivity.Companion.EXTRA_START_MENU_INDEX import io.github.wulkanowy.ui.modules.main.MainView.MenuView
import io.github.wulkanowy.utils.getCompatColor import io.github.wulkanowy.utils.getCompatColor
import io.reactivex.Completable import io.reactivex.Completable
import javax.inject.Inject import javax.inject.Inject
@ -47,9 +47,8 @@ class LuckyNumberWork @Inject constructor(
.setPriority(PRIORITY_HIGH) .setPriority(PRIORITY_HIGH)
.setColor(context.getCompatColor(R.color.colorPrimary)) .setColor(context.getCompatColor(R.color.colorPrimary))
.setContentIntent( .setContentIntent(
PendingIntent.getActivity(context, 0, PendingIntent.getActivity(context, MenuView.MESSAGE.id,
MainActivity.getStartIntent(context).putExtra(EXTRA_START_MENU_INDEX, 4), FLAG_UPDATE_CURRENT) MainActivity.getStartIntent(context, MenuView.LUCKY_NUMBER, true), FLAG_UPDATE_CURRENT))
)
.build()) .build())
} }
} }

View File

@ -16,7 +16,7 @@ import io.github.wulkanowy.data.repositories.message.MessageRepository
import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository
import io.github.wulkanowy.services.sync.channels.NewEntriesChannel import io.github.wulkanowy.services.sync.channels.NewEntriesChannel
import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.main.MainActivity.Companion.EXTRA_START_MENU_INDEX import io.github.wulkanowy.ui.modules.main.MainView.MenuView
import io.github.wulkanowy.utils.getCompatColor import io.github.wulkanowy.utils.getCompatColor
import io.reactivex.Completable import io.reactivex.Completable
import javax.inject.Inject import javax.inject.Inject
@ -48,8 +48,8 @@ class MessageWork @Inject constructor(
.setPriority(PRIORITY_HIGH) .setPriority(PRIORITY_HIGH)
.setColor(context.getCompatColor(R.color.colorPrimary)) .setColor(context.getCompatColor(R.color.colorPrimary))
.setContentIntent( .setContentIntent(
PendingIntent.getActivity(context, 0, PendingIntent.getActivity(context, MenuView.MESSAGE.id,
MainActivity.getStartIntent(context).putExtra(EXTRA_START_MENU_INDEX, 4), FLAG_UPDATE_CURRENT) MainActivity.getStartIntent(context, MenuView.MESSAGE, true), FLAG_UPDATE_CURRENT)
) )
.setStyle(NotificationCompat.InboxStyle().run { .setStyle(NotificationCompat.InboxStyle().run {
setSummaryText(context.resources.getQuantityString(R.plurals.message_number_item, messages.size, messages.size)) setSummaryText(context.resources.getQuantityString(R.plurals.message_number_item, messages.size, messages.size))

View File

@ -15,7 +15,7 @@ import io.github.wulkanowy.data.repositories.note.NoteRepository
import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository
import io.github.wulkanowy.services.sync.channels.NewEntriesChannel import io.github.wulkanowy.services.sync.channels.NewEntriesChannel
import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.main.MainActivity.Companion.EXTRA_START_MENU_INDEX import io.github.wulkanowy.ui.modules.main.MainView.MenuView
import io.github.wulkanowy.utils.getCompatColor import io.github.wulkanowy.utils.getCompatColor
import io.reactivex.Completable import io.reactivex.Completable
import javax.inject.Inject import javax.inject.Inject
@ -47,9 +47,8 @@ class NoteWork @Inject constructor(
.setPriority(PRIORITY_HIGH) .setPriority(PRIORITY_HIGH)
.setColor(context.getCompatColor(R.color.colorPrimary)) .setColor(context.getCompatColor(R.color.colorPrimary))
.setContentIntent( .setContentIntent(
PendingIntent.getActivity(context, 0, PendingIntent.getActivity(context, MenuView.NOTE.id,
MainActivity.getStartIntent(context).putExtra(EXTRA_START_MENU_INDEX, 4), FLAG_UPDATE_CURRENT) MainActivity.getStartIntent(context, MenuView.NOTE, true), FLAG_UPDATE_CURRENT))
)
.setStyle(NotificationCompat.InboxStyle().run { .setStyle(NotificationCompat.InboxStyle().run {
setSummaryText(context.resources.getQuantityString(R.plurals.note_number_item, notes.size, notes.size)) setSummaryText(context.resources.getQuantityString(R.plurals.note_number_item, notes.size, notes.size))
notes.forEach { addLine("${it.teacher}: ${it.category}") } notes.forEach { addLine("${it.teacher}: ${it.category}") }

View File

@ -13,10 +13,10 @@ class RecipientWork @Inject constructor(
) : Work { ) : Work {
override fun create(student: Student, semester: Semester): Completable { override fun create(student: Student, semester: Semester): Completable {
return reportingUnitRepository.getReportingUnits(student) return reportingUnitRepository.getReportingUnits(student, true)
.flatMapCompletable { units -> .flatMapCompletable { units ->
Completable.mergeDelayError(units.map { Completable.mergeDelayError(units.map {
recipientRepository.getRecipients(student, 2, it).ignoreElement() recipientRepository.getRecipients(student, 2, it, true).ignoreElement()
}) })
} }
} }

View File

@ -7,7 +7,7 @@ import io.github.wulkanowy.data.db.SharedPrefHelper
import io.github.wulkanowy.data.repositories.semester.SemesterRepository import io.github.wulkanowy.data.repositories.semester.SemesterRepository
import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.data.repositories.student.StudentRepository
import io.github.wulkanowy.data.repositories.timetable.TimetableRepository import io.github.wulkanowy.data.repositories.timetable.TimetableRepository
import io.github.wulkanowy.ui.widgets.timetable.TimetableWidgetFactory import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetFactory
import io.github.wulkanowy.utils.SchedulersProvider import io.github.wulkanowy.utils.SchedulersProvider
import javax.inject.Inject import javax.inject.Inject

View File

@ -1,34 +1,82 @@
package io.github.wulkanowy.ui.base package io.github.wulkanowy.ui.base
import android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import androidx.fragment.app.Fragment
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import com.google.android.material.snackbar.Snackbar.LENGTH_LONG import com.google.android.material.snackbar.Snackbar.LENGTH_LONG
import dagger.android.support.DaggerAppCompatActivity import dagger.android.AndroidInjection
import dagger.android.DispatchingAndroidInjector
import dagger.android.support.HasSupportFragmentInjector
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.ui.modules.login.LoginActivity
import io.github.wulkanowy.utils.FragmentLifecycleLogger
import javax.inject.Inject
abstract class BaseActivity : DaggerAppCompatActivity(), BaseView { abstract class BaseActivity<T : BasePresenter<out BaseView>> : AppCompatActivity(), BaseView,
HasSupportFragmentInjector {
protected lateinit var messageContainer: View @Inject
lateinit var supportFragmentInjector: DispatchingAndroidInjector<Fragment>
@Inject
lateinit var fragmentLifecycleLogger: FragmentLifecycleLogger
@Inject
lateinit var themeManager: ThemeManager
protected var messageContainer: View? = null
abstract var presenter: T
public override fun onCreate(savedInstanceState: Bundle?) { public override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this)
themeManager.applyTheme(this)
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleLogger, true)
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true) AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
} }
override fun showError(text: String, error: Throwable) { override fun showError(text: String, error: Throwable) {
Snackbar.make(messageContainer, text, LENGTH_LONG).setAction(R.string.all_details) { if (messageContainer != null) {
ErrorDialog.newInstance(error).show(supportFragmentManager, error.toString()) Snackbar.make(messageContainer!!, text, LENGTH_LONG)
}.show() .setAction(R.string.all_details) {
ErrorDialog.newInstance(error).show(supportFragmentManager, error.toString())
}
.show()
} else showMessage(text)
} }
override fun showMessage(text: String) { override fun showMessage(text: String) {
Snackbar.make(messageContainer, text, LENGTH_LONG).show() if (messageContainer != null) Snackbar.make(messageContainer!!, text, LENGTH_LONG).show()
else Toast.makeText(this, text, Toast.LENGTH_LONG).show()
}
override fun showExpiredDialog() {
AlertDialog.Builder(this)
.setTitle(R.string.main_session_expired)
.setMessage(R.string.main_session_relogin)
.setPositiveButton(R.string.main_log_in) { _, _ -> presenter.onExpiredLoginSelected() }
.setNegativeButton(android.R.string.cancel) { _, _ -> }
.show()
}
override fun openClearLoginView() {
startActivity(LoginActivity.getStartIntent(this)
.apply { addFlags(FLAG_ACTIVITY_CLEAR_TASK or FLAG_ACTIVITY_NEW_TASK) })
} }
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
invalidateOptionsMenu() invalidateOptionsMenu()
presenter.onDetachView()
} }
override fun supportFragmentInjector() = supportFragmentInjector
} }

View File

@ -2,6 +2,7 @@ package io.github.wulkanowy.ui.base
import android.view.View import android.view.View
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import com.google.android.material.snackbar.Snackbar.LENGTH_LONG
import dagger.android.support.DaggerFragment import dagger.android.support.DaggerFragment
import io.github.wulkanowy.R import io.github.wulkanowy.R
@ -10,16 +11,30 @@ abstract class BaseFragment : DaggerFragment(), BaseView {
protected var messageContainer: View? = null protected var messageContainer: View? = null
override fun showError(text: String, error: Throwable) { override fun showError(text: String, error: Throwable) {
if (messageContainer == null) (activity as? BaseActivity)?.showError(text, error) if (messageContainer != null) {
else messageContainer?.also { Snackbar.make(messageContainer!!, text, LENGTH_LONG)
Snackbar.make(it, text, Snackbar.LENGTH_LONG).setAction(R.string.all_details) { .setAction(R.string.all_details) {
ErrorDialog.newInstance(error).show(childFragmentManager, error.toString()) if (isAdded) ErrorDialog.newInstance(error).show(childFragmentManager, error.toString())
}.show() }
.show()
} else {
(activity as? BaseActivity<*>)?.showError(text, error)
} }
} }
override fun showMessage(text: String) { override fun showMessage(text: String) {
if (messageContainer == null) (activity as? BaseActivity)?.showMessage(text) if (messageContainer != null) {
else messageContainer?.also { Snackbar.make(it, text, Snackbar.LENGTH_LONG).show() } Snackbar.make(messageContainer!!, text, LENGTH_LONG).show()
} else {
(activity as? BaseActivity<*>)?.showMessage(text)
}
}
override fun showExpiredDialog() {
(activity as? BaseActivity<*>)?.showExpiredDialog()
}
override fun openClearLoginView() {
(activity as? BaseActivity<*>)?.openClearLoginView()
} }
} }

View File

@ -1,8 +1,16 @@
package io.github.wulkanowy.ui.base package io.github.wulkanowy.ui.base
import io.github.wulkanowy.data.repositories.student.StudentRepository
import io.github.wulkanowy.utils.SchedulersProvider
import io.reactivex.Completable
import io.reactivex.disposables.CompositeDisposable import io.reactivex.disposables.CompositeDisposable
import timber.log.Timber
open class BasePresenter<T : BaseView>(private val errorHandler: ErrorHandler) { open class BasePresenter<T : BaseView>(
protected val errorHandler: ErrorHandler,
protected val studentRepository: StudentRepository,
protected val schedulers: SchedulersProvider
) {
val disposable = CompositeDisposable() val disposable = CompositeDisposable()
@ -10,7 +18,33 @@ open class BasePresenter<T : BaseView>(private val errorHandler: ErrorHandler) {
open fun onAttachView(view: T) { open fun onAttachView(view: T) {
this.view = view this.view = view
errorHandler.showErrorMessage = { text, error -> view.showError(text, error) } errorHandler.apply {
showErrorMessage = view::showError
onSessionExpired = view::showExpiredDialog
onNoCurrentStudent = view::openClearLoginView
}
}
fun onExpiredLoginSelected() {
Timber.i("Attempt to switch the student after the session expires")
disposable.add(studentRepository.getCurrentStudent(false)
.flatMapCompletable { studentRepository.logoutStudent(it) }
.andThen(studentRepository.getSavedStudents(false))
.flatMapCompletable {
if (it.isNotEmpty()) {
Timber.i("Switching current student")
studentRepository.switchStudent(it[0])
} else Completable.complete()
}
.subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread)
.subscribe({
Timber.i("Switch student result: Open login view")
view?.openClearLoginView()
}, {
Timber.i("Switch student result: An exception occurred")
errorHandler.dispatch(it)
}))
} }
open fun onDetachView() { open fun onDetachView() {

View File

@ -5,4 +5,8 @@ interface BaseView {
fun showError(text: String, error: Throwable) fun showError(text: String, error: Throwable)
fun showMessage(text: String) fun showMessage(text: String)
fun showExpiredDialog()
fun openClearLoginView()
} }

View File

@ -5,7 +5,10 @@ import com.readystatesoftware.chuck.api.ChuckCollector
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.api.interceptor.FeatureDisabledException import io.github.wulkanowy.api.interceptor.FeatureDisabledException
import io.github.wulkanowy.api.interceptor.ServiceUnavailableException import io.github.wulkanowy.api.interceptor.ServiceUnavailableException
import io.github.wulkanowy.api.login.BadCredentialsException
import io.github.wulkanowy.api.login.NotLoggedInException import io.github.wulkanowy.api.login.NotLoggedInException
import io.github.wulkanowy.data.exceptions.NoCurrentStudentException
import io.github.wulkanowy.utils.security.ScramblerException
import timber.log.Timber import timber.log.Timber
import java.net.SocketTimeoutException import java.net.SocketTimeoutException
import java.net.UnknownHostException import java.net.UnknownHostException
@ -15,6 +18,10 @@ open class ErrorHandler @Inject constructor(protected val resources: Resources,
var showErrorMessage: (String, Throwable) -> Unit = { _, _ -> } var showErrorMessage: (String, Throwable) -> Unit = { _, _ -> }
var onSessionExpired: () -> Unit = {}
var onNoCurrentStudent: () -> Unit = {}
fun dispatch(error: Throwable) { fun dispatch(error: Throwable) {
chuckCollector.onError(error.javaClass.simpleName, error) chuckCollector.onError(error.javaClass.simpleName, error)
Timber.e(error, "An exception occurred while the Wulkanowy was running") Timber.e(error, "An exception occurred while the Wulkanowy was running")
@ -22,17 +29,23 @@ open class ErrorHandler @Inject constructor(protected val resources: Resources,
} }
protected open fun proceed(error: Throwable) { protected open fun proceed(error: Throwable) {
showErrorMessage((when (error) { resources.run {
is UnknownHostException -> resources.getString(R.string.error_no_internet) when (error) {
is SocketTimeoutException -> resources.getString(R.string.error_timeout) is UnknownHostException -> showErrorMessage(getString(R.string.error_no_internet), error)
is NotLoggedInException -> resources.getString(R.string.error_login_failed) is SocketTimeoutException -> showErrorMessage(getString(R.string.error_timeout), error)
is ServiceUnavailableException -> resources.getString(R.string.error_service_unavailable) is NotLoggedInException -> showErrorMessage(getString(R.string.error_login_failed), error)
is FeatureDisabledException -> resources.getString(R.string.error_feature_disabled) is ServiceUnavailableException -> showErrorMessage(getString(R.string.error_service_unavailable), error)
else -> resources.getString(R.string.error_unknown) is FeatureDisabledException -> showErrorMessage(getString(R.string.error_feature_disabled), error)
}), error) is ScramblerException, is BadCredentialsException -> onSessionExpired()
is NoCurrentStudentException -> onNoCurrentStudent()
else -> showErrorMessage(getString(R.string.error_unknown), error)
}
}
} }
open fun clear() { open fun clear() {
showErrorMessage = { _, _ -> } showErrorMessage = { _, _ -> }
onSessionExpired = {}
onNoCurrentStudent = {}
} }
} }

Some files were not shown because too many files have changed in this diff Show More