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 openssl aes-256-cbc -d -in ./app/upload-key-encrypted.jks -k $ENCRYPT_KEY >> ./app/upload-key.jks
- run: - run:
name: Publish release name: Publish release
command: ./gradlew publishRelease --no-daemon --stacktrace --console=plain -PenableCrashlytics -PdisablePreDex command: ./gradlew publish --no-daemon --stacktrace --console=plain -PenableCrashlytics -PdisablePreDex
workflows: workflows:
version: 2 version: 2

View File

@ -11,6 +11,10 @@ cache:
- $HOME/.gradle/caches/ - $HOME/.gradle/caches/
- $HOME/.gradle/wrapper/ - $HOME/.gradle/wrapper/
#branches:
# only:
# - master
android: android:
licenses: licenses:
- android-sdk-preview-license-.+ - 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=$SERVICES_ENCRYPT_KEY ./app/src/release/google-services.json.gpg;
gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/key.p12.gpg; gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/key.p12.gpg;
gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/upload-key.jks.gpg; gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/upload-key.jks.gpg;
./gradlew publishRelease -PenableCrashlytics --stacktrace; ./gradlew publish -PenableCrashlytics --stacktrace;
fi fi
after_success: after_success:

View File

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

View File

@ -5,6 +5,7 @@
-dontskipnonpubliclibraryclasses -dontskipnonpubliclibraryclasses
-dontskipnonpubliclibraryclassmembers -dontskipnonpubliclibraryclassmembers
-dontpreverify -dontpreverify
-dontobfuscate
-allowaccessmodification -allowaccessmodification
-repackageclasses '' -repackageclasses ''
-verbose -verbose
@ -13,7 +14,6 @@
#Config for anallitycs #Config for anallitycs
-keepattributes *Annotation* -keepattributes *Annotation*
-keepattributes SourceFile,LineNumberTable -keepattributes SourceFile,LineNumberTable
-renamesourcefileattribute SourceFile
-keep class com.crashlytics.** {*;} -keep class com.crashlytics.** {*;}
-keep public class * extends java.lang.Exception -keep public class * extends java.lang.Exception
-dontwarn com.crashlytics.** -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" package="io.github.wulkanowy"
android:installLocation="internalOnly"> android:installLocation="internalOnly">
<uses-sdk tools:overrideLibrary="com.readystatesoftware.chuck"/>
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <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 androidx.preference.PreferenceManager
import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings
import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.strategy.SocketInternetObservingStrategy 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.Module
import dagger.Provides import dagger.Provides
import io.github.wulkanowy.api.Api import io.github.wulkanowy.api.Api
import io.github.wulkanowy.data.db.AppDatabase import io.github.wulkanowy.data.db.AppDatabase
import io.github.wulkanowy.data.repositories.PreferencesRepository
import okhttp3.logging.HttpLoggingInterceptor import okhttp3.logging.HttpLoggingInterceptor
import okhttp3.logging.HttpLoggingInterceptor.Level.BASIC import okhttp3.logging.HttpLoggingInterceptor.Level.BASIC
import okhttp3.logging.HttpLoggingInterceptor.Level.NONE import okhttp3.logging.HttpLoggingInterceptor.Level.NONE
@ -30,15 +34,26 @@ internal class RepositoryModule {
@Singleton @Singleton
@Provides @Provides
fun provideApi(): Api { fun provideApi(chuckCollector: ChuckCollector, context: Context): Api {
return Api().apply { return Api().apply {
logLevel = NONE logLevel = NONE
androidVersion = android.os.Build.VERSION.RELEASE androidVersion = android.os.Build.VERSION.RELEASE
buildTag = android.os.Build.MODEL buildTag = android.os.Build.MODEL
setInterceptor(HttpLoggingInterceptor(HttpLoggingInterceptor.Logger { Timber.d(it) }).setLevel(BASIC)) 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 @Singleton
@Provides @Provides
fun provideDatabase(context: Context) = AppDatabase.newInstance(context) 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.ReactiveNetwork
import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings
import io.github.wulkanowy.data.ApiHelper
import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.Message
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.local.MessagesLocal import io.github.wulkanowy.data.repositories.local.MessagesLocal
@ -16,7 +17,8 @@ import javax.inject.Singleton
class MessagesRepository @Inject constructor( class MessagesRepository @Inject constructor(
private val settings: InternetObservingSettings, private val settings: InternetObservingSettings,
private val local: MessagesLocal, private val local: MessagesLocal,
private val remote: MessagesRemote private val remote: MessagesRemote,
private val apiHelper: ApiHelper
) { ) {
enum class MessageFolder(val id: Int = 1) { enum class MessageFolder(val id: Int = 1) {
@ -25,44 +27,50 @@ class MessagesRepository @Inject constructor(
TRASHED(3) TRASHED(3)
} }
fun getMessages(studentId: Int, folder: MessageFolder, forceRefresh: Boolean = false, notify: Boolean = false): Single<List<Message>> { fun getMessages(student: Student, folder: MessageFolder, forceRefresh: Boolean = false, notify: Boolean = false): Single<List<Message>> {
return local.getMessages(studentId, folder).filter { !forceRefresh } return Single.just(apiHelper.initApi(student))
.switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings) .flatMap { _ ->
.flatMap { local.getMessages(student.studentId, folder).filter { !forceRefresh }
if (it) remote.getMessages(studentId, folder) .switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings)
else Single.error(UnknownHostException()) .flatMap {
}.flatMap { new -> if (it) remote.getMessages(student.studentId, folder)
local.getMessages(studentId, folder).toSingle(emptyList()) else Single.error(UnknownHostException())
.doOnSuccess { old -> }.flatMap { new ->
local.deleteMessages(old - new) local.getMessages(student.studentId, folder).toSingle(emptyList())
local.saveMessages((new - old) .doOnSuccess { old ->
.onEach { local.deleteMessages(old - new)
it.isNotified = !notify local.saveMessages((new - old)
}) .onEach {
} it.isNotified = !notify
}.flatMap { local.getMessages(studentId, folder).toSingle(emptyList()) } })
) }
}.flatMap { local.getMessages(student.studentId, folder).toSingle(emptyList()) }
)
}
} }
fun getMessage(studentId: Int, messageId: Int, markAsRead: Boolean = false): Single<Message> { fun getMessage(student: Student, messageId: Int, markAsRead: Boolean = false): Single<Message> {
return local.getMessage(studentId, messageId) return Single.just(apiHelper.initApi(student))
.filter { !it.content.isNullOrEmpty() } .flatMap { _ ->
.switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings) local.getMessage(student.studentId, messageId)
.flatMap { .filter { !it.content.isNullOrEmpty() }
if (it) local.getMessage(studentId, messageId).toSingle() .switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings)
else Single.error(UnknownHostException()) .flatMap {
} if (it) local.getMessage(student.studentId, messageId).toSingle()
.flatMap { dbMessage -> else Single.error(UnknownHostException())
remote.getMessagesContent(dbMessage, markAsRead).doOnSuccess { }
local.updateMessage(dbMessage.copy(unread = false).apply { .flatMap { dbMessage ->
id = dbMessage.id remote.getMessagesContent(dbMessage, markAsRead).doOnSuccess {
content = it local.updateMessage(dbMessage.copy(unread = false).apply {
}) id = dbMessage.id
} content = it
}.flatMap { })
local.getMessage(studentId, messageId).toSingle() }
} }.flatMap {
) local.getMessage(student.studentId, messageId).toSingle()
}
)
}
} }
fun getNewMessages(student: Student): Single<List<Message>> { fun getNewMessages(student: Student): Single<List<Message>> {

View File

@ -44,4 +44,8 @@ class PreferencesRepository @Inject constructor(
val isNotificationsEnable: Boolean val isNotificationsEnable: Boolean
get() = sharedPref.getBoolean(context.getString(R.string.pref_key_notifications_enable), true) get() = sharedPref.getBoolean(context.getString(R.string.pref_key_notifications_enable), true)
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.local.StudentLocal
import io.github.wulkanowy.data.repositories.remote.StudentRemote import io.github.wulkanowy.data.repositories.remote.StudentRemote
import io.reactivex.Completable import io.reactivex.Completable
import io.reactivex.Maybe
import io.reactivex.Single import io.reactivex.Single
import java.net.UnknownHostException import java.net.UnknownHostException
import javax.inject.Inject import javax.inject.Inject
@ -41,7 +42,9 @@ class StudentRepository @Inject constructor(
} }
fun getCurrentStudent(decryptPass: Boolean = true): Single<Student> { 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> { fun saveStudent(student: Student): Single<Long> {

View File

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

View File

@ -13,7 +13,7 @@ abstract class BaseFragment : DaggerFragment(), BaseView {
if (messageContainer == null) (activity as? BaseActivity)?.showError(text, error) if (messageContainer == null) (activity as? BaseActivity)?.showError(text, error)
else messageContainer?.also { else messageContainer?.also {
Snackbar.make(it, text, Snackbar.LENGTH_LONG).setAction(R.string.all_details) { 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() }.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 package io.github.wulkanowy.ui.base
import android.content.res.Resources import android.content.res.Resources
import com.readystatesoftware.chuck.api.ChuckCollector
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.api.interceptor.ServiceUnavailableException import io.github.wulkanowy.api.interceptor.ServiceUnavailableException
import io.github.wulkanowy.api.login.NotLoggedInException import io.github.wulkanowy.api.login.NotLoggedInException
@ -9,11 +10,12 @@ import java.net.SocketTimeoutException
import java.net.UnknownHostException import java.net.UnknownHostException
import javax.inject.Inject 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 = { _, _ -> } var showErrorMessage: (String, Throwable) -> Unit = { _, _ -> }
fun dispatch(error: Throwable) { fun dispatch(error: Throwable) {
chuckCollector.onError(error.javaClass.simpleName, error)
Timber.e(error, "An exception occurred while the Wulkanowy was running") Timber.e(error, "An exception occurred while the Wulkanowy was running")
proceed(error) proceed(error)
} }

View File

@ -1,11 +1,12 @@
package io.github.wulkanowy.ui.base.session package io.github.wulkanowy.ui.base.session
import android.content.res.Resources import android.content.res.Resources
import com.readystatesoftware.chuck.api.ChuckCollector
import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.security.ScramblerException import io.github.wulkanowy.utils.security.ScramblerException
import javax.inject.Inject 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 = {} var onDecryptionFail: () -> Unit = {}

View File

@ -15,6 +15,11 @@ class AboutPresenter @Inject constructor(
private val analytics: FirebaseAnalyticsHelper private val analytics: FirebaseAnalyticsHelper
) : BasePresenter<AboutView>(errorHandler) { ) : BasePresenter<AboutView>(errorHandler) {
override fun onAttachView(view: AboutView) {
super.onAttachView(view)
Timber.i("About view is attached")
}
fun onExtraSelect(type: Libs.SpecialButton?) { fun onExtraSelect(type: Libs.SpecialButton?) {
view?.run { view?.run {
when (type) { 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.ui.base.ErrorHandler
import io.github.wulkanowy.utils.SchedulersProvider import io.github.wulkanowy.utils.SchedulersProvider
import io.reactivex.Single import io.reactivex.Single
import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
class AccountPresenter @Inject constructor( class AccountPresenter @Inject constructor(
@ -16,19 +17,23 @@ class AccountPresenter @Inject constructor(
override fun onAttachView(view: AccountView) { override fun onAttachView(view: AccountView) {
super.onAttachView(view) super.onAttachView(view)
Timber.i("Account dialog is attached")
view.initView() view.initView()
loadData() loadData()
} }
fun onAddSelected() { fun onAddSelected() {
Timber.i("Select add account")
view?.openLoginView() view?.openLoginView()
} }
fun onRemoveSelected() { fun onRemoveSelected() {
Timber.i("Select remove account")
view?.showConfirmDialog() view?.showConfirmDialog()
} }
fun onLogoutConfirm() { fun onLogoutConfirm() {
Timber.i("Attempt to logout current user ")
disposable.add(studentRepository.getCurrentStudent() disposable.add(studentRepository.getCurrentStudent()
.flatMapCompletable { studentRepository.logoutStudent(it) } .flatMapCompletable { studentRepository.logoutStudent(it) }
.andThen(studentRepository.getSavedStudents(false)) .andThen(studentRepository.getSavedStudents(false))
@ -41,31 +46,54 @@ class AccountPresenter @Inject constructor(
.doFinally { view?.dismissView() } .doFinally { view?.dismissView() }
.subscribe({ .subscribe({
view?.apply { view?.apply {
if (it.isEmpty()) openClearLoginView() if (it.isEmpty()) {
else recreateView() 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<*>) { fun onItemSelected(item: AbstractFlexibleItem<*>) {
if (item is AccountItem) { if (item is AccountItem) {
Timber.i("Select student item ${item.student.id}")
if (item.student.isCurrent) { if (item.student.isCurrent) {
view?.dismissView() view?.dismissView()
} else { } else {
Timber.i("Attempt to change a student")
disposable.add(studentRepository.switchStudent(item.student) disposable.add(studentRepository.switchStudent(item.student)
.subscribeOn(schedulers.backgroundThread) .subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread) .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() { private fun loadData() {
Timber.i("Loading account data started")
disposable.add(studentRepository.getSavedStudents(false) disposable.add(studentRepository.getSavedStudents(false)
.map { it.map { item -> AccountItem(item) } } .map { it.map { item -> AccountItem(item) } }
.subscribeOn(schedulers.backgroundThread) .subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread) .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) { override fun showAttendanceDialog(lesson: Attendance) {
AttendanceDialog.newInstance(lesson).show(fragmentManager, lesson.toString()) (activity as? MainActivity)?.showDialogFragment(AttendanceDialog.newInstance(lesson))
} }
override fun openSummaryView() { 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
import org.threeten.bp.LocalDate.now import org.threeten.bp.LocalDate.now
import org.threeten.bp.LocalDate.ofEpochDay import org.threeten.bp.LocalDate.ofEpochDay
import timber.log.Timber
import java.util.concurrent.TimeUnit.MILLISECONDS import java.util.concurrent.TimeUnit.MILLISECONDS
import javax.inject.Inject import javax.inject.Inject
@ -36,6 +37,7 @@ class AttendancePresenter @Inject constructor(
fun onAttachView(view: AttendanceView, date: Long?) { fun onAttachView(view: AttendanceView, date: Long?) {
super.onAttachView(view) super.onAttachView(view)
Timber.i("Attendance view is attached")
view.initView() view.initView()
loadData(ofEpochDay(date ?: now().previousOrSameSchoolDay.toEpochDay())) loadData(ofEpochDay(date ?: now().previousOrSameSchoolDay.toEpochDay()))
reloadView() reloadView()
@ -52,10 +54,12 @@ class AttendancePresenter @Inject constructor(
} }
fun onSwipeRefresh() { fun onSwipeRefresh() {
Timber.i("Force refreshing the attendance")
loadData(currentDate, true) loadData(currentDate, true)
} }
fun onViewReselected() { fun onViewReselected() {
Timber.i("Attendance view is reselected")
view?.also { view -> view?.also { view ->
if (view.currentStackSize == 1) { if (view.currentStackSize == 1) {
now().previousOrSameSchoolDay.also { now().previousOrSameSchoolDay.also {
@ -69,7 +73,10 @@ class AttendancePresenter @Inject constructor(
} }
fun onAttendanceItemSelected(item: AbstractFlexibleItem<*>?) { 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 { fun onSummarySwitchSelected(): Boolean {
@ -78,6 +85,7 @@ class AttendancePresenter @Inject constructor(
} }
private fun loadData(date: LocalDate, forceRefresh: Boolean = false) { private fun loadData(date: LocalDate, forceRefresh: Boolean = false) {
Timber.i("Loading attendance data started")
currentDate = date currentDate = date
disposable.apply { disposable.apply {
clear() clear()
@ -100,6 +108,7 @@ class AttendancePresenter @Inject constructor(
} }
} }
.subscribe({ .subscribe({
Timber.i("Loading attendance result: Success")
view?.apply { view?.apply {
updateData(it) updateData(it)
showEmpty(it.isEmpty()) 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"))) 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) } view?.run { showEmpty(isViewEmpty) }
errorHandler.dispatch(it) errorHandler.dispatch(it)
} }
@ -115,6 +125,7 @@ class AttendancePresenter @Inject constructor(
} }
private fun reloadView() { private fun reloadView() {
Timber.i("Reload attendance view with the date ${currentDate.toFormattedString()}")
view?.apply { view?.apply {
showProgress(true) showProgress(true)
showContent(false) showContent(false)

View File

@ -4,6 +4,7 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.View.GONE import android.view.View.GONE
import android.view.View.INVISIBLE
import android.view.View.VISIBLE import android.view.View.VISIBLE
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ArrayAdapter import android.widget.ArrayAdapter
@ -101,7 +102,7 @@ class AttendanceSummaryFragment : BaseSessionFragment(), AttendanceSummaryView,
} }
override fun showSubjects(show: Boolean) { override fun showSubjects(show: Boolean) {
attendanceSummarySubjects.visibility = if (show) VISIBLE else VISIBLE attendanceSummarySubjectsContainer.visibility = if (show) VISIBLE else INVISIBLE
} }
override fun hideRefresh() { 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.SchedulersProvider
import io.github.wulkanowy.utils.calculatePercentage import io.github.wulkanowy.utils.calculatePercentage
import io.github.wulkanowy.utils.getFormattedName import io.github.wulkanowy.utils.getFormattedName
import timber.log.Timber
import java.lang.String.format import java.lang.String.format
import java.util.Locale.FRANCE import java.util.Locale.FRANCE
import java.util.concurrent.TimeUnit.MILLISECONDS import java.util.concurrent.TimeUnit.MILLISECONDS
@ -34,25 +35,31 @@ class AttendanceSummaryPresenter @Inject constructor(
fun onAttachView(view: AttendanceSummaryView, subjectId: Int?) { fun onAttachView(view: AttendanceSummaryView, subjectId: Int?) {
super.onAttachView(view) super.onAttachView(view)
Timber.i("Attendance summary view is attached with subject id ${subjectId ?: -1}")
view.initView() view.initView()
loadData(subjectId ?: -1) loadData(subjectId ?: -1)
loadSubjects() loadSubjects()
} }
fun onSwipeRefresh() { fun onSwipeRefresh() {
Timber.i("Force refreshing the attendance summary")
loadData(currentSubjectId, true) loadData(currentSubjectId, true)
} }
fun onSubjectSelected(name: String) { fun onSubjectSelected(name: String) {
Timber.i("Select attendance summary subject $name")
view?.run { view?.run {
showContent(false) showContent(false)
showProgress(true) showProgress(true)
clearView() 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) { private fun loadData(subjectId: Int, forceRefresh: Boolean = false) {
Timber.i("Loading attendance summary data started")
currentSubjectId = subjectId currentSubjectId = subjectId
disposable.apply { disposable.apply {
clear() clear()
@ -70,6 +77,7 @@ class AttendanceSummaryPresenter @Inject constructor(
} }
} }
.subscribe({ .subscribe({
Timber.i("Loading attendance summary result: Success")
view?.apply { view?.apply {
showEmpty(it.first.isEmpty()) showEmpty(it.first.isEmpty())
showContent(it.first.isNotEmpty()) 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)) 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) } view?.run { showEmpty(isViewEmpty) }
errorHandler.dispatch(it) errorHandler.dispatch(it)
} }
@ -85,6 +94,7 @@ class AttendanceSummaryPresenter @Inject constructor(
} }
private fun loadSubjects() { private fun loadSubjects() {
Timber.i("Loading attendance summary subjects started")
disposable.add(studentRepository.getCurrentStudent() disposable.add(studentRepository.getCurrentStudent()
.flatMap { semesterRepository.getCurrentSemester(it) } .flatMap { semesterRepository.getCurrentSemester(it) }
.flatMap { subjectRepository.getSubjects(it) } .flatMap { subjectRepository.getSubjects(it) }
@ -93,11 +103,15 @@ class AttendanceSummaryPresenter @Inject constructor(
.subscribeOn(schedulers.backgroundThread) .subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread) .observeOn(schedulers.mainThread)
.subscribe({ .subscribe({
Timber.i("Loading attendance summary subjects result: Success")
view?.run { view?.run {
view?.updateSubjects(it) view?.updateSubjects(it)
showSubjects(true) 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.R
import io.github.wulkanowy.data.db.entities.Exam import io.github.wulkanowy.data.db.entities.Exam
import io.github.wulkanowy.ui.base.session.BaseSessionFragment 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.ui.modules.main.MainView
import io.github.wulkanowy.utils.setOnItemClickListener import io.github.wulkanowy.utils.setOnItemClickListener
import kotlinx.android.synthetic.main.fragment_exam.* import kotlinx.android.synthetic.main.fragment_exam.*
@ -106,7 +107,7 @@ class ExamFragment : BaseSessionFragment(), ExamView, MainView.MainChildView, Ma
} }
override fun showExamDialog(exam: Exam) { override fun showExamDialog(exam: Exam) {
ExamDialog.newInstance(exam).show(fragmentManager, exam.toString()) (activity as? MainActivity)?.showDialogFragment(ExamDialog.newInstance(exam))
} }
override fun onSaveInstanceState(outState: Bundle) { 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
import org.threeten.bp.LocalDate.now import org.threeten.bp.LocalDate.now
import org.threeten.bp.LocalDate.ofEpochDay import org.threeten.bp.LocalDate.ofEpochDay
import timber.log.Timber
import java.util.concurrent.TimeUnit.MILLISECONDS import java.util.concurrent.TimeUnit.MILLISECONDS
import javax.inject.Inject import javax.inject.Inject
@ -35,6 +36,7 @@ class ExamPresenter @Inject constructor(
fun onAttachView(view: ExamView, date: Long?) { fun onAttachView(view: ExamView, date: Long?) {
super.onAttachView(view) super.onAttachView(view)
Timber.i("Exam view is attached")
view.initView() view.initView()
loadData(ofEpochDay(date ?: now().nextOrSameSchoolDay.toEpochDay())) loadData(ofEpochDay(date ?: now().nextOrSameSchoolDay.toEpochDay()))
reloadView() reloadView()
@ -51,14 +53,19 @@ class ExamPresenter @Inject constructor(
} }
fun onSwipeRefresh() { fun onSwipeRefresh() {
Timber.i("Force refreshing the exam")
loadData(currentDate, true) loadData(currentDate, true)
} }
fun onExamItemSelected(item: AbstractFlexibleItem<*>?) { 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() { fun onViewReselected() {
Timber.i("Exam view is reselected")
now().nextOrSameSchoolDay.also { now().nextOrSameSchoolDay.also {
if (currentDate != it) { if (currentDate != it) {
loadData(it) loadData(it)
@ -68,6 +75,7 @@ class ExamPresenter @Inject constructor(
} }
private fun loadData(date: LocalDate, forceRefresh: Boolean = false) { private fun loadData(date: LocalDate, forceRefresh: Boolean = false) {
Timber.i("Loading exam data started")
currentDate = date currentDate = date
disposable.apply { disposable.apply {
clear() clear()
@ -87,6 +95,7 @@ class ExamPresenter @Inject constructor(
} }
} }
.subscribe({ .subscribe({
Timber.i("Loading exam result: Success")
view?.apply { view?.apply {
updateData(it) updateData(it)
showEmpty(it.isEmpty()) 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"))) 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) } view?.run { showEmpty(isViewEmpty) }
errorHandler.dispatch(it) errorHandler.dispatch(it)
}) })
@ -109,6 +119,7 @@ class ExamPresenter @Inject constructor(
} }
private fun reloadView() { private fun reloadView() {
Timber.i("Reload exam view with the date ${currentDate.toFormattedString()}")
view?.apply { view?.apply {
showProgress(true) showProgress(true)
showContent(false) showContent(false)

View File

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

View File

@ -5,7 +5,7 @@ import dagger.Provides
import dagger.android.ContributesAndroidInjector import dagger.android.ContributesAndroidInjector
import io.github.wulkanowy.di.scopes.PerChildFragment import io.github.wulkanowy.di.scopes.PerChildFragment
import io.github.wulkanowy.di.scopes.PerFragment 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.details.GradeDetailsFragment
import io.github.wulkanowy.ui.modules.grade.summary.GradeSummaryFragment import io.github.wulkanowy.ui.modules.grade.summary.GradeSummaryFragment
@ -18,7 +18,7 @@ abstract class GradeModule {
@JvmStatic @JvmStatic
@PerFragment @PerFragment
@Provides @Provides
fun provideGradePagerAdapter(fragment: GradeFragment) = BasePagerAdapter(fragment.childFragmentManager) fun provideGradeAdapter(fragment: GradeFragment) = BaseFragmentPagerAdapter(fragment.childFragmentManager)
} }
@PerChildFragment @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.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.SchedulersProvider import io.github.wulkanowy.utils.SchedulersProvider
import io.reactivex.Completable import io.reactivex.Completable
import timber.log.Timber
import java.util.concurrent.TimeUnit.MILLISECONDS import java.util.concurrent.TimeUnit.MILLISECONDS
import javax.inject.Inject import javax.inject.Inject
@ -28,6 +29,7 @@ class GradePresenter @Inject constructor(
fun onAttachView(view: GradeView, savedIndex: Int?) { fun onAttachView(view: GradeView, savedIndex: Int?) {
super.onAttachView(view) super.onAttachView(view)
Timber.i("Grade view is attached")
disposable.add(Completable.timer(150, MILLISECONDS, schedulers.mainThread) disposable.add(Completable.timer(150, MILLISECONDS, schedulers.mainThread)
.subscribe { .subscribe {
selectedIndex = savedIndex ?: 0 selectedIndex = savedIndex ?: 0
@ -37,6 +39,7 @@ class GradePresenter @Inject constructor(
} }
fun onViewReselected() { fun onViewReselected() {
Timber.i("Grade view is reselected")
view?.run { notifyChildParentReselected(currentPageIndex) } view?.run { notifyChildParentReselected(currentPageIndex) }
} }
@ -47,6 +50,7 @@ class GradePresenter @Inject constructor(
fun onSemesterSelected(index: Int) { fun onSemesterSelected(index: Int) {
if (selectedIndex != index - 1) { if (selectedIndex != index - 1) {
Timber.i("Change semester in grade view to ${index + 1}")
selectedIndex = index + 1 selectedIndex = index + 1
loadedSemesterId.clear() loadedSemesterId.clear()
view?.let { view?.let {
@ -74,6 +78,7 @@ class GradePresenter @Inject constructor(
} }
private fun loadData() { private fun loadData() {
Timber.i("Loading grade data started")
disposable.add(studentRepository.getCurrentStudent() disposable.add(studentRepository.getCurrentStudent()
.flatMap { semesterRepository.getSemesters(it) } .flatMap { semesterRepository.getSemesters(it) }
.doOnSuccess { .doOnSuccess {
@ -84,7 +89,13 @@ class GradePresenter @Inject constructor(
} }
.subscribeOn(schedulers.backgroundThread) .subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread) .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) errorHandler.dispatch(it)
view?.run { view?.run {
showProgress(false) showProgress(false)
@ -96,6 +107,7 @@ class GradePresenter @Inject constructor(
private fun loadChild(index: Int, forceRefresh: Boolean = false) { private fun loadChild(index: Int, forceRefresh: Boolean = false) {
semesters.first { it.semesterName == selectedIndex }.semesterId.also { semesters.first { it.semesterName == selectedIndex }.semesterId.also {
if (forceRefresh || loadedSemesterId[index] != it) { if (forceRefresh || loadedSemesterId[index] != it) {
Timber.i("Load grade child view index: $index")
view?.notifyChildLoadData(index, it, forceRefresh) 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.base.session.BaseSessionFragment
import io.github.wulkanowy.ui.modules.grade.GradeFragment import io.github.wulkanowy.ui.modules.grade.GradeFragment
import io.github.wulkanowy.ui.modules.grade.GradeView import io.github.wulkanowy.ui.modules.grade.GradeView
import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.utils.setOnItemClickListener import io.github.wulkanowy.utils.setOnItemClickListener
import kotlinx.android.synthetic.main.fragment_grade_details.* import kotlinx.android.synthetic.main.fragment_grade_details.*
import javax.inject.Inject import javax.inject.Inject
@ -103,7 +104,7 @@ class GradeDetailsFragment : BaseSessionFragment(), GradeDetailsView, GradeView.
} }
override fun scrollToStart() { override fun scrollToStart() {
gradeDetailsAdapter.smoothScrollToPosition(0) gradeDetailsRecycler.scrollToPosition(0)
} }
override fun getHeaderOfItem(item: AbstractFlexibleItem<*>): IExpandable<*, out IFlexible<*>>? { override fun getHeaderOfItem(item: AbstractFlexibleItem<*>): IExpandable<*, out IFlexible<*>>? {
@ -131,7 +132,7 @@ class GradeDetailsFragment : BaseSessionFragment(), GradeDetailsView, GradeView.
} }
override fun showGradeDialog(grade: Grade) { 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) { override fun onParentLoadData(semesterId: Int, forceRefresh: Boolean) {

View File

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

View File

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

View File

@ -40,6 +40,7 @@ class GradeDetailsPresenter @Inject constructor(
fun onGradeItemSelected(item: AbstractFlexibleItem<*>?) { fun onGradeItemSelected(item: AbstractFlexibleItem<*>?) {
if (item is GradeDetailsItem) { if (item is GradeDetailsItem) {
Timber.i("Select grade item ${item.grade.id}")
view?.apply { view?.apply {
showGradeDialog(item.grade) showGradeDialog(item.grade)
if (!item.grade.isRead) { if (!item.grade.isRead) {
@ -58,18 +59,29 @@ class GradeDetailsPresenter @Inject constructor(
} }
fun onMarkAsReadSelected(): Boolean { fun onMarkAsReadSelected(): Boolean {
Timber.i("Select mark grades as read")
disposable.add(studentRepository.getCurrentStudent() disposable.add(studentRepository.getCurrentStudent()
.flatMap { semesterRepository.getSemesters(it) } .flatMap { semesterRepository.getSemesters(it) }
.flatMap { gradeRepository.getNewGrades(it.first { item -> item.semesterId == currentSemesterId }) } .flatMap { gradeRepository.getNewGrades(it.first { item -> item.semesterId == currentSemesterId }) }
.map { it.map { grade -> grade.apply { isRead = true } } } .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) .subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread) .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 return true
} }
fun onSwipeRefresh() { fun onSwipeRefresh() {
Timber.i("Force refreshing the grade details")
view?.notifyParentRefresh() view?.notifyParentRefresh()
} }
@ -94,9 +106,11 @@ class GradeDetailsPresenter @Inject constructor(
} }
private fun loadData(semesterId: Int, forceRefresh: Boolean) { private fun loadData(semesterId: Int, forceRefresh: Boolean) {
Timber.i("Loading grade details data started")
disposable.add(studentRepository.getCurrentStudent() disposable.add(studentRepository.getCurrentStudent()
.flatMap { semesterRepository.getSemesters(it) } .flatMap { semesterRepository.getSemesters(it) }
.flatMap { gradeRepository.getGrades(it.first { item -> item.semesterId == semesterId }, forceRefresh) } .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 { it.map { item -> item.changeModifier(preferencesRepository.gradePlusModifier, preferencesRepository.gradeMinusModifier) } }
.map { createGradeItems(it.groupBy { grade -> grade.subject }.toSortedMap()) } .map { createGradeItems(it.groupBy { grade -> grade.subject }.toSortedMap()) }
.subscribeOn(schedulers.backgroundThread) .subscribeOn(schedulers.backgroundThread)
@ -109,6 +123,7 @@ class GradeDetailsPresenter @Inject constructor(
} }
} }
.subscribe({ .subscribe({
Timber.i("Loading grade details result: Success")
view?.run { view?.run {
showEmpty(it.isEmpty()) showEmpty(it.isEmpty())
showContent(it.isNotEmpty()) 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)) 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) } view?.run { showEmpty(isViewEmpty) }
errorHandler.dispatch(it) errorHandler.dispatch(it)
}) })
@ -151,10 +167,14 @@ class GradeDetailsPresenter @Inject constructor(
} }
private fun updateGrade(grade: Grade) { private fun updateGrade(grade: Grade) {
Timber.i("Attempt to update grade ${grade.id}")
disposable.add(gradeRepository.updateGrade(grade) disposable.add(gradeRepository.updateGrade(grade)
.subscribeOn(schedulers.backgroundThread) .subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread) .observeOn(schedulers.mainThread)
.subscribe({}) { error -> errorHandler.dispatch(error) }) .subscribe({ Timber.i("Update grade result: Success") })
Timber.d("Grade ${grade.id} updated") { 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() { override fun resetView() {
gradeSummaryAdapter.smoothScrollToPosition(0) gradeSummaryRecycler.scrollToPosition(0)
} }
override fun showContent(show: Boolean) { 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 android.view.View
import eu.davidea.flexibleadapter.FlexibleAdapter 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.flexibleadapter.items.IFlexible
import eu.davidea.viewholders.FlexibleViewHolder import eu.davidea.viewholders.FlexibleViewHolder
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.GradeSummary
import kotlinx.android.extensions.LayoutContainer import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.item_grade_summary.* import kotlinx.android.synthetic.main.item_grade_summary.*
class GradeSummaryItem(header: GradeSummaryHeader, private val grade: String, private val title: String) : class GradeSummaryItem(
AbstractSectionableItem<GradeSummaryItem.ViewHolder, GradeSummaryHeader>(header) { 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 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>? position: Int, payloads: MutableList<Any>?
) { ) {
holder?.run { holder?.run {
gradeSummaryItemGrade.text = grade
gradeSummaryItemTitle.text = title 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 other as GradeSummaryItem
if (grade != other.grade) return false if (average != other.average) return false
if (title != other.title) 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 return true
} }
override fun hashCode(): Int { override fun hashCode(): Int {
var result = header.hashCode() var result = title.hashCode()
result = 31 * result + grade.hashCode() result = 31 * result + average.hashCode()
result = 31 * result + title.hashCode() result = 31 * result + predictedGrade.hashCode()
result = 31 * result + finalGrade.hashCode()
return result 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.SchedulersProvider
import io.github.wulkanowy.utils.calcAverage import io.github.wulkanowy.utils.calcAverage
import io.github.wulkanowy.utils.changeModifier import io.github.wulkanowy.utils.changeModifier
import timber.log.Timber
import java.lang.String.format import java.lang.String.format
import java.util.Locale.FRANCE import java.util.Locale.FRANCE
import javax.inject.Inject import javax.inject.Inject
@ -33,6 +34,7 @@ class GradeSummaryPresenter @Inject constructor(
} }
fun onParentViewLoadData(semesterId: Int, forceRefresh: Boolean) { fun onParentViewLoadData(semesterId: Int, forceRefresh: Boolean) {
Timber.i("Loading grade summary data started")
disposable.add(studentRepository.getCurrentStudent() disposable.add(studentRepository.getCurrentStudent()
.flatMap { semesterRepository.getSemesters(it) } .flatMap { semesterRepository.getSemesters(it) }
.map { semester -> semester.first { it.semesterId == semesterId } } .map { semester -> semester.first { it.semesterId == semesterId } }
@ -64,6 +66,7 @@ class GradeSummaryPresenter @Inject constructor(
notifyParentDataLoaded(semesterId) notifyParentDataLoaded(semesterId)
} }
}.subscribe({ }.subscribe({
Timber.i("Loading grade summary result: Success")
view?.run { view?.run {
showEmpty(it.first.isEmpty()) showEmpty(it.first.isEmpty())
showContent(it.first.isNotEmpty()) 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)) 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) } view?.run { showEmpty(isViewEmpty) }
errorHandler.dispatch(it) errorHandler.dispatch(it)
}) })
} }
fun onSwipeRefresh() { fun onSwipeRefresh() {
Timber.i("Force refreshing the grade summary")
view?.notifyParentRefresh() view?.notifyParentRefresh()
} }
@ -99,23 +104,14 @@ class GradeSummaryPresenter @Inject constructor(
private fun createGradeSummaryItems(gradesSummary: List<GradeSummary>, averages: Map<String, Double>) private fun createGradeSummaryItems(gradesSummary: List<GradeSummary>, averages: Map<String, Double>)
: List<GradeSummaryItem> { : List<GradeSummaryItem> {
return gradesSummary.filter { !checkEmpty(it, averages) } return gradesSummary.filter { !checkEmpty(it, averages) }.map { it ->
.flatMap { gradeSummary -> GradeSummaryItem(
GradeSummaryHeader( title = it.subject,
name = gradeSummary.subject, average = formatAverage(averages.getOrElse(it.subject) { 0.0 }, ""),
average = formatAverage(averages.getOrElse(gradeSummary.subject) { 0.0 }, "") predictedGrade = it.predictedGrade,
).let { finalGrade = it.finalGrade
listOf(GradeSummaryItem( )
header = it, }
title = view?.predictedString.orEmpty(),
grade = gradeSummary.predictedGrade
), GradeSummaryItem(
header = it,
title = view?.finalString.orEmpty(),
grade = gradeSummary.finalGrade
))
}
}
} }
private fun checkEmpty(gradeSummary: GradeSummary, averages: Map<String, Double>): Boolean { 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.R
import io.github.wulkanowy.data.db.entities.Homework import io.github.wulkanowy.data.db.entities.Homework
import io.github.wulkanowy.ui.base.session.BaseSessionFragment 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.ui.modules.main.MainView
import io.github.wulkanowy.utils.setOnItemClickListener import io.github.wulkanowy.utils.setOnItemClickListener
import kotlinx.android.synthetic.main.fragment_homework.* import kotlinx.android.synthetic.main.fragment_homework.*
@ -95,7 +96,7 @@ class HomeworkFragment : BaseSessionFragment(), HomeworkView, MainView.TitledVie
} }
override fun showTimetableDialog(homework: Homework) { override fun showTimetableDialog(homework: Homework) {
HomeworkDialog.newInstance(homework).show(fragmentManager, homework.toString()) (activity as? MainActivity)?.showDialogFragment(HomeworkDialog.newInstance(homework))
} }
override fun onSaveInstanceState(outState: Bundle) { 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.previousSchoolDay
import io.github.wulkanowy.utils.toFormattedString import io.github.wulkanowy.utils.toFormattedString
import org.threeten.bp.LocalDate import org.threeten.bp.LocalDate
import timber.log.Timber
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
@ -32,6 +33,7 @@ class HomeworkPresenter @Inject constructor(
fun onAttachView(view: HomeworkView, date: Long?) { fun onAttachView(view: HomeworkView, date: Long?) {
super.onAttachView(view) super.onAttachView(view)
Timber.i("Homework view is attached")
view.initView() view.initView()
loadData(LocalDate.ofEpochDay(date ?: LocalDate.now().nextOrSameSchoolDay.toEpochDay())) loadData(LocalDate.ofEpochDay(date ?: LocalDate.now().nextOrSameSchoolDay.toEpochDay()))
reloadView() reloadView()
@ -48,14 +50,19 @@ class HomeworkPresenter @Inject constructor(
} }
fun onSwipeRefresh() { fun onSwipeRefresh() {
Timber.i("Force refreshing the homework")
loadData(currentDate, true) loadData(currentDate, true)
} }
fun onHomeworkItemSelected(item: AbstractFlexibleItem<*>?) { 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) { private fun loadData(date: LocalDate, forceRefresh: Boolean = false) {
Timber.i("Loading homework data started")
currentDate = date currentDate = date
disposable.apply { disposable.apply {
clear() clear()
@ -73,6 +80,7 @@ class HomeworkPresenter @Inject constructor(
} }
} }
.subscribe({ .subscribe({
Timber.i("Loading homework result: Success")
view?.apply { view?.apply {
updateData(it) updateData(it)
showEmpty(it.isEmpty()) 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"))) 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()) } view?.run { showEmpty(isViewEmpty()) }
errorHandler.dispatch(it) errorHandler.dispatch(it)
}) })
@ -87,6 +96,7 @@ class HomeworkPresenter @Inject constructor(
} }
private fun reloadView() { private fun reloadView() {
Timber.i("Reload homework view with the date ${currentDate.toFormattedString()}")
view?.apply { view?.apply {
showProgress(true) showProgress(true)
showContent(false) showContent(false)

View File

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

View File

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

View File

@ -5,7 +5,7 @@ import dagger.Provides
import dagger.android.ContributesAndroidInjector import dagger.android.ContributesAndroidInjector
import io.github.wulkanowy.di.scopes.PerActivity import io.github.wulkanowy.di.scopes.PerActivity
import io.github.wulkanowy.di.scopes.PerFragment 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.form.LoginFormFragment
import io.github.wulkanowy.ui.modules.login.options.LoginOptionsFragment import io.github.wulkanowy.ui.modules.login.options.LoginOptionsFragment
@ -18,7 +18,7 @@ internal abstract class LoginModule {
@JvmStatic @JvmStatic
@PerActivity @PerActivity
@Provides @Provides
fun provideLoginAdapter(activity: LoginActivity) = BasePagerAdapter(activity.supportFragmentManager) fun provideLoginAdapter(activity: LoginActivity) = BaseFragmentPagerAdapter(activity.supportFragmentManager)
} }
@PerFragment @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.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.base.ErrorHandler
import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
class LoginPresenter @Inject constructor(errorHandler: ErrorHandler) : BasePresenter<LoginView>(errorHandler) { class LoginPresenter @Inject constructor(errorHandler: ErrorHandler) : BasePresenter<LoginView>(errorHandler) {
@ -12,6 +13,7 @@ class LoginPresenter @Inject constructor(errorHandler: ErrorHandler) : BasePrese
initAdapter() initAdapter()
hideActionBar() hideActionBar()
} }
Timber.i("Login view is attached")
} }
fun onPageSelected(index: Int) { fun onPageSelected(index: Int) {
@ -23,6 +25,7 @@ class LoginPresenter @Inject constructor(errorHandler: ErrorHandler) : BasePrese
} }
fun onBackPressed(default: () -> Unit) { fun onBackPressed(default: () -> Unit) {
Timber.i("Back pressed in login view")
view?.run { view?.run {
if (currentViewIndex == 1) { if (currentViewIndex == 1) {
switchView(0) switchView(0)

View File

@ -45,6 +45,7 @@ class LoginFormPresenter @Inject constructor(
showProgress(true) showProgress(true)
showContent(false) showContent(false)
} }
Timber.i("Login started")
} }
.doFinally { .doFinally {
view?.apply { view?.apply {
@ -58,17 +59,21 @@ class LoginFormPresenter @Inject constructor(
showSymbolInput() showSymbolInput()
wasEmpty = true wasEmpty = true
analytics.logEvent("sign_up_send", mapOf(SUCCESS to false, "students" to 0, "endpoint" to endpoint, GROUP_ID to symbol.ifEmpty { "null" })) 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) { } else if (it.isEmpty() && wasEmpty) {
showSymbolInput() showSymbolInput()
setErrorSymbolIncorrect() 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 { } else {
analytics.logEvent("sign_up_send", mapOf(SUCCESS to true, "students" to it.size, "endpoint" to endpoint, GROUP_ID to symbol)) 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() 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) 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.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.SchedulersProvider import io.github.wulkanowy.utils.SchedulersProvider
import io.reactivex.Single import io.reactivex.Single
import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
class LoginOptionsPresenter @Inject constructor( class LoginOptionsPresenter @Inject constructor(
@ -26,7 +27,10 @@ class LoginOptionsPresenter @Inject constructor(
super.onAttachView(view) super.onAttachView(view)
view.run { view.run {
initView() 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) showContent(false)
showActionBar(false) showActionBar(false)
} }
Timber.i("Registration started")
} }
.subscribe({ .subscribe({
analytics.logEvent(SIGN_UP, mapOf(SUCCESS to true, "endpoint" to student.endpoint, "message" to "Success", GROUP_ID to student.symbol)) 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() view?.openMainView()
}, { }, {
Timber.i("Registration result: An exception occurred ")
errorHandler.dispatch(it) errorHandler.dispatch(it)
view?.apply { view?.apply {
showProgress(false) showProgress(false)

View File

@ -7,6 +7,7 @@ import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import com.aurelhubert.ahbottomnavigation.AHBottomNavigation.TitleState.ALWAYS_SHOW import com.aurelhubert.ahbottomnavigation.AHBottomNavigation.TitleState.ALWAYS_SHOW
import com.aurelhubert.ahbottomnavigation.AHBottomNavigationItem import com.aurelhubert.ahbottomnavigation.AHBottomNavigationItem
@ -70,7 +71,7 @@ class MainActivity : BaseActivity(), MainView {
override fun onStart() { override fun onStart() {
super.onStart() super.onStart()
presenter.onViewStart() presenter.onViewChange()
} }
override fun initView() { override fun initView() {
@ -97,7 +98,7 @@ class MainActivity : BaseActivity(), MainView {
} }
navController.run { navController.run {
setOnViewChangeListener { presenter.onViewStart() } setOnViewChangeListener { presenter.onViewChange() }
fragmentHideStrategy = HIDE fragmentHideStrategy = HIDE
rootFragments = listOf( rootFragments = listOf(
GradeFragment.newInstance(), GradeFragment.newInstance(),
@ -147,6 +148,10 @@ class MainActivity : BaseActivity(), MainView {
(navController.currentStack?.get(0) as? MainView.MainChildView)?.onFragmentReselected() (navController.currentStack?.get(0) as? MainView.MainChildView)?.onFragmentReselected()
} }
fun showDialogFragment(dialog: DialogFragment) {
navController.showDialogFragment(dialog)
}
fun pushView(fragment: Fragment) { fun pushView(fragment: Fragment) {
navController.pushFragment(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.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.SchedulersProvider import io.github.wulkanowy.utils.SchedulersProvider
import io.reactivex.Completable import io.reactivex.Completable
import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
class MainPresenter @Inject constructor( class MainPresenter @Inject constructor(
@ -23,6 +24,7 @@ class MainPresenter @Inject constructor(
fun onAttachView(view: MainView, initMenuIndex: Int) { fun onAttachView(view: MainView, initMenuIndex: Int) {
super.onAttachView(view) super.onAttachView(view)
Timber.i("Main view is attached with $initMenuIndex menu index")
view.run { view.run {
cancelNotifications() cancelNotifications()
startMenuIndex = if (initMenuIndex != -1) initMenuIndex else prefRepository.startMenuIndex startMenuIndex = if (initMenuIndex != -1) initMenuIndex else prefRepository.startMenuIndex
@ -38,7 +40,7 @@ class MainPresenter @Inject constructor(
})) }))
} }
fun onViewStart() { fun onViewChange() {
view?.apply { view?.apply {
currentViewTitle?.let { setViewTitle(it) } currentViewTitle?.let { setViewTitle(it) }
currentStackSize?.let { currentStackSize?.let {
@ -49,16 +51,19 @@ class MainPresenter @Inject constructor(
} }
fun onAccountManagerSelected(): Boolean { fun onAccountManagerSelected(): Boolean {
Timber.i("Select account manager")
view?.showAccountPicker() view?.showAccountPicker()
return true return true
} }
fun onUpNavigate(): Boolean { fun onUpNavigate(): Boolean {
Timber.i("Up navigate pressed")
view?.popView() view?.popView()
return true return true
} }
fun onBackPressed(default: () -> Unit) { fun onBackPressed(default: () -> Unit) {
Timber.i("Back pressed in main view")
view?.run { view?.run {
if (isRootView) default() if (isRootView) default()
else popView() else popView()
@ -67,6 +72,7 @@ class MainPresenter @Inject constructor(
fun onTabSelected(index: Int, wasSelected: Boolean): Boolean { fun onTabSelected(index: Int, wasSelected: Boolean): Boolean {
return view?.run { return view?.run {
Timber.i("Switch main tab index: $index, reselected: $wasSelected")
if (wasSelected) { if (wasSelected) {
notifyMenuViewReselected() notifyMenuViewReselected()
false false
@ -78,15 +84,25 @@ class MainPresenter @Inject constructor(
} }
fun onLoginSelected() { fun onLoginSelected() {
Timber.i("Attempt to switch the student after the session expires")
disposable.add(studentRepository.getCurrentStudent(false) disposable.add(studentRepository.getCurrentStudent(false)
.flatMapCompletable { studentRepository.logoutStudent(it) } .flatMapCompletable { studentRepository.logoutStudent(it) }
.andThen(studentRepository.getSavedStudents(false)) .andThen(studentRepository.getSavedStudents(false))
.flatMapCompletable { .flatMapCompletable {
if (it.isNotEmpty()) studentRepository.switchStudent(it[0]) if (it.isNotEmpty()) {
Timber.i("Switching current student")
studentRepository.switchStudent(it[0])
}
else Completable.complete() else Completable.complete()
} }
.subscribeOn(schedulers.backgroundThread) .subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread) .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.SENT
import io.github.wulkanowy.data.repositories.MessagesRepository.MessageFolder.TRASHED import io.github.wulkanowy.data.repositories.MessagesRepository.MessageFolder.TRASHED
import io.github.wulkanowy.ui.base.BaseFragment 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.main.MainView
import io.github.wulkanowy.ui.modules.message.tab.MessageTabFragment import io.github.wulkanowy.ui.modules.message.tab.MessageTabFragment
import io.github.wulkanowy.utils.setOnSelectPageListener import io.github.wulkanowy.utils.setOnSelectPageListener
@ -24,7 +24,7 @@ class MessageFragment : BaseFragment(), MessageView, MainView.TitledView {
lateinit var presenter: MessagePresenter lateinit var presenter: MessagePresenter
@Inject @Inject
lateinit var pagerAdapter: BasePagerAdapter lateinit var pagerAdapter: BaseFragmentPagerAdapter
companion object { companion object {
fun newInstance() = MessageFragment() fun newInstance() = MessageFragment()
@ -46,11 +46,12 @@ class MessageFragment : BaseFragment(), MessageView, MainView.TitledView {
} }
override fun initView() { override fun initView() {
pagerAdapter.fragments.putAll(mapOf( pagerAdapter.addFragmentsWithTitle(mapOf(
getString(R.string.message_inbox) to MessageTabFragment.newInstance(RECEIVED), MessageTabFragment.newInstance(RECEIVED) to getString(R.string.message_inbox),
getString(R.string.message_sent) to MessageTabFragment.newInstance(SENT), MessageTabFragment.newInstance(SENT) to getString(R.string.message_sent),
getString(R.string.message_trash) to MessageTabFragment.newInstance(TRASHED) MessageTabFragment.newInstance(TRASHED) to getString(R.string.message_trash)
)) ))
messageViewPager.run { messageViewPager.run {
adapter = pagerAdapter adapter = pagerAdapter
offscreenPageLimit = 2 offscreenPageLimit = 2
@ -73,7 +74,7 @@ class MessageFragment : BaseFragment(), MessageView, MainView.TitledView {
} }
override fun notifyChildLoadData(index: Int, forceRefresh: Boolean) { 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() { override fun onDestroyView() {

View File

@ -5,7 +5,7 @@ import dagger.Provides
import dagger.android.ContributesAndroidInjector import dagger.android.ContributesAndroidInjector
import io.github.wulkanowy.di.scopes.PerChildFragment import io.github.wulkanowy.di.scopes.PerChildFragment
import io.github.wulkanowy.di.scopes.PerFragment 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 import io.github.wulkanowy.ui.modules.message.tab.MessageTabFragment
@Module @Module
@ -17,7 +17,7 @@ abstract class MessageModule {
@JvmStatic @JvmStatic
@PerFragment @PerFragment
@Provides @Provides
fun provideGradePagerAdapter(fragment: MessageFragment) = BasePagerAdapter(fragment.childFragmentManager) fun provideMessageAdapter(fragment: MessageFragment) = BaseFragmentPagerAdapter(fragment.childFragmentManager)
} }
@PerChildFragment @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.ui.base.ErrorHandler
import io.github.wulkanowy.utils.SchedulersProvider import io.github.wulkanowy.utils.SchedulersProvider
import io.reactivex.Completable import io.reactivex.Completable
import timber.log.Timber
import java.util.concurrent.TimeUnit.MILLISECONDS import java.util.concurrent.TimeUnit.MILLISECONDS
import javax.inject.Inject import javax.inject.Inject
@ -14,6 +15,7 @@ class MessagePresenter @Inject constructor(
override fun onAttachView(view: MessageView) { override fun onAttachView(view: MessageView) {
super.onAttachView(view) super.onAttachView(view)
Timber.i("Message view is attached")
disposable.add(Completable.timer(150, MILLISECONDS, schedulers.mainThread) disposable.add(Completable.timer(150, MILLISECONDS, schedulers.mainThread)
.subscribe { .subscribe {
view.initView() view.initView()
@ -30,6 +32,7 @@ class MessagePresenter @Inject constructor(
} }
private fun loadChild(index: Int, forceRefresh: Boolean = false) { private fun loadChild(index: Int, forceRefresh: Boolean = false) {
Timber.i("Load message child view index: $index")
view?.notifyChildLoadData(index, forceRefresh) 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.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.SchedulersProvider import io.github.wulkanowy.utils.SchedulersProvider
import io.github.wulkanowy.utils.toFormattedString import io.github.wulkanowy.utils.toFormattedString
import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
class MessagePreviewPresenter @Inject constructor( class MessagePreviewPresenter @Inject constructor(
@ -26,15 +27,17 @@ class MessagePreviewPresenter @Inject constructor(
} }
private fun loadData(id: Int) { private fun loadData(id: Int) {
Timber.i("Loading message $id preview started")
messageId = id messageId = id
disposable.apply { disposable.apply {
clear() clear()
add(studentRepository.getCurrentStudent() add(studentRepository.getCurrentStudent()
.flatMap { messagesRepository.getMessage(it.studentId, messageId, true) } .flatMap { messagesRepository.getMessage(it, messageId, true) }
.subscribeOn(schedulers.backgroundThread) .subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread) .observeOn(schedulers.mainThread)
.doFinally { view?.showProgress(false) } .doFinally { view?.showProgress(false) }
.subscribe({ message -> .subscribe({ message ->
Timber.i("Loading message $id preview result: Success ")
view?.run { view?.run {
message.let { message.let {
setSubject(if (it.subject.isNotBlank()) it.subject else noSubjectString) setSubject(if (it.subject.isNotBlank()) it.subject else noSubjectString)
@ -45,8 +48,9 @@ class MessagePreviewPresenter @Inject constructor(
else setSender(it.sender) 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() view?.showMessageError()
errorHandler.dispatch(it) errorHandler.dispatch(it)
}) })

View File

@ -29,14 +29,16 @@ class MessageTabPresenter @Inject constructor(
} }
fun onSwipeRefresh() { fun onSwipeRefresh() {
Timber.i("Force refreshing the $folder message")
onParentViewLoadData(true) onParentViewLoadData(true)
} }
fun onParentViewLoadData(forceRefresh: Boolean) { fun onParentViewLoadData(forceRefresh: Boolean) {
Timber.i("Loading $folder message data started")
disposable.apply { disposable.apply {
clear() clear()
add(studentRepository.getCurrentStudent() 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()) } } .map { items -> items.map { MessageItem(it, view?.noSubjectString.orEmpty()) } }
.subscribeOn(schedulers.backgroundThread) .subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread) .observeOn(schedulers.mainThread)
@ -48,6 +50,7 @@ class MessageTabPresenter @Inject constructor(
} }
} }
.subscribe({ .subscribe({
Timber.i("Loading $folder message result: Success")
view?.run { view?.run {
showEmpty(it.isEmpty()) showEmpty(it.isEmpty())
showContent(it.isNotEmpty()) showContent(it.isNotEmpty())
@ -55,6 +58,7 @@ class MessageTabPresenter @Inject constructor(
} }
analytics.logEvent("load_messages", mapOf("items" to it.size, "folder" to folder.name)) 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) } view?.run { showEmpty(isViewEmpty) }
errorHandler.dispatch(it) errorHandler.dispatch(it)
}) })
@ -63,6 +67,7 @@ class MessageTabPresenter @Inject constructor(
fun onMessageItemSelected(item: AbstractFlexibleItem<*>) { fun onMessageItemSelected(item: AbstractFlexibleItem<*>) {
if (item is MessageItem) { if (item is MessageItem) {
Timber.i("Select message ${item.message.realId} item")
view?.run { view?.run {
openMessage(item.message.realId) openMessage(item.message.realId)
if (item.message.unread == true) { if (item.message.unread == true) {
@ -75,12 +80,14 @@ class MessageTabPresenter @Inject constructor(
} }
private fun updateMessage(message: Message) { private fun updateMessage(message: Message) {
Timber.i("Attempt to update message ${message.realId}")
disposable.add(messagesRepository.updateMessage(message) disposable.add(messagesRepository.updateMessage(message)
.subscribeOn(schedulers.backgroundThread) .subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread) .observeOn(schedulers.mainThread)
.subscribe({ .subscribe({ Timber.d("Update message ${message.realId} result: Success") })
Timber.d("Message ${message.realId} updated") { error ->
}) { error -> errorHandler.dispatch(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 eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.base.ErrorHandler
import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
class MorePresenter @Inject constructor(errorHandler: ErrorHandler) : BasePresenter<MoreView>(errorHandler) { class MorePresenter @Inject constructor(errorHandler: ErrorHandler) : BasePresenter<MoreView>(errorHandler) {
override fun onAttachView(view: MoreView) { override fun onAttachView(view: MoreView) {
super.onAttachView(view) super.onAttachView(view)
Timber.i("More view is attached")
view.initView() view.initView()
loadData() loadData()
} }
fun onItemSelected(item: AbstractFlexibleItem<*>?) { fun onItemSelected(item: AbstractFlexibleItem<*>?) {
if (item is MoreItem) { if (item is MoreItem) {
Timber.i("Select more item \"${item.title}\"")
view?.run { view?.run {
when (item.title) { when (item.title) {
messagesRes?.first -> openMessagesView() messagesRes?.first -> openMessagesView()
@ -28,10 +31,12 @@ class MorePresenter @Inject constructor(errorHandler: ErrorHandler) : BasePresen
} }
fun onViewReselected() { fun onViewReselected() {
Timber.i("More view is reselected")
view?.popView() view?.popView()
} }
private fun loadData() { private fun loadData() {
Timber.i("Load items for more view")
view?.run { view?.run {
updateData(listOfNotNull( updateData(listOfNotNull(
messagesRes?.let { MoreItem(it.first, it.second) }, 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.R
import io.github.wulkanowy.data.db.entities.Note import io.github.wulkanowy.data.db.entities.Note
import io.github.wulkanowy.ui.base.session.BaseSessionFragment 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.ui.modules.main.MainView
import io.github.wulkanowy.utils.setOnItemClickListener import io.github.wulkanowy.utils.setOnItemClickListener
import kotlinx.android.synthetic.main.fragment_note.* import kotlinx.android.synthetic.main.fragment_note.*
@ -57,7 +58,7 @@ class NoteFragment : BaseSessionFragment(), NoteView, MainView.TitledView {
} }
override fun showNoteDialog(note: Note) { 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>) { override fun updateData(data: List<NoteItem>) {

View File

@ -46,11 +46,14 @@ class NoteItem(val note: Note) : AbstractFlexibleItem<NoteItem.ViewHolder>() {
other as NoteItem other as NoteItem
if (note != other.note) return false if (note != other.note) return false
if (note.id != other.note.id) return false
return true return true
} }
override fun hashCode(): Int { 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 { 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) { override fun onAttachView(view: NoteView) {
super.onAttachView(view) super.onAttachView(view)
Timber.i("Note view is attached")
view.initView() view.initView()
loadData() loadData()
} }
fun onSwipeRefresh() { fun onSwipeRefresh() {
Timber.i("Force refreshing the note")
loadData(true) loadData(true)
} }
private fun loadData(forceRefresh: Boolean = false) { private fun loadData(forceRefresh: Boolean = false) {
Timber.i("Loading note data started")
disposable.add(studentRepository.getCurrentStudent() disposable.add(studentRepository.getCurrentStudent()
.flatMap { semesterRepository.getCurrentSemester(it) } .flatMap { semesterRepository.getCurrentSemester(it) }
.flatMap { noteRepository.getNotes(it, forceRefresh) } .flatMap { noteRepository.getNotes(it, forceRefresh) }
@ -45,6 +48,7 @@ class NotePresenter @Inject constructor(
showProgress(false) showProgress(false)
} }
}.subscribe({ }.subscribe({
Timber.i("Loading note result: Success")
view?.apply { view?.apply {
updateData(it) updateData(it)
showEmpty(it.isEmpty()) showEmpty(it.isEmpty())
@ -52,6 +56,7 @@ class NotePresenter @Inject constructor(
} }
analytics.logEvent("load_note", mapOf("items" to it.size, "force_refresh" to forceRefresh)) 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) } view?.run { showEmpty(isViewEmpty) }
errorHandler.dispatch(it) errorHandler.dispatch(it)
}) })
@ -60,6 +65,7 @@ class NotePresenter @Inject constructor(
fun onNoteItemSelected(item: AbstractFlexibleItem<*>?) { fun onNoteItemSelected(item: AbstractFlexibleItem<*>?) {
if (item is NoteItem) { if (item is NoteItem) {
Timber.i("Select note item ${item.note.id}")
view?.run { view?.run {
showNoteDialog(item.note) showNoteDialog(item.note)
if (!item.note.isRead) { if (!item.note.isRead) {
@ -72,12 +78,14 @@ class NotePresenter @Inject constructor(
} }
private fun updateNote(note: Note) { private fun updateNote(note: Note) {
Timber.i("Attempt to update note ${note.id}")
disposable.add(noteRepository.updateNote(note) disposable.add(noteRepository.updateNote(note)
.subscribeOn(schedulers.backgroundThread) .subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread) .observeOn(schedulers.mainThread)
.subscribe({ .subscribe({ Timber.i("Update note result: Success") })
Timber.d("Note ${note.id} updated") { error ->
}) { error -> errorHandler.dispatch(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 androidx.appcompat.app.AppCompatDelegate
import com.takisoft.preferencex.PreferenceFragmentCompat import com.takisoft.preferencex.PreferenceFragmentCompat
import dagger.android.support.AndroidSupportInjection import dagger.android.support.AndroidSupportInjection
import io.github.wulkanowy.BuildConfig
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.ui.base.BaseActivity import io.github.wulkanowy.ui.base.BaseActivity
import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.ui.modules.main.MainView
@ -36,6 +37,7 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP
override fun onCreatePreferencesFix(savedInstanceState: Bundle?, rootKey: String?) { override fun onCreatePreferencesFix(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.scheme_preferences) addPreferencesFromResource(R.xml.scheme_preferences)
findPreference(getString(R.string.pref_key_debug_chucker_notification)).isVisible = BuildConfig.DEBUG
} }
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) { override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {

View File

@ -1,5 +1,6 @@
package io.github.wulkanowy.ui.modules.settings package io.github.wulkanowy.ui.modules.settings
import com.readystatesoftware.chuck.api.ChuckCollector
import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.services.job.ServiceHelper import io.github.wulkanowy.services.job.ServiceHelper
import io.github.wulkanowy.ui.base.BasePresenter 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.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.isHolidays import io.github.wulkanowy.utils.isHolidays
import org.threeten.bp.LocalDate.now import org.threeten.bp.LocalDate.now
import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
class SettingsPresenter @Inject constructor( class SettingsPresenter @Inject constructor(
errorHandler: ErrorHandler, errorHandler: ErrorHandler,
private val preferencesRepository: PreferencesRepository, private val preferencesRepository: PreferencesRepository,
private val serviceHelper: ServiceHelper, private val serviceHelper: ServiceHelper,
private val analytics: FirebaseAnalyticsHelper private val analytics: FirebaseAnalyticsHelper,
private val chuckCollector: ChuckCollector
) : BasePresenter<SettingsView>(errorHandler) { ) : BasePresenter<SettingsView>(errorHandler) {
override fun onAttachView(view: SettingsView) { override fun onAttachView(view: SettingsView) {
super.onAttachView(view) super.onAttachView(view)
Timber.i("Settings view is attached")
view.run { view.run {
setServicesSuspended(preferencesRepository.serviceEnablesKey, now().isHolidays) setServicesSuspended(preferencesRepository.serviceEnablesKey, now().isHolidays)
@ -25,6 +29,7 @@ class SettingsPresenter @Inject constructor(
} }
fun onSharedPreferenceChanged(key: String) { fun onSharedPreferenceChanged(key: String) {
Timber.i("Change settings $key")
when (key) { when (key) {
preferencesRepository.serviceEnablesKey -> { preferencesRepository.serviceEnablesKey -> {
if (preferencesRepository.isServiceEnabled) serviceHelper.startFullSyncService() if (preferencesRepository.isServiceEnabled) serviceHelper.startFullSyncService()
@ -37,8 +42,10 @@ class SettingsPresenter @Inject constructor(
preferencesRepository.currentThemeKey -> { preferencesRepository.currentThemeKey -> {
view?.setTheme(preferencesRepository.currentTheme) view?.setTheme(preferencesRepository.currentTheme)
} }
preferencesRepository.isShowChuckerNotificationKey -> {
chuckCollector.showNotification(preferencesRepository.isShowChuckerNotification)
}
} }
analytics.logEvent("setting_changed", mapOf("name" to key)) 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.R
import io.github.wulkanowy.data.db.entities.Timetable import io.github.wulkanowy.data.db.entities.Timetable
import io.github.wulkanowy.ui.base.session.BaseSessionFragment 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.ui.modules.main.MainView
import io.github.wulkanowy.utils.setOnItemClickListener import io.github.wulkanowy.utils.setOnItemClickListener
import kotlinx.android.synthetic.main.fragment_timetable.* import kotlinx.android.synthetic.main.fragment_timetable.*
@ -107,7 +108,7 @@ class TimetableFragment : BaseSessionFragment(), TimetableView, MainView.MainChi
} }
override fun showTimetableDialog(lesson: Timetable) { override fun showTimetableDialog(lesson: Timetable) {
TimetableDialog.newInstance(lesson).show(fragmentManager, lesson.toString()) (activity as? MainActivity)?.showDialogFragment(TimetableDialog.newInstance(lesson))
} }
override fun onSaveInstanceState(outState: Bundle) { 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
import org.threeten.bp.LocalDate.now import org.threeten.bp.LocalDate.now
import org.threeten.bp.LocalDate.ofEpochDay import org.threeten.bp.LocalDate.ofEpochDay
import timber.log.Timber
import java.util.concurrent.TimeUnit.MILLISECONDS import java.util.concurrent.TimeUnit.MILLISECONDS
import javax.inject.Inject import javax.inject.Inject
@ -34,6 +35,7 @@ class TimetablePresenter @Inject constructor(
fun onAttachView(view: TimetableView, date: Long?) { fun onAttachView(view: TimetableView, date: Long?) {
super.onAttachView(view) super.onAttachView(view)
Timber.i("Timetable is attached")
view.initView() view.initView()
loadData(ofEpochDay(date ?: now().nextOrSameSchoolDay.toEpochDay())) loadData(ofEpochDay(date ?: now().nextOrSameSchoolDay.toEpochDay()))
reloadView() reloadView()
@ -50,10 +52,12 @@ class TimetablePresenter @Inject constructor(
} }
fun onSwipeRefresh() { fun onSwipeRefresh() {
Timber.i("Force refreshing the timetable")
loadData(currentDate, true) loadData(currentDate, true)
} }
fun onViewReselected() { fun onViewReselected() {
Timber.i("Exam view is reselected")
now().nextOrSameSchoolDay.also { now().nextOrSameSchoolDay.also {
if (currentDate != it) { if (currentDate != it) {
loadData(it) loadData(it)
@ -63,10 +67,14 @@ class TimetablePresenter @Inject constructor(
} }
fun onTimetableItemSelected(item: AbstractFlexibleItem<*>?) { 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) { private fun loadData(date: LocalDate, forceRefresh: Boolean = false) {
Timber.i("Loading timetable data started")
currentDate = date currentDate = date
disposable.apply { disposable.apply {
clear() clear()
@ -85,13 +93,15 @@ class TimetablePresenter @Inject constructor(
} }
} }
.subscribe({ .subscribe({
Timber.i("Loading timetable result: Success")
view?.apply { view?.apply {
updateData(it) updateData(it)
showEmpty(it.isEmpty()) showEmpty(it.isEmpty())
showContent(it.isNotEmpty()) 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) } view?.run { showEmpty(isViewEmpty) }
errorHandler.dispatch(it) errorHandler.dispatch(it)
}) })
@ -99,6 +109,7 @@ class TimetablePresenter @Inject constructor(
} }
private fun reloadView() { private fun reloadView() {
Timber.i("Reload timetable view with the date ${currentDate.toFormattedString()}")
view?.apply { view?.apply {
showProgress(true) showProgress(true)
showContent(false) 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" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:viewportWidth="24.0"> android:viewportHeight="24.0">
<path <path
android:fillColor="#FFFFFFFF" 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> </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:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent"
tools:context=".ui.modules.attendance.summary.AttendanceSummaryFragment">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical"> android:orientation="vertical">
<androidx.appcompat.widget.AppCompatSpinner <LinearLayout
android:id="@+id/attendanceSummarySubjects" android:id="@+id/attendanceSummarySubjectsContainer"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:visibility="invisible"
android:background="?android:attr/windowBackground" android:background="?android:attr/windowBackground"
android:elevation="5dp" android:elevation="5dp"
android:padding="15dp" android:padding="5dp"
android:spinnerMode="dialog" /> 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 <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/attendanceSummarySwipe" android:id="@+id/attendanceSummarySwipe"
@ -27,7 +45,8 @@
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/attendanceSummaryRecycler" android:id="@+id/attendanceSummaryRecycler"
android:layout_width="match_parent" 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> </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</LinearLayout> </LinearLayout>

View File

@ -69,7 +69,7 @@
android:layout_marginBottom="15dp" android:layout_marginBottom="15dp"
android:hint="@string/login_nickname_hint"> android:hint="@string/login_nickname_hint">
<com.google.android.material.textfield.TextInputEditText <androidx.appcompat.widget.AppCompatEditText
android:id="@+id/loginNicknameEdit" android:id="@+id/loginNicknameEdit"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -84,7 +84,7 @@
android:layout_marginBottom="15dp" android:layout_marginBottom="15dp"
android:hint="@string/login_password_hint"> android:hint="@string/login_password_hint">
<com.google.android.material.textfield.TextInputEditText <androidx.appcompat.widget.AppCompatEditText
android:id="@+id/loginPassEdit" android:id="@+id/loginPassEdit"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" 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"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:orientation="vertical">
@ -21,16 +22,16 @@
android:layout_marginEnd="40dp" android:layout_marginEnd="40dp"
android:layout_marginRight="40dp" android:layout_marginRight="40dp"
android:layout_weight="1" android:layout_weight="1"
android:text="@string/app_name" android:textSize="17sp"
android:textSize="17sp" /> tools:text="January" />
<TextView <TextView
android:id="@+id/attendanceSummaryPercentage" android:id="@+id/attendanceSummaryPercentage"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="end" android:gravity="end"
android:text="@string/app_name" android:textSize="12sp"
android:textSize="12sp" /> tools:text="75%" />
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout
@ -60,8 +61,8 @@
android:layout_marginEnd="25dp" android:layout_marginEnd="25dp"
android:layout_marginRight="25dp" android:layout_marginRight="25dp"
android:gravity="end" android:gravity="end"
android:text="@string/app_name" android:textSize="12sp"
android:textSize="12sp" /> tools:text="50" />
</LinearLayout> </LinearLayout>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
@ -91,8 +92,8 @@
android:layout_marginEnd="25dp" android:layout_marginEnd="25dp"
android:layout_marginRight="25dp" android:layout_marginRight="25dp"
android:gravity="end" android:gravity="end"
android:text="@string/app_name" android:textSize="12sp"
android:textSize="12sp" /> tools:text="0" />
</LinearLayout> </LinearLayout>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
@ -122,8 +123,8 @@
android:layout_marginEnd="25dp" android:layout_marginEnd="25dp"
android:layout_marginRight="25dp" android:layout_marginRight="25dp"
android:gravity="end" android:gravity="end"
android:text="@string/app_name" android:textSize="12sp"
android:textSize="12sp" /> tools:text="25" />
</LinearLayout> </LinearLayout>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
@ -153,8 +154,8 @@
android:layout_marginEnd="25dp" android:layout_marginEnd="25dp"
android:layout_marginRight="25dp" android:layout_marginRight="25dp"
android:gravity="end" android:gravity="end"
android:text="@string/app_name" android:textSize="12sp"
android:textSize="12sp" /> tools:text="0" />
</LinearLayout> </LinearLayout>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
@ -184,8 +185,8 @@
android:layout_marginEnd="25dp" android:layout_marginEnd="25dp"
android:layout_marginRight="25dp" android:layout_marginRight="25dp"
android:gravity="end" android:gravity="end"
android:text="@string/app_name" android:textSize="12sp"
android:textSize="12sp" /> tools:text="6" />
</LinearLayout> </LinearLayout>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
@ -215,8 +216,8 @@
android:layout_marginEnd="25dp" android:layout_marginEnd="25dp"
android:layout_marginRight="25dp" android:layout_marginRight="25dp"
android:gravity="end" android:gravity="end"
android:text="@string/app_name" android:textSize="12sp"
android:textSize="12sp" /> tools:text="0" />
</LinearLayout> </LinearLayout>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
@ -246,7 +247,7 @@
android:layout_marginEnd="25dp" android:layout_marginEnd="25dp"
android:layout_marginRight="25dp" android:layout_marginRight="25dp"
android:gravity="end" android:gravity="end"
android:text="@string/app_name" android:textSize="12sp"
android:textSize="12sp" /> tools:text="0" />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>

View File

@ -10,5 +10,6 @@
android:paddingRight="16dp" android:paddingRight="16dp"
android:textAlignment="textStart" android:textAlignment="textStart"
android:paddingBottom="12dp" android:paddingBottom="12dp"
android:ellipsize="end"
android:text="@string/app_name" android:text="@string/app_name"
android:textSize="16sp" /> 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" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="@drawable/ic_all_divider" android:orientation="vertical">
android:minHeight="35dp"
android:orientation="horizontal">
<TextView <LinearLayout
android:id="@+id/gradeSummaryItemTitle" android:layout_width="match_parent"
android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_vertical" android:background="?colorControlHighlight"
android:layout_marginStart="20dp" android:orientation="horizontal"
android:layout_marginLeft="20dp" android:paddingLeft="20dp"
android:layout_weight="1" android:paddingTop="7dp"
android:text="@string/grade_summary_predicted_grade" android:paddingRight="20dp"
android:textSize="14sp" /> android:paddingBottom="7dp">
<TextView <TextView
android:id="@+id/gradeSummaryItemGrade" android:id="@+id/gradeSummaryItemTitle"
android:layout_width="wrap_content" 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_height="wrap_content"
android:layout_gravity="center_vertical" android:background="@drawable/ic_all_divider"
android:layout_marginStart="10dp" android:minHeight="35dp"
android:layout_marginLeft="10dp" android:orientation="horizontal">
android:layout_marginEnd="25dp"
android:layout_marginRight="25dp" <TextView
android:gravity="end" android:layout_width="0dp"
android:text="@string/app_name" android:layout_height="wrap_content"
android:textSize="12sp" /> 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> </LinearLayout>

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