1
0

Compare commits

...

30 Commits
0.6.1 ... 0.6.6

Author SHA1 Message Date
7f162441e2 Version 0.6.6 2019-01-25 22:12:19 +01:00
43f6048c27 Upgrade to Gradle Play Publisher v2 (#228) 2019-01-25 20:41:03 +01:00
ffc2ef9a4e Remove renaming source file in R8 (#227) 2019-01-25 19:20:13 +01:00
d3c13b8fc3 Fix empty fragment list in fragment manager (#226) 2019-01-24 21:16:22 +01:00
c78fb83774 Fix receiving a lot of notifications after turning them off for a while (#225) 2019-01-24 18:32:51 +01:00
941765a3a3 Fix empty Maybe in student repository (#224) 2019-01-23 18:28:51 +01:00
189830e0f4 Version 0.6.5 2019-01-20 23:38:02 +01:00
2061d6408f Change icon of semester switch (#223) 2019-01-20 23:36:16 +01:00
e29886560e Disable obfuscate in R8 (#222) 2019-01-20 22:43:50 +01:00
20d9313257 Version 0.6.4 2019-01-20 15:39:11 +01:00
8476f0e62e Update dependencies (#221) 2019-01-20 15:00:33 +01:00
a174ae998d Add api initialization to the message repository (#220) 2019-01-20 14:16:24 +01:00
c5bab52fa2 Add more logging (#219) 2019-01-19 23:15:14 +01:00
e6d60e670e Fix crash on duplicate notes (#218) 2019-01-18 19:10:14 +01:00
c0ddd82e03 Fix exam_no_items english translation (#217)
Changed `exam_no_items` from **No exams in this week** to **No exams this week**
2019-01-18 12:41:11 +01:00
28f1430be0 Fix crash on Meizu devices (#215) 2019-01-13 23:53:01 +01:00
840b21a213 Fix dialog state (#214) 2019-01-13 23:25:07 +01:00
65230a31ec Version 0.6.3 2019-01-12 14:41:23 +01:00
c2bcbfaaa9 Add grade id to equals (#213) 2019-01-12 14:16:59 +01:00
c3d354cd5b Add chucker okhttp inspector (#205) 2019-01-10 17:10:10 +01:00
ed49eb4c9c Fix stable id in grade fragment (#211) 2019-01-09 17:30:31 +01:00
7d1866c304 Fix NPE on error dialog (#212) 2019-01-09 17:29:55 +01:00
d2a736295a Version 0.6.2 2019-01-07 13:31:49 +01:00
a9e788f7ff Fix scroll to start in grade fragment (#210) 2019-01-07 12:52:55 +01:00
ea6a928cb4 Fix spinner dropdown arrow not displaying in attendance summary (#207) 2019-01-07 00:56:32 +01:00
b9ac592ea9 Fix broken grade summary view (#208) 2019-01-07 00:55:39 +01:00
450ae4e124 Fix item sorting in grades details (#209) 2019-01-06 23:56:01 +01:00
ca504f6efc Change icon of the attendance summary (#206) 2019-01-06 23:53:20 +01:00
4e1fd6b2cf English translation fix (#204) 2019-01-03 09:49:54 +01:00
3ab15e0e00 Added icon for the DEV version (#203) 2018-12-20 15:03:42 +01:00
108 changed files with 727 additions and 389 deletions

View File

@ -159,7 +159,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 publishRelease --no-daemon --stacktrace --console=plain -PenableCrashlytics -PdisablePreDex
command: ./gradlew publish --no-daemon --stacktrace --console=plain -PenableCrashlytics -PdisablePreDex
workflows:
version: 2

View File

@ -11,6 +11,10 @@ cache:
- $HOME/.gradle/caches/
- $HOME/.gradle/wrapper/
#branches:
# only:
# - master
android:
licenses:
- android-sdk-preview-license-.+
@ -56,7 +60,7 @@ script:
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 publishRelease -PenableCrashlytics --stacktrace;
./gradlew publish -PenableCrashlytics --stacktrace;
fi
after_success:

View File

@ -11,24 +11,16 @@ android {
compileSdkVersion 28
buildToolsVersion '28.0.3'
playAccountConfigs {
defaultAccountConfig {
serviceAccountEmail = System.getenv("PLAY_SERVICE_ACCOUNT_EMAIL")
pk12File = file('key.p12')
}
}
defaultConfig {
applicationId "io.github.wulkanowy"
testApplicationId "io.github.tests.wulkanowy"
minSdkVersion 15
targetSdkVersion 28
versionCode 20
versionName "0.6.1"
versionCode 25
versionName "0.6.6"
multiDexEnabled true
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
playAccountConfig = playAccountConfigs.defaultAccountConfig
manifestPlaceholders = [
fabric_api_key: System.getenv("FABRIC_API_KEY") ?: "null",
crashlytics_enabled: project.hasProperty("enableCrashlytics")
@ -48,7 +40,6 @@ android {
release {
buildConfigField "boolean", "CRASHLYTICS_ENABLED", "true"
minifyEnabled true
useProguard false
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release
@ -73,32 +64,30 @@ androidExtensions {
}
play {
serviceAccountEmail = System.getenv("PLAY_SERVICE_ACCOUNT_EMAIL") ?: "jan@fakelog.cf"
serviceAccountCredentials = file('key.p12')
defaultToAppBundles = true
track = 'alpha'
uploadImages = false
}
configurations.all {
resolutionStrategy.force "com.squareup.okhttp3:okhttp-urlconnection:3.11.0"
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
implementation('io.github.wulkanowy:api:0.6.0') { exclude module: "threetenbp" }
implementation('io.github.wulkanowy:api:0.6.6') { exclude module: "threetenbp" }
implementation "androidx.legacy:legacy-support-v4:1.0.0"
implementation "androidx.appcompat:appcompat:1.0.2"
implementation "androidx.cardview:cardview:1.0.0"
implementation "com.google.android.material:material:1.0.0"
implementation 'androidx.multidex:multidex:2.0.0'
implementation 'androidx.multidex:multidex:2.0.1'
implementation 'com.takisoft.preferencex:preferencex:1.0.0'
implementation "com.mikepenz:aboutlibraries:6.2.0"
implementation "com.firebase:firebase-jobdispatcher:0.8.5"
implementation "com.google.dagger:dagger-android-support:2.19"
kapt "com.google.dagger:dagger-compiler:2.19"
kapt "com.google.dagger:dagger-android-processor:2.19"
implementation "com.google.dagger:dagger-android-support:2.21"
kapt "com.google.dagger:dagger-compiler:2.21"
kapt "com.google.dagger:dagger-android-processor:2.21"
implementation "androidx.room:room-runtime:2.1.0-alpha03"
implementation "androidx.room:room-rxjava2:2.1.0-alpha03"
@ -107,25 +96,28 @@ dependencies {
implementation "eu.davidea:flexible-adapter:5.1.0"
implementation "eu.davidea:flexible-adapter-ui:1.0.0"
implementation "com.aurelhubert:ahbottomnavigation:2.2.0"
implementation 'com.ncapdevi:frag-nav:3.0.0'
implementation "com.aurelhubert:ahbottomnavigation:2.3.4"
implementation 'com.ncapdevi:frag-nav:3.1.0'
implementation 'com.github.pwittchen:reactivenetwork-rx2:3.0.1'
implementation 'com.github.pwittchen:reactivenetwork-rx2:3.0.2'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
implementation "io.reactivex.rxjava2:rxjava:2.2.4"
implementation "io.reactivex.rxjava2:rxjava:2.2.5"
implementation "com.jakewharton.threetenabp:threetenabp:1.1.0"
implementation "com.jakewharton.threetenabp:threetenabp:1.1.1"
implementation "com.jakewharton.timber:timber:4.7.1"
implementation "at.favre.lib:slf4j-timber:1.0.1"
implementation 'com.google.firebase:firebase-core:16.0.6'
implementation 'com.crashlytics.sdk.android:crashlytics:2.9.7'
implementation 'com.crashlytics.sdk.android:crashlytics:2.9.8'
releaseImplementation 'fr.o80.chucker:library-no-op:2.0.3'
debugImplementation 'fr.o80.chucker:library:2.0.3'
debugImplementation "com.amitshekhar.android:debug-db:1.0.4"
testImplementation "junit:junit:4.12"
testImplementation "io.mockk:mockk:1.8.13.kotlin13"
testImplementation "io.mockk:mockk:1.9"
testImplementation "org.mockito:mockito-inline:2.23.4"
testImplementation 'org.threeten:threetenbp:1.3.8'

View File

@ -5,6 +5,7 @@
-dontskipnonpubliclibraryclasses
-dontskipnonpubliclibraryclassmembers
-dontpreverify
-dontobfuscate
-allowaccessmodification
-repackageclasses ''
-verbose
@ -13,7 +14,6 @@
#Config for anallitycs
-keepattributes *Annotation*
-keepattributes SourceFile,LineNumberTable
-renamesourcefileattribute SourceFile
-keep class com.crashlytics.** {*;}
-keep public class * extends java.lang.Exception
-dontwarn com.crashlytics.**

View File

@ -0,0 +1,58 @@
<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>

View File

@ -0,0 +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"/>
</adaptive-icon>

View File

@ -0,0 +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"/>
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#D32F2F</color>
</resources>

View File

@ -4,6 +4,8 @@
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" />

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

View File

@ -6,10 +6,14 @@ import android.content.res.Resources
import androidx.preference.PreferenceManager
import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings
import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.strategy.SocketInternetObservingStrategy
import com.readystatesoftware.chuck.api.ChuckCollector
import com.readystatesoftware.chuck.api.ChuckInterceptor
import com.readystatesoftware.chuck.api.RetentionManager
import dagger.Module
import dagger.Provides
import io.github.wulkanowy.api.Api
import io.github.wulkanowy.data.db.AppDatabase
import io.github.wulkanowy.data.repositories.PreferencesRepository
import okhttp3.logging.HttpLoggingInterceptor
import okhttp3.logging.HttpLoggingInterceptor.Level.BASIC
import okhttp3.logging.HttpLoggingInterceptor.Level.NONE
@ -30,15 +34,26 @@ internal class RepositoryModule {
@Singleton
@Provides
fun provideApi(): Api {
fun provideApi(chuckCollector: ChuckCollector, context: Context): Api {
return Api().apply {
logLevel = NONE
androidVersion = android.os.Build.VERSION.RELEASE
buildTag = android.os.Build.MODEL
setInterceptor(HttpLoggingInterceptor(HttpLoggingInterceptor.Logger { Timber.d(it) }).setLevel(BASIC))
// for debug only
setInterceptor(ChuckInterceptor(context, chuckCollector).maxContentLength(250000L), true, 0)
}
}
@Singleton
@Provides
fun provideChuckCollector(context: Context, prefRepository: PreferencesRepository): ChuckCollector {
return ChuckCollector(context)
.showNotification(prefRepository.isShowChuckerNotification)
.retentionManager(RetentionManager(context, ChuckCollector.Period.ONE_HOUR))
}
@Singleton
@Provides
fun provideDatabase(context: Context) = AppDatabase.newInstance(context)

View File

@ -2,6 +2,7 @@ package io.github.wulkanowy.data.repositories
import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork
import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings
import io.github.wulkanowy.data.ApiHelper
import io.github.wulkanowy.data.db.entities.Message
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.local.MessagesLocal
@ -16,7 +17,8 @@ import javax.inject.Singleton
class MessagesRepository @Inject constructor(
private val settings: InternetObservingSettings,
private val local: MessagesLocal,
private val remote: MessagesRemote
private val remote: MessagesRemote,
private val apiHelper: ApiHelper
) {
enum class MessageFolder(val id: Int = 1) {
@ -25,44 +27,50 @@ class MessagesRepository @Inject constructor(
TRASHED(3)
}
fun getMessages(studentId: Int, folder: MessageFolder, forceRefresh: Boolean = false, notify: Boolean = false): Single<List<Message>> {
return local.getMessages(studentId, folder).filter { !forceRefresh }
.switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings)
.flatMap {
if (it) remote.getMessages(studentId, folder)
else Single.error(UnknownHostException())
}.flatMap { new ->
local.getMessages(studentId, folder).toSingle(emptyList())
.doOnSuccess { old ->
local.deleteMessages(old - new)
local.saveMessages((new - old)
.onEach {
it.isNotified = !notify
})
}
}.flatMap { local.getMessages(studentId, folder).toSingle(emptyList()) }
)
fun getMessages(student: Student, folder: MessageFolder, forceRefresh: Boolean = false, notify: Boolean = false): Single<List<Message>> {
return Single.just(apiHelper.initApi(student))
.flatMap { _ ->
local.getMessages(student.studentId, folder).filter { !forceRefresh }
.switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings)
.flatMap {
if (it) remote.getMessages(student.studentId, folder)
else Single.error(UnknownHostException())
}.flatMap { new ->
local.getMessages(student.studentId, folder).toSingle(emptyList())
.doOnSuccess { old ->
local.deleteMessages(old - new)
local.saveMessages((new - old)
.onEach {
it.isNotified = !notify
})
}
}.flatMap { local.getMessages(student.studentId, folder).toSingle(emptyList()) }
)
}
}
fun getMessage(studentId: Int, messageId: Int, markAsRead: Boolean = false): Single<Message> {
return local.getMessage(studentId, messageId)
.filter { !it.content.isNullOrEmpty() }
.switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings)
.flatMap {
if (it) local.getMessage(studentId, messageId).toSingle()
else Single.error(UnknownHostException())
}
.flatMap { dbMessage ->
remote.getMessagesContent(dbMessage, markAsRead).doOnSuccess {
local.updateMessage(dbMessage.copy(unread = false).apply {
id = dbMessage.id
content = it
})
}
}.flatMap {
local.getMessage(studentId, messageId).toSingle()
}
)
fun getMessage(student: Student, messageId: Int, markAsRead: Boolean = false): Single<Message> {
return Single.just(apiHelper.initApi(student))
.flatMap { _ ->
local.getMessage(student.studentId, messageId)
.filter { !it.content.isNullOrEmpty() }
.switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings)
.flatMap {
if (it) local.getMessage(student.studentId, messageId).toSingle()
else Single.error(UnknownHostException())
}
.flatMap { dbMessage ->
remote.getMessagesContent(dbMessage, markAsRead).doOnSuccess {
local.updateMessage(dbMessage.copy(unread = false).apply {
id = dbMessage.id
content = it
})
}
}.flatMap {
local.getMessage(student.studentId, messageId).toSingle()
}
)
}
}
fun getNewMessages(student: Student): Single<List<Message>> {

View File

@ -44,4 +44,8 @@ class PreferencesRepository @Inject constructor(
val isNotificationsEnable: Boolean
get() = sharedPref.getBoolean(context.getString(R.string.pref_key_notifications_enable), true)
val isShowChuckerNotificationKey: String = context.getString(R.string.pref_key_debug_chucker_notification)
val isShowChuckerNotification: Boolean
get() = sharedPref.getBoolean(isShowChuckerNotificationKey, false)
}

View File

@ -7,6 +7,7 @@ import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.local.StudentLocal
import io.github.wulkanowy.data.repositories.remote.StudentRemote
import io.reactivex.Completable
import io.reactivex.Maybe
import io.reactivex.Single
import java.net.UnknownHostException
import javax.inject.Inject
@ -41,7 +42,9 @@ class StudentRepository @Inject constructor(
}
fun getCurrentStudent(decryptPass: Boolean = true): Single<Student> {
return local.getCurrentStudent(decryptPass).toSingle()
return local.getCurrentStudent(decryptPass)
.switchIfEmpty(Maybe.error(NoSuchElementException("No current student")))
.toSingle()
}
fun saveStudent(student: Student): Single<Long> {

View File

@ -80,30 +80,33 @@ class SyncWorker : SimpleJobService() {
val end = LocalDate.now().friday
if (start.isHolidays) return RESULT_FAIL_NORETRY
if (!student.isStudentSaved) return RESULT_FAIL_RETRY
var error: Throwable? = null
val notify = prefRepository.isNotificationsEnable
disposable.add(student.getCurrentStudent()
.flatMap { semester.getCurrentSemester(it, true) }
.flatMap { semester.getCurrentSemester(it, true).map { semester -> semester to it } }
.flatMapPublisher {
Single.merge(
listOf(
gradesDetails.getGrades(it, true, true),
gradesSummary.getGradesSummary(it, true),
attendance.getAttendance(it, start, end, true),
exam.getExams(it, start, end, true),
timetable.getTimetable(it, start, end, true),
message.getMessages(it.studentId, RECEIVED, true, true),
note.getNotes(it, true, true),
homework.getHomework(it, LocalDate.now(), true),
homework.getHomework(it, LocalDate.now().plusDays(1), true)
gradesDetails.getGrades(it.first, true, notify),
gradesSummary.getGradesSummary(it.first, true),
attendance.getAttendance(it.first, start, end, true),
exam.getExams(it.first, start, end, true),
timetable.getTimetable(it.first, start, end, true),
message.getMessages(it.second, RECEIVED, true, notify),
note.getNotes(it.first, true, notify),
homework.getHomework(it.first, LocalDate.now(), true),
homework.getHomework(it.first, LocalDate.now().plusDays(1), true)
)
)
}
.subscribe({}, { error = it }))
return if (null === error) {
if (prefRepository.isNotificationsEnable) sendNotifications()
if (notify) sendNotifications()
Timber.d("Synchronization successful")
RESULT_SUCCESS
} else {
@ -138,7 +141,7 @@ class SyncWorker : SimpleJobService() {
disposable.add(student.getCurrentStudent()
.flatMap { message.getNewMessages(it) }
.map { it.filter { message -> !message.isNotified } }
.doOnSuccess{
.doOnSuccess {
if (it.isNotEmpty()) {
Timber.d("Found ${it.size} unread messages")
MessageNotification(applicationContext).sendNotification(it)

View File

@ -13,7 +13,7 @@ abstract class BaseFragment : DaggerFragment(), BaseView {
if (messageContainer == null) (activity as? BaseActivity)?.showError(text, error)
else messageContainer?.also {
Snackbar.make(it, text, Snackbar.LENGTH_LONG).setAction(R.string.all_details) {
ErrorDialog.newInstance(error).show(fragmentManager, error.toString())
ErrorDialog.newInstance(error).show(childFragmentManager, error.toString())
}.show()
}
}

View File

@ -0,0 +1,36 @@
package io.github.wulkanowy.ui.base
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentPagerAdapter
class BaseFragmentPagerAdapter(private val fragmentManager: FragmentManager) : FragmentPagerAdapter(fragmentManager) {
private val pages = mutableMapOf<Fragment, String?>()
private var containerId = 0
fun getFragmentInstance(position: Int): Fragment? {
return fragmentManager.findFragmentByTag("android:switcher:$containerId:$position")
}
fun addFragments(fragments: List<Fragment>) {
fragments.forEach { pages[it] = null }
}
fun addFragmentsWithTitle(pages: Map<Fragment, String>) {
this.pages.putAll(pages)
}
override fun instantiateItem(container: ViewGroup, position: Int): Any {
containerId = container.id
return super.instantiateItem(container, position)
}
override fun getItem(position: Int) = pages.keys.elementAt(position)
override fun getCount() = pages.size
override fun getPageTitle(position: Int) = pages.values.elementAt(position)
}

View File

@ -1,32 +0,0 @@
package io.github.wulkanowy.ui.base
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentStatePagerAdapter
class BasePagerAdapter(fragmentManager: FragmentManager) : FragmentStatePagerAdapter(fragmentManager) {
val fragments = mutableMapOf<String?, Fragment>()
val registeredFragments = mutableMapOf<Int, Fragment>()
override fun getItem(position: Int) = fragments.values.elementAt(position)
override fun getCount() = fragments.size
override fun getPageTitle(position: Int): CharSequence? {
return fragments.keys.elementAtOrNull(position)
}
override fun instantiateItem(container: ViewGroup, position: Int): Any {
return super.instantiateItem(container, position).also {
registeredFragments[position] = it as Fragment
}
}
override fun destroyItem(container: ViewGroup, position: Int, fragment: Any) {
registeredFragments.remove(position)
super.destroyItem(container, position, fragment)
}
}

View File

@ -1,6 +1,7 @@
package io.github.wulkanowy.ui.base
import android.content.res.Resources
import com.readystatesoftware.chuck.api.ChuckCollector
import io.github.wulkanowy.R
import io.github.wulkanowy.api.interceptor.ServiceUnavailableException
import io.github.wulkanowy.api.login.NotLoggedInException
@ -9,11 +10,12 @@ import java.net.SocketTimeoutException
import java.net.UnknownHostException
import javax.inject.Inject
open class ErrorHandler @Inject constructor(protected val resources: Resources) {
open class ErrorHandler @Inject constructor(protected val resources: Resources, private val chuckCollector: ChuckCollector) {
var showErrorMessage: (String, Throwable) -> Unit = { _, _ -> }
fun dispatch(error: Throwable) {
chuckCollector.onError(error.javaClass.simpleName, error)
Timber.e(error, "An exception occurred while the Wulkanowy was running")
proceed(error)
}

View File

@ -1,11 +1,12 @@
package io.github.wulkanowy.ui.base.session
import android.content.res.Resources
import com.readystatesoftware.chuck.api.ChuckCollector
import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.security.ScramblerException
import javax.inject.Inject
class SessionErrorHandler @Inject constructor(resources: Resources) : ErrorHandler(resources) {
class SessionErrorHandler @Inject constructor(resources: Resources, chuckCollector: ChuckCollector) : ErrorHandler(resources, chuckCollector) {
var onDecryptionFail: () -> Unit = {}

View File

@ -15,6 +15,11 @@ class AboutPresenter @Inject constructor(
private val analytics: FirebaseAnalyticsHelper
) : BasePresenter<AboutView>(errorHandler) {
override fun onAttachView(view: AboutView) {
super.onAttachView(view)
Timber.i("About view is attached")
}
fun onExtraSelect(type: Libs.SpecialButton?) {
view?.run {
when (type) {

View File

@ -6,6 +6,7 @@ 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 timber.log.Timber
import javax.inject.Inject
class AccountPresenter @Inject constructor(
@ -16,19 +17,23 @@ class AccountPresenter @Inject constructor(
override fun onAttachView(view: AccountView) {
super.onAttachView(view)
Timber.i("Account dialog is attached")
view.initView()
loadData()
}
fun onAddSelected() {
Timber.i("Select add account")
view?.openLoginView()
}
fun onRemoveSelected() {
Timber.i("Select remove account")
view?.showConfirmDialog()
}
fun onLogoutConfirm() {
Timber.i("Attempt to logout current user ")
disposable.add(studentRepository.getCurrentStudent()
.flatMapCompletable { studentRepository.logoutStudent(it) }
.andThen(studentRepository.getSavedStudents(false))
@ -41,31 +46,54 @@ class AccountPresenter @Inject constructor(
.doFinally { view?.dismissView() }
.subscribe({
view?.apply {
if (it.isEmpty()) openClearLoginView()
else recreateView()
if (it.isEmpty()) {
Timber.i("Logout result: Open login view")
openClearLoginView()
} else {
Timber.i("Logout result: Switch to another student")
recreateView()
}
}
}, { errorHandler.dispatch(it) }))
}, {
Timber.i("Logout result: An exception occurred")
errorHandler.dispatch(it)
}))
}
fun onItemSelected(item: AbstractFlexibleItem<*>) {
if (item is AccountItem) {
Timber.i("Select student item ${item.student.id}")
if (item.student.isCurrent) {
view?.dismissView()
} else {
Timber.i("Attempt to change a student")
disposable.add(studentRepository.switchStudent(item.student)
.subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread)
.subscribe({ view?.recreateView() }, { errorHandler.dispatch(it) }))
.subscribe({
Timber.i("Change a student result: Success")
view?.recreateView()
}, {
Timber.i("Change a student result: An exception occurred")
errorHandler.dispatch(it)
}))
}
}
}
private fun loadData() {
Timber.i("Loading account data started")
disposable.add(studentRepository.getSavedStudents(false)
.map { it.map { item -> AccountItem(item) } }
.subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread)
.subscribe({ view?.updateData(it) }, { errorHandler.dispatch(it) }))
.subscribe({
Timber.i("Loading account result: Success")
view?.updateData(it)
}, {
Timber.i("Loading account result: An exception occurred")
errorHandler.dispatch(it)
}))
}
}

View File

@ -130,7 +130,7 @@ class AttendanceFragment : BaseSessionFragment(), AttendanceView, MainView.MainC
}
override fun showAttendanceDialog(lesson: Attendance) {
AttendanceDialog.newInstance(lesson).show(fragmentManager, lesson.toString())
(activity as? MainActivity)?.showDialogFragment(AttendanceDialog.newInstance(lesson))
}
override fun openSummaryView() {

View File

@ -18,6 +18,7 @@ import io.github.wulkanowy.utils.toFormattedString
import org.threeten.bp.LocalDate
import org.threeten.bp.LocalDate.now
import org.threeten.bp.LocalDate.ofEpochDay
import timber.log.Timber
import java.util.concurrent.TimeUnit.MILLISECONDS
import javax.inject.Inject
@ -36,6 +37,7 @@ class AttendancePresenter @Inject constructor(
fun onAttachView(view: AttendanceView, date: Long?) {
super.onAttachView(view)
Timber.i("Attendance view is attached")
view.initView()
loadData(ofEpochDay(date ?: now().previousOrSameSchoolDay.toEpochDay()))
reloadView()
@ -52,10 +54,12 @@ class AttendancePresenter @Inject constructor(
}
fun onSwipeRefresh() {
Timber.i("Force refreshing the attendance")
loadData(currentDate, true)
}
fun onViewReselected() {
Timber.i("Attendance view is reselected")
view?.also { view ->
if (view.currentStackSize == 1) {
now().previousOrSameSchoolDay.also {
@ -69,7 +73,10 @@ class AttendancePresenter @Inject constructor(
}
fun onAttendanceItemSelected(item: AbstractFlexibleItem<*>?) {
if (item is AttendanceItem) view?.showAttendanceDialog(item.attendance)
if (item is AttendanceItem) {
Timber.i("Select attendance item ${item.attendance.id}")
view?.showAttendanceDialog(item.attendance)
}
}
fun onSummarySwitchSelected(): Boolean {
@ -78,6 +85,7 @@ class AttendancePresenter @Inject constructor(
}
private fun loadData(date: LocalDate, forceRefresh: Boolean = false) {
Timber.i("Loading attendance data started")
currentDate = date
disposable.apply {
clear()
@ -100,6 +108,7 @@ class AttendancePresenter @Inject constructor(
}
}
.subscribe({
Timber.i("Loading attendance result: Success")
view?.apply {
updateData(it)
showEmpty(it.isEmpty())
@ -107,6 +116,7 @@ class AttendancePresenter @Inject constructor(
}
analytics.logEvent("load_attendance", mapOf("items" to it.size, "force_refresh" to forceRefresh, START_DATE to currentDate.toFormattedString("yyyy-MM-dd")))
}) {
Timber.i("Loading attendance result: An exception occurred")
view?.run { showEmpty(isViewEmpty) }
errorHandler.dispatch(it)
}
@ -115,6 +125,7 @@ class AttendancePresenter @Inject constructor(
}
private fun reloadView() {
Timber.i("Reload attendance view with the date ${currentDate.toFormattedString()}")
view?.apply {
showProgress(true)
showContent(false)

View File

@ -4,6 +4,7 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.View.GONE
import android.view.View.INVISIBLE
import android.view.View.VISIBLE
import android.view.ViewGroup
import android.widget.ArrayAdapter
@ -101,7 +102,7 @@ class AttendanceSummaryFragment : BaseSessionFragment(), AttendanceSummaryView,
}
override fun showSubjects(show: Boolean) {
attendanceSummarySubjects.visibility = if (show) VISIBLE else VISIBLE
attendanceSummarySubjectsContainer.visibility = if (show) VISIBLE else INVISIBLE
}
override fun hideRefresh() {

View File

@ -12,6 +12,7 @@ import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.SchedulersProvider
import io.github.wulkanowy.utils.calculatePercentage
import io.github.wulkanowy.utils.getFormattedName
import timber.log.Timber
import java.lang.String.format
import java.util.Locale.FRANCE
import java.util.concurrent.TimeUnit.MILLISECONDS
@ -34,25 +35,31 @@ class AttendanceSummaryPresenter @Inject constructor(
fun onAttachView(view: AttendanceSummaryView, subjectId: Int?) {
super.onAttachView(view)
Timber.i("Attendance summary view is attached with subject id ${subjectId ?: -1}")
view.initView()
loadData(subjectId ?: -1)
loadSubjects()
}
fun onSwipeRefresh() {
Timber.i("Force refreshing the attendance summary")
loadData(currentSubjectId, true)
}
fun onSubjectSelected(name: String) {
Timber.i("Select attendance summary subject $name")
view?.run {
showContent(false)
showProgress(true)
clearView()
}
loadData(subjects.singleOrNull { it.name == name }?.realId ?: -1)
(subjects.singleOrNull { it.name == name }?.realId ?: -1).let {
if (it != currentSubjectId) loadData(it)
}
}
private fun loadData(subjectId: Int, forceRefresh: Boolean = false) {
Timber.i("Loading attendance summary data started")
currentSubjectId = subjectId
disposable.apply {
clear()
@ -70,6 +77,7 @@ class AttendanceSummaryPresenter @Inject constructor(
}
}
.subscribe({
Timber.i("Loading attendance summary result: Success")
view?.apply {
showEmpty(it.first.isEmpty())
showContent(it.first.isNotEmpty())
@ -77,6 +85,7 @@ class AttendanceSummaryPresenter @Inject constructor(
}
analytics.logEvent("load_attendance_summary", mapOf("items" to it.first.size, "force_refresh" to forceRefresh, "item_id" to subjectId))
}) {
Timber.i("Loading attendance summary result: An exception occurred")
view?.run { showEmpty(isViewEmpty) }
errorHandler.dispatch(it)
}
@ -85,6 +94,7 @@ class AttendanceSummaryPresenter @Inject constructor(
}
private fun loadSubjects() {
Timber.i("Loading attendance summary subjects started")
disposable.add(studentRepository.getCurrentStudent()
.flatMap { semesterRepository.getCurrentSemester(it) }
.flatMap { subjectRepository.getSubjects(it) }
@ -93,11 +103,15 @@ class AttendanceSummaryPresenter @Inject constructor(
.subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread)
.subscribe({
Timber.i("Loading attendance summary subjects result: Success")
view?.run {
view?.updateSubjects(it)
showSubjects(true)
}
}, { errorHandler.dispatch(it) })
}, {
Timber.i("Loading attendance summary subjects result: An exception occurred")
errorHandler.dispatch(it)
})
)
}

View File

@ -13,6 +13,7 @@ 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.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.setOnItemClickListener
import kotlinx.android.synthetic.main.fragment_exam.*
@ -106,7 +107,7 @@ class ExamFragment : BaseSessionFragment(), ExamView, MainView.MainChildView, Ma
}
override fun showExamDialog(exam: Exam) {
ExamDialog.newInstance(exam).show(fragmentManager, exam.toString())
(activity as? MainActivity)?.showDialogFragment(ExamDialog.newInstance(exam))
}
override fun onSaveInstanceState(outState: Bundle) {

View File

@ -18,6 +18,7 @@ import io.github.wulkanowy.utils.toFormattedString
import org.threeten.bp.LocalDate
import org.threeten.bp.LocalDate.now
import org.threeten.bp.LocalDate.ofEpochDay
import timber.log.Timber
import java.util.concurrent.TimeUnit.MILLISECONDS
import javax.inject.Inject
@ -35,6 +36,7 @@ class ExamPresenter @Inject constructor(
fun onAttachView(view: ExamView, date: Long?) {
super.onAttachView(view)
Timber.i("Exam view is attached")
view.initView()
loadData(ofEpochDay(date ?: now().nextOrSameSchoolDay.toEpochDay()))
reloadView()
@ -51,14 +53,19 @@ class ExamPresenter @Inject constructor(
}
fun onSwipeRefresh() {
Timber.i("Force refreshing the exam")
loadData(currentDate, true)
}
fun onExamItemSelected(item: AbstractFlexibleItem<*>?) {
if (item is ExamItem) view?.showExamDialog(item.exam)
if (item is ExamItem) {
Timber.i("Select exam item ${item.exam.id}")
view?.showExamDialog(item.exam)
}
}
fun onViewReselected() {
Timber.i("Exam view is reselected")
now().nextOrSameSchoolDay.also {
if (currentDate != it) {
loadData(it)
@ -68,6 +75,7 @@ class ExamPresenter @Inject constructor(
}
private fun loadData(date: LocalDate, forceRefresh: Boolean = false) {
Timber.i("Loading exam data started")
currentDate = date
disposable.apply {
clear()
@ -87,6 +95,7 @@ class ExamPresenter @Inject constructor(
}
}
.subscribe({
Timber.i("Loading exam result: Success")
view?.apply {
updateData(it)
showEmpty(it.isEmpty())
@ -94,6 +103,7 @@ class ExamPresenter @Inject constructor(
}
analytics.logEvent("load_exam", mapOf("items" to it.size, "force_refresh" to forceRefresh, START_DATE to currentDate.toFormattedString("yyyy-MM-dd")))
}) {
Timber.i("Loading exam result: An exception occurred")
view?.run { showEmpty(isViewEmpty) }
errorHandler.dispatch(it)
})
@ -109,6 +119,7 @@ class ExamPresenter @Inject constructor(
}
private fun reloadView() {
Timber.i("Reload exam view with the date ${currentDate.toFormattedString()}")
view?.apply {
showProgress(true)
showContent(false)

View File

@ -11,7 +11,7 @@ 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.BasePagerAdapter
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.summary.GradeSummaryFragment
@ -26,7 +26,7 @@ class GradeFragment : BaseSessionFragment(), GradeView, MainView.MainChildView,
lateinit var presenter: GradePresenter
@Inject
lateinit var pagerAdapter: BasePagerAdapter
lateinit var pagerAdapter: BaseFragmentPagerAdapter
companion object {
private const val SAVED_SEMESTER_KEY = "CURRENT_SEMESTER"
@ -59,10 +59,11 @@ class GradeFragment : BaseSessionFragment(), GradeView, MainView.MainChildView,
}
override fun initView() {
pagerAdapter.fragments.putAll(mapOf(
getString(R.string.all_details) to GradeDetailsFragment.newInstance(),
getString(R.string.grade_menu_summary) to GradeSummaryFragment.newInstance()
pagerAdapter.addFragmentsWithTitle(mapOf(
GradeDetailsFragment.newInstance() to getString(R.string.all_details),
GradeSummaryFragment.newInstance() to getString(R.string.grade_menu_summary)
))
gradeViewPager.run {
adapter = pagerAdapter
setOnSelectPageListener { presenter.onPageSelected(it) }
@ -117,15 +118,15 @@ class GradeFragment : BaseSessionFragment(), GradeView, MainView.MainChildView,
}
override fun notifyChildLoadData(index: Int, semesterId: Int, forceRefresh: Boolean) {
(childFragmentManager.fragments[index] as GradeView.GradeChildView).onParentLoadData(semesterId, forceRefresh)
(pagerAdapter.getFragmentInstance(index) as? GradeView.GradeChildView)?.onParentLoadData(semesterId, forceRefresh)
}
override fun notifyChildParentReselected(index: Int) {
(pagerAdapter.registeredFragments[index] as? GradeView.GradeChildView)?.onParentReselected()
(pagerAdapter.getFragmentInstance(index) as? GradeView.GradeChildView)?.onParentReselected()
}
override fun notifyChildSemesterChange(index: Int) {
(pagerAdapter.registeredFragments[index] as? GradeView.GradeChildView)?.onParentChangeSemester()
(pagerAdapter.getFragmentInstance(index) as? GradeView.GradeChildView)?.onParentChangeSemester()
}
override fun onSaveInstanceState(outState: Bundle) {

View File

@ -5,7 +5,7 @@ import dagger.Provides
import dagger.android.ContributesAndroidInjector
import io.github.wulkanowy.di.scopes.PerChildFragment
import io.github.wulkanowy.di.scopes.PerFragment
import io.github.wulkanowy.ui.base.BasePagerAdapter
import io.github.wulkanowy.ui.base.BaseFragmentPagerAdapter
import io.github.wulkanowy.ui.modules.grade.details.GradeDetailsFragment
import io.github.wulkanowy.ui.modules.grade.summary.GradeSummaryFragment
@ -18,7 +18,7 @@ abstract class GradeModule {
@JvmStatic
@PerFragment
@Provides
fun provideGradePagerAdapter(fragment: GradeFragment) = BasePagerAdapter(fragment.childFragmentManager)
fun provideGradeAdapter(fragment: GradeFragment) = BaseFragmentPagerAdapter(fragment.childFragmentManager)
}
@PerChildFragment

View File

@ -8,6 +8,7 @@ import io.github.wulkanowy.ui.base.session.SessionErrorHandler
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.SchedulersProvider
import io.reactivex.Completable
import timber.log.Timber
import java.util.concurrent.TimeUnit.MILLISECONDS
import javax.inject.Inject
@ -28,6 +29,7 @@ class GradePresenter @Inject constructor(
fun onAttachView(view: GradeView, savedIndex: Int?) {
super.onAttachView(view)
Timber.i("Grade view is attached")
disposable.add(Completable.timer(150, MILLISECONDS, schedulers.mainThread)
.subscribe {
selectedIndex = savedIndex ?: 0
@ -37,6 +39,7 @@ class GradePresenter @Inject constructor(
}
fun onViewReselected() {
Timber.i("Grade view is reselected")
view?.run { notifyChildParentReselected(currentPageIndex) }
}
@ -47,6 +50,7 @@ class GradePresenter @Inject constructor(
fun onSemesterSelected(index: Int) {
if (selectedIndex != index - 1) {
Timber.i("Change semester in grade view to ${index + 1}")
selectedIndex = index + 1
loadedSemesterId.clear()
view?.let {
@ -74,6 +78,7 @@ class GradePresenter @Inject constructor(
}
private fun loadData() {
Timber.i("Loading grade data started")
disposable.add(studentRepository.getCurrentStudent()
.flatMap { semesterRepository.getSemesters(it) }
.doOnSuccess {
@ -84,7 +89,13 @@ class GradePresenter @Inject constructor(
}
.subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread)
.subscribe({ view?.run { loadChild(currentPageIndex) } }) {
.subscribe({
view?.run {
Timber.i("Loading grade result: Attempt load index $currentPageIndex")
loadChild(currentPageIndex)
}
}) {
Timber.i("Loading grade result: An exception occurred")
errorHandler.dispatch(it)
view?.run {
showProgress(false)
@ -96,6 +107,7 @@ class GradePresenter @Inject constructor(
private fun loadChild(index: Int, forceRefresh: Boolean = false) {
semesters.first { it.semesterName == selectedIndex }.semesterId.also {
if (forceRefresh || loadedSemesterId[index] != it) {
Timber.i("Load grade child view index: $index")
view?.notifyChildLoadData(index, it, forceRefresh)
}
}

View File

@ -20,6 +20,7 @@ import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.ui.base.session.BaseSessionFragment
import io.github.wulkanowy.ui.modules.grade.GradeFragment
import io.github.wulkanowy.ui.modules.grade.GradeView
import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.utils.setOnItemClickListener
import kotlinx.android.synthetic.main.fragment_grade_details.*
import javax.inject.Inject
@ -103,7 +104,7 @@ class GradeDetailsFragment : BaseSessionFragment(), GradeDetailsView, GradeView.
}
override fun scrollToStart() {
gradeDetailsAdapter.smoothScrollToPosition(0)
gradeDetailsRecycler.scrollToPosition(0)
}
override fun getHeaderOfItem(item: AbstractFlexibleItem<*>): IExpandable<*, out IFlexible<*>>? {
@ -131,7 +132,7 @@ class GradeDetailsFragment : BaseSessionFragment(), GradeDetailsView, GradeView.
}
override fun showGradeDialog(grade: Grade) {
GradeDetailsDialog.newInstance(grade).show(fragmentManager, grade.toString())
(activity as? MainActivity)?.showDialogFragment(GradeDetailsDialog.newInstance(grade))
}
override fun onParentLoadData(semesterId: Int, forceRefresh: Boolean) {

View File

@ -52,6 +52,7 @@ class GradeDetailsHeader(
if (subject != other.subject) return false
if (number != other.number) return false
if (average != other.average) return false
if (isExpandable != other.isExpandable) return false
return true
}
@ -60,6 +61,7 @@ class GradeDetailsHeader(
var result = subject.hashCode()
result = 31 * result + number.hashCode()
result = 31 * result + average.hashCode()
result = 31 * result + isExpandable.hashCode()
return result
}

View File

@ -14,8 +14,8 @@ import io.github.wulkanowy.utils.toFormattedString
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.item_grade_details.*
class GradeDetailsItem(val grade: Grade, private val weightString: String, private val valueColor: Int)
: AbstractFlexibleItem<GradeDetailsItem.ViewHolder>() {
class GradeDetailsItem(val grade: Grade, private val weightString: String, private val valueColor: Int) :
AbstractFlexibleItem<GradeDetailsItem.ViewHolder>() {
override fun getLayoutRes() = R.layout.item_grade_details
@ -24,8 +24,10 @@ class GradeDetailsItem(val grade: Grade, private val weightString: String, priva
}
@SuppressLint("SetTextI18n")
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>, holder: ViewHolder,
position: Int, payloads: MutableList<Any>?) {
override fun bindViewHolder(
adapter: FlexibleAdapter<IFlexible<*>>, holder: ViewHolder,
position: Int, payloads: MutableList<Any>?
) {
holder.run {
gradeItemValue.run {
text = grade.entry
@ -45,6 +47,7 @@ class GradeDetailsItem(val grade: Grade, private val weightString: String, priva
other as GradeDetailsItem
if (grade != other.grade) return false
if (grade.id != other.grade.id) return false
if (weightString != other.weightString) return false
if (valueColor != other.valueColor) return false
@ -53,14 +56,14 @@ class GradeDetailsItem(val grade: Grade, private val weightString: String, priva
override fun hashCode(): Int {
var result = grade.hashCode()
result = 31 * result + grade.id.toInt()
result = 31 * result + weightString.hashCode()
result = 31 * result + valueColor
return result
}
class ViewHolder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter),
LayoutContainer {
LayoutContainer {
override val containerView: View
get() = contentView

View File

@ -40,6 +40,7 @@ class GradeDetailsPresenter @Inject constructor(
fun onGradeItemSelected(item: AbstractFlexibleItem<*>?) {
if (item is GradeDetailsItem) {
Timber.i("Select grade item ${item.grade.id}")
view?.apply {
showGradeDialog(item.grade)
if (!item.grade.isRead) {
@ -58,18 +59,29 @@ class GradeDetailsPresenter @Inject constructor(
}
fun onMarkAsReadSelected(): Boolean {
Timber.i("Select mark grades as read")
disposable.add(studentRepository.getCurrentStudent()
.flatMap { semesterRepository.getSemesters(it) }
.flatMap { gradeRepository.getNewGrades(it.first { item -> item.semesterId == currentSemesterId }) }
.map { it.map { grade -> grade.apply { isRead = true } } }
.flatMapCompletable { gradeRepository.updateGrades(it) }
.flatMapCompletable {
Timber.i("Mark as read ${it.size} grades")
gradeRepository.updateGrades(it)
}
.subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread)
.subscribe({ loadData(currentSemesterId, false) }, { errorHandler.dispatch(it) }))
.subscribe({
Timber.i("Mark as read result: Success")
loadData(currentSemesterId, false)
}, {
Timber.i("Mark as read result: An exception occurred")
errorHandler.dispatch(it)
}))
return true
}
fun onSwipeRefresh() {
Timber.i("Force refreshing the grade details")
view?.notifyParentRefresh()
}
@ -94,9 +106,11 @@ class GradeDetailsPresenter @Inject constructor(
}
private fun loadData(semesterId: Int, forceRefresh: Boolean) {
Timber.i("Loading grade details data started")
disposable.add(studentRepository.getCurrentStudent()
.flatMap { semesterRepository.getSemesters(it) }
.flatMap { gradeRepository.getGrades(it.first { item -> item.semesterId == semesterId }, forceRefresh) }
.map { it.sortedByDescending { grade -> grade.date } }
.map { it.map { item -> item.changeModifier(preferencesRepository.gradePlusModifier, preferencesRepository.gradeMinusModifier) } }
.map { createGradeItems(it.groupBy { grade -> grade.subject }.toSortedMap()) }
.subscribeOn(schedulers.backgroundThread)
@ -109,6 +123,7 @@ class GradeDetailsPresenter @Inject constructor(
}
}
.subscribe({
Timber.i("Loading grade details result: Success")
view?.run {
showEmpty(it.isEmpty())
showContent(it.isNotEmpty())
@ -116,6 +131,7 @@ class GradeDetailsPresenter @Inject constructor(
}
analytics.logEvent("load_grade_details", mapOf("items" to it.size, "force_refresh" to forceRefresh))
}) {
Timber.i("Loading grade details result: An exception occurred")
view?.run { showEmpty(isViewEmpty) }
errorHandler.dispatch(it)
})
@ -151,10 +167,14 @@ class GradeDetailsPresenter @Inject constructor(
}
private fun updateGrade(grade: Grade) {
Timber.i("Attempt to update grade ${grade.id}")
disposable.add(gradeRepository.updateGrade(grade)
.subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread)
.subscribe({}) { error -> errorHandler.dispatch(error) })
Timber.d("Grade ${grade.id} updated")
.subscribe({ Timber.i("Update grade result: Success") })
{ error ->
Timber.i("Update grade result: An exception occurred")
errorHandler.dispatch(error)
})
}
}

View File

@ -71,7 +71,7 @@ class GradeSummaryFragment : BaseSessionFragment(), GradeSummaryView, GradeView.
}
override fun resetView() {
gradeSummaryAdapter.smoothScrollToPosition(0)
gradeSummaryRecycler.scrollToPosition(0)
}
override fun showContent(show: Boolean) {

View File

@ -1,52 +0,0 @@
package io.github.wulkanowy.ui.modules.grade.summary
import android.view.View
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractHeaderItem
import eu.davidea.flexibleadapter.items.IFlexible
import eu.davidea.viewholders.FlexibleViewHolder
import io.github.wulkanowy.R
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.header_grade_summary.*
class GradeSummaryHeader(private val name: String, private val average: String) : AbstractHeaderItem<GradeSummaryHeader.ViewHolder>() {
override fun getLayoutRes() = R.layout.header_grade_summary
override fun createViewHolder(view: View?, adapter: FlexibleAdapter<IFlexible<*>>?): ViewHolder {
return ViewHolder(view, adapter)
}
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>?, holder: ViewHolder?,
position: Int, payloads: MutableList<Any>?) {
holder?.run {
gradeSummaryHeaderName.text = name
gradeSummaryHeaderAverage.text = average
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as GradeSummaryHeader
if (name != other.name) return false
if (average != other.average) return false
return true
}
override fun hashCode(): Int {
var result = name.hashCode()
result = 31 * result + average.hashCode()
return result
}
class ViewHolder(view: View?, adapter: FlexibleAdapter<IFlexible<*>>?) :
FlexibleViewHolder(view, adapter), LayoutContainer {
override val containerView: View?
get() = contentView
}
}

View File

@ -2,16 +2,19 @@ package io.github.wulkanowy.ui.modules.grade.summary
import android.view.View
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractSectionableItem
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import eu.davidea.flexibleadapter.items.IFlexible
import eu.davidea.viewholders.FlexibleViewHolder
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.GradeSummary
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.item_grade_summary.*
class GradeSummaryItem(header: GradeSummaryHeader, private val grade: String, private val title: String) :
AbstractSectionableItem<GradeSummaryItem.ViewHolder, GradeSummaryHeader>(header) {
class GradeSummaryItem(
private val title: String,
private val average: String,
private val predictedGrade: String,
private val finalGrade: String
) : AbstractFlexibleItem<GradeSummaryItem.ViewHolder>() {
override fun getLayoutRes() = R.layout.item_grade_summary
@ -24,8 +27,10 @@ class GradeSummaryItem(header: GradeSummaryHeader, private val grade: String, pr
position: Int, payloads: MutableList<Any>?
) {
holder?.run {
gradeSummaryItemGrade.text = grade
gradeSummaryItemTitle.text = title
gradeSummaryItemAverage.text = average
gradeSummaryItemPredicted.text = predictedGrade
gradeSummaryItemFinal.text = finalGrade
}
}
@ -35,17 +40,19 @@ class GradeSummaryItem(header: GradeSummaryHeader, private val grade: String, pr
other as GradeSummaryItem
if (grade != other.grade) return false
if (average != other.average) return false
if (title != other.title) return false
if (header != other.header) return false
if (predictedGrade != other.predictedGrade) return false
if (finalGrade != other.finalGrade) return false
return true
}
override fun hashCode(): Int {
var result = header.hashCode()
result = 31 * result + grade.hashCode()
result = 31 * result + title.hashCode()
var result = title.hashCode()
result = 31 * result + average.hashCode()
result = 31 * result + predictedGrade.hashCode()
result = 31 * result + finalGrade.hashCode()
return result
}

View File

@ -12,6 +12,7 @@ import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.SchedulersProvider
import io.github.wulkanowy.utils.calcAverage
import io.github.wulkanowy.utils.changeModifier
import timber.log.Timber
import java.lang.String.format
import java.util.Locale.FRANCE
import javax.inject.Inject
@ -33,6 +34,7 @@ class GradeSummaryPresenter @Inject constructor(
}
fun onParentViewLoadData(semesterId: Int, forceRefresh: Boolean) {
Timber.i("Loading grade summary data started")
disposable.add(studentRepository.getCurrentStudent()
.flatMap { semesterRepository.getSemesters(it) }
.map { semester -> semester.first { it.semesterId == semesterId } }
@ -64,6 +66,7 @@ class GradeSummaryPresenter @Inject constructor(
notifyParentDataLoaded(semesterId)
}
}.subscribe({
Timber.i("Loading grade summary result: Success")
view?.run {
showEmpty(it.first.isEmpty())
showContent(it.first.isNotEmpty())
@ -71,12 +74,14 @@ class GradeSummaryPresenter @Inject constructor(
}
analytics.logEvent("load_grade_summary", mapOf("items" to it.first.size, "force_refresh" to forceRefresh))
}) {
Timber.i("Loading grade summary result: An exception occurred")
view?.run { showEmpty(isViewEmpty) }
errorHandler.dispatch(it)
})
}
fun onSwipeRefresh() {
Timber.i("Force refreshing the grade summary")
view?.notifyParentRefresh()
}
@ -99,23 +104,14 @@ class GradeSummaryPresenter @Inject constructor(
private fun createGradeSummaryItems(gradesSummary: List<GradeSummary>, averages: Map<String, Double>)
: List<GradeSummaryItem> {
return gradesSummary.filter { !checkEmpty(it, averages) }
.flatMap { gradeSummary ->
GradeSummaryHeader(
name = gradeSummary.subject,
average = formatAverage(averages.getOrElse(gradeSummary.subject) { 0.0 }, "")
).let {
listOf(GradeSummaryItem(
header = it,
title = view?.predictedString.orEmpty(),
grade = gradeSummary.predictedGrade
), GradeSummaryItem(
header = it,
title = view?.finalString.orEmpty(),
grade = gradeSummary.finalGrade
))
}
}
return gradesSummary.filter { !checkEmpty(it, averages) }.map { it ->
GradeSummaryItem(
title = it.subject,
average = formatAverage(averages.getOrElse(it.subject) { 0.0 }, ""),
predictedGrade = it.predictedGrade,
finalGrade = it.finalGrade
)
}
}
private fun checkEmpty(gradeSummary: GradeSummary, averages: Map<String, Double>): Boolean {

View File

@ -10,6 +10,7 @@ import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Homework
import io.github.wulkanowy.ui.base.session.BaseSessionFragment
import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.setOnItemClickListener
import kotlinx.android.synthetic.main.fragment_homework.*
@ -95,7 +96,7 @@ class HomeworkFragment : BaseSessionFragment(), HomeworkView, MainView.TitledVie
}
override fun showTimetableDialog(homework: Homework) {
HomeworkDialog.newInstance(homework).show(fragmentManager, homework.toString())
(activity as? MainActivity)?.showDialogFragment(HomeworkDialog.newInstance(homework))
}
override fun onSaveInstanceState(outState: Bundle) {

View File

@ -15,6 +15,7 @@ import io.github.wulkanowy.utils.nextSchoolDay
import io.github.wulkanowy.utils.previousSchoolDay
import io.github.wulkanowy.utils.toFormattedString
import org.threeten.bp.LocalDate
import timber.log.Timber
import java.util.concurrent.TimeUnit
import javax.inject.Inject
@ -32,6 +33,7 @@ class HomeworkPresenter @Inject constructor(
fun onAttachView(view: HomeworkView, date: Long?) {
super.onAttachView(view)
Timber.i("Homework view is attached")
view.initView()
loadData(LocalDate.ofEpochDay(date ?: LocalDate.now().nextOrSameSchoolDay.toEpochDay()))
reloadView()
@ -48,14 +50,19 @@ class HomeworkPresenter @Inject constructor(
}
fun onSwipeRefresh() {
Timber.i("Force refreshing the homework")
loadData(currentDate, true)
}
fun onHomeworkItemSelected(item: AbstractFlexibleItem<*>?) {
if (item is HomeworkItem) view?.showTimetableDialog(item.homework)
if (item is HomeworkItem) {
Timber.i("Select homework item ${item.homework.id}")
view?.showTimetableDialog(item.homework)
}
}
private fun loadData(date: LocalDate, forceRefresh: Boolean = false) {
Timber.i("Loading homework data started")
currentDate = date
disposable.apply {
clear()
@ -73,6 +80,7 @@ class HomeworkPresenter @Inject constructor(
}
}
.subscribe({
Timber.i("Loading homework result: Success")
view?.apply {
updateData(it)
showEmpty(it.isEmpty())
@ -80,6 +88,7 @@ class HomeworkPresenter @Inject constructor(
}
analytics.logEvent("load_homework", mapOf("items" to it.size, "force_refresh" to forceRefresh, START_DATE to currentDate.toFormattedString("yyyy-MM-dd")))
}) {
Timber.i("Loading homework result: An exception occurred")
view?.run { showEmpty(isViewEmpty()) }
errorHandler.dispatch(it)
})
@ -87,6 +96,7 @@ class HomeworkPresenter @Inject constructor(
}
private fun reloadView() {
Timber.i("Reload homework view with the date ${currentDate.toFormattedString()}")
view?.apply {
showProgress(true)
showContent(false)

View File

@ -5,7 +5,7 @@ import android.content.Intent
import android.os.Bundle
import io.github.wulkanowy.R
import io.github.wulkanowy.ui.base.BaseActivity
import io.github.wulkanowy.ui.base.BasePagerAdapter
import io.github.wulkanowy.ui.base.BaseFragmentPagerAdapter
import io.github.wulkanowy.ui.modules.login.form.LoginFormFragment
import io.github.wulkanowy.ui.modules.login.options.LoginOptionsFragment
import io.github.wulkanowy.utils.setOnSelectPageListener
@ -18,7 +18,7 @@ class LoginActivity : BaseActivity(), LoginView {
lateinit var presenter: LoginPresenter
@Inject
lateinit var loginAdapter: BasePagerAdapter
lateinit var loginAdapter: BaseFragmentPagerAdapter
companion object {
fun getStartIntent(context: Context) = Intent(context, LoginActivity::class.java)
@ -36,9 +36,9 @@ class LoginActivity : BaseActivity(), LoginView {
}
override fun initAdapter() {
loginAdapter.fragments.putAll(mapOf(
"1" to LoginFormFragment.newInstance(),
"2" to LoginOptionsFragment.newInstance()
loginAdapter.addFragments(listOf(
LoginFormFragment.newInstance(),
LoginOptionsFragment.newInstance()
))
loginViewpager.run {
@ -52,7 +52,7 @@ class LoginActivity : BaseActivity(), LoginView {
}
override fun notifyOptionsViewLoadData() {
(supportFragmentManager.fragments[1] as? LoginOptionsFragment)?.onParentLoadData()
(loginAdapter.getFragmentInstance(1) as? LoginOptionsFragment)?.onParentLoadData()
}
fun onChildFragmentSwitchOptions() {

View File

@ -2,12 +2,13 @@ package io.github.wulkanowy.ui.modules.login
import android.content.res.Resources
import android.database.sqlite.SQLiteConstraintException
import com.readystatesoftware.chuck.api.ChuckCollector
import io.github.wulkanowy.R
import io.github.wulkanowy.api.login.BadCredentialsException
import io.github.wulkanowy.ui.base.ErrorHandler
import javax.inject.Inject
class LoginErrorHandler @Inject constructor(resources: Resources) : ErrorHandler(resources) {
class LoginErrorHandler @Inject constructor(resources: Resources, chuckCollector: ChuckCollector) : ErrorHandler(resources, chuckCollector) {
var onBadCredentials: () -> Unit = {}

View File

@ -5,7 +5,7 @@ import dagger.Provides
import dagger.android.ContributesAndroidInjector
import io.github.wulkanowy.di.scopes.PerActivity
import io.github.wulkanowy.di.scopes.PerFragment
import io.github.wulkanowy.ui.base.BasePagerAdapter
import io.github.wulkanowy.ui.base.BaseFragmentPagerAdapter
import io.github.wulkanowy.ui.modules.login.form.LoginFormFragment
import io.github.wulkanowy.ui.modules.login.options.LoginOptionsFragment
@ -18,7 +18,7 @@ internal abstract class LoginModule {
@JvmStatic
@PerActivity
@Provides
fun provideLoginAdapter(activity: LoginActivity) = BasePagerAdapter(activity.supportFragmentManager)
fun provideLoginAdapter(activity: LoginActivity) = BaseFragmentPagerAdapter(activity.supportFragmentManager)
}
@PerFragment

View File

@ -2,6 +2,7 @@ package io.github.wulkanowy.ui.modules.login
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
import timber.log.Timber
import javax.inject.Inject
class LoginPresenter @Inject constructor(errorHandler: ErrorHandler) : BasePresenter<LoginView>(errorHandler) {
@ -12,6 +13,7 @@ class LoginPresenter @Inject constructor(errorHandler: ErrorHandler) : BasePrese
initAdapter()
hideActionBar()
}
Timber.i("Login view is attached")
}
fun onPageSelected(index: Int) {
@ -23,6 +25,7 @@ class LoginPresenter @Inject constructor(errorHandler: ErrorHandler) : BasePrese
}
fun onBackPressed(default: () -> Unit) {
Timber.i("Back pressed in login view")
view?.run {
if (currentViewIndex == 1) {
switchView(0)

View File

@ -45,6 +45,7 @@ class LoginFormPresenter @Inject constructor(
showProgress(true)
showContent(false)
}
Timber.i("Login started")
}
.doFinally {
view?.apply {
@ -58,17 +59,21 @@ class LoginFormPresenter @Inject constructor(
showSymbolInput()
wasEmpty = true
analytics.logEvent("sign_up_send", mapOf(SUCCESS to false, "students" to 0, "endpoint" to endpoint, GROUP_ID to symbol.ifEmpty { "null" }))
Timber.i("Login result: Empty student list")
} else if (it.isEmpty() && wasEmpty) {
showSymbolInput()
setErrorSymbolIncorrect()
analytics.logEvent("sign_up_send", mapOf(SUCCESS to false, "students" to it.size, "endpoint" to endpoint, GROUP_ID to symbol.ifEmpty { "nil" }))
analytics.logEvent("sign_up_send", mapOf(SUCCESS to false, "students" to it.size, "endpoint" to endpoint, GROUP_ID to symbol.ifEmpty { "null" }))
Timber.i("Login result: Wrong symbol")
} else {
analytics.logEvent("sign_up_send", mapOf(SUCCESS to true, "students" to it.size, "endpoint" to endpoint, GROUP_ID to symbol))
Timber.i("Login result: Success")
switchOptionsView()
}
}
}, {
analytics.logEvent(SIGN_UP, mapOf(SUCCESS to false, "endpoint" to endpoint, "message" to it.localizedMessage, GROUP_ID to symbol.ifEmpty { "nil" }))
analytics.logEvent(SIGN_UP, mapOf(SUCCESS to false, "endpoint" to endpoint, "message" to it.localizedMessage, GROUP_ID to symbol.ifEmpty { "null" }))
Timber.i("Login result: An exception occurred")
errorHandler.dispatch(it)
}))
}

View File

@ -12,6 +12,7 @@ import io.github.wulkanowy.ui.modules.login.LoginErrorHandler
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.SchedulersProvider
import io.reactivex.Single
import timber.log.Timber
import javax.inject.Inject
class LoginOptionsPresenter @Inject constructor(
@ -26,7 +27,10 @@ class LoginOptionsPresenter @Inject constructor(
super.onAttachView(view)
view.run {
initView()
errorHandler.onStudentDuplicate = { showMessage(it) }
errorHandler.onStudentDuplicate = {
showMessage(it)
Timber.i("The student already registered in the app was selected")
}
}
}
@ -58,11 +62,14 @@ class LoginOptionsPresenter @Inject constructor(
showContent(false)
showActionBar(false)
}
Timber.i("Registration started")
}
.subscribe({
analytics.logEvent(SIGN_UP, mapOf(SUCCESS to true, "endpoint" to student.endpoint, "message" to "Success", GROUP_ID to student.symbol))
Timber.i("Registration result: Success")
view?.openMainView()
}, {
Timber.i("Registration result: An exception occurred ")
errorHandler.dispatch(it)
view?.apply {
showProgress(false)

View File

@ -7,6 +7,7 @@ import android.view.Menu
import android.view.MenuItem
import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
import com.aurelhubert.ahbottomnavigation.AHBottomNavigation.TitleState.ALWAYS_SHOW
import com.aurelhubert.ahbottomnavigation.AHBottomNavigationItem
@ -70,7 +71,7 @@ class MainActivity : BaseActivity(), MainView {
override fun onStart() {
super.onStart()
presenter.onViewStart()
presenter.onViewChange()
}
override fun initView() {
@ -97,7 +98,7 @@ class MainActivity : BaseActivity(), MainView {
}
navController.run {
setOnViewChangeListener { presenter.onViewStart() }
setOnViewChangeListener { presenter.onViewChange() }
fragmentHideStrategy = HIDE
rootFragments = listOf(
GradeFragment.newInstance(),
@ -147,6 +148,10 @@ class MainActivity : BaseActivity(), MainView {
(navController.currentStack?.get(0) as? MainView.MainChildView)?.onFragmentReselected()
}
fun showDialogFragment(dialog: DialogFragment) {
navController.showDialogFragment(dialog)
}
fun pushView(fragment: Fragment) {
navController.pushFragment(fragment)
}

View File

@ -10,6 +10,7 @@ import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.SchedulersProvider
import io.reactivex.Completable
import timber.log.Timber
import javax.inject.Inject
class MainPresenter @Inject constructor(
@ -23,6 +24,7 @@ class MainPresenter @Inject constructor(
fun onAttachView(view: MainView, initMenuIndex: Int) {
super.onAttachView(view)
Timber.i("Main view is attached with $initMenuIndex menu index")
view.run {
cancelNotifications()
startMenuIndex = if (initMenuIndex != -1) initMenuIndex else prefRepository.startMenuIndex
@ -38,7 +40,7 @@ class MainPresenter @Inject constructor(
}))
}
fun onViewStart() {
fun onViewChange() {
view?.apply {
currentViewTitle?.let { setViewTitle(it) }
currentStackSize?.let {
@ -49,16 +51,19 @@ class MainPresenter @Inject constructor(
}
fun onAccountManagerSelected(): Boolean {
Timber.i("Select account manager")
view?.showAccountPicker()
return true
}
fun onUpNavigate(): Boolean {
Timber.i("Up navigate pressed")
view?.popView()
return true
}
fun onBackPressed(default: () -> Unit) {
Timber.i("Back pressed in main view")
view?.run {
if (isRootView) default()
else popView()
@ -67,6 +72,7 @@ class MainPresenter @Inject constructor(
fun onTabSelected(index: Int, wasSelected: Boolean): Boolean {
return view?.run {
Timber.i("Switch main tab index: $index, reselected: $wasSelected")
if (wasSelected) {
notifyMenuViewReselected()
false
@ -78,15 +84,25 @@ class MainPresenter @Inject constructor(
}
fun onLoginSelected() {
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()) studentRepository.switchStudent(it[0])
if (it.isNotEmpty()) {
Timber.i("Switching current student")
studentRepository.switchStudent(it[0])
}
else Completable.complete()
}
.subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread)
.subscribe({ view?.openLoginView() }, { errorHandler.dispatch(it) }))
.subscribe({
Timber.i("Switch student result: Open login view")
view?.openLoginView()
}, {
Timber.i("Switch student result: An exception occurred")
errorHandler.dispatch(it)
}))
}
}

View File

@ -11,7 +11,7 @@ import io.github.wulkanowy.data.repositories.MessagesRepository.MessageFolder.RE
import io.github.wulkanowy.data.repositories.MessagesRepository.MessageFolder.SENT
import io.github.wulkanowy.data.repositories.MessagesRepository.MessageFolder.TRASHED
import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.base.BasePagerAdapter
import io.github.wulkanowy.ui.base.BaseFragmentPagerAdapter
import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.ui.modules.message.tab.MessageTabFragment
import io.github.wulkanowy.utils.setOnSelectPageListener
@ -24,7 +24,7 @@ class MessageFragment : BaseFragment(), MessageView, MainView.TitledView {
lateinit var presenter: MessagePresenter
@Inject
lateinit var pagerAdapter: BasePagerAdapter
lateinit var pagerAdapter: BaseFragmentPagerAdapter
companion object {
fun newInstance() = MessageFragment()
@ -46,11 +46,12 @@ class MessageFragment : BaseFragment(), MessageView, MainView.TitledView {
}
override fun initView() {
pagerAdapter.fragments.putAll(mapOf(
getString(R.string.message_inbox) to MessageTabFragment.newInstance(RECEIVED),
getString(R.string.message_sent) to MessageTabFragment.newInstance(SENT),
getString(R.string.message_trash) to MessageTabFragment.newInstance(TRASHED)
pagerAdapter.addFragmentsWithTitle(mapOf(
MessageTabFragment.newInstance(RECEIVED) to getString(R.string.message_inbox),
MessageTabFragment.newInstance(SENT) to getString(R.string.message_sent),
MessageTabFragment.newInstance(TRASHED) to getString(R.string.message_trash)
))
messageViewPager.run {
adapter = pagerAdapter
offscreenPageLimit = 2
@ -73,7 +74,7 @@ class MessageFragment : BaseFragment(), MessageView, MainView.TitledView {
}
override fun notifyChildLoadData(index: Int, forceRefresh: Boolean) {
(childFragmentManager.fragments[index] as MessageView.MessageChildView).onParentLoadData(forceRefresh)
(pagerAdapter.getFragmentInstance(index) as? MessageView.MessageChildView)?.onParentLoadData(forceRefresh)
}
override fun onDestroyView() {

View File

@ -5,7 +5,7 @@ import dagger.Provides
import dagger.android.ContributesAndroidInjector
import io.github.wulkanowy.di.scopes.PerChildFragment
import io.github.wulkanowy.di.scopes.PerFragment
import io.github.wulkanowy.ui.base.BasePagerAdapter
import io.github.wulkanowy.ui.base.BaseFragmentPagerAdapter
import io.github.wulkanowy.ui.modules.message.tab.MessageTabFragment
@Module
@ -17,7 +17,7 @@ abstract class MessageModule {
@JvmStatic
@PerFragment
@Provides
fun provideGradePagerAdapter(fragment: MessageFragment) = BasePagerAdapter(fragment.childFragmentManager)
fun provideMessageAdapter(fragment: MessageFragment) = BaseFragmentPagerAdapter(fragment.childFragmentManager)
}
@PerChildFragment

View File

@ -4,6 +4,7 @@ import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.SchedulersProvider
import io.reactivex.Completable
import timber.log.Timber
import java.util.concurrent.TimeUnit.MILLISECONDS
import javax.inject.Inject
@ -14,6 +15,7 @@ class MessagePresenter @Inject constructor(
override fun onAttachView(view: MessageView) {
super.onAttachView(view)
Timber.i("Message view is attached")
disposable.add(Completable.timer(150, MILLISECONDS, schedulers.mainThread)
.subscribe {
view.initView()
@ -30,6 +32,7 @@ class MessagePresenter @Inject constructor(
}
private fun loadChild(index: Int, forceRefresh: Boolean = false) {
Timber.i("Load message child view index: $index")
view?.notifyChildLoadData(index, forceRefresh)
}

View File

@ -8,6 +8,7 @@ import io.github.wulkanowy.ui.base.session.SessionErrorHandler
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.SchedulersProvider
import io.github.wulkanowy.utils.toFormattedString
import timber.log.Timber
import javax.inject.Inject
class MessagePreviewPresenter @Inject constructor(
@ -26,15 +27,17 @@ class MessagePreviewPresenter @Inject constructor(
}
private fun loadData(id: Int) {
Timber.i("Loading message $id preview started")
messageId = id
disposable.apply {
clear()
add(studentRepository.getCurrentStudent()
.flatMap { messagesRepository.getMessage(it.studentId, messageId, true) }
.flatMap { messagesRepository.getMessage(it, messageId, true) }
.subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread)
.doFinally { view?.showProgress(false) }
.subscribe({ message ->
Timber.i("Loading message $id preview result: Success ")
view?.run {
message.let {
setSubject(if (it.subject.isNotBlank()) it.subject else noSubjectString)
@ -45,8 +48,9 @@ class MessagePreviewPresenter @Inject constructor(
else setSender(it.sender)
}
}
analytics.logEvent("load_attendance", mapOf(START_DATE to message.date?.toFormattedString("yyyy.MM.dd"), "lenght" to message.content?.length))
analytics.logEvent("load_message_preview", mapOf(START_DATE to message.date?.toFormattedString("yyyy.MM.dd"), "lenght" to message.content?.length))
}) {
Timber.i("Loading message $id preview result: An excception occurred ")
view?.showMessageError()
errorHandler.dispatch(it)
})

View File

@ -29,14 +29,16 @@ class MessageTabPresenter @Inject constructor(
}
fun onSwipeRefresh() {
Timber.i("Force refreshing the $folder message")
onParentViewLoadData(true)
}
fun onParentViewLoadData(forceRefresh: Boolean) {
Timber.i("Loading $folder message data started")
disposable.apply {
clear()
add(studentRepository.getCurrentStudent()
.flatMap { messagesRepository.getMessages(it.studentId, folder, forceRefresh) }
.flatMap { messagesRepository.getMessages(it, folder, forceRefresh) }
.map { items -> items.map { MessageItem(it, view?.noSubjectString.orEmpty()) } }
.subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread)
@ -48,6 +50,7 @@ class MessageTabPresenter @Inject constructor(
}
}
.subscribe({
Timber.i("Loading $folder message result: Success")
view?.run {
showEmpty(it.isEmpty())
showContent(it.isNotEmpty())
@ -55,6 +58,7 @@ class MessageTabPresenter @Inject constructor(
}
analytics.logEvent("load_messages", mapOf("items" to it.size, "folder" to folder.name))
}) {
Timber.i("Loading $folder message result: An exception occurred")
view?.run { showEmpty(isViewEmpty) }
errorHandler.dispatch(it)
})
@ -63,6 +67,7 @@ class MessageTabPresenter @Inject constructor(
fun onMessageItemSelected(item: AbstractFlexibleItem<*>) {
if (item is MessageItem) {
Timber.i("Select message ${item.message.realId} item")
view?.run {
openMessage(item.message.realId)
if (item.message.unread == true) {
@ -75,12 +80,14 @@ class MessageTabPresenter @Inject constructor(
}
private fun updateMessage(message: Message) {
Timber.i("Attempt to update message ${message.realId}")
disposable.add(messagesRepository.updateMessage(message)
.subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread)
.subscribe({
Timber.d("Message ${message.realId} updated")
}) { error -> errorHandler.dispatch(error) }
)
.subscribe({ Timber.d("Update message ${message.realId} result: Success") })
{ error ->
Timber.i("Update message ${message.realId} result: An exception occurred")
errorHandler.dispatch(error)
})
}
}

View File

@ -3,18 +3,21 @@ package io.github.wulkanowy.ui.modules.more
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
import timber.log.Timber
import javax.inject.Inject
class MorePresenter @Inject constructor(errorHandler: ErrorHandler) : BasePresenter<MoreView>(errorHandler) {
override fun onAttachView(view: MoreView) {
super.onAttachView(view)
Timber.i("More view is attached")
view.initView()
loadData()
}
fun onItemSelected(item: AbstractFlexibleItem<*>?) {
if (item is MoreItem) {
Timber.i("Select more item \"${item.title}\"")
view?.run {
when (item.title) {
messagesRes?.first -> openMessagesView()
@ -28,10 +31,12 @@ class MorePresenter @Inject constructor(errorHandler: ErrorHandler) : BasePresen
}
fun onViewReselected() {
Timber.i("More view is reselected")
view?.popView()
}
private fun loadData() {
Timber.i("Load items for more view")
view?.run {
updateData(listOfNotNull(
messagesRes?.let { MoreItem(it.first, it.second) },

View File

@ -12,6 +12,7 @@ import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Note
import io.github.wulkanowy.ui.base.session.BaseSessionFragment
import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.setOnItemClickListener
import kotlinx.android.synthetic.main.fragment_note.*
@ -57,7 +58,7 @@ class NoteFragment : BaseSessionFragment(), NoteView, MainView.TitledView {
}
override fun showNoteDialog(note: Note) {
NoteDialog.newInstance(note).show(fragmentManager, note.toString())
(activity as? MainActivity)?.showDialogFragment(NoteDialog.newInstance(note))
}
override fun updateData(data: List<NoteItem>) {

View File

@ -46,11 +46,14 @@ class NoteItem(val note: Note) : AbstractFlexibleItem<NoteItem.ViewHolder>() {
other as NoteItem
if (note != other.note) return false
if (note.id != other.note.id) return false
return true
}
override fun hashCode(): Int {
return note.hashCode()
var result = note.hashCode()
result = 31 * result + note.id.toInt()
return result
}
class ViewHolder(val view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter), LayoutContainer {

View File

@ -23,15 +23,18 @@ class NotePresenter @Inject constructor(
override fun onAttachView(view: NoteView) {
super.onAttachView(view)
Timber.i("Note view is attached")
view.initView()
loadData()
}
fun onSwipeRefresh() {
Timber.i("Force refreshing the note")
loadData(true)
}
private fun loadData(forceRefresh: Boolean = false) {
Timber.i("Loading note data started")
disposable.add(studentRepository.getCurrentStudent()
.flatMap { semesterRepository.getCurrentSemester(it) }
.flatMap { noteRepository.getNotes(it, forceRefresh) }
@ -45,6 +48,7 @@ class NotePresenter @Inject constructor(
showProgress(false)
}
}.subscribe({
Timber.i("Loading note result: Success")
view?.apply {
updateData(it)
showEmpty(it.isEmpty())
@ -52,6 +56,7 @@ class NotePresenter @Inject constructor(
}
analytics.logEvent("load_note", mapOf("items" to it.size, "force_refresh" to forceRefresh))
}, {
Timber.i("Loading note result: An exception occurred")
view?.run { showEmpty(isViewEmpty) }
errorHandler.dispatch(it)
})
@ -60,6 +65,7 @@ class NotePresenter @Inject constructor(
fun onNoteItemSelected(item: AbstractFlexibleItem<*>?) {
if (item is NoteItem) {
Timber.i("Select note item ${item.note.id}")
view?.run {
showNoteDialog(item.note)
if (!item.note.isRead) {
@ -72,12 +78,14 @@ class NotePresenter @Inject constructor(
}
private fun updateNote(note: Note) {
Timber.i("Attempt to update note ${note.id}")
disposable.add(noteRepository.updateNote(note)
.subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread)
.subscribe({
Timber.d("Note ${note.id} updated")
}) { error -> errorHandler.dispatch(error) }
)
.subscribe({ Timber.i("Update note result: Success") })
{ error ->
Timber.i("Update note result: An exception occurred")
errorHandler.dispatch(error)
})
}
}

View File

@ -6,6 +6,7 @@ import android.os.Bundle
import androidx.appcompat.app.AppCompatDelegate
import com.takisoft.preferencex.PreferenceFragmentCompat
import dagger.android.support.AndroidSupportInjection
import io.github.wulkanowy.BuildConfig
import io.github.wulkanowy.R
import io.github.wulkanowy.ui.base.BaseActivity
import io.github.wulkanowy.ui.modules.main.MainView
@ -36,6 +37,7 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP
override fun onCreatePreferencesFix(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.scheme_preferences)
findPreference(getString(R.string.pref_key_debug_chucker_notification)).isVisible = BuildConfig.DEBUG
}
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {

View File

@ -1,5 +1,6 @@
package io.github.wulkanowy.ui.modules.settings
import com.readystatesoftware.chuck.api.ChuckCollector
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.services.job.ServiceHelper
import io.github.wulkanowy.ui.base.BasePresenter
@ -7,17 +8,20 @@ import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.isHolidays
import org.threeten.bp.LocalDate.now
import timber.log.Timber
import javax.inject.Inject
class SettingsPresenter @Inject constructor(
errorHandler: ErrorHandler,
private val preferencesRepository: PreferencesRepository,
private val serviceHelper: ServiceHelper,
private val analytics: FirebaseAnalyticsHelper
private val analytics: FirebaseAnalyticsHelper,
private val chuckCollector: ChuckCollector
) : BasePresenter<SettingsView>(errorHandler) {
override fun onAttachView(view: SettingsView) {
super.onAttachView(view)
Timber.i("Settings view is attached")
view.run {
setServicesSuspended(preferencesRepository.serviceEnablesKey, now().isHolidays)
@ -25,6 +29,7 @@ class SettingsPresenter @Inject constructor(
}
fun onSharedPreferenceChanged(key: String) {
Timber.i("Change settings $key")
when (key) {
preferencesRepository.serviceEnablesKey -> {
if (preferencesRepository.isServiceEnabled) serviceHelper.startFullSyncService()
@ -37,8 +42,10 @@ class SettingsPresenter @Inject constructor(
preferencesRepository.currentThemeKey -> {
view?.setTheme(preferencesRepository.currentTheme)
}
preferencesRepository.isShowChuckerNotificationKey -> {
chuckCollector.showNotification(preferencesRepository.isShowChuckerNotification)
}
}
analytics.logEvent("setting_changed", mapOf("name" to key))
}
}

View File

@ -10,6 +10,7 @@ import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Timetable
import io.github.wulkanowy.ui.base.session.BaseSessionFragment
import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.setOnItemClickListener
import kotlinx.android.synthetic.main.fragment_timetable.*
@ -107,7 +108,7 @@ class TimetableFragment : BaseSessionFragment(), TimetableView, MainView.MainChi
}
override fun showTimetableDialog(lesson: Timetable) {
TimetableDialog.newInstance(lesson).show(fragmentManager, lesson.toString())
(activity as? MainActivity)?.showDialogFragment(TimetableDialog.newInstance(lesson))
}
override fun onSaveInstanceState(outState: Bundle) {

View File

@ -17,6 +17,7 @@ import io.github.wulkanowy.utils.toFormattedString
import org.threeten.bp.LocalDate
import org.threeten.bp.LocalDate.now
import org.threeten.bp.LocalDate.ofEpochDay
import timber.log.Timber
import java.util.concurrent.TimeUnit.MILLISECONDS
import javax.inject.Inject
@ -34,6 +35,7 @@ class TimetablePresenter @Inject constructor(
fun onAttachView(view: TimetableView, date: Long?) {
super.onAttachView(view)
Timber.i("Timetable is attached")
view.initView()
loadData(ofEpochDay(date ?: now().nextOrSameSchoolDay.toEpochDay()))
reloadView()
@ -50,10 +52,12 @@ class TimetablePresenter @Inject constructor(
}
fun onSwipeRefresh() {
Timber.i("Force refreshing the timetable")
loadData(currentDate, true)
}
fun onViewReselected() {
Timber.i("Exam view is reselected")
now().nextOrSameSchoolDay.also {
if (currentDate != it) {
loadData(it)
@ -63,10 +67,14 @@ class TimetablePresenter @Inject constructor(
}
fun onTimetableItemSelected(item: AbstractFlexibleItem<*>?) {
if (item is TimetableItem) view?.showTimetableDialog(item.lesson)
if (item is TimetableItem) {
Timber.i("Select exam item ${item.lesson.id}")
view?.showTimetableDialog(item.lesson)
}
}
private fun loadData(date: LocalDate, forceRefresh: Boolean = false) {
Timber.i("Loading timetable data started")
currentDate = date
disposable.apply {
clear()
@ -85,13 +93,15 @@ class TimetablePresenter @Inject constructor(
}
}
.subscribe({
Timber.i("Loading timetable result: Success")
view?.apply {
updateData(it)
showEmpty(it.isEmpty())
showContent(it.isNotEmpty())
}
analytics.logEvent("load_attendance", mapOf("items" to it.size, "force_refresh" to forceRefresh, START_DATE to currentDate.toFormattedString("yyyy-MM-dd")))
analytics.logEvent("load_timetable", mapOf("items" to it.size, "force_refresh" to forceRefresh, START_DATE to currentDate.toFormattedString("yyyy-MM-dd")))
}) {
Timber.i("Loading timetable result: An exception occurred")
view?.run { showEmpty(isViewEmpty) }
errorHandler.dispatch(it)
})
@ -99,6 +109,7 @@ class TimetablePresenter @Inject constructor(
}
private fun reloadView() {
Timber.i("Reload timetable view with the date ${currentDate.toFormattedString()}")
view?.apply {
showProgress(true)
showContent(false)

View File

Before

Width:  |  Height:  |  Size: 111 KiB

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

View File

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 96 KiB

View File

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

View File

@ -1,16 +0,0 @@
Wersja 0.6.0
Aplikacja została całkowicie przepisana!
Przez wielkie zmian, które w niej zaszły, wymagane jest ponowne logowanie w aplikacji
oraz ponowne dodanie widgetu planu lekcji do ekranu głównego.
Mamy nową ikonę aplikacji! 🎉
Dodaliśmy nowe funkcje:
- przeglądanie wiadomości (wysyłanie pojawi się w przyszłości)
- zadania domowe
- statystyki frekwencji
- menadżer kont
- możliwość zmiany wagi plusów i minusów
Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases/tag/0.6.0

View File

@ -0,0 +1,7 @@
Wersja 0.6.6
- poprawiono problemy ze stabilnością w widoku ocen i wiadomości
- naprawiono wyświetlanie powiadomień po ich ponownym włączeniu po długim czasie
- ograniczono ilość zbędnych informacji na widżecie planu lekcji
Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases/tag/0.6.6

View File

@ -1,9 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M10,18h4v-2h-4v2zM3,6v2h18L21,6L3,6zM6,13h12v-2L6,11v2z" />
android:pathData="M21,17V8H7V17H21M21,3A2,2 0,0 1,23 5V17A2,2 0,0 1,21 19H7C5.89,19 5,18.1 5,17V5A2,2 0,0 1,7 3H8V1H10V3H18V1H20V3H21M3,21H17V23H3C1.89,23 1,22.1 1,21V9H3V21M19,15H15V11H19V15Z" />
</vector>

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M19,3L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM9,17L7,17v-7h2v7zM13,17h-2L11,7h2v10zM17,17h-2v-4h2v4z"/>
</vector>

View File

@ -2,22 +2,40 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
tools:context=".ui.modules.attendance.summary.AttendanceSummaryFragment">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatSpinner
android:id="@+id/attendanceSummarySubjects"
<LinearLayout
android:id="@+id/attendanceSummarySubjectsContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="invisible"
android:background="?android:attr/windowBackground"
android:elevation="5dp"
android:padding="15dp"
android:spinnerMode="dialog" />
android:padding="5dp"
android:visibility="invisible"
tools:listitem="@layout/item_attendance_summary"
tools:targetApi="lollipop"
tools:visibility="visible">
<androidx.appcompat.widget.AppCompatSpinner
android:id="@+id/attendanceSummarySubjects"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="middle"
android:entries="@array/endpoints_keys"
android:paddingStart="10dp"
android:paddingLeft="10dp"
android:paddingTop="10dp"
android:paddingEnd="30dp"
android:paddingRight="30dp"
android:paddingBottom="10dp"
android:spinnerMode="dialog" />
</LinearLayout>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/attendanceSummarySwipe"
@ -27,7 +45,8 @@
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/attendanceSummaryRecycler"
android:layout_width="match_parent"
android:layout_height="match_parent" />
android:layout_height="match_parent"
tools:listitem="@layout/item_attendance_summary" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</LinearLayout>

View File

@ -69,7 +69,7 @@
android:layout_marginBottom="15dp"
android:hint="@string/login_nickname_hint">
<com.google.android.material.textfield.TextInputEditText
<androidx.appcompat.widget.AppCompatEditText
android:id="@+id/loginNicknameEdit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -84,7 +84,7 @@
android:layout_marginBottom="15dp"
android:hint="@string/login_password_hint">
<com.google.android.material.textfield.TextInputEditText
<androidx.appcompat.widget.AppCompatEditText
android:id="@+id/loginPassEdit"
android:layout_width="match_parent"
android:layout_height="wrap_content"

View File

@ -1,30 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?colorControlHighlight"
android:orientation="horizontal"
android:paddingLeft="20dp"
android:paddingTop="7dp"
android:paddingRight="20dp"
android:paddingBottom="7dp">
<TextView
android:id="@+id/gradeSummaryHeaderName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="40dp"
android:layout_marginRight="40dp"
android:layout_weight="1"
android:text="@string/app_name"
android:textSize="17sp" />
<TextView
android:id="@+id/gradeSummaryHeaderAverage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="end"
android:text="@string/app_name"
android:textSize="12sp" />
</LinearLayout>

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
@ -21,16 +22,16 @@
android:layout_marginEnd="40dp"
android:layout_marginRight="40dp"
android:layout_weight="1"
android:text="@string/app_name"
android:textSize="17sp" />
android:textSize="17sp"
tools:text="January" />
<TextView
android:id="@+id/attendanceSummaryPercentage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="end"
android:text="@string/app_name"
android:textSize="12sp" />
android:textSize="12sp"
tools:text="75%" />
</LinearLayout>
<LinearLayout
@ -60,8 +61,8 @@
android:layout_marginEnd="25dp"
android:layout_marginRight="25dp"
android:gravity="end"
android:text="@string/app_name"
android:textSize="12sp" />
android:textSize="12sp"
tools:text="50" />
</LinearLayout>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
@ -91,8 +92,8 @@
android:layout_marginEnd="25dp"
android:layout_marginRight="25dp"
android:gravity="end"
android:text="@string/app_name"
android:textSize="12sp" />
android:textSize="12sp"
tools:text="0" />
</LinearLayout>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
@ -122,8 +123,8 @@
android:layout_marginEnd="25dp"
android:layout_marginRight="25dp"
android:gravity="end"
android:text="@string/app_name"
android:textSize="12sp" />
android:textSize="12sp"
tools:text="25" />
</LinearLayout>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
@ -153,8 +154,8 @@
android:layout_marginEnd="25dp"
android:layout_marginRight="25dp"
android:gravity="end"
android:text="@string/app_name"
android:textSize="12sp" />
android:textSize="12sp"
tools:text="0" />
</LinearLayout>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
@ -184,8 +185,8 @@
android:layout_marginEnd="25dp"
android:layout_marginRight="25dp"
android:gravity="end"
android:text="@string/app_name"
android:textSize="12sp" />
android:textSize="12sp"
tools:text="6" />
</LinearLayout>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
@ -215,8 +216,8 @@
android:layout_marginEnd="25dp"
android:layout_marginRight="25dp"
android:gravity="end"
android:text="@string/app_name"
android:textSize="12sp" />
android:textSize="12sp"
tools:text="0" />
</LinearLayout>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
@ -246,7 +247,7 @@
android:layout_marginEnd="25dp"
android:layout_marginRight="25dp"
android:gravity="end"
android:text="@string/app_name"
android:textSize="12sp" />
android:textSize="12sp"
tools:text="0" />
</LinearLayout>
</LinearLayout>

View File

@ -10,5 +10,6 @@
android:paddingRight="16dp"
android:textAlignment="textStart"
android:paddingBottom="12dp"
android:ellipsize="end"
android:text="@string/app_name"
android:textSize="16sp" />

View File

@ -1,31 +1,98 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/ic_all_divider"
android:minHeight="35dp"
android:orientation="horizontal">
android:orientation="vertical">
<TextView
android:id="@+id/gradeSummaryItemTitle"
android:layout_width="0dp"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="20dp"
android:layout_marginLeft="20dp"
android:layout_weight="1"
android:text="@string/grade_summary_predicted_grade"
android:textSize="14sp" />
android:background="?colorControlHighlight"
android:orientation="horizontal"
android:paddingLeft="20dp"
android:paddingTop="7dp"
android:paddingRight="20dp"
android:paddingBottom="7dp">
<TextView
android:id="@+id/gradeSummaryItemGrade"
android:layout_width="wrap_content"
<TextView
android:id="@+id/gradeSummaryItemTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="40dp"
android:layout_marginRight="40dp"
android:layout_weight="1"
android:textSize="17sp"
tools:text="@tools:sample/lorem" />
<TextView
android:id="@+id/gradeSummaryItemAverage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="end"
android:textSize="12sp"
tools:text="@string/aboutlibrary_lib_version" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="10dp"
android:layout_marginLeft="10dp"
android:layout_marginEnd="25dp"
android:layout_marginRight="25dp"
android:gravity="end"
android:text="@string/app_name"
android:textSize="12sp" />
android:background="@drawable/ic_all_divider"
android:minHeight="35dp"
android:orientation="horizontal">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="20dp"
android:layout_marginLeft="20dp"
android:layout_weight="1"
android:text="@string/grade_summary_predicted_grade"
android:textSize="14sp" />
<TextView
android:id="@+id/gradeSummaryItemPredicted"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="10dp"
android:layout_marginLeft="10dp"
android:layout_marginEnd="25dp"
android:layout_marginRight="25dp"
android:gravity="end"
android:textSize="12sp"
tools:text="@string/aboutlibrary_lib_version" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/ic_all_divider"
android:minHeight="35dp"
android:orientation="horizontal">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="20dp"
android:layout_marginLeft="20dp"
android:layout_weight="1"
android:text="@string/grade_summary_final_average"
android:textSize="14sp" />
<TextView
android:id="@+id/gradeSummaryItemFinal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="10dp"
android:layout_marginLeft="10dp"
android:layout_marginEnd="25dp"
android:layout_marginRight="25dp"
android:gravity="end"
android:textSize="12sp"
tools:text="@string/aboutlibrary_lib_version" />
</LinearLayout>
</LinearLayout>

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