forked from github/wulkanowy-mirror
Compare commits
136 Commits
Author | SHA1 | Date | |
---|---|---|---|
750fa9a76d | |||
d0ad4d9364 | |||
4e0f41dcb2 | |||
c3bb489851 | |||
e64e6676f3 | |||
108440b1ca | |||
d54f5ed1aa | |||
0aac6459f3 | |||
bb9ea7eda1 | |||
455b04f183 | |||
39fb4f5def | |||
c60428e29b | |||
5413ecabb4 | |||
c6d9dfa0c9 | |||
b0033af048 | |||
405d37e822 | |||
0d514b7dc9 | |||
c3596aa45c | |||
d34bdb2ce3 | |||
c91870cc04 | |||
2f6862967e | |||
7525395665 | |||
657e2b280e | |||
4ea9be582f | |||
3dcc8247bf | |||
c45b7c04b9 | |||
33d6ae8afc | |||
6871606bef | |||
6e266acec9 | |||
ba84f2be6e | |||
db7b7dbadf | |||
6bd07d2651 | |||
070fba734c | |||
72ef366829 | |||
4adfb268a3 | |||
bf0ea1b012 | |||
1267a39e32 | |||
462b917832 | |||
c5b16bb0d0 | |||
91268dcc67 | |||
13f9981be6 | |||
f9b3bd7b3a | |||
39916c2796 | |||
03f5ddaf0d | |||
ea4f55c06d | |||
5b0232f77e | |||
0b68091e55 | |||
7136c9282e | |||
09303153a5 | |||
bdf0fba95b | |||
86f24e5821 | |||
35f094b983 | |||
12cf1e0b66 | |||
68b37fc5dd | |||
ba5bad042a | |||
c5323ee811 | |||
df9c685217 | |||
73fa21d45f | |||
344fa1bbd7 | |||
01318c8c29 | |||
851486df28 | |||
d8b3c5d9d6 | |||
2883b21ddf | |||
4956cf3988 | |||
bdbcec786a | |||
c40bbf2398 | |||
08ecbb5341 | |||
e38e458386 | |||
14674b7778 | |||
c6f0588165 | |||
7591af0de1 | |||
cbfed09b52 | |||
5c185c5eca | |||
4a026e4a70 | |||
0bccbc6011 | |||
ed6a0f8cd0 | |||
58d5e4da0e | |||
ba6fb1a4b9 | |||
b6b862d4c3 | |||
9b044a19fe | |||
7485cb2a39 | |||
75122d0dcd | |||
39af56484b | |||
05f7e1d115 | |||
9cd5377438 | |||
7a61b233f0 | |||
83dbd9874e | |||
1d9a49d552 | |||
6175081b88 | |||
116e551186 | |||
713500d892 | |||
dfdbe374c2 | |||
b1e3bab5e7 | |||
0b2ef367da | |||
28f27db2b5 | |||
5c70cd8b8c | |||
0f75ff3206 | |||
383cab4dae | |||
e1b0db76c2 | |||
f10684097c | |||
35d4342bec | |||
068aee215a | |||
033074c66b | |||
0de534cb66 | |||
26e4619dde | |||
cf08d1ff24 | |||
96c400a0bd | |||
e238f65dde | |||
ba7966125b | |||
1536208e45 | |||
24cc07264a | |||
1ad5232520 | |||
07f3333029 | |||
214afb82a6 | |||
7a69710261 | |||
64d3afbdb3 | |||
387e46f72d | |||
89acc2f384 | |||
4ef1439878 | |||
6efd170e03 | |||
82cd39329a | |||
b38e0d04e7 | |||
90b2ffe250 | |||
33951dff54 | |||
9ced00c4d7 | |||
2ac1638911 | |||
463a7e2744 | |||
d14382f3b4 | |||
efa0b19cc1 | |||
7cb893a254 | |||
f7371f7b73 | |||
9c626ad517 | |||
9c4c6b0192 | |||
8efc4d750d | |||
e4a6caa13e | |||
209e75160a |
@ -7,7 +7,7 @@ references:
|
||||
|
||||
container_config: &container_config
|
||||
docker:
|
||||
- image: circleci/android:api-28
|
||||
- image: circleci/android@sha256:5cdc8626cc6f13efe5ed982cdcdb432b0472f8740fed8743a6461e025ad6cdfc
|
||||
working_directory: *workspace_root
|
||||
environment:
|
||||
environment:
|
||||
@ -35,7 +35,7 @@ jobs:
|
||||
command: ./gradlew dependencies --no-daemon --stacktrace --console=plain -PdisablePreDex || true
|
||||
- run:
|
||||
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:
|
||||
name: Run FOSSA
|
||||
command: fossa --no-ansi || true
|
||||
@ -56,7 +56,7 @@ jobs:
|
||||
<<: *general_cache_key
|
||||
- run:
|
||||
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:
|
||||
path: ./app/build/reports/
|
||||
destination: lint_reports/app/
|
||||
@ -75,7 +75,7 @@ jobs:
|
||||
<<: *general_cache_key
|
||||
- run:
|
||||
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:
|
||||
name: Upload unit code coverage to codecov
|
||||
command: bash <(curl -s https://codecov.io/bash) -F app
|
||||
@ -93,6 +93,9 @@ jobs:
|
||||
<<: *container_config
|
||||
steps:
|
||||
- *attach_workspace
|
||||
- run:
|
||||
name: Accept licenses
|
||||
command: yes | sdkmanager --licenses && yes | sdkmanager --update
|
||||
- run:
|
||||
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"
|
||||
@ -113,7 +116,7 @@ jobs:
|
||||
adb shell input keyevent 82
|
||||
- run:
|
||||
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:
|
||||
name: Collect logs from emulator
|
||||
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
|
||||
- run:
|
||||
name: Publish release
|
||||
command: ./gradlew publish --no-daemon --stacktrace --console=plain -PenableCrashlytics -PdisablePreDex
|
||||
command: ./gradlew publishPlayRelease --no-daemon --stacktrace --console=plain -PenableCrashlytics -PdisablePreDex
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
|
113
.gitignore
vendored
113
.gitignore
vendored
@ -1,39 +1,90 @@
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
|
||||
## https://gist.github.com/iainconnor/8605514
|
||||
# Created by https://www.gitignore.io
|
||||
|
||||
# Built application files
|
||||
/build
|
||||
/*/build/
|
||||
*.apk
|
||||
*.ap_
|
||||
*.aab
|
||||
|
||||
# Crashlytics configuations
|
||||
com_crashlytics_export_strings.xml
|
||||
# Files for the ART/Dalvik VM
|
||||
*.dex
|
||||
|
||||
# Java class files
|
||||
*.class
|
||||
|
||||
# Generated files
|
||||
bin/
|
||||
gen/
|
||||
out/
|
||||
|
||||
# Gradle files
|
||||
.gradle/
|
||||
build/
|
||||
|
||||
# Local configuration file (sdk path, etc)
|
||||
local.properties
|
||||
|
||||
# Gradle generated files
|
||||
.gradle/
|
||||
# Proguard folder generated by Eclipse
|
||||
proguard/
|
||||
|
||||
# Signing files
|
||||
.signing/
|
||||
# Log Files
|
||||
*.log
|
||||
|
||||
# User-specific configurations
|
||||
.idea/copyright/profiles_settings.xml
|
||||
# Android Studio Navigation editor temp files
|
||||
.navigation/
|
||||
|
||||
# Android Studio captures folder
|
||||
captures/
|
||||
|
||||
# IntelliJ configurations
|
||||
*.iml
|
||||
.idea/workspace.xml
|
||||
.idea/tasks.xml
|
||||
.idea/gradle.xml
|
||||
.idea/assetWizardSettings.xml
|
||||
.idea/dictionaries
|
||||
.idea/libraries
|
||||
.idea/caches
|
||||
.idea/modules.xml
|
||||
.idea/navEditor.xml
|
||||
.idea/caches/
|
||||
.idea/libraries/
|
||||
.idea/inspectionProfiles/
|
||||
.idea/shelf/
|
||||
.idea/.name
|
||||
.idea/compiler.xml
|
||||
.idea/copyright/profiles_settings.xml
|
||||
.idea/encodings.xml
|
||||
.idea/misc.xml
|
||||
.idea/modules.xml
|
||||
.idea/scopes/scope_settings.xml
|
||||
.idea/tasks.xml
|
||||
.idea/vcs.xml
|
||||
.idea/workspace.xml
|
||||
.idea/caches/
|
||||
*.iml
|
||||
.idea/jsLibraryMappings.xml
|
||||
.idea/datasources.xml
|
||||
.idea/dataSources.ids
|
||||
.idea/sqlDataSources.xml
|
||||
.idea/dynamic.xml
|
||||
.idea/uiDesigner.xml
|
||||
.idea/runConfigurations.xml
|
||||
|
||||
# Keystore files
|
||||
*.jks
|
||||
*.keystore
|
||||
*.p12
|
||||
|
||||
# External native build folder generated in Android Studio 2.2 and later
|
||||
.externalNativeBuild
|
||||
|
||||
# Version control
|
||||
vcs.xml
|
||||
|
||||
# lint
|
||||
lint/intermediates/
|
||||
lint/generated/
|
||||
lint/outputs/
|
||||
lint/tmp/
|
||||
lint/reports/
|
||||
|
||||
### Android Patch ###
|
||||
gen-external-apklibs
|
||||
output.json
|
||||
|
||||
# OS-specific files
|
||||
.DS_Store
|
||||
@ -43,8 +94,20 @@ local.properties
|
||||
.Trashes
|
||||
ehthumbs.db
|
||||
Thumbs.db
|
||||
.idea/caches/
|
||||
app/key.p12
|
||||
app/upload-key.jks
|
||||
*.log
|
||||
.idea/assetWizardSettings.xml
|
||||
|
||||
# Legacy Eclipse project files
|
||||
.classpath
|
||||
.project
|
||||
.cproject
|
||||
.settings/
|
||||
|
||||
# Mobile Tools for Java (J2ME)
|
||||
.mtj.tmp/
|
||||
|
||||
# Package Files #
|
||||
*.war
|
||||
*.ear
|
||||
|
||||
### AndroidStudio Patch ###
|
||||
|
||||
!/gradle/wrapper/gradle-wrapper.jar
|
@ -12,7 +12,7 @@ build:
|
||||
script:
|
||||
- ./gradlew --no-daemon --stacktrace dependencies || true
|
||||
- ./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:
|
||||
name: "${CI_PROJECT_NAME}_${CI_BUILD_REF_NAME}-${CI_BUILD_ID}"
|
||||
paths:
|
||||
@ -26,7 +26,7 @@ tests:
|
||||
- .gradle
|
||||
policy: pull
|
||||
script:
|
||||
- ./gradlew --no-daemon --stacktrace -x fabricGenerateResourcesRelease test
|
||||
- ./gradlew --no-daemon --stacktrace -x fabricGenerateResourcesFdroidRelease -x fabricGenerateResourcesPlayRelease test
|
||||
artifacts:
|
||||
paths:
|
||||
- app/build/reports/tests
|
||||
@ -39,7 +39,7 @@ lint:
|
||||
- .gradle
|
||||
policy: pull
|
||||
script:
|
||||
- ./gradlew --no-daemon --stacktrace -x fabricGenerateResourcesRelease lint
|
||||
- ./gradlew --no-daemon --stacktrace -x fabricGenerateResourcesFdroidRelease -x fabricGenerateResourcesPlayRelease lint
|
||||
artifacts:
|
||||
paths:
|
||||
- app/build/reports
|
||||
|
22
.idea/codeStyles/Project.xml
generated
22
.idea/codeStyles/Project.xml
generated
@ -1,9 +1,6 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<code_scheme name="Project" version="173">
|
||||
<option name="LINE_SEPARATOR" value=" " />
|
||||
<AndroidXmlCodeStyleSettings>
|
||||
<option name="USE_CUSTOM_SETTINGS" value="true" />
|
||||
</AndroidXmlCodeStyleSettings>
|
||||
<JetCodeStyleSettings>
|
||||
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
|
||||
<value>
|
||||
@ -21,13 +18,10 @@
|
||||
<option name="CONTINUATION_INDENT_IN_ELVIS" value="false" />
|
||||
<option name="WRAP_ELVIS_EXPRESSIONS" value="0" />
|
||||
</JetCodeStyleSettings>
|
||||
<XML>
|
||||
<option name="XML_KEEP_LINE_BREAKS" value="false" />
|
||||
<option name="XML_ALIGN_ATTRIBUTES" value="false" />
|
||||
<option name="XML_SPACE_INSIDE_EMPTY_TAG" value="true" />
|
||||
</XML>
|
||||
<MarkdownNavigatorCodeStyleSettings>
|
||||
<option name="RIGHT_MARGIN" value="72" />
|
||||
</MarkdownNavigatorCodeStyleSettings>
|
||||
<codeStyleSettings language="XML">
|
||||
<option name="FORCE_REARRANGE_MODE" value="1" />
|
||||
<indentOptions>
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
</indentOptions>
|
||||
@ -38,6 +32,7 @@
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>xmlns:android</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
@ -48,6 +43,7 @@
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>xmlns:.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
@ -59,6 +55,7 @@
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:id</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
@ -69,6 +66,7 @@
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:name</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
@ -79,6 +77,7 @@
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>name</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
@ -89,6 +88,7 @@
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>style</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
@ -99,6 +99,7 @@
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
@ -110,6 +111,7 @@
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
@ -121,6 +123,7 @@
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>.*</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
@ -138,7 +141,6 @@
|
||||
<option name="METHOD_PARAMETERS_LPAREN_ON_NEXT_LINE" value="true" />
|
||||
<option name="METHOD_PARAMETERS_RPAREN_ON_NEXT_LINE" value="true" />
|
||||
<option name="EXTENDS_LIST_WRAP" value="1" />
|
||||
<option name="METHOD_CALL_CHAIN_WRAP" value="1" />
|
||||
<indentOptions>
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
</indentOptions>
|
||||
|
18
.idea/gradle.xml
generated
18
.idea/gradle.xml
generated
@ -1,18 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
<option value="$PROJECT_DIR$/app" />
|
||||
</set>
|
||||
</option>
|
||||
<option name="resolveModulePerSourceSet" value="false" />
|
||||
</GradleProjectSettings>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
12
.idea/runConfigurations.xml
generated
12
.idea/runConfigurations.xml
generated
@ -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>
|
18
.travis.yml
18
.travis.yml
@ -11,10 +11,10 @@ cache:
|
||||
- $HOME/.gradle/caches/
|
||||
- $HOME/.gradle/wrapper/
|
||||
|
||||
#branches:
|
||||
# only:
|
||||
# - master
|
||||
# - 0.8.x
|
||||
branches:
|
||||
only:
|
||||
- develop
|
||||
- 0.10.0
|
||||
|
||||
android:
|
||||
licenses:
|
||||
@ -48,20 +48,20 @@ before_script:
|
||||
script:
|
||||
- ./gradlew dependencies --stacktrace --daemon
|
||||
- fossa --no-ansi || true
|
||||
- ./gradlew lint -x fabricGenerateResourcesRelease --stacktrace --daemon
|
||||
- ./gradlew test -x fabricGenerateResourcesRelease --stacktrace --daemon
|
||||
- ./gradlew createDebugCoverageReport --stacktrace --daemon
|
||||
#- ./gradlew lintPlayRelease -x fabricGenerateResourcesPlayRelease --stacktrace --daemon
|
||||
- ./gradlew testPlayDebugUnitTest -x fabricGenerateResourcesPlay --stacktrace --daemon
|
||||
- ./gradlew createPlayDebugCoverageReport --stacktrace --daemon
|
||||
- ./gradlew jacocoTestReport --stacktrace --daemon
|
||||
- if [ -z ${SONAR_HOST+x} ]; then echo "sonar scan skipped"; else
|
||||
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
|
||||
- |
|
||||
if [ $TRAVIS_TAG ]; then
|
||||
gpg --yes --batch --passphrase=$SERVICES_ENCRYPT_KEY ./app/src/release/google-services.json.gpg;
|
||||
gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/key.p12.gpg;
|
||||
gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/upload-key.jks.gpg;
|
||||
./gradlew publish -PenableCrashlytics --stacktrace;
|
||||
./gradlew publishPlayRelease -PenableCrashlytics --stacktrace;
|
||||
fi
|
||||
|
||||
after_success:
|
||||
|
2
LICENSE
2
LICENSE
@ -186,7 +186,7 @@
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2017 wulkanowy
|
||||
Copyright 2019 Wulkanowy
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
61
README.en.md
Normal file
61
README.en.md
Normal file
@ -0,0 +1,61 @@
|
||||
[Polska wersja README](README.md)
|
||||
|
||||
# Wulkanowy
|
||||
[](https://travis-ci.com/wulkanowy/wulkanowy)
|
||||
[](https://codecov.io/gh/wulkanowy/wulkanowy)
|
||||
[](https://discord.gg/vccAQBr)
|
||||
[](https://f-droid.org/packages/io.github.wulkanowy/)
|
||||
[](https://github.com/wulkanowy/wulkanowy/releases)
|
||||
|
||||
Unofficial android VULCAN UONET+ register client for student and parent
|
||||
|
||||
## Features
|
||||
|
||||
* logging in using the email and password
|
||||
* functions from the register website:
|
||||
* grades
|
||||
* grade statistics
|
||||
* attendance
|
||||
* percentage of attendance
|
||||
* exams
|
||||
* timetable
|
||||
* completed lessons
|
||||
* messages
|
||||
* homework
|
||||
* notes
|
||||
* lucky number
|
||||
* calculation of the average
|
||||
* notifications, e.g. about a new grade
|
||||
* dark and black (AMOLED) theme
|
||||
* offline mode
|
||||
* no ads
|
||||
|
||||
## Download
|
||||
|
||||
You can download the current beta from the Google Play or Fdroid store
|
||||
|
||||
[<img src="https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png"
|
||||
alt="Get it on Google Play"
|
||||
height="80">](https://play.google.com/store/apps/details?id=io.github.wulkanowy)
|
||||
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
|
||||
alt="Get it on Fdroid"
|
||||
height="80">](https://f-droid.org/packages/io.github.wulkanowy/)
|
||||
|
||||
You can also download a [development version](https://wulkanowy.github.io/#download) that includes new features prepared for the next release
|
||||
|
||||
## Built With
|
||||
|
||||
|
||||
* [Wulkanowy API](https://github.com/wulkanowy/api)
|
||||
* [RxJava 2](https://github.com/ReactiveX/RxJava)
|
||||
* [Dagger 2](https://github.com/google/dagger)
|
||||
* [Room](https://developer.android.com/topic/libraries/architecture/room)
|
||||
* [WorkManager](https://developer.android.com/topic/libraries/architecture/workmanager)
|
||||
|
||||
## Contributing
|
||||
|
||||
Please contribute to the project either by creating a PR or submitting an issue on GitHub.
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details
|
64
README.md
64
README.md
@ -1,22 +1,62 @@
|
||||
[English version of README](README.en.md)
|
||||
|
||||
# Wulkanowy
|
||||
|
||||
[](https://circleci.com/gh/wulkanowy/wulkanowy)
|
||||
[](https://travis-ci.com/wulkanowy/wulkanowy)
|
||||
[](https://www.bitrise.io/app/daeff1893f3c8128)
|
||||
[](https://codecov.io/gh/wulkanowy/wulkanowy)
|
||||
[](https://bettercodehub.com/)
|
||||
[](https://sonarcloud.io/dashboard?id=io.github.wulkanowy%3Aapp)
|
||||
[](https://app.fossa.com/projects/custom%2B5644%2Fgithub.com%2Fwulkanowy%2Fwulkanowy?ref=badge_shield)
|
||||
[](https://discord.gg/vccAQBr)
|
||||
[](https://f-droid.org/packages/io.github.wulkanowy/)
|
||||
[](https://github.com/wulkanowy/wulkanowy/releases)
|
||||
|
||||
[Pobierz wersję beta z Google Play](https://play.google.com/store/apps/details?id=io.github.wulkanowy&utm_source=vcs)
|
||||
Nieoficjalny klient dziennika VULCAN UONET+ dla ucznia i rodzica
|
||||
|
||||
[Pobierz wersję DEV](https://bitrise-redirector.herokuapp.com/v0.1/apps/f841f20d8f8b1dc8/builds/master/artifacts/0)
|
||||
[(Więcej wersji DEV)](https://wulkanowy.github.io/dev.html)
|
||||
## Funkcje
|
||||
|
||||
Androidowy klient dziennika VULCAN UONET+.
|
||||
* logowanie za pomocą e-maila i hasła
|
||||
* funkcje ze strony internetowej dziennika:
|
||||
* oceny
|
||||
* statystyki ocen
|
||||
* frekwencja
|
||||
* procent frekwencji
|
||||
* sprawdziany
|
||||
* plan lekcji
|
||||
* lekcje zrealizowane
|
||||
* wiadomości
|
||||
* zadania domowe
|
||||
* uwagi
|
||||
* szczęśliwy numerek
|
||||
* obliczanie średniej
|
||||
* powiadomienia np. o nowej ocenie
|
||||
* ciemny i czarny (AMOLED) motyw
|
||||
* tryb offilne
|
||||
* brak reklam
|
||||
|
||||
## Pobierz
|
||||
|
||||
Aktualną wersję beta możesz pobrać ze sklepu Google Play lub Fdroid
|
||||
|
||||
[<img src="https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png"
|
||||
alt="Pobierz z Google Play"
|
||||
height="80">](https://play.google.com/store/apps/details?id=io.github.wulkanowy)
|
||||
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
|
||||
alt="Pobierz z Fdroid"
|
||||
height="80">](https://f-droid.org/packages/io.github.wulkanowy/)
|
||||
|
||||
|
||||
## License
|
||||
Możesz także pobrać [wersję rozwojową](https://wulkanowy.github.io/#download), która zawiera nowe funkcje przygotowywane do następnego wydania
|
||||
|
||||
[](https://app.fossa.com/projects/custom%2B5644%2Fgithub.com%2Fwulkanowy%2Fwulkanowy?ref=badge_large)
|
||||
|
||||
## Zbudowana za pomocą
|
||||
|
||||
* [Wulkanowy API](https://github.com/wulkanowy/api)
|
||||
* [RxJava 2](https://github.com/ReactiveX/RxJava)
|
||||
* [Dagger 2](https://github.com/google/dagger)
|
||||
* [Room](https://developer.android.com/topic/libraries/architecture/room)
|
||||
* [WorkManager](https://developer.android.com/topic/libraries/architecture/workmanager)
|
||||
|
||||
## Współpraca
|
||||
|
||||
Wnieś swój wkład w projekt, tworząc PR lub wysyłając issue na GitHub.
|
||||
|
||||
## Licencja
|
||||
|
||||
Ten projekt jest licencjonowany w ramach Apache License 2.0 - szczegóły w pliku [LICENSE](LICENSE)
|
1
app/.gitignore
vendored
1
app/.gitignore
vendored
@ -1 +0,0 @@
|
||||
/build
|
145
app/build.gradle
145
app/build.gradle
@ -6,6 +6,7 @@ apply plugin: 'io.fabric'
|
||||
apply plugin: 'com.github.triplet.play'
|
||||
apply from: 'jacoco.gradle'
|
||||
apply from: 'sonarqube.gradle'
|
||||
apply from: 'hooks.gradle'
|
||||
|
||||
android {
|
||||
compileSdkVersion 28
|
||||
@ -14,10 +15,10 @@ android {
|
||||
defaultConfig {
|
||||
applicationId "io.github.wulkanowy"
|
||||
testApplicationId "io.github.tests.wulkanowy"
|
||||
minSdkVersion 15
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 28
|
||||
versionCode 36
|
||||
versionName "0.8.3"
|
||||
versionCode 43
|
||||
versionName "0.10.0"
|
||||
multiDexEnabled true
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
@ -27,7 +28,8 @@ android {
|
||||
]
|
||||
javaCompileOptions {
|
||||
annotationProcessorOptions {
|
||||
arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]
|
||||
arguments = ["room.schemaLocation": "$projectDir/schemas".toString(),
|
||||
"room.incremental" : "true"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -63,6 +65,19 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
flavorDimensions "platform"
|
||||
|
||||
productFlavors {
|
||||
play {
|
||||
dimension "platform"
|
||||
}
|
||||
|
||||
fdroid {
|
||||
buildConfigField "boolean", "CRASHLYTICS_ENABLED", "false"
|
||||
dimension "platform"
|
||||
}
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
disable 'HardwareIds'
|
||||
}
|
||||
@ -71,6 +86,15 @@ android {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
exclude 'META-INF/library_release.kotlin_module'
|
||||
exclude 'META-INF/library-core_release.kotlin_module'
|
||||
}
|
||||
}
|
||||
|
||||
androidExtensions {
|
||||
@ -84,71 +108,100 @@ play {
|
||||
track = 'alpha'
|
||||
}
|
||||
|
||||
ext {
|
||||
work_manager = "2.2.0"
|
||||
room = "2.2.0-beta01"
|
||||
dagger = "2.24"
|
||||
chucker = "2.0.4"
|
||||
mockk = "1.9.2"
|
||||
mockito_core = "3.0.6"
|
||||
}
|
||||
|
||||
configurations.all {
|
||||
resolutionStrategy.force "androidx.constraintlayout:constraintlayout:1.1.3"
|
||||
resolutionStrategy.force "com.google.android.material:material:1.1.0-alpha07"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'io.github.wulkanowy:api:0.8.3'
|
||||
implementation "io.github.wulkanowy:api:0.10.0"
|
||||
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||
implementation "androidx.legacy:legacy-support-v4:1.0.0"
|
||||
implementation "androidx.appcompat:appcompat:1.0.2"
|
||||
implementation 'androidx.multidex:multidex:2.0.1'
|
||||
implementation "androidx.core:core-ktx:1.1.0-rc03"
|
||||
implementation "androidx.activity:activity-ktx:1.0.0-rc01"
|
||||
implementation "androidx.appcompat:appcompat:1.1.0-rc01"
|
||||
implementation "androidx.appcompat:appcompat-resources:1.1.0-rc01"
|
||||
implementation "androidx.fragment:fragment-ktx:1.1.0-rc04"
|
||||
implementation "androidx.annotation:annotation:1.1.0"
|
||||
implementation "androidx.multidex:multidex:2.0.1"
|
||||
|
||||
implementation "androidx.cardview:cardview:1.0.0"
|
||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||
implementation "com.google.android.material:material:1.1.0-alpha05"
|
||||
implementation 'com.github.wulkanowy:MaterialChipsInput:b72fd0ee6f'
|
||||
implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0'
|
||||
implementation "androidx.preference:preference-ktx:1.1.0-rc01"
|
||||
implementation "androidx.recyclerview:recyclerview:1.1.0-beta03"
|
||||
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.1.0-beta01"
|
||||
implementation "com.google.android.material:material:1.1.0-alpha07"
|
||||
implementation "com.github.wulkanowy:material-chips-input:2.0.1"
|
||||
implementation "com.github.PhilJay:MPAndroidChart:v3.1.0"
|
||||
implementation "me.zhanghai.android.materialprogressbar:library:1.6.1"
|
||||
|
||||
implementation "androidx.work:work-runtime:2.0.1"
|
||||
implementation "androidx.work:work-rxjava2:2.0.1"
|
||||
implementation "androidx.work:work-runtime-ktx:$work_manager"
|
||||
implementation "androidx.work:work-rxjava2:$work_manager"
|
||||
implementation "androidx.work:work-gcm:$work_manager"
|
||||
|
||||
implementation "androidx.room:room-runtime:2.1.0-alpha07"
|
||||
implementation "androidx.room:room-rxjava2:2.1.0-alpha07"
|
||||
kapt "androidx.room:room-compiler:2.1.0-alpha07"
|
||||
implementation "androidx.room:room-runtime:$room"
|
||||
implementation "androidx.room:room-rxjava2:$room"
|
||||
implementation "androidx.room:room-ktx:$room"
|
||||
kapt "androidx.room:room-compiler:$room"
|
||||
|
||||
implementation "com.google.dagger:dagger-android-support:2.22.1"
|
||||
kapt "com.google.dagger:dagger-compiler:2.22.1"
|
||||
kapt "com.google.dagger:dagger-android-processor:2.22.1"
|
||||
implementation 'com.squareup.inject:assisted-inject-annotations-dagger2:0.4.0'
|
||||
kapt 'com.squareup.inject:assisted-inject-processor-dagger2:0.4.0'
|
||||
implementation "com.google.dagger:dagger-android-support:$dagger"
|
||||
kapt "com.google.dagger:dagger-compiler:$dagger"
|
||||
kapt "com.google.dagger:dagger-android-processor:$dagger"
|
||||
implementation "com.squareup.inject:assisted-inject-annotations-dagger2:0.5.0"
|
||||
kapt "com.squareup.inject:assisted-inject-processor-dagger2:0.5.0"
|
||||
|
||||
implementation "eu.davidea:flexible-adapter:5.1.0"
|
||||
implementation "eu.davidea:flexible-adapter-ui:1.0.0"
|
||||
implementation "com.aurelhubert:ahbottomnavigation:2.3.4"
|
||||
implementation 'com.ncapdevi:frag-nav:3.2.0'
|
||||
implementation "com.ncapdevi:frag-nav:3.3.0"
|
||||
|
||||
implementation 'com.github.pwittchen:reactivenetwork-rx2:3.0.2'
|
||||
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
|
||||
implementation "io.reactivex.rxjava2:rxjava:2.2.8"
|
||||
implementation "com.github.pwittchen:reactivenetwork-rx2:3.0.6"
|
||||
implementation "io.reactivex.rxjava2:rxandroid:2.1.1"
|
||||
implementation "io.reactivex.rxjava2:rxjava:2.2.12"
|
||||
|
||||
implementation 'com.google.code.gson:gson:2.8.5'
|
||||
implementation "com.jakewharton.threetenabp:threetenabp:1.2.0"
|
||||
implementation "com.google.code.gson:gson:2.8.5"
|
||||
implementation "com.jakewharton.threetenabp:threetenabp:1.2.1"
|
||||
implementation "com.jakewharton.timber:timber:4.7.1"
|
||||
implementation "at.favre.lib:slf4j-timber:1.0.1"
|
||||
implementation "com.squareup.okhttp3:logging-interceptor:3.12.1"
|
||||
implementation "com.squareup.okhttp3:logging-interceptor:3.12.3"
|
||||
implementation "com.mikepenz:aboutlibraries:7.0.3"
|
||||
|
||||
implementation "com.mikepenz:aboutlibraries:6.2.3"
|
||||
implementation 'com.takisoft.preferencex:preferencex:1.0.0'
|
||||
playImplementation "com.google.firebase:firebase-core:17.2.0"
|
||||
playImplementation "com.crashlytics.sdk.android:crashlytics:2.10.1"
|
||||
|
||||
implementation 'com.google.firebase:firebase-core:16.0.8'
|
||||
implementation 'com.crashlytics.sdk.android:crashlytics:2.9.9'
|
||||
releaseImplementation "fr.o80.chucker:library-no-op:$chucker"
|
||||
|
||||
releaseImplementation 'fr.o80.chucker:library-no-op:2.0.4'
|
||||
|
||||
debugImplementation 'fr.o80.chucker:library:2.0.4'
|
||||
debugImplementation "fr.o80.chucker:library:$chucker"
|
||||
debugImplementation "com.amitshekhar.android:debug-db:1.0.6"
|
||||
|
||||
testImplementation "junit:junit:4.12"
|
||||
testImplementation "io.mockk:mockk:1.9.2"
|
||||
testImplementation "org.mockito:mockito-inline:2.27.0"
|
||||
testImplementation 'org.threeten:threetenbp:1.3.8'
|
||||
testImplementation "io.mockk:mockk:$mockk"
|
||||
testImplementation "org.threeten:threetenbp:1.4.0"
|
||||
testImplementation "org.mockito:mockito-core:$mockito_core"
|
||||
testImplementation("org.mockito:mockito-inline:3.0.6") {
|
||||
exclude group: "org.mockito", module: "mockito-core"
|
||||
}
|
||||
|
||||
androidTestImplementation 'androidx.test:core:1.1.0'
|
||||
androidTestImplementation 'androidx.test:runner: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.27.0'
|
||||
androidTestImplementation "androidx.room:room-testing:2.1.0-alpha07"
|
||||
androidTestImplementation "androidx.test:core:1.2.0"
|
||||
androidTestImplementation "androidx.test:runner:1.2.0"
|
||||
androidTestImplementation "androidx.test.ext:junit:1.1.1"
|
||||
androidTestImplementation "io.mockk:mockk-android:$mockk"
|
||||
androidTestImplementation "androidx.room:room-testing:$room"
|
||||
androidTestImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
|
||||
androidTestImplementation "org.mockito:mockito-core:$mockito_core"
|
||||
androidTestImplementation("org.mockito:mockito-android:3.0.6") {
|
||||
exclude group: 'org.mockito', module: 'mockito-core'
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'com.google.gms.google-services'
|
||||
|
10
app/hooks.gradle
Normal file
10
app/hooks.gradle
Normal file
@ -0,0 +1,10 @@
|
||||
apply plugin: "com.star-zero.gradle.githook"
|
||||
|
||||
githook {
|
||||
failOnMissingHooksDir = false
|
||||
hooks {
|
||||
"pre-push" {
|
||||
shell = "./app/play-publish-lint.sh"
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
apply plugin: "jacoco"
|
||||
|
||||
jacoco {
|
||||
toolVersion "0.8.3"
|
||||
toolVersion "0.8.4"
|
||||
reportsDir = file("$buildDir/reports")
|
||||
}
|
||||
|
||||
@ -35,11 +35,14 @@ task jacocoTestReport(type: JacocoReport) {
|
||||
dir: "$buildDir/intermediates/classes/debug",
|
||||
excludes: excludes
|
||||
) + fileTree(
|
||||
dir: "$buildDir/tmp/kotlin-classes/debug",
|
||||
dir: "$buildDir/tmp/kotlin-classes/playDebug",
|
||||
excludes: excludes
|
||||
))
|
||||
|
||||
sourceDirectories.setFrom(files("$project.projectDir/src/main/java"))
|
||||
sourceDirectories.setFrom(files([
|
||||
"src/main/java",
|
||||
"src/play/java"
|
||||
]))
|
||||
executionData.setFrom(fileTree(
|
||||
dir: project.projectDir,
|
||||
includes: ["**/*.exec", "**/*.ec"]
|
||||
|
Binary file not shown.
7
app/play-publish-lint.sh
Executable file
7
app/play-publish-lint.sh
Executable 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
|
10
app/proguard-rules.pro
vendored
10
app/proguard-rules.pro
vendored
@ -11,6 +11,10 @@
|
||||
-verbose
|
||||
|
||||
|
||||
#Keep all wulkanowy files
|
||||
-keep class io.github.wulkanowy.** {*;}
|
||||
|
||||
|
||||
#Config for anallitycs
|
||||
-keepattributes *Annotation*
|
||||
-keepattributes SourceFile,LineNumberTable
|
||||
@ -32,8 +36,10 @@
|
||||
-dontwarn rx.internal.util.**
|
||||
-dontwarn sun.misc.Unsafe
|
||||
|
||||
|
||||
#Config for MPAndroidChart
|
||||
-keep class com.github.mikephil.charting.** { *; }
|
||||
|
||||
#Config for API
|
||||
-keep class io.github.wulkanowy.api.** {*;}
|
||||
|
||||
#Config for Material Components
|
||||
-keep class com.google.android.material.tabs.**
|
1386
app/schemas/io.github.wulkanowy.data.db.AppDatabase/14.json
Normal file
1386
app/schemas/io.github.wulkanowy.data.db.AppDatabase/14.json
Normal file
File diff suppressed because it is too large
Load Diff
1430
app/schemas/io.github.wulkanowy.data.db.AppDatabase/15.json
Normal file
1430
app/schemas/io.github.wulkanowy.data.db.AppDatabase/15.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -29,5 +29,6 @@ sonarqube {
|
||||
property "sonar.java.coveragePlugin", "jacoco"
|
||||
property "sonar.android.lint.report", "build/reports/lint-results.xml"
|
||||
property "sonar.jacoco.reportPaths", fileTree(dir: project.projectDir, includes: ['**/*.exec', '**/*.ec'])
|
||||
property "sonar.coverage.jacoco.xmlReportPaths", "build/reports/jacocoTestReport/jacocoTestReport.xml"
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,12 @@ abstract class AbstractMigrationTest {
|
||||
fun getMigratedRoomDatabase(): AppDatabase {
|
||||
val database = Room.databaseBuilder(ApplicationProvider.getApplicationContext(),
|
||||
AppDatabase::class.java, dbName)
|
||||
.addMigrations(Migration12(), Migration13())
|
||||
.addMigrations(
|
||||
Migration12(),
|
||||
Migration13(),
|
||||
Migration14(),
|
||||
Migration15()
|
||||
)
|
||||
.build()
|
||||
// close the database and release any stream resources when the test finishes
|
||||
helper.closeWhenFinished(database)
|
||||
|
@ -5,7 +5,7 @@ import androidx.room.Room
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import io.github.wulkanowy.data.db.AppDatabase
|
||||
import io.github.wulkanowy.data.db.SharedPrefHelper
|
||||
import io.github.wulkanowy.data.db.SharedPrefProvider
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
@ -21,14 +21,14 @@ class StudentLocalTest {
|
||||
|
||||
private lateinit var testDb: AppDatabase
|
||||
|
||||
private lateinit var sharedHelper: SharedPrefHelper
|
||||
private lateinit var sharedProvider: SharedPrefProvider
|
||||
|
||||
@Before
|
||||
fun createDb() {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
testDb = Room.inMemoryDatabaseBuilder(context, AppDatabase::class.java)
|
||||
.build()
|
||||
sharedHelper = SharedPrefHelper(context.getSharedPreferences("TEST", Context.MODE_PRIVATE))
|
||||
sharedProvider = SharedPrefProvider(context.getSharedPreferences("TEST", Context.MODE_PRIVATE))
|
||||
studentLocal = StudentLocal(testDb.studentDao, context)
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,7 @@ import org.threeten.bp.LocalDateTime.now
|
||||
import io.github.wulkanowy.api.timetable.Timetable as TimetableRemote
|
||||
import io.github.wulkanowy.data.db.entities.Timetable as TimetableLocal
|
||||
|
||||
fun createTimetableLocal(number: Int, start: LocalDateTime, room: String = "", subject: String = "", teacher: String = ""): TimetableLocal {
|
||||
fun createTimetableLocal(number: Int, start: LocalDateTime, room: String = "", subject: String = "", teacher: String = "", changes: Boolean = false): TimetableLocal {
|
||||
return TimetableLocal(
|
||||
studentId = 1,
|
||||
diaryId = 2,
|
||||
@ -23,12 +23,12 @@ fun createTimetableLocal(number: Int, start: LocalDateTime, room: String = "", s
|
||||
teacher = teacher,
|
||||
teacherOld = "",
|
||||
info = "",
|
||||
changes = false,
|
||||
changes = changes,
|
||||
canceled = false
|
||||
)
|
||||
}
|
||||
|
||||
fun createTimetableRemote(number: Int, start: LocalDateTime, room: String, subject: String = "", teacher: String = ""): TimetableRemote {
|
||||
fun createTimetableRemote(number: Int, start: LocalDateTime, room: String, subject: String = "", teacher: String = "", changes: Boolean = false): TimetableRemote {
|
||||
return TimetableRemote(
|
||||
number = number,
|
||||
start = start.toDate(),
|
||||
@ -39,7 +39,7 @@ fun createTimetableRemote(number: Int, start: LocalDateTime, room: String, subje
|
||||
room = room,
|
||||
teacher = teacher,
|
||||
info = "",
|
||||
changes = false,
|
||||
changes = changes,
|
||||
canceled = false
|
||||
)
|
||||
}
|
||||
|
@ -60,7 +60,7 @@ class TimetableRepositoryTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun copyDetailsToCompletedFromPrevious() {
|
||||
fun copyRoomToCompletedFromPrevious() {
|
||||
timetableLocal.saveTimetable(listOf(
|
||||
createTimetableLocal(1, of(2019, 3, 5, 8, 0), "123", "Przyroda"),
|
||||
createTimetableLocal(2, of(2019, 3, 5, 8, 50), "321", "Religia"),
|
||||
@ -83,7 +83,32 @@ class TimetableRepositoryTest {
|
||||
assertEquals("123", lessons[0].room)
|
||||
assertEquals("321", lessons[1].room)
|
||||
assertEquals("213", lessons[2].room)
|
||||
}
|
||||
|
||||
assertEquals("", lessons[3].teacher)
|
||||
@Test
|
||||
fun copyTeacherToCompletedFromPrevious() {
|
||||
timetableLocal.saveTimetable(listOf(
|
||||
createTimetableLocal(1, of(2019, 3, 5, 8, 0), "123", "Przyroda", "Jan Garnkiewicz", false),
|
||||
createTimetableLocal(2, of(2019, 3, 5, 8, 50), "321", "Religia", "Paweł Jumper", false),
|
||||
createTimetableLocal(3, of(2019, 3, 5, 9, 40), "213", "W-F", "", true),
|
||||
createTimetableLocal(4, of(2019, 3, 5, 10, 30), "213", "W-F", "", false)
|
||||
))
|
||||
|
||||
every { mockApi.getTimetable(any(), any()) } returns Single.just(listOf(
|
||||
createTimetableRemote(1, of(2019, 3, 5, 8, 0), "", "Przyroda", "", true), // should override local
|
||||
createTimetableRemote(2, of(2019, 3, 5, 8, 50), "", "Religia", "", false),
|
||||
createTimetableRemote(3, of(2019, 3, 5, 9, 40), "", "W-F", "Jan Garnkiewicz", false),
|
||||
createTimetableRemote(4, of(2019, 3, 5, 10, 30), "", "W-F", "Paweł Jumper", false)
|
||||
))
|
||||
|
||||
val lessons = TimetableRepository(settings, timetableLocal, timetableRemote)
|
||||
.getTimetable(semesterMock, LocalDate.of(2019, 3, 5), LocalDate.of(2019, 3, 5), true)
|
||||
.blockingGet()
|
||||
|
||||
assertEquals(4, lessons.size)
|
||||
assertEquals("", lessons[0].teacher)
|
||||
assertEquals("Paweł Jumper", lessons[1].teacher)
|
||||
assertEquals("Jan Garnkiewicz", lessons[2].teacher)
|
||||
assertEquals("Paweł Jumper", lessons[3].teacher)
|
||||
}
|
||||
}
|
||||
|
@ -1,58 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="1926.9231"
|
||||
android:viewportHeight="1926.9231">
|
||||
<group android:translateX="462.46155"
|
||||
android:translateY="462.46155">
|
||||
<path
|
||||
android:pathData="M2000,1440.1V2002H1240.1L238.1,1000v0l-120,-120c0.2,-0.2 -0.7,-1.6 -0.6,-1.8 1.6,-3.1 3.4,-6 5,-9.1 0.4,-0.8 0.6,-1.6 0.6,-2.5 9.2,-15.1 20.2,-28.9 31.4,-42.6 0.1,-0.2 0.3,-0.3 0.4,-0.5 5.1,-6.3 10.3,-12.6 15.3,-18.9 1,-1.2 1.9,-2.4 2.9,-3.7 0.2,-0.2 0.3,-0.4 0.5,-0.6 7.6,-9.7 14.8,-19.7 21.2,-30.2 1,-1.7 2,-3.4 2.9,-5.1 0.1,-0.2 0.2,-0.3 0.3,-0.5 6.2,-11.4 11.3,-23.3 17.7,-34.6 4.5,-8 9.3,-15.9 13.8,-23.9 0.1,-0.2 0.2,-0.3 0.3,-0.5 0.8,-1.5 1.6,-3 2.4,-4.4 0.2,-0.3 0.3,-0.6 0.5,-0.9 2,-3.7 3.8,-7.4 5.6,-11.2 6.2,-13.4 10.2,-27.7 14,-41.9 0,-0.2 0.1,-0.3 0.1,-0.5 0.3,-1 0.5,-2 0.8,-2.9 4,-15.3 7.3,-31.4 13.5,-45.9 1.4,-3.3 3,-6.5 4.6,-9.6 0.1,-0.2 0.2,-0.3 0.3,-0.5 5,-9.7 10.6,-19.1 13.5,-29.7 2.6,-9.2 3.5,-19 3.6,-28.8 0,-0.2 0,-0.3 0,-0.5 0.1,-7.2 -0.1,-14.4 -0.4,-21.3 0.9,-5.6 1.9,-11.3 3,-16.9 0.2,-0.9 0.3,-1.7 0.5,-2.6 1.2,-5.9 2.5,-11.7 4.2,-17.4 0,-0.1 0.1,-0.3 0.1,-0.4 0,-0.2 0.1,-0.3 0.1,-0.5 0.2,-2.1 -0.8,-3.7 -2.4,-4.5l7.7,-7.6c1.2,0.9 2.7,1.3 4.3,0.9 12.8,-3.9 24.9,-9.9 36.3,-16.8 2.8,-1.7 5.6,-3.4 8.3,-5.2 0.2,-0.1 0.3,-0.2 0.5,-0.3 7.7,-4.9 15.2,-10.2 22.1,-16 3.1,-2.7 5.9,-5.6 8.3,-8.9 0.1,-0.2 0.2,-0.3 0.4,-0.5 2.3,-3.4 4.2,-7.2 5.3,-11.4 2.4,-9.2 1.9,-19 1.9,-28.4 0,-0.2 0,-0.4 0,-0.6l5.7,-5.6 -0.3,0.1 4.1,1.3 45.3,45 9.6,9.6 14.3,14.3 100.8,100.8 17.2,17.2 16.3,16.3 30.3,-30.3 -192.9,-191.5c0.5,-0.5 1.1,-1.6 1.4,-2.3 0.3,-0.6 0.5,-1.2 0.4,-1.9 0,-0.8 -0.2,-1.6 -0.6,-2.3 -0.1,-0.3 -0.3,-0.7 -0.4,-1 -0.2,-0.4 -0.5,-0.7 -0.9,-1.1l27.8,-27.8 191.1,-191.1c0.7,0.4 1.5,0.6 2.5,0.7 4.1,0 8.2,0.1 11.9,1.9 0.5,0.3 1.3,0.7 2.1,1.1 1.7,2.3 3.5,4.5 5.7,6.2 2.7,2.1 8.2,3 9.4,0.3z"
|
||||
android:fillColor="#AD2A2A"/>
|
||||
<path
|
||||
android:pathData="m616.3,796.7c0.4,1.2 0.5,2.4 0.5,3.7l-5.8,73.3c-0.4,5.2 -5,9.3 -10.6,9.3h-187.2c-4,0 -7.7,-2.3 -9.5,-5.6l-30.9,-57.4c-0.2,-0.3 -0.3,-0.6 -0.4,-0.9l-29.1,-70.5c-1.1,-2.5 -0.9,-5.4 0.3,-7.8l48.3,-94.5c1.2,-2.3 1.4,-5 0.5,-7.4l-23.6,-65.4c-0.8,-2.3 -0.7,-4.9 0.3,-7.1l39.4,-83.9c4,-8.4 17.1,-7.7 19.9,1.1l12.3,38.2 37.5,100.4c1,2.8 3.4,5 6.4,6l80,27.3c3.1,1.1 5.5,3.4 6.5,6.3z"
|
||||
android:fillColor="#FFFFFF"/>
|
||||
<path
|
||||
android:pathData="m386.7,253.5c0,-22.9 14.5,-42.6 35.3,-51.8 -1.5,-2.5 -2.4,-5.2 -2.4,-8.1 0,-11.1 12.8,-20.1 28.5,-20.1 0.4,0 0.7,0 1.1,0 6.5,-10.8 18.9,-18.1 33.1,-18.1 1,0 2,0 3,0.1 1.3,0.1 2.6,-0.5 3.3,-1.5 10.5,-16 37.1,-27.4 68.2,-27.4 11.5,0 22.4,1.6 32.1,4.3 2.3,-3.8 7.4,-6.4 13.3,-6.4 6.7,0 12.4,3.4 14.1,8.1 8.2,-9.5 22.1,-15.7 37.9,-15.7 25.2,0 45.6,15.9 45.6,35.5 0,2.4 -0.3,4.7 -0.9,7 -0.4,1.6 0.5,3.2 2.2,3.8 16.1,5.9 27,17.1 27,29.9 0,14.6 -14.1,27.1 -34,32 -1.6,0.4 -2.6,1.8 -2.6,3.3 0,0.1 0,0.2 0,0.3 0,11.3 -11.2,20.6 -25.6,21.6 0.1,0.5 0.1,1 0.1,1.6 0,21.7 -41.3,39.2 -92.2,39.2 -11,0 -21.5,-0.8 -31.2,-2.3 0,0.2 0,0.3 0,0.5 0,9 -11.6,16.3 -25.8,16.3 -0.8,0 -1.5,0 -2.2,-0.1 1,2.2 1.5,4.5 1.5,6.9 0,14 -17.7,25.3 -39.6,25.3 -2.4,0 -4.7,-0.1 -7,-0.4 -1.8,-0.2 -3.5,0.9 -3.9,2.6 -1.9,7 -8,12.1 -15.3,12.1 -8.8,0 -15.8,-7.5 -15.8,-16.7 0,-4.3 1.5,-8.2 4,-11.1 0.9,-1 1.2,-2.4 0.6,-3.6 -1.4,-2.6 -2,-5.3 -2,-8.2v0c0,-1.6 -1.3,-2.9 -2.9,-3.3 -27.1,-6 -47.5,-28.6 -47.5,-55.6z"
|
||||
android:fillColor="#FFFFFF"/>
|
||||
<path
|
||||
android:pathData="m870.4,883h-124.4c-3.6,0 -6.9,-1.8 -8.9,-4.6l-83.7,-117.7c-0.5,-0.8 -1,-1.6 -1.3,-2.5l-41.5,-121.2c-0.8,-2.4 -2.7,-4.5 -5.1,-5.7l-101.6,-51.3c-2.8,-1.4 -4.8,-4 -5.4,-6.9l-15,-74.4c-0.2,-1.2 -0.7,-2.4 -1.5,-3.5l-34.5,-50.7c-1.9,-2.8 -2.2,-6.2 -0.8,-9.2l21,-44.9c1.6,-3.4 5.1,-5.7 9.1,-5.9l39,-2.3c2.3,-0.1 4.4,-0.9 6.1,-2.3l28.7,-22.3c5.4,-4.2 13.6,-2.4 16.5,3.5l98.9,201.2c0.4,0.9 0.7,1.8 0.9,2.7l12.2,80.9c0.3,1.9 1.1,3.6 2.4,5l197,215.6c5.8,6.4 0.9,16.5 -8.1,16.5z"
|
||||
android:fillColor="#FFFFFF"/>
|
||||
<path
|
||||
android:pathData="m335.8,838.9c0.8,2.6 0.4,5.4 -1,7.7l-19.3,31.3c-1.9,3.1 -5.4,5.1 -9.2,5.1h-180.6c-8.4,0 -13.5,-8.8 -9,-15.4l116.3,-171.3c0.7,-1 1.1,-2.1 1.4,-3.2l53.3,-227.6c0.6,-2.7 2.4,-5 5,-6.4l72.2,-39.6c2.6,-1.4 4.4,-3.7 5,-6.5l9.7,-42.3c2,-8.8 14.6,-10.7 19.6,-3l3.5,5.6c1.5,2.4 1.9,5.4 0.9,8.1l-65.8,190.2c-0.5,1.5 -0.7,3.1 -0.4,4.7l16.4,91c0.3,1.8 0.1,3.7 -0.7,5.4l-39.1,87.8c-0.9,2.1 -1.1,4.4 -0.4,6.5z"
|
||||
android:fillColor="#FFFFFF"/>
|
||||
<path
|
||||
android:pathData="m424.22,657.05 l1580.54,-0.05c0.54,0 0,292 0,292l-1580.54,-0.03c-10.78,0 -19.46,-8.68 -19.46,-19.46l0,-252.99c0,-10.78 8.68,-19.46 19.46,-19.46z"
|
||||
android:strokeAlpha="0.49803922"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="4"
|
||||
android:fillColor="#3f3f3f"
|
||||
android:strokeColor="#00000000"
|
||||
android:fillType="nonZero"
|
||||
android:fillAlpha="1"
|
||||
android:strokeLineCap="square"/>
|
||||
<path
|
||||
android:pathData="m728.71,710.91l0,19.89l-88.7,0l0,59.3l77.04,0L717.05,810l-77.04,0l0,65.39l89.84,0l0,19.9L616.56,895.29L616.56,791.16l-52.35,-52.35 -0.77,-0.18c5.41,6.64 9.65,14.32 12.69,23.09 3.72,10.39 5.62,21.92 5.7,34.6L581.84,809.87c-0.08,12.67 -1.98,24.25 -5.7,34.72 -3.63,10.39 -8.95,19.35 -15.97,26.87 -6.93,7.43 -15.46,13.26 -25.6,17.49 -7.54,3.1 -15.86,5.07 -24.97,5.89 -3.04,0.27 -6.16,0.42 -9.37,0.45L457.53,895.29l0,-6.55l-0.37,6.82 53.8,53.45l415.85,0c0.24,0 0.47,-0.02 0.69,-0.05L1132.91,948.97L953.5,769.55l-58.39,-58.39 -0.28,0.06 -58.03,184.06l-20.4,0l-44.66,-141.34zM783.29,711.07 L826.41,857.52 850.08,777.86zM509.6,711.29c1.52,0.15 3.01,0.33 4.49,0.54 -1.47,-0.21 -2.97,-0.39 -4.49,-0.54zM481.35,730.04l0,146.11l18.88,0c9.97,-0.08 18.59,-1.81 25.85,-5.19 7.27,-3.46 13.26,-8.15 17.99,-14.07 4.82,-5.91 8.36,-12.88 10.64,-20.91 2.37,-8.03 3.59,-16.73 3.68,-26.11l0,-13.81c-0.08,-9.38 -1.31,-18.04 -3.68,-25.98 -2.37,-8.03 -5.91,-14.95 -10.64,-20.78 -4.73,-5.91 -10.73,-10.56 -17.99,-13.94 -7.27,-3.38 -15.88,-5.15 -25.85,-5.32zM560.17,734.86c0.65,0.69 1.26,1.4 1.88,2.11 -0.61,-0.72 -1.23,-1.42 -1.88,-2.11z"
|
||||
android:strokeAlpha="1"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="1.94642854"
|
||||
android:fillColor="#000000"
|
||||
android:strokeColor="#00000000"
|
||||
android:fillAlpha="0.18431373"
|
||||
android:strokeLineCap="butt"/>
|
||||
<path
|
||||
android:pathData="m457.53,895.28l0,-184.51l42.7,0q19.26,0.25 34.34,6.59 15.21,6.21 25.6,17.49 10.52,11.15 15.97,26.86 5.58,15.59 5.7,34.59l0,13.56q-0.13,19.01 -5.7,34.72 -5.45,15.59 -15.97,26.86 -10.39,11.15 -25.6,17.49 -15.08,6.21 -34.34,6.34zM481.35,730.04l0,146.11l18.88,0q14.95,-0.13 25.85,-5.2 10.9,-5.2 17.99,-14.07 7.22,-8.87 10.64,-20.91 3.55,-12.04 3.67,-26.1l0,-13.81q-0.13,-14.07 -3.67,-25.98 -3.55,-12.04 -10.64,-20.78 -7.1,-8.87 -17.99,-13.94 -10.9,-5.07 -25.85,-5.32z"
|
||||
android:fillColor="#ffffff"
|
||||
android:strokeColor="#00000000"
|
||||
android:fillAlpha="1"/>
|
||||
<path
|
||||
android:pathData="M717.05,810L640.01,810l0,65.39l89.84,0l0,19.9l-113.29,0l0,-184.51l112.15,0l0,20.02L640.01,730.8l0,59.31l77.05,0z"
|
||||
android:fillColor="#ffffff"
|
||||
android:strokeColor="#00000000"
|
||||
android:fillAlpha="1"/>
|
||||
<path
|
||||
android:pathData="m826.41,857.52 l43.59,-146.74l24.96,0l-58.16,184.51L816.4,895.28l-58.29,-184.51l25.09,0z"
|
||||
android:fillColor="#ffffff"
|
||||
android:strokeColor="#00000000"
|
||||
android:fillAlpha="1"/>
|
||||
</group>
|
||||
</vector>
|
34
app/src/debug/res/drawable/ic_launcher_foreground_dev.xml
Normal file
34
app/src/debug/res/drawable/ic_launcher_foreground_dev.xml
Normal file
@ -0,0 +1,34 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="1926"
|
||||
android:viewportHeight="1926">
|
||||
<path
|
||||
android:fillColor="#ad2a2a"
|
||||
android:pathData="M2465,1904v561h-759L704,1463l-120,-120 -1,-1 5,-9 1,-3c9,-15 20,-29 31,-43l16,-19 3,-4 21,-30 3,-6c7,-11 12,-23 18,-35l14,-24 3,-4v-1a234,234 0,0 0,20 -53v-1l1,-3c4,-15 7,-31 13,-46l5,-9v-1c5,-10 11,-19 14, -30 2,-9 3,-19 3,-28v-1,-21l3,-17v-3l4,-17h1v-1c0,-2 -1,-4 -3,-4l8,-8 4,1c13,-4 25,-10 37,-17l8,-5 22,-16 9,-9v-1l5, -11c3,-9 2,-19 2,-29l6,-6 4,2 45,45 10,9 14,14 101,101 17,17 16,17 31,-31 -193,-191 1,-2 1,-2 -1,-3v-1l-1,-1 27,-27 192,-192 2,1a27,27 0,0 1,14 3l6,6c2,2 8,3 9,1l1310,1310z" />
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M1082,1260v4l-6,73c0,5 -5,9 -10,9H879c-4,0 -8,-2 -10,-5l-31,-58 -29,-71v-8l48,-94c2,-3 2,-5 1, -8l-24,-65c-1,-2 0,-5 1,-7l39,-84c4,-8 17,-8 20,1l12,38 38,101c1,2 3,5 6,6l80,27c3,1 6,3 7,6l45,135zM852,717c0,-23 15,-43 35,-52l-2,-8c0,-11 13,-20 29,-20h1a38,38 0,0 1,36 -18l3,-2c11,-16 37,-27 68,-27 12,0 23,2 32,4 3,-3 8,-6 14,-6s12,3 14,8c8,-9 22,-16 38,-16 25,0 45,16 45,36l-1,7 3,4c16,6 27,17 27,30 0,14 -15,27 -34,32 -2,0 -3,1 -3,3 0,11 -11,21 -26,22v1c0,22 -41,40 -92,40 -11,0 -21,-1 -31,-3v1c0,9 -12,16 -26,16h-2l2,7c0,14 -18,25 -40,25h-7l-4, 2c-2,7 -8,12 -15,12 -9,0 -16,-7 -16,-16 0,-5 1,-8 4,-11l1,-4c-2,-3 -2,-5 -2,-8 0,-2 -2,-3 -3,-3 -27,-6 -48,-29 -48,-56z" />
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M1336,1346h-125c-3,0 -6,-1 -8,-4l-84,-118 -1,-2 -42,-122 -5,-5 -102,-52 -5,-6 -15,-75 -2,-3 -34,-51c-2,-3 -2,-6 -1,-9l21,-45c2,-4 5,-6 9,-6l39,-2c3,0 5,-1 6,-3l29,-22c5,-4 14,-2 17,4l98,201 1,2 13,81 2,5 197,216c6,6 1,16 -8,16zM801,1302c1,3 1,6 -1,8l-19,31c-2,3 -5,5 -9,5H591c-8,0 -13,-8 -9,-15l116,-171 2,-3 53, -228c1,-3 3,-5 5,-6l72,-40c3,-1 5,-4 5,-7l10,-42c2,-9 15,-11 20,-3l3,6c2,2 2,5 1,8l-66,190v5l16,91v5l-40,88v6l22,72z" />
|
||||
<path
|
||||
android:fillColor="#3f3f3f"
|
||||
android:pathData="M886.7,1119.5h1580.5c0.6,0 0,292 0,292H886.7a19.4,19.4 0,0 1,-19.5 -19.5v-253c0,-10.8 8.7,-19.5 19.5,-19.5z"
|
||||
android:strokeWidth="4"
|
||||
android:strokeAlpha=".5"
|
||||
android:strokeColor="#000"
|
||||
android:strokeLineCap="square"
|
||||
android:strokeLineJoin="round" />
|
||||
<path
|
||||
android:fillAlpha=".2"
|
||||
android:fillColor="#000"
|
||||
android:pathData="M1191.2,1173.4v19.9h-88.7v59.3h77v19.9h-77v65.4h89.8v19.9L1079,1357.8v-104.2l-52.3,-52.3 -0.8, -0.2a75.1,75.1 0,0 1,12.7 23,104 104,0 0,1 5.7,34.7v13.5c0,12.7 -2,24.3 -5.7,34.8a72.8,72.8 0,0 1,-41.6 44.4,85.5 85.5,0 0,1 -34.3,6.3L920,1357.8v-6.6l-0.4,6.8 53.8,53.5h622L1416,1232l-58.4,-58.4h-0.3l-58,184.1h -20.4l-44.7,-141.3zM1245.8,1173.5l43,146.5 23.7,-79.7zM972.1,1173.8zM943.8,1192.5v146.1h18.9a62,62 0,0 0,25.8 -5.2,50.3 50.3,0 0,0 18,-14c4.9,-6 8.4,-13 10.7,-21 2.3,-8 3.6,-16.7 3.7,-26v-13.9c-0.1,-9.4 -1.4,-18 -3.7,-26 -2.4,-8 -6,-15 -10.7,-20.7a49.3,49.3 0,0 0,-18 -14,63.4 63.4,0 0,0 -25.8,-5.3zM1022.6,1197.3l2,2.1 -2,-2z"
|
||||
android:strokeWidth="1.9"
|
||||
android:strokeColor="#000" />
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M920,1357.7v-184.5h42.7q19.3,0.3 34.3,6.6 15.2,6.2 25.6,17.5 10.6,11.2 16,26.9 5.6,15.6 5.7, 34.6v13.5q-0.1,19 -5.7,34.7 -5.4,15.6 -16,26.9 -10.4,11.2 -25.6,17.5 -15,6.2 -34.3,6.3zM943.8,1192.5v146.1h18.9q15, -0.1 25.8,-5.2 11,-5.2 18,-14 7.3,-9 10.7,-21 3.5,-12 3.6,-26v-13.9q0,-14 -3.6,-26t-10.7,-20.7q-7,-9 -18,-14 -10.9,-5 -25.8,-5.3zM1179.5,1272.5h-77v65.4h89.8v19.9L1079,1357.8v-184.6h112.2v20h-88.7v59.4h77zM1288.9, 1320l43.6,-146.8h25l-58.2,184.6h-20.4l-58.3,-184.6h25z"
|
||||
android:strokeWidth="1"
|
||||
android:strokeColor="#000" />
|
||||
</vector>
|
@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
<background android:drawable="@color/colorPrimary" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground_dev" />
|
||||
</adaptive-icon>
|
@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
<background android:drawable="@color/colorPrimary" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground_dev" />
|
||||
</adaptive-icon>
|
@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#D32F2F</color>
|
||||
</resources>
|
@ -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
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -4,8 +4,6 @@
|
||||
package="io.github.wulkanowy"
|
||||
android:installLocation="internalOnly">
|
||||
|
||||
<uses-sdk tools:overrideLibrary="com.readystatesoftware.chuck" />
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
@ -45,6 +43,7 @@
|
||||
android:name=".ui.modules.message.send.SendMessageActivity"
|
||||
android:configChanges="orientation|screenSize"
|
||||
android:label="@string/send_message_title"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
android:theme="@style/WulkanowyTheme.NoActionBar" />
|
||||
<activity
|
||||
android:name=".ui.modules.timetablewidget.TimetableWidgetConfigureActivity"
|
||||
@ -80,7 +79,6 @@
|
||||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/provider_widget_timetable" />
|
||||
</receiver>
|
||||
|
||||
<receiver
|
||||
android:name=".ui.modules.luckynumberwidget.LuckyNumberWidgetProvider"
|
||||
android:label="@string/lucky_number_title">
|
||||
|
@ -1,35 +1,40 @@
|
||||
package io.github.wulkanowy
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log.INFO
|
||||
import android.util.Log.VERBOSE
|
||||
import androidx.multidex.MultiDex
|
||||
import androidx.work.Configuration
|
||||
import androidx.work.WorkManager
|
||||
import com.crashlytics.android.Crashlytics
|
||||
import com.crashlytics.android.core.CrashlyticsCore
|
||||
import com.jakewharton.threetenabp.AndroidThreeTen
|
||||
import dagger.android.AndroidInjector
|
||||
import dagger.android.support.DaggerApplication
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.davidea.flexibleadapter.utils.Log
|
||||
import io.fabric.sdk.android.Fabric
|
||||
import io.github.wulkanowy.BuildConfig.CRASHLYTICS_ENABLED
|
||||
import io.github.wulkanowy.BuildConfig.DEBUG
|
||||
import io.github.wulkanowy.di.DaggerAppComponent
|
||||
import io.github.wulkanowy.services.sync.SyncWorkerFactory
|
||||
import io.github.wulkanowy.ui.base.ThemeManager
|
||||
import io.github.wulkanowy.utils.ActivityLifecycleLogger
|
||||
import io.github.wulkanowy.utils.AppInfo
|
||||
import io.github.wulkanowy.utils.CrashlyticsTree
|
||||
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 java.io.IOException
|
||||
import javax.inject.Inject
|
||||
|
||||
class WulkanowyApp : DaggerApplication() {
|
||||
class WulkanowyApp : DaggerApplication(), Configuration.Provider {
|
||||
|
||||
@Inject
|
||||
lateinit var workerFactory: SyncWorkerFactory
|
||||
|
||||
@Inject
|
||||
lateinit var themeManager: ThemeManager
|
||||
|
||||
@Inject
|
||||
lateinit var appInfo: AppInfo
|
||||
|
||||
override fun attachBaseContext(base: Context?) {
|
||||
super.attachBaseContext(base)
|
||||
MultiDex.install(this)
|
||||
@ -38,15 +43,15 @@ class WulkanowyApp : DaggerApplication() {
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
AndroidThreeTen.init(this)
|
||||
WorkManager.initialize(this, Configuration.Builder().setWorkerFactory(workerFactory).build())
|
||||
RxJavaPlugins.setErrorHandler(::onError)
|
||||
themeManager.applyDefaultTheme()
|
||||
|
||||
initCrashlytics()
|
||||
initLogging()
|
||||
initCrashlytics(this, appInfo)
|
||||
}
|
||||
|
||||
private fun initLogging() {
|
||||
if (DEBUG) {
|
||||
if (appInfo.isDebug) {
|
||||
Timber.plant(DebugLogTree())
|
||||
FlexibleAdapter.enableLogs(Log.Level.DEBUG)
|
||||
} else {
|
||||
@ -55,19 +60,20 @@ class WulkanowyApp : DaggerApplication() {
|
||||
registerActivityLifecycleCallbacks(ActivityLifecycleLogger())
|
||||
}
|
||||
|
||||
private fun initCrashlytics() {
|
||||
Fabric.with(Fabric.Builder(this).kits(
|
||||
Crashlytics.Builder().core(CrashlyticsCore.Builder().disabled(!CRASHLYTICS_ENABLED).build()).build()
|
||||
).debuggable(DEBUG).build())
|
||||
}
|
||||
|
||||
private fun onError(error: Throwable) {
|
||||
if (error is UndeliverableException && error.cause is IOException || error.cause is InterruptedException) {
|
||||
Timber.e(error.cause, "An undeliverable error occurred")
|
||||
//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> {
|
||||
return DaggerAppComponent.factory().create(this)
|
||||
}
|
||||
|
||||
override fun getWorkManagerConfiguration() = Configuration.Builder()
|
||||
.setWorkerFactory(workerFactory)
|
||||
.setMinimumLoggingLevel(if (appInfo.isDebug) VERBOSE else INFO)
|
||||
.build()
|
||||
}
|
||||
|
@ -132,4 +132,8 @@ internal class RepositoryModule {
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideRecipientDao(database: AppDatabase) = database.recipientDao
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideMobileDevicesDao(database: AppDatabase) = database.mobileDeviceDao
|
||||
}
|
||||
|
@ -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.LuckyNumberDao
|
||||
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.RecipientDao
|
||||
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.LuckyNumber
|
||||
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.Recipient
|
||||
import io.github.wulkanowy.data.db.entities.ReportingUnit
|
||||
@ -44,6 +46,8 @@ import io.github.wulkanowy.data.db.migrations.Migration10
|
||||
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.Migration3
|
||||
import io.github.wulkanowy.data.db.migrations.Migration4
|
||||
@ -73,7 +77,8 @@ import javax.inject.Singleton
|
||||
LuckyNumber::class,
|
||||
CompletedLesson::class,
|
||||
ReportingUnit::class,
|
||||
Recipient::class
|
||||
Recipient::class,
|
||||
MobileDevice::class
|
||||
],
|
||||
version = AppDatabase.VERSION_SCHEMA,
|
||||
exportSchema = true
|
||||
@ -82,7 +87,7 @@ import javax.inject.Singleton
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
|
||||
companion object {
|
||||
const val VERSION_SCHEMA = 13
|
||||
const val VERSION_SCHEMA = 15
|
||||
|
||||
fun newInstance(context: Context): AppDatabase {
|
||||
return Room.databaseBuilder(context, AppDatabase::class.java, "wulkanowy_database")
|
||||
@ -101,7 +106,9 @@ abstract class AppDatabase : RoomDatabase() {
|
||||
Migration10(),
|
||||
Migration11(),
|
||||
Migration12(),
|
||||
Migration13()
|
||||
Migration13(),
|
||||
Migration14(),
|
||||
Migration15()
|
||||
)
|
||||
.build()
|
||||
}
|
||||
@ -140,4 +147,6 @@ abstract class AppDatabase : RoomDatabase() {
|
||||
abstract val reportingUnitDao: ReportingUnitDao
|
||||
|
||||
abstract val recipientDao: RecipientDao
|
||||
|
||||
abstract val mobileDeviceDao: MobileDeviceDao
|
||||
}
|
||||
|
@ -1,18 +1,15 @@
|
||||
package io.github.wulkanowy.data.db
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.SharedPreferences
|
||||
import androidx.core.content.edit
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
@SuppressLint("ApplySharedPref")
|
||||
class SharedPrefHelper @Inject constructor(private val sharedPref: SharedPreferences) {
|
||||
class SharedPrefProvider @Inject constructor(private val sharedPref: SharedPreferences) {
|
||||
|
||||
fun putLong(key: String, value: Long, sync: Boolean = false) {
|
||||
sharedPref.edit().putLong(key, value).apply {
|
||||
if (sync) commit() else apply()
|
||||
}
|
||||
sharedPref.edit(sync) { putLong(key, value) }
|
||||
}
|
||||
|
||||
fun getLong(key: String, defaultValue: Long) = sharedPref.getLong(key, defaultValue)
|
@ -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>>
|
||||
}
|
@ -13,11 +13,26 @@ data class GradeSummary(
|
||||
@ColumnInfo(name = "student_id")
|
||||
val studentId: Int,
|
||||
|
||||
val position: Int,
|
||||
|
||||
val subject: String,
|
||||
|
||||
@ColumnInfo(name = "predicted_grade")
|
||||
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)
|
||||
var id: Long = 0
|
||||
|
@ -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
|
||||
}
|
@ -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
|
||||
)
|
||||
""")
|
||||
}
|
||||
}
|
@ -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
|
||||
)
|
||||
""")
|
||||
}
|
||||
}
|
@ -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
|
||||
)
|
@ -18,9 +18,14 @@ class GradeSummaryRemote @Inject constructor(private val api: Api) {
|
||||
GradeSummary(
|
||||
semesterId = semester.semesterId,
|
||||
studentId = semester.studentId,
|
||||
position = it.order,
|
||||
subject = it.name,
|
||||
predictedGrade = it.predicted,
|
||||
finalGrade = it.final
|
||||
finalGrade = it.final,
|
||||
pointsSum = it.pointsSum,
|
||||
proposedPoints = it.proposedPoints,
|
||||
finalPoints = it.finalPoints,
|
||||
average = it.average
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -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() }
|
||||
}
|
||||
}
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
@ -20,6 +20,9 @@ class PreferencesRepository @Inject constructor(
|
||||
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
|
||||
get() = !sharedPref.getBoolean(context.getString(R.string.pref_key_expand_grade), false)
|
||||
|
||||
@ -40,7 +43,7 @@ class PreferencesRepository @Inject constructor(
|
||||
|
||||
val servicesOnlyWifiKey: String = context.getString(R.string.pref_key_services_wifi_only)
|
||||
val isServicesOnlyWifi: Boolean
|
||||
get() = sharedPref.getBoolean(servicesOnlyWifiKey, true)
|
||||
get() = sharedPref.getBoolean(servicesOnlyWifiKey, false)
|
||||
|
||||
val isNotificationsEnable: Boolean
|
||||
get() = sharedPref.getBoolean(context.getString(R.string.pref_key_notifications_enable), true)
|
||||
|
@ -32,10 +32,11 @@ class TimetableRepository @Inject constructor(
|
||||
.doOnSuccess { old ->
|
||||
local.deleteTimetable(old.uniqueSubtract(new))
|
||||
local.saveTimetable(new.uniqueSubtract(old).map { item ->
|
||||
item.apply {
|
||||
old.singleOrNull { this.start == it.start }?.let {
|
||||
return@map copy(
|
||||
room = if (room.isEmpty()) it.room else room
|
||||
item.also { new ->
|
||||
old.singleOrNull { new.start == it.start }?.let { old ->
|
||||
return@map new.copy(
|
||||
room = if (new.room.isEmpty()) old.room else new.room,
|
||||
teacher = if (new.teacher.isEmpty() && !new.changes) old.teacher else new.teacher
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ import javax.inject.Singleton
|
||||
AppModule::class,
|
||||
RepositoryModule::class,
|
||||
ServicesModule::class,
|
||||
BuilderModule::class])
|
||||
BindingModule::class])
|
||||
interface AppComponent : AndroidInjector<WulkanowyApp> {
|
||||
|
||||
@Component.Factory
|
||||
|
@ -2,15 +2,13 @@ package io.github.wulkanowy.di
|
||||
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.content.Context
|
||||
import com.google.firebase.analytics.FirebaseAnalytics
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
||||
import io.github.wulkanowy.BuildConfig.DEBUG
|
||||
import io.github.wulkanowy.WulkanowyApp
|
||||
import io.github.wulkanowy.utils.AppInfo
|
||||
import io.github.wulkanowy.utils.SchedulersProvider
|
||||
import javax.inject.Named
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@ -27,16 +25,11 @@ internal class AppModule {
|
||||
@Provides
|
||||
fun provideFlexibleAdapter() = FlexibleAdapter<AbstractFlexibleItem<*>>(null, null, true)
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideFirebaseAnalytics(context: Context) = FirebaseAnalytics.getInstance(context)
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideAppWidgetManager(context: Context): AppWidgetManager = AppWidgetManager.getInstance(context)
|
||||
|
||||
@Singleton
|
||||
@Named("isDebug")
|
||||
@Provides
|
||||
fun provideIsDebug() = DEBUG
|
||||
fun provideAppInfo() = AppInfo()
|
||||
}
|
||||
|
@ -6,16 +6,17 @@ import io.github.wulkanowy.di.scopes.PerActivity
|
||||
import io.github.wulkanowy.ui.modules.login.LoginActivity
|
||||
import io.github.wulkanowy.ui.modules.login.LoginModule
|
||||
import io.github.wulkanowy.ui.modules.luckynumberwidget.LuckyNumberWidgetConfigureActivity
|
||||
import io.github.wulkanowy.ui.modules.luckynumberwidget.LuckyNumberWidgetProvider
|
||||
import io.github.wulkanowy.ui.modules.main.MainActivity
|
||||
import io.github.wulkanowy.ui.modules.main.MainModule
|
||||
import io.github.wulkanowy.ui.modules.message.send.SendMessageActivity
|
||||
import io.github.wulkanowy.ui.modules.splash.SplashActivity
|
||||
import io.github.wulkanowy.ui.modules.luckynumberwidget.LuckyNumberWidgetProvider
|
||||
import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetConfigureActivity
|
||||
import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider
|
||||
|
||||
@Suppress("unused")
|
||||
@Module
|
||||
internal abstract class BuilderModule {
|
||||
internal abstract class BindingModule {
|
||||
|
||||
@PerActivity
|
||||
@ContributesAndroidInjector
|
@ -1,8 +1,6 @@
|
||||
package io.github.wulkanowy.services
|
||||
|
||||
import android.app.NotificationManager
|
||||
import android.content.Context
|
||||
import android.content.Context.NOTIFICATION_SERVICE
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.work.WorkManager
|
||||
import com.squareup.inject.assisted.dagger2.AssistedModule
|
||||
@ -28,6 +26,7 @@ import io.github.wulkanowy.services.sync.works.Work
|
||||
import io.github.wulkanowy.services.widgets.TimetableWidgetService
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Suppress("unused")
|
||||
@AssistedModule
|
||||
@Module(includes = [AssistedInject_ServicesModule::class])
|
||||
abstract class ServicesModule {
|
||||
@ -37,17 +36,12 @@ abstract class ServicesModule {
|
||||
|
||||
@JvmStatic
|
||||
@Provides
|
||||
fun provideWorkManager() = WorkManager.getInstance()
|
||||
fun provideWorkManager(context: Context) = WorkManager.getInstance(context)
|
||||
|
||||
@JvmStatic
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideNotificationManagerCompat(context: Context) = NotificationManagerCompat.from(context)
|
||||
|
||||
@JvmStatic
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideNotificationManager(context: Context) = context.getSystemService(NOTIFICATION_SERVICE) as NotificationManager
|
||||
fun provideNotificationManager(context: Context) = NotificationManagerCompat.from(context)
|
||||
}
|
||||
|
||||
@ContributesAndroidInjector
|
||||
|
@ -6,44 +6,59 @@ import androidx.work.BackoffPolicy.EXPONENTIAL
|
||||
import androidx.work.Constraints
|
||||
import androidx.work.ExistingPeriodicWorkPolicy.KEEP
|
||||
import androidx.work.ExistingPeriodicWorkPolicy.REPLACE
|
||||
import androidx.work.NetworkType.METERED
|
||||
import androidx.work.NetworkType.CONNECTED
|
||||
import androidx.work.NetworkType.UNMETERED
|
||||
import androidx.work.PeriodicWorkRequest
|
||||
import androidx.work.PeriodicWorkRequestBuilder
|
||||
import androidx.work.WorkManager
|
||||
import io.github.wulkanowy.data.db.SharedPrefProvider
|
||||
import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository
|
||||
import io.github.wulkanowy.services.sync.channels.DebugChannel
|
||||
import io.github.wulkanowy.services.sync.channels.NewEntriesChannel
|
||||
import io.github.wulkanowy.utils.AppInfo
|
||||
import io.github.wulkanowy.utils.isHolidays
|
||||
import org.threeten.bp.LocalDate.now
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.TimeUnit.MINUTES
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Named
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class SyncManager @Inject constructor(
|
||||
private val workManager: WorkManager,
|
||||
private val preferencesRepository: PreferencesRepository,
|
||||
sharedPrefProvider: SharedPrefProvider,
|
||||
newEntriesChannel: NewEntriesChannel,
|
||||
debugChannel: DebugChannel,
|
||||
@Named("isDebug") isDebug: Boolean
|
||||
appInfo: AppInfo
|
||||
) {
|
||||
|
||||
companion object {
|
||||
private const val APP_VERSION_CODE_KEY = "app_version_code"
|
||||
}
|
||||
|
||||
init {
|
||||
if (SDK_INT >= O) newEntriesChannel.create()
|
||||
if (SDK_INT >= O && isDebug) debugChannel.create()
|
||||
if (now().isHolidays) stopSyncWorker()
|
||||
|
||||
if (SDK_INT > O) {
|
||||
newEntriesChannel.create()
|
||||
if (appInfo.isDebug) debugChannel.create()
|
||||
}
|
||||
|
||||
if (sharedPrefProvider.getLong(APP_VERSION_CODE_KEY, -1L) != appInfo.versionCode.toLong()) {
|
||||
startSyncWorker(true)
|
||||
sharedPrefProvider.putLong(APP_VERSION_CODE_KEY, appInfo.versionCode.toLong(), true)
|
||||
}
|
||||
Timber.i("SyncManager was initialized")
|
||||
}
|
||||
|
||||
fun startSyncWorker(restart: Boolean = false) {
|
||||
if (preferencesRepository.isServiceEnabled && !now().isHolidays) {
|
||||
workManager.enqueueUniquePeriodicWork(SyncWorker::class.java.simpleName, if (restart) REPLACE else KEEP,
|
||||
PeriodicWorkRequest.Builder(SyncWorker::class.java, preferencesRepository.servicesInterval, MINUTES, 10, MINUTES)
|
||||
PeriodicWorkRequestBuilder<SyncWorker>(preferencesRepository.servicesInterval, MINUTES)
|
||||
.setInitialDelay(10, MINUTES)
|
||||
.setBackoffCriteria(EXPONENTIAL, 30, MINUTES)
|
||||
.setConstraints(Constraints.Builder()
|
||||
.setRequiredNetworkType(if (preferencesRepository.isServicesOnlyWifi) METERED else UNMETERED)
|
||||
.setRequiredNetworkType(if (preferencesRepository.isServicesOnlyWifi) UNMETERED else CONNECTED)
|
||||
.build())
|
||||
.build())
|
||||
}
|
||||
|
@ -64,7 +64,7 @@ class SyncWorker @AssistedInject constructor(
|
||||
private fun notify(result: Result) {
|
||||
notificationManager.notify(Random.nextInt(Int.MAX_VALUE), NotificationCompat.Builder(applicationContext, DebugChannel.CHANNEL_ID)
|
||||
.setContentTitle("Debug notification")
|
||||
.setSmallIcon(R.drawable.ic_more_settings_24dp)
|
||||
.setSmallIcon(R.drawable.ic_more_settings)
|
||||
.setAutoCancel(true)
|
||||
.setColor(applicationContext.getCompatColor(R.color.colorPrimary))
|
||||
.setStyle(BigTextStyle().bigText("${SyncWorker::class.java.simpleName} result: $result"))
|
||||
|
@ -3,15 +3,15 @@ package io.github.wulkanowy.services.sync.channels
|
||||
import android.annotation.TargetApi
|
||||
import android.app.Notification.VISIBILITY_PUBLIC
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.NotificationManager.IMPORTANCE_DEFAULT
|
||||
import android.content.Context
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import io.github.wulkanowy.R
|
||||
import javax.inject.Inject
|
||||
|
||||
@TargetApi(26)
|
||||
class DebugChannel @Inject constructor(
|
||||
private val notificationManager: NotificationManager,
|
||||
private val notificationManager: NotificationManagerCompat,
|
||||
private val context: Context
|
||||
) {
|
||||
|
||||
@ -21,8 +21,9 @@ class DebugChannel @Inject constructor(
|
||||
|
||||
fun create() {
|
||||
notificationManager.createNotificationChannel(
|
||||
NotificationChannel(CHANNEL_ID, context.getString(R.string.channel_debug), IMPORTANCE_DEFAULT).apply {
|
||||
lockscreenVisibility = VISIBILITY_PUBLIC
|
||||
})
|
||||
NotificationChannel(CHANNEL_ID, context.getString(R.string.channel_debug), IMPORTANCE_DEFAULT)
|
||||
.apply {
|
||||
lockscreenVisibility = VISIBILITY_PUBLIC
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -3,15 +3,15 @@ package io.github.wulkanowy.services.sync.channels
|
||||
import android.annotation.TargetApi
|
||||
import android.app.Notification.VISIBILITY_PUBLIC
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.NotificationManager.IMPORTANCE_HIGH
|
||||
import android.content.Context
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import io.github.wulkanowy.R
|
||||
import javax.inject.Inject
|
||||
|
||||
@TargetApi(26)
|
||||
class NewEntriesChannel @Inject constructor(
|
||||
private val notificationManager: NotificationManager,
|
||||
private val notificationManager: NotificationManagerCompat,
|
||||
private val context: Context
|
||||
) {
|
||||
|
||||
@ -21,10 +21,11 @@ class NewEntriesChannel @Inject constructor(
|
||||
|
||||
fun create() {
|
||||
notificationManager.createNotificationChannel(
|
||||
NotificationChannel(CHANNEL_ID, context.getString(R.string.channel_new_entries), IMPORTANCE_HIGH).apply {
|
||||
enableLights(true)
|
||||
enableVibration(true)
|
||||
lockscreenVisibility = VISIBILITY_PUBLIC
|
||||
})
|
||||
NotificationChannel(CHANNEL_ID, context.getString(R.string.channel_new_entries), IMPORTANCE_HIGH)
|
||||
.apply {
|
||||
enableLights(true)
|
||||
enableVibration(true)
|
||||
lockscreenVisibility = VISIBILITY_PUBLIC
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ import io.github.wulkanowy.data.repositories.grade.GradeRepository
|
||||
import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository
|
||||
import io.github.wulkanowy.services.sync.channels.NewEntriesChannel
|
||||
import io.github.wulkanowy.ui.modules.main.MainActivity
|
||||
import io.github.wulkanowy.ui.modules.main.MainView.MenuView
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import io.github.wulkanowy.utils.getCompatColor
|
||||
import io.reactivex.Completable
|
||||
import javax.inject.Inject
|
||||
@ -41,14 +41,14 @@ class GradeWork @Inject constructor(
|
||||
notificationManager.notify(Random.nextInt(Int.MAX_VALUE), NotificationCompat.Builder(context, NewEntriesChannel.CHANNEL_ID)
|
||||
.setContentTitle(context.resources.getQuantityString(R.plurals.grade_new_items, grades.size, grades.size))
|
||||
.setContentText(context.resources.getQuantityString(R.plurals.grade_notify_new_items, grades.size, grades.size))
|
||||
.setSmallIcon(R.drawable.ic_stat_notify_grade)
|
||||
.setSmallIcon(R.drawable.ic_stat_grade)
|
||||
.setAutoCancel(true)
|
||||
.setPriority(PRIORITY_HIGH)
|
||||
.setDefaults(DEFAULT_ALL)
|
||||
.setColor(context.getCompatColor(R.color.colorPrimary))
|
||||
.setContentIntent(
|
||||
PendingIntent.getActivity(context, MenuView.GRADE.id,
|
||||
MainActivity.getStartIntent(context, MenuView.GRADE, true), FLAG_UPDATE_CURRENT))
|
||||
PendingIntent.getActivity(context, MainView.Section.GRADE.id,
|
||||
MainActivity.getStartIntent(context, MainView.Section.GRADE, true), FLAG_UPDATE_CURRENT))
|
||||
.setStyle(NotificationCompat.InboxStyle().run {
|
||||
setSummaryText(context.resources.getQuantityString(R.plurals.grade_number_item, grades.size, grades.size))
|
||||
grades.forEach { addLine("${it.subject}: ${it.entry}") }
|
||||
|
@ -15,7 +15,7 @@ import io.github.wulkanowy.data.repositories.luckynumber.LuckyNumberRepository
|
||||
import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository
|
||||
import io.github.wulkanowy.services.sync.channels.NewEntriesChannel
|
||||
import io.github.wulkanowy.ui.modules.main.MainActivity
|
||||
import io.github.wulkanowy.ui.modules.main.MainView.MenuView
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import io.github.wulkanowy.utils.getCompatColor
|
||||
import io.reactivex.Completable
|
||||
import javax.inject.Inject
|
||||
@ -41,14 +41,14 @@ class LuckyNumberWork @Inject constructor(
|
||||
notificationManager.notify(Random.nextInt(Int.MAX_VALUE), NotificationCompat.Builder(context, NewEntriesChannel.CHANNEL_ID)
|
||||
.setContentTitle(context.getString(R.string.lucky_number_notify_new_item_title))
|
||||
.setContentText(context.getString(R.string.lucky_number_notify_new_item, luckyNumber.luckyNumber))
|
||||
.setSmallIcon(R.drawable.ic_stat_notify_lucky_number)
|
||||
.setSmallIcon(R.drawable.ic_stat_luckynumber)
|
||||
.setAutoCancel(true)
|
||||
.setDefaults(DEFAULT_ALL)
|
||||
.setPriority(PRIORITY_HIGH)
|
||||
.setColor(context.getCompatColor(R.color.colorPrimary))
|
||||
.setContentIntent(
|
||||
PendingIntent.getActivity(context, MenuView.MESSAGE.id,
|
||||
MainActivity.getStartIntent(context, MenuView.LUCKY_NUMBER, true), FLAG_UPDATE_CURRENT))
|
||||
PendingIntent.getActivity(context, MainView.Section.MESSAGE.id,
|
||||
MainActivity.getStartIntent(context, MainView.Section.LUCKY_NUMBER, true), FLAG_UPDATE_CURRENT))
|
||||
.build())
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ import io.github.wulkanowy.data.repositories.message.MessageRepository
|
||||
import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository
|
||||
import io.github.wulkanowy.services.sync.channels.NewEntriesChannel
|
||||
import io.github.wulkanowy.ui.modules.main.MainActivity
|
||||
import io.github.wulkanowy.ui.modules.main.MainView.MenuView
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import io.github.wulkanowy.utils.getCompatColor
|
||||
import io.reactivex.Completable
|
||||
import javax.inject.Inject
|
||||
@ -42,14 +42,14 @@ class MessageWork @Inject constructor(
|
||||
notificationManager.notify(Random.nextInt(Int.MAX_VALUE), NotificationCompat.Builder(context, NewEntriesChannel.CHANNEL_ID)
|
||||
.setContentTitle(context.resources.getQuantityString(R.plurals.message_new_items, messages.size, messages.size))
|
||||
.setContentText(context.resources.getQuantityString(R.plurals.message_notify_new_items, messages.size, messages.size))
|
||||
.setSmallIcon(R.drawable.ic_stat_notify_message)
|
||||
.setSmallIcon(R.drawable.ic_stat_message)
|
||||
.setAutoCancel(true)
|
||||
.setDefaults(DEFAULT_ALL)
|
||||
.setPriority(PRIORITY_HIGH)
|
||||
.setColor(context.getCompatColor(R.color.colorPrimary))
|
||||
.setContentIntent(
|
||||
PendingIntent.getActivity(context, MenuView.MESSAGE.id,
|
||||
MainActivity.getStartIntent(context, MenuView.MESSAGE, true), FLAG_UPDATE_CURRENT)
|
||||
PendingIntent.getActivity(context, MainView.Section.MESSAGE.id,
|
||||
MainActivity.getStartIntent(context, MainView.Section.MESSAGE, true), FLAG_UPDATE_CURRENT)
|
||||
)
|
||||
.setStyle(NotificationCompat.InboxStyle().run {
|
||||
setSummaryText(context.resources.getQuantityString(R.plurals.message_number_item, messages.size, messages.size))
|
||||
|
@ -15,7 +15,7 @@ import io.github.wulkanowy.data.repositories.note.NoteRepository
|
||||
import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository
|
||||
import io.github.wulkanowy.services.sync.channels.NewEntriesChannel
|
||||
import io.github.wulkanowy.ui.modules.main.MainActivity
|
||||
import io.github.wulkanowy.ui.modules.main.MainView.MenuView
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import io.github.wulkanowy.utils.getCompatColor
|
||||
import io.reactivex.Completable
|
||||
import javax.inject.Inject
|
||||
@ -41,14 +41,14 @@ class NoteWork @Inject constructor(
|
||||
notificationManager.notify(Random.nextInt(Int.MAX_VALUE), NotificationCompat.Builder(context, NewEntriesChannel.CHANNEL_ID)
|
||||
.setContentTitle(context.resources.getQuantityString(R.plurals.note_new_items, notes.size, notes.size))
|
||||
.setContentText(context.resources.getQuantityString(R.plurals.note_notify_new_items, notes.size, notes.size))
|
||||
.setSmallIcon(R.drawable.ic_stat_notify_note)
|
||||
.setSmallIcon(R.drawable.ic_stat_note)
|
||||
.setAutoCancel(true)
|
||||
.setDefaults(DEFAULT_ALL)
|
||||
.setPriority(PRIORITY_HIGH)
|
||||
.setColor(context.getCompatColor(R.color.colorPrimary))
|
||||
.setContentIntent(
|
||||
PendingIntent.getActivity(context, MenuView.NOTE.id,
|
||||
MainActivity.getStartIntent(context, MenuView.NOTE, true), FLAG_UPDATE_CURRENT))
|
||||
PendingIntent.getActivity(context, MainView.Section.NOTE.id,
|
||||
MainActivity.getStartIntent(context, MainView.Section.NOTE, true), FLAG_UPDATE_CURRENT))
|
||||
.setStyle(NotificationCompat.InboxStyle().run {
|
||||
setSummaryText(context.resources.getQuantityString(R.plurals.note_number_item, notes.size, notes.size))
|
||||
notes.forEach { addLine("${it.teacher}: ${it.category}") }
|
||||
|
@ -3,7 +3,7 @@ package io.github.wulkanowy.services.widgets
|
||||
import android.content.Intent
|
||||
import android.widget.RemoteViewsService
|
||||
import dagger.android.AndroidInjection
|
||||
import io.github.wulkanowy.data.db.SharedPrefHelper
|
||||
import io.github.wulkanowy.data.db.SharedPrefProvider
|
||||
import io.github.wulkanowy.data.repositories.semester.SemesterRepository
|
||||
import io.github.wulkanowy.data.repositories.student.StudentRepository
|
||||
import io.github.wulkanowy.data.repositories.timetable.TimetableRepository
|
||||
@ -23,7 +23,7 @@ class TimetableWidgetService : RemoteViewsService() {
|
||||
lateinit var semesterRepo: SemesterRepository
|
||||
|
||||
@Inject
|
||||
lateinit var sharedPref: SharedPrefHelper
|
||||
lateinit var sharedPref: SharedPrefProvider
|
||||
|
||||
@Inject
|
||||
lateinit var schedulers: SchedulersProvider
|
||||
|
@ -1,24 +1,31 @@
|
||||
package io.github.wulkanowy.ui.base
|
||||
|
||||
import android.app.ActivityManager
|
||||
import android.os.Build.VERSION.SDK_INT
|
||||
import android.os.Build.VERSION_CODES.LOLLIPOP
|
||||
import android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
import android.os.Bundle
|
||||
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.fragment.app.Fragment
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.google.android.material.snackbar.Snackbar.LENGTH_LONG
|
||||
import dagger.android.AndroidInjection
|
||||
import dagger.android.DispatchingAndroidInjector
|
||||
import dagger.android.support.HasSupportFragmentInjector
|
||||
import dagger.android.HasAndroidInjector
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.ui.modules.login.LoginActivity
|
||||
import io.github.wulkanowy.utils.FragmentLifecycleLogger
|
||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||
import javax.inject.Inject
|
||||
|
||||
abstract class BaseActivity : AppCompatActivity(), BaseView, HasSupportFragmentInjector {
|
||||
abstract class BaseActivity<T : BasePresenter<out BaseView>> : AppCompatActivity(), BaseView, HasAndroidInjector {
|
||||
|
||||
@Inject
|
||||
lateinit var supportFragmentInjector: DispatchingAndroidInjector<Fragment>
|
||||
lateinit var androidInjector: DispatchingAndroidInjector<Any>
|
||||
|
||||
@Inject
|
||||
lateinit var fragmentLifecycleLogger: FragmentLifecycleLogger
|
||||
@ -28,12 +35,19 @@ abstract class BaseActivity : AppCompatActivity(), BaseView, HasSupportFragmentI
|
||||
|
||||
protected var messageContainer: View? = null
|
||||
|
||||
abstract var presenter: T
|
||||
|
||||
public override fun onCreate(savedInstanceState: Bundle?) {
|
||||
AndroidInjection.inject(this)
|
||||
themeManager.applyTheme(this)
|
||||
themeManager.applyActivityTheme(this)
|
||||
super.onCreate(savedInstanceState)
|
||||
supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleLogger, true)
|
||||
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
|
||||
|
||||
if (SDK_INT >= LOLLIPOP) {
|
||||
@Suppress("DEPRECATION")
|
||||
setTaskDescription(ActivityManager.TaskDescription(null, null, getThemeAttrColor(R.attr.colorSurface)))
|
||||
}
|
||||
}
|
||||
|
||||
override fun showError(text: String, error: Throwable) {
|
||||
@ -51,10 +65,25 @@ abstract class BaseActivity : AppCompatActivity(), BaseView, HasSupportFragmentI
|
||||
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() {
|
||||
super.onDestroy()
|
||||
invalidateOptionsMenu()
|
||||
presenter.onDetachView()
|
||||
}
|
||||
|
||||
override fun supportFragmentInjector() = supportFragmentInjector
|
||||
override fun androidInjector() = androidInjector
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ abstract class BaseFragment : DaggerFragment(), BaseView {
|
||||
}
|
||||
.show()
|
||||
} else {
|
||||
(activity as? BaseActivity)?.showError(text, error)
|
||||
(activity as? BaseActivity<*>)?.showError(text, error)
|
||||
}
|
||||
}
|
||||
|
||||
@ -26,7 +26,15 @@ abstract class BaseFragment : DaggerFragment(), BaseView {
|
||||
if (messageContainer != null) {
|
||||
Snackbar.make(messageContainer!!, text, LENGTH_LONG).show()
|
||||
} else {
|
||||
(activity as? BaseActivity)?.showMessage(text)
|
||||
(activity as? BaseActivity<*>)?.showMessage(text)
|
||||
}
|
||||
}
|
||||
|
||||
override fun showExpiredDialog() {
|
||||
(activity as? BaseActivity<*>)?.showExpiredDialog()
|
||||
}
|
||||
|
||||
override fun openClearLoginView() {
|
||||
(activity as? BaseActivity<*>)?.openClearLoginView()
|
||||
}
|
||||
}
|
||||
|
@ -4,14 +4,15 @@ import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.fragment.app.FragmentPagerAdapter
|
||||
|
||||
class BaseFragmentPagerAdapter(private val fragmentManager: FragmentManager) : FragmentPagerAdapter(fragmentManager) {
|
||||
class BaseFragmentPagerAdapter(private val fragmentManager: FragmentManager) :
|
||||
FragmentPagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
|
||||
|
||||
private val pages = mutableMapOf<Fragment, String?>()
|
||||
|
||||
var containerId = 0
|
||||
|
||||
fun getFragmentInstance(position: Int): Fragment? {
|
||||
if (containerId == 0) throw IllegalArgumentException("Container id is 0")
|
||||
require(containerId != 0) { "Container id is 0" }
|
||||
return fragmentManager.findFragmentByTag("android:switcher:$containerId:$position")
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,16 @@
|
||||
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 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()
|
||||
|
||||
@ -10,7 +18,33 @@ open class BasePresenter<T : BaseView>(private val errorHandler: ErrorHandler) {
|
||||
|
||||
open fun onAttachView(view: T) {
|
||||
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() {
|
||||
|
@ -5,4 +5,8 @@ interface BaseView {
|
||||
fun showError(text: String, error: Throwable)
|
||||
|
||||
fun showMessage(text: String)
|
||||
|
||||
fun showExpiredDialog()
|
||||
|
||||
fun openClearLoginView()
|
||||
}
|
||||
|
@ -2,13 +2,13 @@ package io.github.wulkanowy.ui.base
|
||||
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context.CLIPBOARD_SERVICE
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import android.widget.Toast.LENGTH_LONG
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import io.github.wulkanowy.R
|
||||
import kotlinx.android.synthetic.main.dialog_error.*
|
||||
@ -49,7 +49,7 @@ class ErrorDialog : DialogFragment() {
|
||||
errorDialogContent.text = writer.toString()
|
||||
errorDialogCopy.setOnClickListener {
|
||||
ClipData.newPlainText("wulkanowyError", writer.toString()).let { clip ->
|
||||
(activity?.getSystemService(CLIPBOARD_SERVICE) as? ClipboardManager)?.primaryClip = clip
|
||||
activity?.getSystemService<ClipboardManager>()?.primaryClip = clip
|
||||
}
|
||||
Toast.makeText(context, R.string.all_copied, LENGTH_LONG).show()
|
||||
}
|
||||
|
@ -5,7 +5,10 @@ import com.readystatesoftware.chuck.api.ChuckCollector
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.api.interceptor.FeatureDisabledException
|
||||
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.data.exceptions.NoCurrentStudentException
|
||||
import io.github.wulkanowy.utils.security.ScramblerException
|
||||
import timber.log.Timber
|
||||
import java.net.SocketTimeoutException
|
||||
import java.net.UnknownHostException
|
||||
@ -15,6 +18,10 @@ open class ErrorHandler @Inject constructor(protected val resources: Resources,
|
||||
|
||||
var showErrorMessage: (String, Throwable) -> Unit = { _, _ -> }
|
||||
|
||||
var onSessionExpired: () -> Unit = {}
|
||||
|
||||
var onNoCurrentStudent: () -> Unit = {}
|
||||
|
||||
fun dispatch(error: Throwable) {
|
||||
chuckCollector.onError(error.javaClass.simpleName, error)
|
||||
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) {
|
||||
showErrorMessage((when (error) {
|
||||
is UnknownHostException -> resources.getString(R.string.error_no_internet)
|
||||
is SocketTimeoutException -> resources.getString(R.string.error_timeout)
|
||||
is NotLoggedInException -> resources.getString(R.string.error_login_failed)
|
||||
is ServiceUnavailableException -> resources.getString(R.string.error_service_unavailable)
|
||||
is FeatureDisabledException -> resources.getString(R.string.error_feature_disabled)
|
||||
else -> resources.getString(R.string.error_unknown)
|
||||
}), error)
|
||||
resources.run {
|
||||
when (error) {
|
||||
is UnknownHostException -> showErrorMessage(getString(R.string.error_no_internet), error)
|
||||
is SocketTimeoutException -> showErrorMessage(getString(R.string.error_timeout), error)
|
||||
is NotLoggedInException -> showErrorMessage(getString(R.string.error_login_failed), error)
|
||||
is ServiceUnavailableException -> showErrorMessage(getString(R.string.error_service_unavailable), error)
|
||||
is FeatureDisabledException -> showErrorMessage(getString(R.string.error_feature_disabled), error)
|
||||
is ScramblerException, is BadCredentialsException -> onSessionExpired()
|
||||
is NoCurrentStudentException -> onNoCurrentStudent()
|
||||
else -> showErrorMessage(getString(R.string.error_unknown), error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open fun clear() {
|
||||
showErrorMessage = { _, _ -> }
|
||||
onSessionExpired = {}
|
||||
onNoCurrentStudent = {}
|
||||
}
|
||||
}
|
||||
|
@ -2,29 +2,31 @@ package io.github.wulkanowy.ui.base
|
||||
|
||||
import android.content.pm.PackageManager.GET_ACTIVITIES
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_NO
|
||||
import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_YES
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class ThemeManager @Inject constructor(private val preferencesRepository: PreferencesRepository) {
|
||||
|
||||
fun applyTheme(activity: AppCompatActivity) {
|
||||
fun applyActivityTheme(activity: AppCompatActivity) {
|
||||
if (isThemeApplicable(activity)) {
|
||||
activity.delegate.apply {
|
||||
when (preferencesRepository.appTheme) {
|
||||
"light" -> setLocalNightMode(MODE_NIGHT_NO)
|
||||
"dark" -> setLocalNightMode(MODE_NIGHT_YES)
|
||||
"black" -> {
|
||||
setLocalNightMode(MODE_NIGHT_YES)
|
||||
activity.setTheme(R.style.WulkanowyTheme_Black)
|
||||
}
|
||||
}
|
||||
}
|
||||
applyDefaultTheme()
|
||||
if (preferencesRepository.appTheme == "black") activity.setTheme(R.style.WulkanowyTheme_Black)
|
||||
}
|
||||
}
|
||||
|
||||
fun applyDefaultTheme() {
|
||||
AppCompatDelegate.setDefaultNightMode(
|
||||
if (preferencesRepository.appTheme == "light") MODE_NIGHT_NO
|
||||
else MODE_NIGHT_YES
|
||||
)
|
||||
}
|
||||
|
||||
private fun isThemeApplicable(activity: AppCompatActivity): Boolean {
|
||||
return activity.packageManager.getPackageInfo(activity.packageName, GET_ACTIVITIES)
|
||||
.activities.singleOrNull { it.name == activity::class.java.canonicalName }?.theme
|
||||
|
@ -1,21 +0,0 @@
|
||||
package io.github.wulkanowy.ui.base.session
|
||||
|
||||
import android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
import io.github.wulkanowy.ui.base.BaseFragment
|
||||
import io.github.wulkanowy.ui.modules.login.LoginActivity
|
||||
import io.github.wulkanowy.ui.modules.main.MainActivity
|
||||
|
||||
open class BaseSessionFragment : BaseFragment(), BaseSessionView {
|
||||
|
||||
override fun showExpiredDialog() {
|
||||
(activity as? MainActivity)?.showExpiredDialog()
|
||||
}
|
||||
|
||||
override fun openLoginView() {
|
||||
activity?.also {
|
||||
startActivity(LoginActivity.getStartIntent(it)
|
||||
.apply { addFlags(FLAG_ACTIVITY_CLEAR_TASK or FLAG_ACTIVITY_NEW_TASK) })
|
||||
}
|
||||
}
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
package io.github.wulkanowy.ui.base.session
|
||||
|
||||
import io.github.wulkanowy.ui.base.BasePresenter
|
||||
|
||||
open class BaseSessionPresenter<T : BaseSessionView>(private val errorHandler: SessionErrorHandler) :
|
||||
BasePresenter<T>(errorHandler) {
|
||||
|
||||
override fun onAttachView(view: T) {
|
||||
super.onAttachView(view)
|
||||
errorHandler.apply {
|
||||
onDecryptionFail = { view.showExpiredDialog() }
|
||||
onNoCurrentStudent = { view.openLoginView() }
|
||||
}
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
package io.github.wulkanowy.ui.base.session
|
||||
|
||||
import io.github.wulkanowy.ui.base.BaseView
|
||||
|
||||
interface BaseSessionView : BaseView {
|
||||
|
||||
fun showExpiredDialog()
|
||||
|
||||
fun openLoginView()
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
package io.github.wulkanowy.ui.base.session
|
||||
|
||||
import android.content.res.Resources
|
||||
import com.readystatesoftware.chuck.api.ChuckCollector
|
||||
import io.github.wulkanowy.data.exceptions.NoCurrentStudentException
|
||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||
import io.github.wulkanowy.utils.security.ScramblerException
|
||||
import javax.inject.Inject
|
||||
|
||||
open class SessionErrorHandler @Inject constructor(
|
||||
resources: Resources,
|
||||
chuckCollector: ChuckCollector
|
||||
) : ErrorHandler(resources, chuckCollector) {
|
||||
|
||||
var onDecryptionFail: () -> Unit = {}
|
||||
|
||||
var onNoCurrentStudent: () -> Unit = {}
|
||||
|
||||
override fun proceed(error: Throwable) {
|
||||
when (error) {
|
||||
is ScramblerException -> onDecryptionFail()
|
||||
is NoCurrentStudentException -> onNoCurrentStudent()
|
||||
else -> super.proceed(error)
|
||||
}
|
||||
}
|
||||
|
||||
override fun clear() {
|
||||
super.clear()
|
||||
onDecryptionFail = {}
|
||||
onNoCurrentStudent = {}
|
||||
}
|
||||
}
|
@ -1,19 +1,29 @@
|
||||
package io.github.wulkanowy.ui.modules.about
|
||||
|
||||
import android.content.Intent
|
||||
import android.content.Intent.ACTION_SENDTO
|
||||
import android.content.Intent.EXTRA_EMAIL
|
||||
import android.content.Intent.EXTRA_SUBJECT
|
||||
import android.content.Intent.EXTRA_TEXT
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.mikepenz.aboutlibraries.LibsBuilder
|
||||
import com.mikepenz.aboutlibraries.LibsFragmentCompat
|
||||
import io.github.wulkanowy.BuildConfig
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
|
||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.ui.base.BaseFragment
|
||||
import io.github.wulkanowy.ui.modules.about.license.LicenseFragment
|
||||
import io.github.wulkanowy.ui.modules.main.MainActivity
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import io.github.wulkanowy.utils.AppInfo
|
||||
import io.github.wulkanowy.utils.getCompatDrawable
|
||||
import io.github.wulkanowy.utils.openInternetBrowser
|
||||
import io.github.wulkanowy.utils.withOnExtraListener
|
||||
import io.github.wulkanowy.utils.setOnItemClickListener
|
||||
import kotlinx.android.synthetic.main.fragment_about.*
|
||||
import javax.inject.Inject
|
||||
|
||||
class AboutFragment : BaseFragment(), AboutView, MainView.TitledView {
|
||||
@ -22,60 +32,94 @@ class AboutFragment : BaseFragment(), AboutView, MainView.TitledView {
|
||||
lateinit var presenter: AboutPresenter
|
||||
|
||||
@Inject
|
||||
lateinit var fragmentCompat: LibsFragmentCompat
|
||||
lateinit var aboutAdapter: FlexibleAdapter<AbstractFlexibleItem<*>>
|
||||
|
||||
@Inject
|
||||
lateinit var appInfo: AppInfo
|
||||
|
||||
override val versionRes: Triple<String, String, Drawable?>?
|
||||
get() = context?.run {
|
||||
Triple(getString(R.string.about_version), "${appInfo.versionName} (${appInfo.versionCode})", getCompatDrawable(R.drawable.ic_all_about))
|
||||
}
|
||||
|
||||
override val feedbackRes: Triple<String, String, Drawable?>?
|
||||
get() = context?.run {
|
||||
Triple(getString(R.string.about_feedback), getString(R.string.about_feedback_summary), getCompatDrawable(R.drawable.ic_about_feedback))
|
||||
}
|
||||
|
||||
override val discordRes: Triple<String, String, Drawable?>?
|
||||
get() = context?.run {
|
||||
Triple(getString(R.string.about_discord), getString(R.string.about_discord_summary), getCompatDrawable(R.drawable.ic_about_discord))
|
||||
}
|
||||
|
||||
override val homepageRes: Triple<String, String, Drawable?>?
|
||||
get() = context?.run {
|
||||
Triple(getString(R.string.about_homepage), getString(R.string.about_homepage_summary), getCompatDrawable(R.drawable.ic_about_homepage))
|
||||
}
|
||||
|
||||
override val licensesRes: Triple<String, String, Drawable?>?
|
||||
get() = context?.run {
|
||||
Triple(getString(R.string.about_licenses), getString(R.string.about_licenses_summary), getCompatDrawable(R.drawable.ic_about_licenses))
|
||||
}
|
||||
|
||||
override val privacyRes: Triple<String, String, Drawable?>?
|
||||
get() = context?.run {
|
||||
Triple(getString(R.string.about_privacy), getString(R.string.about_privacy_summary), getCompatDrawable(R.drawable.ic_about_privacy))
|
||||
}
|
||||
|
||||
override val titleStringId get() = R.string.about_title
|
||||
|
||||
companion object {
|
||||
fun newInstance() = AboutFragment()
|
||||
}
|
||||
|
||||
override val titleStringId: Int
|
||||
get() = R.string.about_title
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
presenter.onAttachView(this)
|
||||
return Bundle().apply {
|
||||
putSerializable("data", LibsBuilder()
|
||||
.withAboutAppName(getString(R.string.app_name))
|
||||
.withAboutVersionShown(true)
|
||||
.withAboutIconShown(true)
|
||||
.withLicenseShown(true)
|
||||
.withAboutSpecial1(getString(R.string.about_discord_invite))
|
||||
.withAboutSpecial2(getString(R.string.about_homepage))
|
||||
.withAboutSpecial3(getString(R.string.about_feedback))
|
||||
.withFields(R.string::class.java.fields)
|
||||
.withCheckCachedDetection(false)
|
||||
.withExcludedLibraries("fastadapter", "AndroidIconics", "Jsoup", "Retrofit", "okio",
|
||||
"Butterknife", "CircleImageView")
|
||||
.withOnExtraListener { presenter.onExtraSelect(it) })
|
||||
}.let {
|
||||
fragmentCompat.onCreateView(inflater.context, inflater, container, savedInstanceState, it)
|
||||
}
|
||||
return inflater.inflate(R.layout.fragment_about, container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
fragmentCompat.onViewCreated(view, savedInstanceState)
|
||||
presenter.onAttachView(this)
|
||||
}
|
||||
|
||||
override fun openDiscordInviteView() {
|
||||
override fun initView() {
|
||||
aboutAdapter.setOnItemClickListener(presenter::onItemSelected)
|
||||
|
||||
with(aboutRecycler) {
|
||||
layoutManager = SmoothScrollLinearLayoutManager(context)
|
||||
adapter = aboutAdapter
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateData(header: AboutScrollableHeader, items: List<AboutItem>) {
|
||||
with(aboutAdapter) {
|
||||
removeAllScrollableHeaders()
|
||||
addScrollableHeader(header)
|
||||
updateDataSet(items)
|
||||
}
|
||||
}
|
||||
|
||||
override fun openDiscordInvite() {
|
||||
context?.openInternetBrowser("https://discord.gg/vccAQBr", ::showMessage)
|
||||
}
|
||||
|
||||
override fun openHomepageWebView() {
|
||||
override fun openHomepage() {
|
||||
context?.openInternetBrowser("https://wulkanowy.github.io/", ::showMessage)
|
||||
}
|
||||
|
||||
override fun openEmailClientView() {
|
||||
val intent = Intent(Intent.ACTION_SENDTO).apply {
|
||||
data = Uri.parse("mailto:")
|
||||
putExtra(Intent.EXTRA_EMAIL, Array(1) { "wulkanowyinc@gmail.com" })
|
||||
putExtra(Intent.EXTRA_SUBJECT, "Zgłoszenie błędu")
|
||||
putExtra(Intent.EXTRA_TEXT, "Tu umieść treść zgłoszenia\n\n" + "-".repeat(40) + "\n" + """
|
||||
Build: ${BuildConfig.VERSION_CODE}
|
||||
SDK: ${android.os.Build.VERSION.SDK_INT}
|
||||
Device: ${android.os.Build.MANUFACTURER} ${android.os.Build.MODEL}
|
||||
""".trimIndent())
|
||||
}
|
||||
override fun openEmailClient() {
|
||||
val intent = Intent(ACTION_SENDTO)
|
||||
.apply {
|
||||
data = Uri.parse("mailto:")
|
||||
putExtra(EXTRA_EMAIL, arrayOf("wulkanowyinc@gmail.com"))
|
||||
putExtra(EXTRA_SUBJECT, "Zgłoszenie błędu")
|
||||
putExtra(EXTRA_TEXT, "Tu umieść treść zgłoszenia\n\n${"-".repeat(40)}\n " +
|
||||
"""
|
||||
Build: ${appInfo.versionCode}
|
||||
SDK: ${appInfo.systemVersion}
|
||||
Device: ${appInfo.systemManufacturer} ${appInfo.systemModel}
|
||||
""".trimIndent())
|
||||
}
|
||||
|
||||
context?.let {
|
||||
if (intent.resolveActivity(it.packageManager) != null) {
|
||||
@ -86,8 +130,15 @@ class AboutFragment : BaseFragment(), AboutView, MainView.TitledView {
|
||||
}
|
||||
}
|
||||
|
||||
override fun openLicenses() {
|
||||
(activity as? MainActivity)?.pushView(LicenseFragment.newInstance())
|
||||
}
|
||||
|
||||
override fun openPrivacyPolicy() {
|
||||
context?.openInternetBrowser("https://wulkanowy.github.io/polityka-prywatnosci.html", ::showMessage)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
fragmentCompat.onDestroyView()
|
||||
presenter.onDetachView()
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
@ -0,0 +1,56 @@
|
||||
package io.github.wulkanowy.ui.modules.about
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.view.View
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
||||
import eu.davidea.flexibleadapter.items.IFlexible
|
||||
import eu.davidea.viewholders.FlexibleViewHolder
|
||||
import io.github.wulkanowy.R
|
||||
import kotlinx.android.extensions.LayoutContainer
|
||||
import kotlinx.android.synthetic.main.item_about.*
|
||||
|
||||
class AboutItem(
|
||||
val title: String,
|
||||
private val summary: String,
|
||||
private val image: Drawable?
|
||||
) : AbstractFlexibleItem<AboutItem.ViewHolder>() {
|
||||
|
||||
override fun getLayoutRes() = R.layout.item_about
|
||||
|
||||
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>) = ViewHolder(view, adapter)
|
||||
|
||||
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>, holder: ViewHolder, position: Int, payloads: MutableList<Any>) {
|
||||
with(holder) {
|
||||
aboutItemImage.setImageDrawable(image)
|
||||
aboutItemTitle.text = title
|
||||
aboutItemSummary.text = summary
|
||||
}
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as AboutItem
|
||||
|
||||
if (title != other.title) return false
|
||||
if (summary != other.summary) return false
|
||||
if (image != other.image) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = title.hashCode()
|
||||
result = 31 * result + summary.hashCode()
|
||||
result = 31 * result + (image?.hashCode() ?: 0)
|
||||
return result
|
||||
}
|
||||
|
||||
class ViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>) : FlexibleViewHolder(view, adapter),
|
||||
LayoutContainer {
|
||||
|
||||
override val containerView: View? get() = contentView
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
package io.github.wulkanowy.ui.modules.about
|
||||
|
||||
import com.mikepenz.aboutlibraries.LibsFragmentCompat
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import io.github.wulkanowy.di.scopes.PerFragment
|
||||
|
||||
@Module
|
||||
class AboutModule {
|
||||
|
||||
@PerFragment
|
||||
@Provides
|
||||
fun provideLibsFragmentCompat() = LibsFragmentCompat()
|
||||
}
|
@ -1,44 +1,70 @@
|
||||
package io.github.wulkanowy.ui.modules.about
|
||||
|
||||
import com.mikepenz.aboutlibraries.Libs
|
||||
import com.mikepenz.aboutlibraries.Libs.SpecialButton.SPECIAL1
|
||||
import com.mikepenz.aboutlibraries.Libs.SpecialButton.SPECIAL2
|
||||
import com.mikepenz.aboutlibraries.Libs.SpecialButton.SPECIAL3
|
||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
||||
import io.github.wulkanowy.data.repositories.student.StudentRepository
|
||||
import io.github.wulkanowy.ui.base.BasePresenter
|
||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
|
||||
import io.github.wulkanowy.utils.SchedulersProvider
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
class AboutPresenter @Inject constructor(
|
||||
schedulers: SchedulersProvider,
|
||||
errorHandler: ErrorHandler,
|
||||
studentRepository: StudentRepository,
|
||||
private val analytics: FirebaseAnalyticsHelper
|
||||
) : BasePresenter<AboutView>(errorHandler) {
|
||||
) : BasePresenter<AboutView>(errorHandler, studentRepository, schedulers) {
|
||||
|
||||
override fun onAttachView(view: AboutView) {
|
||||
super.onAttachView(view)
|
||||
view.initView()
|
||||
Timber.i("About view was initialized")
|
||||
loadData()
|
||||
}
|
||||
|
||||
fun onExtraSelect(type: Libs.SpecialButton?) {
|
||||
fun onItemSelected(item: AbstractFlexibleItem<*>) {
|
||||
if (item !is AboutItem) return
|
||||
view?.run {
|
||||
when (type) {
|
||||
SPECIAL1 -> {
|
||||
Timber.i("Opening discord invide page")
|
||||
analytics.logEvent("open_page", "name" to "discord")
|
||||
openDiscordInviteView()
|
||||
when (item.title) {
|
||||
feedbackRes?.first -> {
|
||||
Timber.i("Opening email client ")
|
||||
openEmailClient()
|
||||
analytics.logEvent("about_open", "name" to "feedback")
|
||||
}
|
||||
SPECIAL2 -> {
|
||||
Timber.i("Opening home page")
|
||||
analytics.logEvent("open_page", "name" to "home")
|
||||
openHomepageWebView()
|
||||
discordRes?.first -> {
|
||||
Timber.i("Opening discord")
|
||||
openDiscordInvite()
|
||||
analytics.logEvent("about_open", "name" to "discord")
|
||||
}
|
||||
SPECIAL3 -> {
|
||||
Timber.i("Opening email client")
|
||||
analytics.logEvent("open_page", "name" to "email")
|
||||
openEmailClientView()
|
||||
homepageRes?.first -> {
|
||||
Timber.i("Opening homepage")
|
||||
openHomepage()
|
||||
analytics.logEvent("about_open", "name" to "homepage")
|
||||
}
|
||||
licensesRes?.first -> {
|
||||
Timber.i("Opening licenses view")
|
||||
openLicenses()
|
||||
analytics.logEvent("about_open", "name" to "licenses")
|
||||
}
|
||||
privacyRes?.first -> {
|
||||
Timber.i("Opening privacy page ")
|
||||
openPrivacyPolicy()
|
||||
analytics.logEvent("about_open", "name" to "privacy")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadData() {
|
||||
view?.run {
|
||||
updateData(AboutScrollableHeader(), listOfNotNull(
|
||||
versionRes?.let { (title, summary, image) -> AboutItem(title, summary, image) },
|
||||
feedbackRes?.let { (title, summary, image) -> AboutItem(title, summary, image) },
|
||||
discordRes?.let { (title, summary, image) -> AboutItem(title, summary, image) },
|
||||
homepageRes?.let { (title, summary, image) -> AboutItem(title, summary, image) },
|
||||
licensesRes?.let { (title, summary, image) -> AboutItem(title, summary, image) },
|
||||
privacyRes?.let { (title, summary, image) -> AboutItem(title, summary, image) }))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,41 @@
|
||||
package io.github.wulkanowy.ui.modules.about
|
||||
|
||||
import android.view.View
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
||||
import eu.davidea.flexibleadapter.items.IFlexible
|
||||
import eu.davidea.viewholders.FlexibleViewHolder
|
||||
import io.github.wulkanowy.R
|
||||
import kotlinx.android.extensions.LayoutContainer
|
||||
import kotlinx.android.synthetic.main.scrollable_header_about.*
|
||||
|
||||
class AboutScrollableHeader : AbstractFlexibleItem<AboutScrollableHeader.ViewHolder>() {
|
||||
|
||||
override fun getLayoutRes() = R.layout.scrollable_header_about
|
||||
|
||||
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>) = ViewHolder(view, adapter)
|
||||
|
||||
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>, holder: ViewHolder, position: Int, payloads: MutableList<Any>) {
|
||||
with(holder) {
|
||||
val context = contentView.context
|
||||
val drawable = ResourcesCompat.getDrawableForDensity(context.resources, context.applicationInfo.icon, 640, null)
|
||||
|
||||
aboutScrollableHeaderIcon.setImageDrawable(drawable)
|
||||
}
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode() = javaClass.hashCode()
|
||||
|
||||
class ViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>) : FlexibleViewHolder(view, adapter),
|
||||
LayoutContainer {
|
||||
|
||||
override val containerView: View? get() = contentView
|
||||
}
|
||||
}
|
@ -1,12 +1,33 @@
|
||||
package io.github.wulkanowy.ui.modules.about
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
import io.github.wulkanowy.ui.base.BaseView
|
||||
|
||||
interface AboutView : BaseView {
|
||||
|
||||
fun openDiscordInviteView()
|
||||
val versionRes: Triple<String, String, Drawable?>?
|
||||
|
||||
fun openEmailClientView()
|
||||
val feedbackRes: Triple<String, String, Drawable?>?
|
||||
|
||||
fun openHomepageWebView()
|
||||
val discordRes: Triple<String, String, Drawable?>?
|
||||
|
||||
val homepageRes: Triple<String, String, Drawable?>?
|
||||
|
||||
val licensesRes: Triple<String, String, Drawable?>?
|
||||
|
||||
val privacyRes: Triple<String, String, Drawable?>?
|
||||
|
||||
fun initView()
|
||||
|
||||
fun updateData(header: AboutScrollableHeader, items: List<AboutItem>)
|
||||
|
||||
fun openDiscordInvite()
|
||||
|
||||
fun openEmailClient()
|
||||
|
||||
fun openHomepage()
|
||||
|
||||
fun openLicenses()
|
||||
|
||||
fun openPrivacyPolicy()
|
||||
}
|
||||
|
@ -0,0 +1,86 @@
|
||||
package io.github.wulkanowy.ui.modules.about.license
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.View.GONE
|
||||
import android.view.View.VISIBLE
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.text.parseAsHtml
|
||||
import com.mikepenz.aboutlibraries.Libs
|
||||
import com.mikepenz.aboutlibraries.entity.Library
|
||||
import dagger.Lazy
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
|
||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.ui.base.BaseFragment
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import io.github.wulkanowy.utils.setOnItemClickListener
|
||||
import kotlinx.android.synthetic.main.fragment_license.*
|
||||
import javax.inject.Inject
|
||||
|
||||
class LicenseFragment : BaseFragment(), LicenseView, MainView.TitledView {
|
||||
|
||||
@Inject
|
||||
lateinit var presenter: LicensePresenter
|
||||
|
||||
@Inject
|
||||
lateinit var licenseAdapter: FlexibleAdapter<AbstractFlexibleItem<*>>
|
||||
|
||||
@Inject
|
||||
lateinit var libs: Lazy<Libs>
|
||||
|
||||
override val titleStringId get() = R.string.license_title
|
||||
|
||||
override val appLibraries: ArrayList<Library>?
|
||||
get() = context?.let {
|
||||
libs.get().prepareLibraries(it, emptyArray(), emptyArray(), autoDetect = true, checkCachedDetection = true, sort = true)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun newInstance() = LicenseFragment()
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
return inflater.inflate(R.layout.fragment_license, container, false)
|
||||
}
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
presenter.onAttachView(this)
|
||||
}
|
||||
|
||||
override fun initView() {
|
||||
with(licenseRecycler) {
|
||||
layoutManager = SmoothScrollLinearLayoutManager(context)
|
||||
adapter = licenseAdapter
|
||||
}
|
||||
licenseAdapter.setOnItemClickListener(presenter::onItemSelected)
|
||||
}
|
||||
|
||||
override fun updateData(data: List<LicenseItem>) {
|
||||
licenseAdapter.updateDataSet(data)
|
||||
}
|
||||
|
||||
override fun openLicense(licenseHtml: String) {
|
||||
context?.let {
|
||||
AlertDialog.Builder(it).apply {
|
||||
setTitle(R.string.license_dialog_title)
|
||||
setMessage(licenseHtml.parseAsHtml())
|
||||
setPositiveButton(android.R.string.ok) { _, _ -> }
|
||||
show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun showProgress(show: Boolean) {
|
||||
licenseProgress.visibility = if (show) VISIBLE else GONE
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
presenter.onDetachView()
|
||||
super.onDestroyView()
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package io.github.wulkanowy.ui.modules.about.license
|
||||
|
||||
import android.view.View
|
||||
import com.mikepenz.aboutlibraries.entity.Library
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
||||
import eu.davidea.flexibleadapter.items.IFlexible
|
||||
import eu.davidea.viewholders.FlexibleViewHolder
|
||||
import io.github.wulkanowy.R
|
||||
import kotlinx.android.extensions.LayoutContainer
|
||||
import kotlinx.android.synthetic.main.item_license.*
|
||||
|
||||
class LicenseItem(val library: Library) : AbstractFlexibleItem<LicenseItem.ViewHolder>() {
|
||||
|
||||
override fun getLayoutRes() = R.layout.item_license
|
||||
|
||||
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>) = ViewHolder(view, adapter)
|
||||
|
||||
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>, holder: ViewHolder, position: Int, payloads: MutableList<Any>) {
|
||||
with(holder) {
|
||||
licenseItemName.text = library.libraryName
|
||||
licenseItemSummary.text = library.license?.licenseName
|
||||
}
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as LicenseItem
|
||||
|
||||
if (library != other.library) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode() = library.hashCode()
|
||||
|
||||
class ViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>) : FlexibleViewHolder(view, adapter),
|
||||
LayoutContainer {
|
||||
|
||||
override val containerView: View? get() = contentView
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package io.github.wulkanowy.ui.modules.about.license
|
||||
|
||||
import android.content.Context
|
||||
import com.mikepenz.aboutlibraries.Libs
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import io.github.wulkanowy.di.scopes.PerFragment
|
||||
|
||||
@Module
|
||||
class LicenseModule {
|
||||
|
||||
@PerFragment
|
||||
@Provides
|
||||
fun provideLibs(context: Context) = Libs(context)
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package io.github.wulkanowy.ui.modules.about.license
|
||||
|
||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
||||
import io.github.wulkanowy.data.repositories.student.StudentRepository
|
||||
import io.github.wulkanowy.ui.base.BasePresenter
|
||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||
import io.github.wulkanowy.utils.SchedulersProvider
|
||||
import io.reactivex.Single
|
||||
import javax.inject.Inject
|
||||
|
||||
class LicensePresenter @Inject constructor(
|
||||
schedulers: SchedulersProvider,
|
||||
errorHandler: ErrorHandler,
|
||||
studentRepository: StudentRepository
|
||||
) : BasePresenter<LicenseView>(errorHandler, studentRepository, schedulers) {
|
||||
|
||||
override fun onAttachView(view: LicenseView) {
|
||||
super.onAttachView(view)
|
||||
view.initView()
|
||||
loadData()
|
||||
}
|
||||
|
||||
fun onItemSelected(item: AbstractFlexibleItem<*>) {
|
||||
if (item !is LicenseItem) return
|
||||
view?.run { item.library.license?.licenseDescription?.let { openLicense(it) } }
|
||||
}
|
||||
|
||||
private fun loadData() {
|
||||
disposable.add(Single.fromCallable { view?.appLibraries }
|
||||
.map {
|
||||
val exclude = listOf("Android-Iconics", "CircleImageView", "FastAdapter", "Jsoup", "okio", "Retrofit")
|
||||
it.filter { library -> !exclude.contains(library.libraryName) }
|
||||
}
|
||||
.map { it.map { library -> LicenseItem(library) } }
|
||||
.subscribeOn(schedulers.backgroundThread)
|
||||
.observeOn(schedulers.mainThread)
|
||||
.doOnEvent { _, _ -> view?.showProgress(false) }
|
||||
.subscribe({ view?.run { updateData(it) } }, { errorHandler.dispatch(it) }))
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package io.github.wulkanowy.ui.modules.about.license
|
||||
|
||||
import com.mikepenz.aboutlibraries.entity.Library
|
||||
import io.github.wulkanowy.ui.base.BaseView
|
||||
|
||||
interface LicenseView : BaseView {
|
||||
|
||||
val appLibraries: ArrayList<Library>?
|
||||
|
||||
fun initView()
|
||||
|
||||
fun updateData(data: List<LicenseItem>)
|
||||
|
||||
fun openLicense(licenseHtml: String)
|
||||
|
||||
fun showProgress(show: Boolean)
|
||||
}
|
@ -1,7 +1,5 @@
|
||||
package io.github.wulkanowy.ui.modules.account
|
||||
|
||||
import android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
@ -14,6 +12,7 @@ import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
|
||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.ui.base.BaseActivity
|
||||
import io.github.wulkanowy.ui.modules.login.LoginActivity
|
||||
import io.github.wulkanowy.utils.setOnItemClickListener
|
||||
import kotlinx.android.synthetic.main.dialog_account.*
|
||||
@ -73,16 +72,17 @@ class AccountDialog : DaggerAppCompatDialogFragment(), AccountView {
|
||||
}
|
||||
|
||||
override fun openLoginView() {
|
||||
activity?.also {
|
||||
activity?.let {
|
||||
startActivity(LoginActivity.getStartIntent(it))
|
||||
}
|
||||
}
|
||||
|
||||
override fun showExpiredDialog() {
|
||||
(activity as? BaseActivity<*>)?.showExpiredDialog()
|
||||
}
|
||||
|
||||
override fun openClearLoginView() {
|
||||
activity?.also {
|
||||
startActivity(LoginActivity.getStartIntent(it)
|
||||
.apply { addFlags(FLAG_ACTIVITY_CLEAR_TASK or FLAG_ACTIVITY_NEW_TASK) })
|
||||
}
|
||||
(activity as? BaseActivity<*>)?.openClearLoginView()
|
||||
}
|
||||
|
||||
override fun showConfirmDialog() {
|
||||
|
@ -1,13 +1,16 @@
|
||||
package io.github.wulkanowy.ui.modules.account
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.graphics.PorterDuff
|
||||
import android.view.View
|
||||
import androidx.core.graphics.ColorUtils
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
||||
import eu.davidea.flexibleadapter.items.IFlexible
|
||||
import eu.davidea.viewholders.FlexibleViewHolder
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||
import kotlinx.android.extensions.LayoutContainer
|
||||
import kotlinx.android.synthetic.main.item_account.*
|
||||
|
||||
@ -15,16 +18,19 @@ class AccountItem(val student: Student) : AbstractFlexibleItem<AccountItem.ViewH
|
||||
|
||||
override fun getLayoutRes() = R.layout.item_account
|
||||
|
||||
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>): ViewHolder {
|
||||
return ViewHolder(view, adapter)
|
||||
}
|
||||
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>) = ViewHolder(view, adapter)
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>, holder: ViewHolder, position: Int, payloads: MutableList<Any>?) {
|
||||
holder.apply {
|
||||
val context = holder.contentView.context
|
||||
|
||||
val colorImage = if (student.isCurrent) context.getThemeAttrColor(R.attr.colorPrimary)
|
||||
else ColorUtils.setAlphaComponent(context.getThemeAttrColor(R.attr.colorOnSurface), 153)
|
||||
|
||||
with(holder) {
|
||||
accountItemName.text = "${student.studentName} ${student.className}"
|
||||
accountItemSchool.text = student.schoolName
|
||||
accountItemImage.setBackgroundResource(if (student.isCurrent) R.drawable.ic_account_circular_border else 0)
|
||||
accountItemImage.setColorFilter(colorImage, PorterDuff.Mode.SRC_IN)
|
||||
}
|
||||
}
|
||||
|
||||
@ -45,8 +51,9 @@ class AccountItem(val student: Student) : AbstractFlexibleItem<AccountItem.ViewH
|
||||
return result
|
||||
}
|
||||
|
||||
class ViewHolder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter), LayoutContainer {
|
||||
override val containerView: View
|
||||
get() = contentView
|
||||
class ViewHolder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter),
|
||||
LayoutContainer {
|
||||
|
||||
override val containerView: View? get() = contentView
|
||||
}
|
||||
}
|
||||
|
@ -11,11 +11,11 @@ import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
class AccountPresenter @Inject constructor(
|
||||
private val errorHandler: ErrorHandler,
|
||||
private val studentRepository: StudentRepository,
|
||||
private val syncManager: SyncManager,
|
||||
private val schedulers: SchedulersProvider
|
||||
) : BasePresenter<AccountView>(errorHandler) {
|
||||
schedulers: SchedulersProvider,
|
||||
errorHandler: ErrorHandler,
|
||||
studentRepository: StudentRepository,
|
||||
private val syncManager: SyncManager
|
||||
) : BasePresenter<AccountView>(errorHandler, studentRepository, schedulers) {
|
||||
|
||||
override fun onAttachView(view: AccountView) {
|
||||
super.onAttachView(view)
|
||||
|
@ -14,8 +14,6 @@ interface AccountView : BaseView {
|
||||
|
||||
fun openLoginView()
|
||||
|
||||
fun openClearLoginView()
|
||||
|
||||
fun recreateMainView()
|
||||
}
|
||||
|
||||
|
@ -13,15 +13,17 @@ import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
|
||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.Attendance
|
||||
import io.github.wulkanowy.ui.base.session.BaseSessionFragment
|
||||
import io.github.wulkanowy.ui.base.BaseFragment
|
||||
import io.github.wulkanowy.ui.modules.attendance.summary.AttendanceSummaryFragment
|
||||
import io.github.wulkanowy.ui.modules.main.MainActivity
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import io.github.wulkanowy.utils.dpToPx
|
||||
import io.github.wulkanowy.utils.setOnItemClickListener
|
||||
import kotlinx.android.synthetic.main.fragment_attendance.*
|
||||
import javax.inject.Inject
|
||||
|
||||
class AttendanceFragment : BaseSessionFragment(), AttendanceView, MainView.MainChildView, MainView.TitledView {
|
||||
class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MainChildView,
|
||||
MainView.TitledView {
|
||||
|
||||
@Inject
|
||||
lateinit var presenter: AttendancePresenter
|
||||
@ -35,14 +37,11 @@ class AttendanceFragment : BaseSessionFragment(), AttendanceView, MainView.MainC
|
||||
fun newInstance() = AttendanceFragment()
|
||||
}
|
||||
|
||||
override val titleStringId: Int
|
||||
get() = R.string.attendance_title
|
||||
override val titleStringId get() = R.string.attendance_title
|
||||
|
||||
override val isViewEmpty: Boolean
|
||||
get() = attendanceAdapter.isEmpty
|
||||
override val isViewEmpty get() = attendanceAdapter.isEmpty
|
||||
|
||||
override val currentStackSize: Int?
|
||||
get() = (activity as? MainActivity)?.currentStackSize
|
||||
override val currentStackSize get() = (activity as? MainActivity)?.currentStackSize
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@ -60,21 +59,21 @@ class AttendanceFragment : BaseSessionFragment(), AttendanceView, MainView.MainC
|
||||
}
|
||||
|
||||
override fun initView() {
|
||||
attendanceAdapter.apply {
|
||||
setOnItemClickListener { presenter.onAttendanceItemSelected(it) }
|
||||
}
|
||||
attendanceAdapter.setOnItemClickListener(presenter::onAttendanceItemSelected)
|
||||
|
||||
attendanceRecycler.run {
|
||||
with(attendanceRecycler) {
|
||||
layoutManager = SmoothScrollLinearLayoutManager(context)
|
||||
adapter = attendanceAdapter
|
||||
addItemDecoration(FlexibleItemDecoration(context)
|
||||
.withDefaultDivider()
|
||||
.withDrawDividerOnLastItem(false)
|
||||
)
|
||||
.withDrawDividerOnLastItem(false))
|
||||
}
|
||||
attendanceSwipe.setOnRefreshListener { presenter.onSwipeRefresh() }
|
||||
|
||||
attendanceSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
|
||||
attendancePreviousButton.setOnClickListener { presenter.onPreviousDay() }
|
||||
attendanceNextButton.setOnClickListener { presenter.onNextDay() }
|
||||
|
||||
attendanceNavContainer.setElevationCompat(requireContext().dpToPx(8f))
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
|
@ -1,15 +1,15 @@
|
||||
package io.github.wulkanowy.ui.modules.attendance
|
||||
|
||||
import com.google.firebase.analytics.FirebaseAnalytics.Param.START_DATE
|
||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
||||
import io.github.wulkanowy.data.repositories.attendance.AttendanceRepository
|
||||
import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository
|
||||
import io.github.wulkanowy.data.repositories.semester.SemesterRepository
|
||||
import io.github.wulkanowy.data.repositories.student.StudentRepository
|
||||
import io.github.wulkanowy.ui.base.session.BaseSessionPresenter
|
||||
import io.github.wulkanowy.ui.base.session.SessionErrorHandler
|
||||
import io.github.wulkanowy.ui.base.BasePresenter
|
||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
|
||||
import io.github.wulkanowy.utils.SchedulersProvider
|
||||
import io.github.wulkanowy.utils.getLastSchoolDayIfHoliday
|
||||
import io.github.wulkanowy.utils.isHolidays
|
||||
import io.github.wulkanowy.utils.nextSchoolDay
|
||||
import io.github.wulkanowy.utils.previousOrSameSchoolDay
|
||||
@ -23,14 +23,16 @@ import java.util.concurrent.TimeUnit.MILLISECONDS
|
||||
import javax.inject.Inject
|
||||
|
||||
class AttendancePresenter @Inject constructor(
|
||||
private val errorHandler: SessionErrorHandler,
|
||||
private val schedulers: SchedulersProvider,
|
||||
schedulers: SchedulersProvider,
|
||||
errorHandler: ErrorHandler,
|
||||
studentRepository: StudentRepository,
|
||||
private val attendanceRepository: AttendanceRepository,
|
||||
private val studentRepository: StudentRepository,
|
||||
private val semesterRepository: SemesterRepository,
|
||||
private val prefRepository: PreferencesRepository,
|
||||
private val analytics: FirebaseAnalyticsHelper
|
||||
) : BaseSessionPresenter<AttendanceView>(errorHandler) {
|
||||
) : BasePresenter<AttendanceView>(errorHandler, studentRepository, schedulers) {
|
||||
|
||||
private var baseDate: LocalDate = now().previousOrSameSchoolDay
|
||||
|
||||
lateinit var currentDate: LocalDate
|
||||
private set
|
||||
@ -39,7 +41,8 @@ class AttendancePresenter @Inject constructor(
|
||||
super.onAttachView(view)
|
||||
view.initView()
|
||||
Timber.i("Attendance view was initialized")
|
||||
loadData(ofEpochDay(date ?: now().previousOrSameSchoolDay.toEpochDay()))
|
||||
loadData(ofEpochDay(date ?: baseDate.toEpochDay()))
|
||||
if (currentDate.isHolidays) setBaseDateOnHolidays()
|
||||
reloadView()
|
||||
}
|
||||
|
||||
@ -62,7 +65,7 @@ class AttendancePresenter @Inject constructor(
|
||||
Timber.i("Attendance view is reselected")
|
||||
view?.also { view ->
|
||||
if (view.currentStackSize == 1) {
|
||||
now().previousOrSameSchoolDay.also {
|
||||
baseDate.also {
|
||||
if (currentDate != it) {
|
||||
loadData(it)
|
||||
reloadView()
|
||||
@ -84,6 +87,20 @@ class AttendancePresenter @Inject constructor(
|
||||
return true
|
||||
}
|
||||
|
||||
private fun setBaseDateOnHolidays() {
|
||||
disposable.add(studentRepository.getCurrentStudent()
|
||||
.flatMap { semesterRepository.getCurrentSemester(it) }
|
||||
.subscribeOn(schedulers.backgroundThread)
|
||||
.observeOn(schedulers.mainThread)
|
||||
.subscribe({
|
||||
baseDate = baseDate.getLastSchoolDayIfHoliday(it.schoolYear)
|
||||
currentDate = baseDate
|
||||
reloadNavigation()
|
||||
}) {
|
||||
Timber.i("Loading semester result: An exception occurred")
|
||||
})
|
||||
}
|
||||
|
||||
private fun loadData(date: LocalDate, forceRefresh: Boolean = false) {
|
||||
Timber.i("Loading attendance data started")
|
||||
currentDate = date
|
||||
@ -115,7 +132,7 @@ class AttendancePresenter @Inject constructor(
|
||||
showEmpty(it.isEmpty())
|
||||
showContent(it.isNotEmpty())
|
||||
}
|
||||
analytics.logEvent("load_attendance", "items" to it.size, "force_refresh" to forceRefresh, START_DATE to currentDate.toFormattedString("yyyy-MM-dd"))
|
||||
analytics.logEvent("load_attendance", "items" to it.size, "force_refresh" to forceRefresh)
|
||||
}) {
|
||||
Timber.i("Loading attendance result: An exception occurred")
|
||||
view?.run { showEmpty(isViewEmpty) }
|
||||
@ -133,8 +150,14 @@ class AttendancePresenter @Inject constructor(
|
||||
showContent(false)
|
||||
showEmpty(false)
|
||||
clearData()
|
||||
showNextButton(!currentDate.plusDays(1).isHolidays)
|
||||
reloadNavigation()
|
||||
}
|
||||
}
|
||||
|
||||
private fun reloadNavigation() {
|
||||
view?.apply {
|
||||
showPreButton(!currentDate.minusDays(1).isHolidays)
|
||||
showNextButton(!currentDate.plusDays(1).isHolidays)
|
||||
updateNavigationDay(currentDate.toFormattedString("EEEE\ndd.MM.YYYY").capitalize())
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
package io.github.wulkanowy.ui.modules.attendance
|
||||
|
||||
import io.github.wulkanowy.data.db.entities.Attendance
|
||||
import io.github.wulkanowy.ui.base.session.BaseSessionView
|
||||
import io.github.wulkanowy.ui.base.BaseView
|
||||
|
||||
interface AttendanceView : BaseSessionView {
|
||||
interface AttendanceView : BaseView {
|
||||
|
||||
val isViewEmpty: Boolean
|
||||
|
||||
|
@ -13,13 +13,14 @@ import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
|
||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.ui.base.session.BaseSessionFragment
|
||||
import io.github.wulkanowy.ui.base.BaseFragment
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import io.github.wulkanowy.utils.dpToPx
|
||||
import io.github.wulkanowy.utils.setOnItemSelectedListener
|
||||
import kotlinx.android.synthetic.main.fragment_attendance_summary.*
|
||||
import javax.inject.Inject
|
||||
|
||||
class AttendanceSummaryFragment : BaseSessionFragment(), AttendanceSummaryView, MainView.TitledView {
|
||||
class AttendanceSummaryFragment : BaseFragment(), AttendanceSummaryView, MainView.TitledView {
|
||||
|
||||
@Inject
|
||||
lateinit var presenter: AttendanceSummaryPresenter
|
||||
@ -35,11 +36,9 @@ class AttendanceSummaryFragment : BaseSessionFragment(), AttendanceSummaryView,
|
||||
fun newInstance() = AttendanceSummaryFragment()
|
||||
}
|
||||
|
||||
override val titleStringId: Int
|
||||
get() = R.string.attendance_title
|
||||
override val titleStringId get() = R.string.attendance_title
|
||||
|
||||
override val isViewEmpty
|
||||
get() = attendanceSummaryAdapter.isEmpty
|
||||
override val isViewEmpty get() = attendanceSummaryAdapter.isEmpty
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
return inflater.inflate(R.layout.fragment_attendance_summary, container, false)
|
||||
@ -52,25 +51,26 @@ class AttendanceSummaryFragment : BaseSessionFragment(), AttendanceSummaryView,
|
||||
}
|
||||
|
||||
override fun initView() {
|
||||
attendanceSummaryRecycler.run {
|
||||
with(attendanceSummaryRecycler) {
|
||||
layoutManager = SmoothScrollLinearLayoutManager(context)
|
||||
adapter = attendanceSummaryAdapter
|
||||
}
|
||||
attendanceSummarySwipe.setOnRefreshListener { presenter.onSwipeRefresh() }
|
||||
|
||||
context?.let {
|
||||
subjectsAdapter = ArrayAdapter(it, android.R.layout.simple_spinner_item, ArrayList<String>())
|
||||
subjectsAdapter.setDropDownViewResource(R.layout.item_attendance_summary_subject)
|
||||
}
|
||||
attendanceSummarySwipe.setOnRefreshListener(presenter::onSwipeRefresh)
|
||||
|
||||
attendanceSummarySubjects.run {
|
||||
subjectsAdapter = ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item, mutableListOf())
|
||||
subjectsAdapter.setDropDownViewResource(R.layout.item_attendance_summary_subject)
|
||||
|
||||
with(attendanceSummarySubjects) {
|
||||
adapter = subjectsAdapter
|
||||
setOnItemSelectedListener { presenter.onSubjectSelected((it as TextView).text.toString()) }
|
||||
setOnItemSelectedListener<TextView> { presenter.onSubjectSelected(it?.text?.toString()) }
|
||||
}
|
||||
|
||||
attendanceSummarySubjectsContainer.setElevationCompat(requireContext().dpToPx(1f))
|
||||
}
|
||||
|
||||
override fun updateSubjects(data: ArrayList<String>) {
|
||||
subjectsAdapter.run {
|
||||
with(subjectsAdapter) {
|
||||
clear()
|
||||
addAll(data)
|
||||
notifyDataSetChanged()
|
||||
@ -78,9 +78,9 @@ class AttendanceSummaryFragment : BaseSessionFragment(), AttendanceSummaryView,
|
||||
}
|
||||
|
||||
override fun updateDataSet(data: List<AttendanceSummaryItem>, header: AttendanceSummaryScrollableHeader) {
|
||||
attendanceSummaryAdapter.apply {
|
||||
with(attendanceSummaryAdapter) {
|
||||
updateDataSet(data, true)
|
||||
removeAllScrollableHeaders()
|
||||
removeAllScrollableFooters()
|
||||
addScrollableHeader(header)
|
||||
}
|
||||
}
|
||||
|
@ -6,8 +6,8 @@ import io.github.wulkanowy.data.repositories.attendancesummary.AttendanceSummary
|
||||
import io.github.wulkanowy.data.repositories.semester.SemesterRepository
|
||||
import io.github.wulkanowy.data.repositories.student.StudentRepository
|
||||
import io.github.wulkanowy.data.repositories.subject.SubjectRepository
|
||||
import io.github.wulkanowy.ui.base.session.BaseSessionPresenter
|
||||
import io.github.wulkanowy.ui.base.session.SessionErrorHandler
|
||||
import io.github.wulkanowy.ui.base.BasePresenter
|
||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
|
||||
import io.github.wulkanowy.utils.SchedulersProvider
|
||||
import io.github.wulkanowy.utils.calculatePercentage
|
||||
@ -19,14 +19,14 @@ import java.util.concurrent.TimeUnit.MILLISECONDS
|
||||
import javax.inject.Inject
|
||||
|
||||
class AttendanceSummaryPresenter @Inject constructor(
|
||||
private val errorHandler: SessionErrorHandler,
|
||||
schedulers: SchedulersProvider,
|
||||
errorHandler: ErrorHandler,
|
||||
studentRepository: StudentRepository,
|
||||
private val attendanceSummaryRepository: AttendanceSummaryRepository,
|
||||
private val subjectRepository: SubjectRepository,
|
||||
private val studentRepository: StudentRepository,
|
||||
private val semesterRepository: SemesterRepository,
|
||||
private val schedulers: SchedulersProvider,
|
||||
private val analytics: FirebaseAnalyticsHelper
|
||||
) : BaseSessionPresenter<AttendanceSummaryView>(errorHandler) {
|
||||
) : BasePresenter<AttendanceSummaryView>(errorHandler, studentRepository, schedulers) {
|
||||
|
||||
private var subjects = emptyList<Subject>()
|
||||
|
||||
@ -46,7 +46,7 @@ class AttendanceSummaryPresenter @Inject constructor(
|
||||
loadData(currentSubjectId, true)
|
||||
}
|
||||
|
||||
fun onSubjectSelected(name: String) {
|
||||
fun onSubjectSelected(name: String?) {
|
||||
Timber.i("Select attendance summary subject $name")
|
||||
view?.run {
|
||||
showContent(false)
|
||||
|
@ -1,8 +1,8 @@
|
||||
package io.github.wulkanowy.ui.modules.attendance.summary
|
||||
|
||||
import io.github.wulkanowy.ui.base.session.BaseSessionView
|
||||
import io.github.wulkanowy.ui.base.BaseView
|
||||
|
||||
interface AttendanceSummaryView : BaseSessionView {
|
||||
interface AttendanceSummaryView : BaseView {
|
||||
|
||||
val isViewEmpty: Boolean
|
||||
|
||||
|
@ -13,14 +13,15 @@ import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
|
||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.Exam
|
||||
import io.github.wulkanowy.ui.base.session.BaseSessionFragment
|
||||
import io.github.wulkanowy.ui.base.BaseFragment
|
||||
import io.github.wulkanowy.ui.modules.main.MainActivity
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import io.github.wulkanowy.utils.dpToPx
|
||||
import io.github.wulkanowy.utils.setOnItemClickListener
|
||||
import kotlinx.android.synthetic.main.fragment_exam.*
|
||||
import javax.inject.Inject
|
||||
|
||||
class ExamFragment : BaseSessionFragment(), ExamView, MainView.MainChildView, MainView.TitledView {
|
||||
class ExamFragment : BaseFragment(), ExamView, MainView.MainChildView, MainView.TitledView {
|
||||
|
||||
@Inject
|
||||
lateinit var presenter: ExamPresenter
|
||||
@ -34,11 +35,9 @@ class ExamFragment : BaseSessionFragment(), ExamView, MainView.MainChildView, Ma
|
||||
fun newInstance() = ExamFragment()
|
||||
}
|
||||
|
||||
override val titleStringId: Int
|
||||
get() = R.string.exam_title
|
||||
override val titleStringId get() = R.string.exam_title
|
||||
|
||||
override val isViewEmpty: Boolean
|
||||
get() = examAdapter.isEmpty
|
||||
override val isViewEmpty get() = examAdapter.isEmpty
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
return inflater.inflate(R.layout.fragment_exam, container, false)
|
||||
@ -51,20 +50,21 @@ class ExamFragment : BaseSessionFragment(), ExamView, MainView.MainChildView, Ma
|
||||
}
|
||||
|
||||
override fun initView() {
|
||||
examAdapter.run {
|
||||
setOnItemClickListener { presenter.onExamItemSelected(it) }
|
||||
}
|
||||
examRecycler.run {
|
||||
examAdapter.setOnItemClickListener(presenter::onExamItemSelected)
|
||||
|
||||
with(examRecycler) {
|
||||
layoutManager = SmoothScrollLinearLayoutManager(context)
|
||||
adapter = examAdapter
|
||||
addItemDecoration(FlexibleItemDecoration(context)
|
||||
.withDefaultDivider(R.layout.item_exam)
|
||||
.withDrawDividerOnLastItem(false)
|
||||
)
|
||||
.withDrawDividerOnLastItem(false))
|
||||
}
|
||||
examSwipe.setOnRefreshListener { presenter.onSwipeRefresh() }
|
||||
|
||||
examSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
|
||||
examPreviousButton.setOnClickListener { presenter.onPreviousWeek() }
|
||||
examNextButton.setOnClickListener { presenter.onNextWeek() }
|
||||
|
||||
examNavContainer.setElevationCompat(requireContext().dpToPx(8f))
|
||||
}
|
||||
|
||||
override fun hideRefresh() {
|
||||
|
@ -1,16 +1,16 @@
|
||||
package io.github.wulkanowy.ui.modules.exam
|
||||
|
||||
import com.google.firebase.analytics.FirebaseAnalytics.Param.START_DATE
|
||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
||||
import io.github.wulkanowy.data.db.entities.Exam
|
||||
import io.github.wulkanowy.data.repositories.exam.ExamRepository
|
||||
import io.github.wulkanowy.data.repositories.semester.SemesterRepository
|
||||
import io.github.wulkanowy.data.repositories.student.StudentRepository
|
||||
import io.github.wulkanowy.ui.base.session.BaseSessionPresenter
|
||||
import io.github.wulkanowy.ui.base.session.SessionErrorHandler
|
||||
import io.github.wulkanowy.ui.base.BasePresenter
|
||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
|
||||
import io.github.wulkanowy.utils.SchedulersProvider
|
||||
import io.github.wulkanowy.utils.friday
|
||||
import io.github.wulkanowy.utils.getLastSchoolDayIfHoliday
|
||||
import io.github.wulkanowy.utils.isHolidays
|
||||
import io.github.wulkanowy.utils.monday
|
||||
import io.github.wulkanowy.utils.nextOrSameSchoolDay
|
||||
@ -23,13 +23,15 @@ import java.util.concurrent.TimeUnit.MILLISECONDS
|
||||
import javax.inject.Inject
|
||||
|
||||
class ExamPresenter @Inject constructor(
|
||||
private val errorHandler: SessionErrorHandler,
|
||||
private val schedulers: SchedulersProvider,
|
||||
schedulers: SchedulersProvider,
|
||||
errorHandler: ErrorHandler,
|
||||
studentRepository: StudentRepository,
|
||||
private val examRepository: ExamRepository,
|
||||
private val studentRepository: StudentRepository,
|
||||
private val semesterRepository: SemesterRepository,
|
||||
private val analytics: FirebaseAnalyticsHelper
|
||||
) : BaseSessionPresenter<ExamView>(errorHandler) {
|
||||
) : BasePresenter<ExamView>(errorHandler, studentRepository, schedulers) {
|
||||
|
||||
private var baseDate: LocalDate = now().nextOrSameSchoolDay
|
||||
|
||||
lateinit var currentDate: LocalDate
|
||||
private set
|
||||
@ -38,7 +40,8 @@ class ExamPresenter @Inject constructor(
|
||||
super.onAttachView(view)
|
||||
view.initView()
|
||||
Timber.i("Exam view was initialized")
|
||||
loadData(ofEpochDay(date ?: now().nextOrSameSchoolDay.toEpochDay()))
|
||||
loadData(ofEpochDay(date ?: baseDate.toEpochDay()))
|
||||
if (currentDate.isHolidays) setBaseDateOnHolidays()
|
||||
reloadView()
|
||||
}
|
||||
|
||||
@ -66,7 +69,7 @@ class ExamPresenter @Inject constructor(
|
||||
|
||||
fun onViewReselected() {
|
||||
Timber.i("Exam view is reselected")
|
||||
now().nextOrSameSchoolDay.also {
|
||||
baseDate.also {
|
||||
if (currentDate != it) {
|
||||
loadData(it)
|
||||
reloadView()
|
||||
@ -74,6 +77,20 @@ class ExamPresenter @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun setBaseDateOnHolidays() {
|
||||
disposable.add(studentRepository.getCurrentStudent()
|
||||
.flatMap { semesterRepository.getCurrentSemester(it) }
|
||||
.subscribeOn(schedulers.backgroundThread)
|
||||
.observeOn(schedulers.mainThread)
|
||||
.subscribe({
|
||||
baseDate = baseDate.getLastSchoolDayIfHoliday(it.schoolYear)
|
||||
currentDate = baseDate
|
||||
reloadNavigation()
|
||||
}) {
|
||||
Timber.i("Loading semester result: An exception occurred")
|
||||
})
|
||||
}
|
||||
|
||||
private fun loadData(date: LocalDate, forceRefresh: Boolean = false) {
|
||||
Timber.i("Loading exam data started")
|
||||
currentDate = date
|
||||
@ -82,9 +99,8 @@ class ExamPresenter @Inject constructor(
|
||||
add(studentRepository.getCurrentStudent()
|
||||
.delay(200, MILLISECONDS)
|
||||
.flatMap { semesterRepository.getCurrentSemester(it) }
|
||||
.flatMap {
|
||||
examRepository.getExams(it, currentDate.monday, currentDate.friday, forceRefresh)
|
||||
}.map { it.groupBy { exam -> exam.date }.toSortedMap() }
|
||||
.flatMap { examRepository.getExams(it, currentDate.monday, currentDate.friday, forceRefresh) }
|
||||
.map { it.groupBy { exam -> exam.date }.toSortedMap() }
|
||||
.map { createExamItems(it) }
|
||||
.subscribeOn(schedulers.backgroundThread)
|
||||
.observeOn(schedulers.mainThread)
|
||||
@ -102,7 +118,7 @@ class ExamPresenter @Inject constructor(
|
||||
showEmpty(it.isEmpty())
|
||||
showContent(it.isNotEmpty())
|
||||
}
|
||||
analytics.logEvent("load_exam", "items" to it.size, "force_refresh" to forceRefresh, START_DATE to currentDate.toFormattedString("yyyy-MM-dd"))
|
||||
analytics.logEvent("load_exam", "items" to it.size, "force_refresh" to forceRefresh)
|
||||
}) {
|
||||
Timber.i("Loading exam result: An exception occurred")
|
||||
view?.run { showEmpty(isViewEmpty) }
|
||||
@ -127,6 +143,12 @@ class ExamPresenter @Inject constructor(
|
||||
showContent(false)
|
||||
showEmpty(false)
|
||||
clearData()
|
||||
reloadNavigation()
|
||||
}
|
||||
}
|
||||
|
||||
private fun reloadNavigation() {
|
||||
view?.apply {
|
||||
showPreButton(!currentDate.minusDays(7).isHolidays)
|
||||
showNextButton(!currentDate.plusDays(7).isHolidays)
|
||||
updateNavigationWeek("${currentDate.monday.toFormattedString("dd.MM")} - " +
|
||||
|
@ -1,9 +1,9 @@
|
||||
package io.github.wulkanowy.ui.modules.exam
|
||||
|
||||
import io.github.wulkanowy.data.db.entities.Exam
|
||||
import io.github.wulkanowy.ui.base.session.BaseSessionView
|
||||
import io.github.wulkanowy.ui.base.BaseView
|
||||
|
||||
interface ExamView : BaseSessionView {
|
||||
interface ExamView : BaseView {
|
||||
|
||||
val isViewEmpty: Boolean
|
||||
|
||||
|
@ -3,16 +3,20 @@ package io.github.wulkanowy.ui.modules.grade
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.repositories.grade.GradeRepository
|
||||
import io.github.wulkanowy.data.repositories.gradessummary.GradeSummaryRepository
|
||||
import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository
|
||||
import io.github.wulkanowy.utils.calcAverage
|
||||
import io.github.wulkanowy.utils.changeModifier
|
||||
import io.reactivex.Maybe
|
||||
import io.reactivex.Single
|
||||
import javax.inject.Inject
|
||||
|
||||
class GradeAverageProvider @Inject constructor(
|
||||
private val preferencesRepository: PreferencesRepository,
|
||||
private val gradeRepository: GradeRepository
|
||||
private val gradeRepository: GradeRepository,
|
||||
private val gradeSummaryRepository: GradeSummaryRepository
|
||||
) {
|
||||
|
||||
fun getGradeAverage(student: Student, semesters: List<Semester>, selectedSemesterId: Int, forceRefresh: Boolean): Single<Map<String, Double>> {
|
||||
return when (preferencesRepository.gradeAverageMode) {
|
||||
"all_year" -> getAllYearAverage(student, semesters, selectedSemesterId, forceRefresh)
|
||||
@ -27,16 +31,19 @@ class GradeAverageProvider @Inject constructor(
|
||||
val plusModifier = preferencesRepository.gradePlusModifier
|
||||
val minusModifier = preferencesRepository.gradeMinusModifier
|
||||
|
||||
return gradeRepository.getGrades(student, selectedSemester, forceRefresh)
|
||||
.flatMap { firstGrades ->
|
||||
if (selectedSemester == firstSemester) Single.just(firstGrades)
|
||||
else gradeRepository.getGrades(student, firstSemester)
|
||||
.map { secondGrades -> secondGrades + firstGrades }
|
||||
}.map { grades ->
|
||||
grades.map { it.changeModifier(plusModifier, minusModifier) }
|
||||
.groupBy { it.subject }
|
||||
.mapValues { it.value.calcAverage() }
|
||||
}
|
||||
return getAverageFromGradeSummary(selectedSemester, forceRefresh)
|
||||
.switchIfEmpty(gradeRepository.getGrades(student, selectedSemester, forceRefresh)
|
||||
.flatMap { firstGrades ->
|
||||
if (selectedSemester == firstSemester) Single.just(firstGrades)
|
||||
else {
|
||||
gradeRepository.getGrades(student, firstSemester)
|
||||
.map { secondGrades -> secondGrades + firstGrades }
|
||||
}
|
||||
}.map { grades ->
|
||||
grades.map { it.changeModifier(plusModifier, minusModifier) }
|
||||
.groupBy { it.subject }
|
||||
.mapValues { it.value.calcAverage() }
|
||||
})
|
||||
}
|
||||
|
||||
private fun getOnlyOneSemesterAverage(student: Student, semesters: List<Semester>, semesterId: Int, forceRefresh: Boolean): Single<Map<String, Double>> {
|
||||
@ -44,11 +51,22 @@ class GradeAverageProvider @Inject constructor(
|
||||
val plusModifier = preferencesRepository.gradePlusModifier
|
||||
val minusModifier = preferencesRepository.gradeMinusModifier
|
||||
|
||||
return gradeRepository.getGrades(student, selectedSemester, forceRefresh)
|
||||
.map { grades ->
|
||||
grades.map { it.changeModifier(plusModifier, minusModifier) }
|
||||
.groupBy { it.subject }
|
||||
.mapValues { it.value.calcAverage() }
|
||||
}
|
||||
return getAverageFromGradeSummary(selectedSemester, forceRefresh)
|
||||
.switchIfEmpty(gradeRepository.getGrades(student, selectedSemester, forceRefresh)
|
||||
.map { grades ->
|
||||
grades.map { it.changeModifier(plusModifier, minusModifier) }
|
||||
.groupBy { it.subject }
|
||||
.mapValues { it.value.calcAverage() }
|
||||
})
|
||||
}
|
||||
|
||||
private fun getAverageFromGradeSummary(selectedSemester: Semester, forceRefresh: Boolean): Maybe<Map<String, Double>> {
|
||||
return gradeSummaryRepository.getGradesSummary(selectedSemester, forceRefresh)
|
||||
.toMaybe()
|
||||
.flatMap {
|
||||
if (it.any { summary -> summary.average != .0 }) {
|
||||
Maybe.just(it.map { summary -> summary.subject to summary.average }.toMap())
|
||||
} else Maybe.empty()
|
||||
}.filter { !preferencesRepository.gradeAverageForceCalc }
|
||||
}
|
||||
}
|
||||
|
@ -11,17 +11,18 @@ import android.view.View.VISIBLE
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.ui.base.BaseFragment
|
||||
import io.github.wulkanowy.ui.base.BaseFragmentPagerAdapter
|
||||
import io.github.wulkanowy.ui.base.session.BaseSessionFragment
|
||||
import io.github.wulkanowy.ui.modules.grade.details.GradeDetailsFragment
|
||||
import io.github.wulkanowy.ui.modules.grade.statistics.GradeStatisticsFragment
|
||||
import io.github.wulkanowy.ui.modules.grade.summary.GradeSummaryFragment
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import io.github.wulkanowy.utils.dpToPx
|
||||
import io.github.wulkanowy.utils.setOnSelectPageListener
|
||||
import kotlinx.android.synthetic.main.fragment_grade.*
|
||||
import javax.inject.Inject
|
||||
|
||||
class GradeFragment : BaseSessionFragment(), GradeView, MainView.MainChildView, MainView.TitledView {
|
||||
class GradeFragment : BaseFragment(), GradeView, MainView.MainChildView, MainView.TitledView {
|
||||
|
||||
@Inject
|
||||
lateinit var presenter: GradePresenter
|
||||
@ -37,11 +38,9 @@ class GradeFragment : BaseSessionFragment(), GradeView, MainView.MainChildView,
|
||||
fun newInstance() = GradeFragment()
|
||||
}
|
||||
|
||||
override val titleStringId: Int
|
||||
get() = R.string.grade_title
|
||||
override val titleStringId get() = R.string.grade_title
|
||||
|
||||
override val currentPageIndex: Int
|
||||
get() = gradeViewPager.currentItem
|
||||
override val currentPageIndex get() = gradeViewPager.currentItem
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@ -64,7 +63,7 @@ class GradeFragment : BaseSessionFragment(), GradeView, MainView.MainChildView,
|
||||
}
|
||||
|
||||
override fun initView() {
|
||||
pagerAdapter.apply {
|
||||
with(pagerAdapter) {
|
||||
containerId = gradeViewPager.id
|
||||
addFragmentsWithTitle(mapOf(
|
||||
GradeDetailsFragment.newInstance() to getString(R.string.all_details),
|
||||
@ -73,13 +72,18 @@ class GradeFragment : BaseSessionFragment(), GradeView, MainView.MainChildView,
|
||||
))
|
||||
}
|
||||
|
||||
gradeViewPager.run {
|
||||
with(gradeViewPager) {
|
||||
adapter = pagerAdapter
|
||||
offscreenPageLimit = 3
|
||||
setOnSelectPageListener { presenter.onPageSelected(it) }
|
||||
setOnSelectPageListener(presenter::onPageSelected)
|
||||
}
|
||||
gradeTabLayout.setupWithViewPager(gradeViewPager)
|
||||
gradeSwipe.setOnRefreshListener { presenter.onSwipeRefresh() }
|
||||
|
||||
with(gradeTabLayout) {
|
||||
setupWithViewPager(gradeViewPager)
|
||||
setElevationCompat(context.dpToPx(4f))
|
||||
}
|
||||
|
||||
gradeSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
@ -117,19 +121,19 @@ class GradeFragment : BaseSessionFragment(), GradeView, MainView.MainChildView,
|
||||
}
|
||||
|
||||
override fun showSemesterDialog(selectedIndex: Int) {
|
||||
arrayOf(getString(R.string.grade_semester, 1),
|
||||
getString(R.string.grade_semester, 2)).also { array ->
|
||||
context?.let {
|
||||
AlertDialog.Builder(it)
|
||||
.setSingleChoiceItems(array, selectedIndex) { dialog, which ->
|
||||
presenter.onSemesterSelected(which)
|
||||
dialog.dismiss()
|
||||
}
|
||||
.setTitle(R.string.grade_switch_semester)
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||
.show()
|
||||
val choices = arrayOf(
|
||||
getString(R.string.grade_semester, 1),
|
||||
getString(R.string.grade_semester, 2)
|
||||
)
|
||||
|
||||
AlertDialog.Builder(requireContext())
|
||||
.setSingleChoiceItems(choices, selectedIndex) { dialog, which ->
|
||||
presenter.onSemesterSelected(which)
|
||||
dialog.dismiss()
|
||||
}
|
||||
}
|
||||
.setTitle(R.string.grade_switch_semester)
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||
.show()
|
||||
}
|
||||
|
||||
fun onChildRefresh() {
|
||||
|
@ -10,6 +10,7 @@ import io.github.wulkanowy.ui.modules.grade.details.GradeDetailsFragment
|
||||
import io.github.wulkanowy.ui.modules.grade.statistics.GradeStatisticsFragment
|
||||
import io.github.wulkanowy.ui.modules.grade.summary.GradeSummaryFragment
|
||||
|
||||
@Suppress("unused")
|
||||
@Module
|
||||
abstract class GradeModule {
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user