Merge branch 'release/0.10.0'

This commit is contained in:
Mikołaj Pich 2019-09-04 22:59:26 +02:00
commit 750fa9a76d
274 changed files with 2784 additions and 1996 deletions

View File

@ -1,9 +1,6 @@
<component name="ProjectCodeStyleConfiguration"> <component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173"> <code_scheme name="Project" version="173">
<option name="LINE_SEPARATOR" value="&#10;" /> <option name="LINE_SEPARATOR" value="&#10;" />
<AndroidXmlCodeStyleSettings>
<option name="USE_CUSTOM_SETTINGS" value="true" />
</AndroidXmlCodeStyleSettings>
<JetCodeStyleSettings> <JetCodeStyleSettings>
<option name="PACKAGES_TO_USE_STAR_IMPORTS"> <option name="PACKAGES_TO_USE_STAR_IMPORTS">
<value> <value>
@ -24,13 +21,7 @@
<MarkdownNavigatorCodeStyleSettings> <MarkdownNavigatorCodeStyleSettings>
<option name="RIGHT_MARGIN" value="72" /> <option name="RIGHT_MARGIN" value="72" />
</MarkdownNavigatorCodeStyleSettings> </MarkdownNavigatorCodeStyleSettings>
<XML>
<option name="XML_KEEP_LINE_BREAKS" value="false" />
<option name="XML_ALIGN_ATTRIBUTES" value="false" />
<option name="XML_SPACE_INSIDE_EMPTY_TAG" value="true" />
</XML>
<codeStyleSettings language="XML"> <codeStyleSettings language="XML">
<option name="FORCE_REARRANGE_MODE" value="1" />
<indentOptions> <indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" /> <option name="CONTINUATION_INDENT_SIZE" value="4" />
</indentOptions> </indentOptions>
@ -41,6 +32,7 @@
<match> <match>
<AND> <AND>
<NAME>xmlns:android</NAME> <NAME>xmlns:android</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE> <XML_NAMESPACE>^$</XML_NAMESPACE>
</AND> </AND>
</match> </match>
@ -51,6 +43,7 @@
<match> <match>
<AND> <AND>
<NAME>xmlns:.*</NAME> <NAME>xmlns:.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE> <XML_NAMESPACE>^$</XML_NAMESPACE>
</AND> </AND>
</match> </match>
@ -62,6 +55,7 @@
<match> <match>
<AND> <AND>
<NAME>.*:id</NAME> <NAME>.*:id</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE> <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND> </AND>
</match> </match>
@ -72,6 +66,7 @@
<match> <match>
<AND> <AND>
<NAME>.*:name</NAME> <NAME>.*:name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE> <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND> </AND>
</match> </match>
@ -82,6 +77,7 @@
<match> <match>
<AND> <AND>
<NAME>name</NAME> <NAME>name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE> <XML_NAMESPACE>^$</XML_NAMESPACE>
</AND> </AND>
</match> </match>
@ -92,6 +88,7 @@
<match> <match>
<AND> <AND>
<NAME>style</NAME> <NAME>style</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE> <XML_NAMESPACE>^$</XML_NAMESPACE>
</AND> </AND>
</match> </match>
@ -102,6 +99,7 @@
<match> <match>
<AND> <AND>
<NAME>.*</NAME> <NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE> <XML_NAMESPACE>^$</XML_NAMESPACE>
</AND> </AND>
</match> </match>
@ -113,6 +111,7 @@
<match> <match>
<AND> <AND>
<NAME>.*</NAME> <NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE> <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND> </AND>
</match> </match>
@ -124,6 +123,7 @@
<match> <match>
<AND> <AND>
<NAME>.*</NAME> <NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>.*</XML_NAMESPACE> <XML_NAMESPACE>.*</XML_NAMESPACE>
</AND> </AND>
</match> </match>
@ -141,7 +141,6 @@
<option name="METHOD_PARAMETERS_LPAREN_ON_NEXT_LINE" value="true" /> <option name="METHOD_PARAMETERS_LPAREN_ON_NEXT_LINE" value="true" />
<option name="METHOD_PARAMETERS_RPAREN_ON_NEXT_LINE" value="true" /> <option name="METHOD_PARAMETERS_RPAREN_ON_NEXT_LINE" value="true" />
<option name="EXTENDS_LIST_WRAP" value="1" /> <option name="EXTENDS_LIST_WRAP" value="1" />
<option name="METHOD_CALL_CHAIN_WRAP" value="1" />
<indentOptions> <indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" /> <option name="CONTINUATION_INDENT_SIZE" value="4" />
</indentOptions> </indentOptions>

View File

@ -14,7 +14,7 @@ cache:
branches: branches:
only: only:
- develop - develop
- 0.9.4 - 0.10.0
android: android:
licenses: licenses:

View File

@ -17,8 +17,8 @@ android {
testApplicationId "io.github.tests.wulkanowy" testApplicationId "io.github.tests.wulkanowy"
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 28 targetSdkVersion 28
versionCode 42 versionCode 43
versionName "0.9.4" versionName "0.10.0"
multiDexEnabled true multiDexEnabled true
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true
@ -28,7 +28,8 @@ android {
] ]
javaCompileOptions { javaCompileOptions {
annotationProcessorOptions { annotationProcessorOptions {
arguments = ["room.schemaLocation": "$projectDir/schemas".toString()] arguments = ["room.schemaLocation": "$projectDir/schemas".toString(),
"room.incremental" : "true"]
} }
} }
} }
@ -65,6 +66,7 @@ android {
} }
flavorDimensions "platform" flavorDimensions "platform"
productFlavors { productFlavors {
play { play {
dimension "platform" dimension "platform"
@ -84,6 +86,15 @@ android {
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8
} }
kotlinOptions {
jvmTarget = "1.8"
}
packagingOptions {
exclude 'META-INF/library_release.kotlin_module'
exclude 'META-INF/library-core_release.kotlin_module'
}
} }
androidExtensions { androidExtensions {
@ -98,38 +109,49 @@ play {
} }
ext { ext {
work_manager = "2.1.0" work_manager = "2.2.0"
room = "2.1.0" room = "2.2.0-beta01"
dagger = "2.24" dagger = "2.24"
chucker = "2.0.4" chucker = "2.0.4"
mockk = "1.9.2" mockk = "1.9.2"
mockito_core = "3.0.4" mockito_core = "3.0.6"
}
configurations.all {
resolutionStrategy.force "androidx.constraintlayout:constraintlayout:1.1.3"
resolutionStrategy.force "com.google.android.material:material:1.1.0-alpha07"
} }
dependencies { dependencies {
implementation "io.github.wulkanowy:api:0.9.4" implementation "io.github.wulkanowy:api:0.10.0"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation "androidx.core:core:1.0.2" implementation "androidx.core:core-ktx:1.1.0-rc03"
implementation "androidx.appcompat:appcompat:1.0.2" implementation "androidx.activity:activity-ktx:1.0.0-rc01"
implementation "androidx.fragment:fragment:1.0.0" implementation "androidx.appcompat:appcompat:1.1.0-rc01"
implementation "androidx.appcompat:appcompat-resources:1.1.0-rc01"
implementation "androidx.fragment:fragment-ktx:1.1.0-rc04"
implementation "androidx.annotation:annotation:1.1.0" implementation "androidx.annotation:annotation:1.1.0"
implementation "androidx.multidex:multidex:2.0.1" implementation "androidx.multidex:multidex:2.0.1"
implementation "androidx.recyclerview:recyclerview:1.0.0" implementation "androidx.preference:preference-ktx:1.1.0-rc01"
implementation "androidx.recyclerview:recyclerview:1.1.0-beta03"
implementation "androidx.viewpager:viewpager:1.0.0" implementation "androidx.viewpager:viewpager:1.0.0"
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0" implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"
implementation "androidx.constraintlayout:constraintlayout:1.1.3" implementation "androidx.constraintlayout:constraintlayout:1.1.3"
implementation "androidx.coordinatorlayout:coordinatorlayout:1.0.0" implementation "androidx.coordinatorlayout:coordinatorlayout:1.1.0-beta01"
implementation "com.google.android.material:material:1.1.0-alpha07" implementation "com.google.android.material:material:1.1.0-alpha07"
implementation "com.github.wulkanowy:MaterialChipsInput:b72fd0ee6f" implementation "com.github.wulkanowy:material-chips-input:2.0.1"
implementation "com.github.PhilJay:MPAndroidChart:v3.1.0" implementation "com.github.PhilJay:MPAndroidChart:v3.1.0"
implementation "me.zhanghai.android.materialprogressbar:library:1.6.1"
implementation "androidx.work:work-runtime:$work_manager" implementation "androidx.work:work-runtime-ktx:$work_manager"
implementation "androidx.work:work-rxjava2:$work_manager" implementation "androidx.work:work-rxjava2:$work_manager"
implementation "androidx.work:work-gcm:$work_manager"
implementation "androidx.room:room-runtime:$room" implementation "androidx.room:room-runtime:$room"
implementation "androidx.room:room-rxjava2:$room" implementation "androidx.room:room-rxjava2:$room"
implementation "androidx.room:room-ktx:$room"
kapt "androidx.room:room-compiler:$room" kapt "androidx.room:room-compiler:$room"
implementation "com.google.dagger:dagger-android-support:$dagger" implementation "com.google.dagger:dagger-android-support:$dagger"
@ -143,20 +165,18 @@ dependencies {
implementation "com.aurelhubert:ahbottomnavigation:2.3.4" implementation "com.aurelhubert:ahbottomnavigation:2.3.4"
implementation "com.ncapdevi:frag-nav:3.3.0" implementation "com.ncapdevi:frag-nav:3.3.0"
implementation "com.github.pwittchen:reactivenetwork-rx2:3.0.4" implementation "com.github.pwittchen:reactivenetwork-rx2:3.0.6"
implementation "io.reactivex.rxjava2:rxandroid:2.1.1" implementation "io.reactivex.rxjava2:rxandroid:2.1.1"
implementation "io.reactivex.rxjava2:rxjava:2.2.11" implementation "io.reactivex.rxjava2:rxjava:2.2.12"
implementation "com.google.code.gson:gson:2.8.5" implementation "com.google.code.gson:gson:2.8.5"
implementation "com.jakewharton.threetenabp:threetenabp:1.2.1" implementation "com.jakewharton.threetenabp:threetenabp:1.2.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.squareup.okhttp3:logging-interceptor:3.12.3" implementation "com.squareup.okhttp3:logging-interceptor:3.12.3"
implementation "com.mikepenz:aboutlibraries:7.0.3"
implementation "com.mikepenz:aboutlibraries:6.2.3" playImplementation "com.google.firebase:firebase-core:17.2.0"
implementation "com.takisoft.preferencex:preferencex:1.0.0"
playImplementation "com.google.firebase:firebase-core:17.0.1"
playImplementation "com.crashlytics.sdk.android:crashlytics:2.10.1" playImplementation "com.crashlytics.sdk.android:crashlytics:2.10.1"
releaseImplementation "fr.o80.chucker:library-no-op:$chucker" releaseImplementation "fr.o80.chucker:library-no-op:$chucker"
@ -168,7 +188,7 @@ dependencies {
testImplementation "io.mockk:mockk:$mockk" testImplementation "io.mockk:mockk:$mockk"
testImplementation "org.threeten:threetenbp:1.4.0" testImplementation "org.threeten:threetenbp:1.4.0"
testImplementation "org.mockito:mockito-core:$mockito_core" testImplementation "org.mockito:mockito-core:$mockito_core"
testImplementation("org.mockito:mockito-inline:3.0.4") { testImplementation("org.mockito:mockito-inline:3.0.6") {
exclude group: "org.mockito", module: "mockito-core" exclude group: "org.mockito", module: "mockito-core"
} }
@ -179,7 +199,7 @@ dependencies {
androidTestImplementation "androidx.room:room-testing:$room" androidTestImplementation "androidx.room:room-testing:$room"
androidTestImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version" androidTestImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
androidTestImplementation "org.mockito:mockito-core:$mockito_core" androidTestImplementation "org.mockito:mockito-core:$mockito_core"
androidTestImplementation("org.mockito:mockito-android:3.0.4") { androidTestImplementation("org.mockito:mockito-android:3.0.6") {
exclude group: 'org.mockito', module: 'mockito-core' exclude group: 'org.mockito', module: 'mockito-core'
} }
} }

View File

@ -1,7 +1,7 @@
apply plugin: "jacoco" apply plugin: "jacoco"
jacoco { jacoco {
toolVersion "0.8.3" toolVersion "0.8.4"
reportsDir = file("$buildDir/reports") reportsDir = file("$buildDir/reports")
} }

View File

@ -11,6 +11,10 @@
-verbose -verbose
#Keep all wulkanowy files
-keep class io.github.wulkanowy.** {*;}
#Config for anallitycs #Config for anallitycs
-keepattributes *Annotation* -keepattributes *Annotation*
-keepattributes SourceFile,LineNumberTable -keepattributes SourceFile,LineNumberTable
@ -32,11 +36,10 @@
-dontwarn rx.internal.util.** -dontwarn rx.internal.util.**
-dontwarn sun.misc.Unsafe -dontwarn sun.misc.Unsafe
#Config for MPAndroidChart #Config for MPAndroidChart
-keep class com.github.mikephil.charting.** { *; } -keep class com.github.mikephil.charting.** { *; }
#Config for API
-keep class io.github.wulkanowy.api.** {*;}
#Config for Material Components #Config for Material Components
-keep class com.google.android.material.tabs.** {*;} -keep class com.google.android.material.tabs.**

View File

@ -5,7 +5,7 @@ import androidx.room.Room
import androidx.test.core.app.ApplicationProvider import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import io.github.wulkanowy.data.db.AppDatabase import io.github.wulkanowy.data.db.AppDatabase
import io.github.wulkanowy.data.db.SharedPrefHelper import io.github.wulkanowy.data.db.SharedPrefProvider
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import org.junit.After import org.junit.After
import org.junit.Before import org.junit.Before
@ -21,14 +21,14 @@ class StudentLocalTest {
private lateinit var testDb: AppDatabase private lateinit var testDb: AppDatabase
private lateinit var sharedHelper: SharedPrefHelper private lateinit var sharedProvider: SharedPrefProvider
@Before @Before
fun createDb() { fun createDb() {
val context = ApplicationProvider.getApplicationContext<Context>() val context = ApplicationProvider.getApplicationContext<Context>()
testDb = Room.inMemoryDatabaseBuilder(context, AppDatabase::class.java) testDb = Room.inMemoryDatabaseBuilder(context, AppDatabase::class.java)
.build() .build()
sharedHelper = SharedPrefHelper(context.getSharedPreferences("TEST", Context.MODE_PRIVATE)) sharedProvider = SharedPrefProvider(context.getSharedPreferences("TEST", Context.MODE_PRIVATE))
studentLocal = StudentLocal(testDb.studentDao, context) studentLocal = StudentLocal(testDb.studentDao, context)
} }

View File

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

View File

@ -60,7 +60,7 @@ class TimetableRepositoryTest {
} }
@Test @Test
fun copyDetailsToCompletedFromPrevious() { fun copyRoomToCompletedFromPrevious() {
timetableLocal.saveTimetable(listOf( timetableLocal.saveTimetable(listOf(
createTimetableLocal(1, of(2019, 3, 5, 8, 0), "123", "Przyroda"), createTimetableLocal(1, of(2019, 3, 5, 8, 0), "123", "Przyroda"),
createTimetableLocal(2, of(2019, 3, 5, 8, 50), "321", "Religia"), createTimetableLocal(2, of(2019, 3, 5, 8, 50), "321", "Religia"),
@ -83,7 +83,32 @@ class TimetableRepositoryTest {
assertEquals("123", lessons[0].room) assertEquals("123", lessons[0].room)
assertEquals("321", lessons[1].room) assertEquals("321", lessons[1].room)
assertEquals("213", lessons[2].room) assertEquals("213", lessons[2].room)
}
assertEquals("", lessons[3].teacher) @Test
fun copyTeacherToCompletedFromPrevious() {
timetableLocal.saveTimetable(listOf(
createTimetableLocal(1, of(2019, 3, 5, 8, 0), "123", "Przyroda", "Jan Garnkiewicz", false),
createTimetableLocal(2, of(2019, 3, 5, 8, 50), "321", "Religia", "Paweł Jumper", false),
createTimetableLocal(3, of(2019, 3, 5, 9, 40), "213", "W-F", "", true),
createTimetableLocal(4, of(2019, 3, 5, 10, 30), "213", "W-F", "", false)
))
every { mockApi.getTimetable(any(), any()) } returns Single.just(listOf(
createTimetableRemote(1, of(2019, 3, 5, 8, 0), "", "Przyroda", "", true), // should override local
createTimetableRemote(2, of(2019, 3, 5, 8, 50), "", "Religia", "", false),
createTimetableRemote(3, of(2019, 3, 5, 9, 40), "", "W-F", "Jan Garnkiewicz", false),
createTimetableRemote(4, of(2019, 3, 5, 10, 30), "", "W-F", "Paweł Jumper", false)
))
val lessons = TimetableRepository(settings, timetableLocal, timetableRemote)
.getTimetable(semesterMock, LocalDate.of(2019, 3, 5), LocalDate.of(2019, 3, 5), true)
.blockingGet()
assertEquals(4, lessons.size)
assertEquals("", lessons[0].teacher)
assertEquals("Paweł Jumper", lessons[1].teacher)
assertEquals("Jan Garnkiewicz", lessons[2].teacher)
assertEquals("Paweł Jumper", lessons[3].teacher)
} }
} }

View File

@ -1,58 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="1926.9231"
android:viewportHeight="1926.9231">
<group android:translateX="462.46155"
android:translateY="462.46155">
<path
android:pathData="M2000,1440.1V2002H1240.1L238.1,1000v0l-120,-120c0.2,-0.2 -0.7,-1.6 -0.6,-1.8 1.6,-3.1 3.4,-6 5,-9.1 0.4,-0.8 0.6,-1.6 0.6,-2.5 9.2,-15.1 20.2,-28.9 31.4,-42.6 0.1,-0.2 0.3,-0.3 0.4,-0.5 5.1,-6.3 10.3,-12.6 15.3,-18.9 1,-1.2 1.9,-2.4 2.9,-3.7 0.2,-0.2 0.3,-0.4 0.5,-0.6 7.6,-9.7 14.8,-19.7 21.2,-30.2 1,-1.7 2,-3.4 2.9,-5.1 0.1,-0.2 0.2,-0.3 0.3,-0.5 6.2,-11.4 11.3,-23.3 17.7,-34.6 4.5,-8 9.3,-15.9 13.8,-23.9 0.1,-0.2 0.2,-0.3 0.3,-0.5 0.8,-1.5 1.6,-3 2.4,-4.4 0.2,-0.3 0.3,-0.6 0.5,-0.9 2,-3.7 3.8,-7.4 5.6,-11.2 6.2,-13.4 10.2,-27.7 14,-41.9 0,-0.2 0.1,-0.3 0.1,-0.5 0.3,-1 0.5,-2 0.8,-2.9 4,-15.3 7.3,-31.4 13.5,-45.9 1.4,-3.3 3,-6.5 4.6,-9.6 0.1,-0.2 0.2,-0.3 0.3,-0.5 5,-9.7 10.6,-19.1 13.5,-29.7 2.6,-9.2 3.5,-19 3.6,-28.8 0,-0.2 0,-0.3 0,-0.5 0.1,-7.2 -0.1,-14.4 -0.4,-21.3 0.9,-5.6 1.9,-11.3 3,-16.9 0.2,-0.9 0.3,-1.7 0.5,-2.6 1.2,-5.9 2.5,-11.7 4.2,-17.4 0,-0.1 0.1,-0.3 0.1,-0.4 0,-0.2 0.1,-0.3 0.1,-0.5 0.2,-2.1 -0.8,-3.7 -2.4,-4.5l7.7,-7.6c1.2,0.9 2.7,1.3 4.3,0.9 12.8,-3.9 24.9,-9.9 36.3,-16.8 2.8,-1.7 5.6,-3.4 8.3,-5.2 0.2,-0.1 0.3,-0.2 0.5,-0.3 7.7,-4.9 15.2,-10.2 22.1,-16 3.1,-2.7 5.9,-5.6 8.3,-8.9 0.1,-0.2 0.2,-0.3 0.4,-0.5 2.3,-3.4 4.2,-7.2 5.3,-11.4 2.4,-9.2 1.9,-19 1.9,-28.4 0,-0.2 0,-0.4 0,-0.6l5.7,-5.6 -0.3,0.1 4.1,1.3 45.3,45 9.6,9.6 14.3,14.3 100.8,100.8 17.2,17.2 16.3,16.3 30.3,-30.3 -192.9,-191.5c0.5,-0.5 1.1,-1.6 1.4,-2.3 0.3,-0.6 0.5,-1.2 0.4,-1.9 0,-0.8 -0.2,-1.6 -0.6,-2.3 -0.1,-0.3 -0.3,-0.7 -0.4,-1 -0.2,-0.4 -0.5,-0.7 -0.9,-1.1l27.8,-27.8 191.1,-191.1c0.7,0.4 1.5,0.6 2.5,0.7 4.1,0 8.2,0.1 11.9,1.9 0.5,0.3 1.3,0.7 2.1,1.1 1.7,2.3 3.5,4.5 5.7,6.2 2.7,2.1 8.2,3 9.4,0.3z"
android:fillColor="#AD2A2A"/>
<path
android:pathData="m616.3,796.7c0.4,1.2 0.5,2.4 0.5,3.7l-5.8,73.3c-0.4,5.2 -5,9.3 -10.6,9.3h-187.2c-4,0 -7.7,-2.3 -9.5,-5.6l-30.9,-57.4c-0.2,-0.3 -0.3,-0.6 -0.4,-0.9l-29.1,-70.5c-1.1,-2.5 -0.9,-5.4 0.3,-7.8l48.3,-94.5c1.2,-2.3 1.4,-5 0.5,-7.4l-23.6,-65.4c-0.8,-2.3 -0.7,-4.9 0.3,-7.1l39.4,-83.9c4,-8.4 17.1,-7.7 19.9,1.1l12.3,38.2 37.5,100.4c1,2.8 3.4,5 6.4,6l80,27.3c3.1,1.1 5.5,3.4 6.5,6.3z"
android:fillColor="#FFFFFF"/>
<path
android:pathData="m386.7,253.5c0,-22.9 14.5,-42.6 35.3,-51.8 -1.5,-2.5 -2.4,-5.2 -2.4,-8.1 0,-11.1 12.8,-20.1 28.5,-20.1 0.4,0 0.7,0 1.1,0 6.5,-10.8 18.9,-18.1 33.1,-18.1 1,0 2,0 3,0.1 1.3,0.1 2.6,-0.5 3.3,-1.5 10.5,-16 37.1,-27.4 68.2,-27.4 11.5,0 22.4,1.6 32.1,4.3 2.3,-3.8 7.4,-6.4 13.3,-6.4 6.7,0 12.4,3.4 14.1,8.1 8.2,-9.5 22.1,-15.7 37.9,-15.7 25.2,0 45.6,15.9 45.6,35.5 0,2.4 -0.3,4.7 -0.9,7 -0.4,1.6 0.5,3.2 2.2,3.8 16.1,5.9 27,17.1 27,29.9 0,14.6 -14.1,27.1 -34,32 -1.6,0.4 -2.6,1.8 -2.6,3.3 0,0.1 0,0.2 0,0.3 0,11.3 -11.2,20.6 -25.6,21.6 0.1,0.5 0.1,1 0.1,1.6 0,21.7 -41.3,39.2 -92.2,39.2 -11,0 -21.5,-0.8 -31.2,-2.3 0,0.2 0,0.3 0,0.5 0,9 -11.6,16.3 -25.8,16.3 -0.8,0 -1.5,0 -2.2,-0.1 1,2.2 1.5,4.5 1.5,6.9 0,14 -17.7,25.3 -39.6,25.3 -2.4,0 -4.7,-0.1 -7,-0.4 -1.8,-0.2 -3.5,0.9 -3.9,2.6 -1.9,7 -8,12.1 -15.3,12.1 -8.8,0 -15.8,-7.5 -15.8,-16.7 0,-4.3 1.5,-8.2 4,-11.1 0.9,-1 1.2,-2.4 0.6,-3.6 -1.4,-2.6 -2,-5.3 -2,-8.2v0c0,-1.6 -1.3,-2.9 -2.9,-3.3 -27.1,-6 -47.5,-28.6 -47.5,-55.6z"
android:fillColor="#FFFFFF"/>
<path
android:pathData="m870.4,883h-124.4c-3.6,0 -6.9,-1.8 -8.9,-4.6l-83.7,-117.7c-0.5,-0.8 -1,-1.6 -1.3,-2.5l-41.5,-121.2c-0.8,-2.4 -2.7,-4.5 -5.1,-5.7l-101.6,-51.3c-2.8,-1.4 -4.8,-4 -5.4,-6.9l-15,-74.4c-0.2,-1.2 -0.7,-2.4 -1.5,-3.5l-34.5,-50.7c-1.9,-2.8 -2.2,-6.2 -0.8,-9.2l21,-44.9c1.6,-3.4 5.1,-5.7 9.1,-5.9l39,-2.3c2.3,-0.1 4.4,-0.9 6.1,-2.3l28.7,-22.3c5.4,-4.2 13.6,-2.4 16.5,3.5l98.9,201.2c0.4,0.9 0.7,1.8 0.9,2.7l12.2,80.9c0.3,1.9 1.1,3.6 2.4,5l197,215.6c5.8,6.4 0.9,16.5 -8.1,16.5z"
android:fillColor="#FFFFFF"/>
<path
android:pathData="m335.8,838.9c0.8,2.6 0.4,5.4 -1,7.7l-19.3,31.3c-1.9,3.1 -5.4,5.1 -9.2,5.1h-180.6c-8.4,0 -13.5,-8.8 -9,-15.4l116.3,-171.3c0.7,-1 1.1,-2.1 1.4,-3.2l53.3,-227.6c0.6,-2.7 2.4,-5 5,-6.4l72.2,-39.6c2.6,-1.4 4.4,-3.7 5,-6.5l9.7,-42.3c2,-8.8 14.6,-10.7 19.6,-3l3.5,5.6c1.5,2.4 1.9,5.4 0.9,8.1l-65.8,190.2c-0.5,1.5 -0.7,3.1 -0.4,4.7l16.4,91c0.3,1.8 0.1,3.7 -0.7,5.4l-39.1,87.8c-0.9,2.1 -1.1,4.4 -0.4,6.5z"
android:fillColor="#FFFFFF"/>
<path
android:pathData="m424.22,657.05 l1580.54,-0.05c0.54,0 0,292 0,292l-1580.54,-0.03c-10.78,0 -19.46,-8.68 -19.46,-19.46l0,-252.99c0,-10.78 8.68,-19.46 19.46,-19.46z"
android:strokeAlpha="0.49803922"
android:strokeLineJoin="round"
android:strokeWidth="4"
android:fillColor="#3f3f3f"
android:strokeColor="#00000000"
android:fillType="nonZero"
android:fillAlpha="1"
android:strokeLineCap="square"/>
<path
android:pathData="m728.71,710.91l0,19.89l-88.7,0l0,59.3l77.04,0L717.05,810l-77.04,0l0,65.39l89.84,0l0,19.9L616.56,895.29L616.56,791.16l-52.35,-52.35 -0.77,-0.18c5.41,6.64 9.65,14.32 12.69,23.09 3.72,10.39 5.62,21.92 5.7,34.6L581.84,809.87c-0.08,12.67 -1.98,24.25 -5.7,34.72 -3.63,10.39 -8.95,19.35 -15.97,26.87 -6.93,7.43 -15.46,13.26 -25.6,17.49 -7.54,3.1 -15.86,5.07 -24.97,5.89 -3.04,0.27 -6.16,0.42 -9.37,0.45L457.53,895.29l0,-6.55l-0.37,6.82 53.8,53.45l415.85,0c0.24,0 0.47,-0.02 0.69,-0.05L1132.91,948.97L953.5,769.55l-58.39,-58.39 -0.28,0.06 -58.03,184.06l-20.4,0l-44.66,-141.34zM783.29,711.07 L826.41,857.52 850.08,777.86zM509.6,711.29c1.52,0.15 3.01,0.33 4.49,0.54 -1.47,-0.21 -2.97,-0.39 -4.49,-0.54zM481.35,730.04l0,146.11l18.88,0c9.97,-0.08 18.59,-1.81 25.85,-5.19 7.27,-3.46 13.26,-8.15 17.99,-14.07 4.82,-5.91 8.36,-12.88 10.64,-20.91 2.37,-8.03 3.59,-16.73 3.68,-26.11l0,-13.81c-0.08,-9.38 -1.31,-18.04 -3.68,-25.98 -2.37,-8.03 -5.91,-14.95 -10.64,-20.78 -4.73,-5.91 -10.73,-10.56 -17.99,-13.94 -7.27,-3.38 -15.88,-5.15 -25.85,-5.32zM560.17,734.86c0.65,0.69 1.26,1.4 1.88,2.11 -0.61,-0.72 -1.23,-1.42 -1.88,-2.11z"
android:strokeAlpha="1"
android:strokeLineJoin="miter"
android:strokeWidth="1.94642854"
android:fillColor="#000000"
android:strokeColor="#00000000"
android:fillAlpha="0.18431373"
android:strokeLineCap="butt"/>
<path
android:pathData="m457.53,895.28l0,-184.51l42.7,0q19.26,0.25 34.34,6.59 15.21,6.21 25.6,17.49 10.52,11.15 15.97,26.86 5.58,15.59 5.7,34.59l0,13.56q-0.13,19.01 -5.7,34.72 -5.45,15.59 -15.97,26.86 -10.39,11.15 -25.6,17.49 -15.08,6.21 -34.34,6.34zM481.35,730.04l0,146.11l18.88,0q14.95,-0.13 25.85,-5.2 10.9,-5.2 17.99,-14.07 7.22,-8.87 10.64,-20.91 3.55,-12.04 3.67,-26.1l0,-13.81q-0.13,-14.07 -3.67,-25.98 -3.55,-12.04 -10.64,-20.78 -7.1,-8.87 -17.99,-13.94 -10.9,-5.07 -25.85,-5.32z"
android:fillColor="#ffffff"
android:strokeColor="#00000000"
android:fillAlpha="1"/>
<path
android:pathData="M717.05,810L640.01,810l0,65.39l89.84,0l0,19.9l-113.29,0l0,-184.51l112.15,0l0,20.02L640.01,730.8l0,59.31l77.05,0z"
android:fillColor="#ffffff"
android:strokeColor="#00000000"
android:fillAlpha="1"/>
<path
android:pathData="m826.41,857.52 l43.59,-146.74l24.96,0l-58.16,184.51L816.4,895.28l-58.29,-184.51l25.09,0z"
android:fillColor="#ffffff"
android:strokeColor="#00000000"
android:fillAlpha="1"/>
</group>
</vector>

View File

@ -0,0 +1,34 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="1926"
android:viewportHeight="1926">
<path
android:fillColor="#ad2a2a"
android:pathData="M2465,1904v561h-759L704,1463l-120,-120 -1,-1 5,-9 1,-3c9,-15 20,-29 31,-43l16,-19 3,-4 21,-30 3,-6c7,-11 12,-23 18,-35l14,-24 3,-4v-1a234,234 0,0 0,20 -53v-1l1,-3c4,-15 7,-31 13,-46l5,-9v-1c5,-10 11,-19 14, -30 2,-9 3,-19 3,-28v-1,-21l3,-17v-3l4,-17h1v-1c0,-2 -1,-4 -3,-4l8,-8 4,1c13,-4 25,-10 37,-17l8,-5 22,-16 9,-9v-1l5, -11c3,-9 2,-19 2,-29l6,-6 4,2 45,45 10,9 14,14 101,101 17,17 16,17 31,-31 -193,-191 1,-2 1,-2 -1,-3v-1l-1,-1 27,-27 192,-192 2,1a27,27 0,0 1,14 3l6,6c2,2 8,3 9,1l1310,1310z" />
<path
android:fillColor="#fff"
android:pathData="M1082,1260v4l-6,73c0,5 -5,9 -10,9H879c-4,0 -8,-2 -10,-5l-31,-58 -29,-71v-8l48,-94c2,-3 2,-5 1, -8l-24,-65c-1,-2 0,-5 1,-7l39,-84c4,-8 17,-8 20,1l12,38 38,101c1,2 3,5 6,6l80,27c3,1 6,3 7,6l45,135zM852,717c0,-23 15,-43 35,-52l-2,-8c0,-11 13,-20 29,-20h1a38,38 0,0 1,36 -18l3,-2c11,-16 37,-27 68,-27 12,0 23,2 32,4 3,-3 8,-6 14,-6s12,3 14,8c8,-9 22,-16 38,-16 25,0 45,16 45,36l-1,7 3,4c16,6 27,17 27,30 0,14 -15,27 -34,32 -2,0 -3,1 -3,3 0,11 -11,21 -26,22v1c0,22 -41,40 -92,40 -11,0 -21,-1 -31,-3v1c0,9 -12,16 -26,16h-2l2,7c0,14 -18,25 -40,25h-7l-4, 2c-2,7 -8,12 -15,12 -9,0 -16,-7 -16,-16 0,-5 1,-8 4,-11l1,-4c-2,-3 -2,-5 -2,-8 0,-2 -2,-3 -3,-3 -27,-6 -48,-29 -48,-56z" />
<path
android:fillColor="#fff"
android:pathData="M1336,1346h-125c-3,0 -6,-1 -8,-4l-84,-118 -1,-2 -42,-122 -5,-5 -102,-52 -5,-6 -15,-75 -2,-3 -34,-51c-2,-3 -2,-6 -1,-9l21,-45c2,-4 5,-6 9,-6l39,-2c3,0 5,-1 6,-3l29,-22c5,-4 14,-2 17,4l98,201 1,2 13,81 2,5 197,216c6,6 1,16 -8,16zM801,1302c1,3 1,6 -1,8l-19,31c-2,3 -5,5 -9,5H591c-8,0 -13,-8 -9,-15l116,-171 2,-3 53, -228c1,-3 3,-5 5,-6l72,-40c3,-1 5,-4 5,-7l10,-42c2,-9 15,-11 20,-3l3,6c2,2 2,5 1,8l-66,190v5l16,91v5l-40,88v6l22,72z" />
<path
android:fillColor="#3f3f3f"
android:pathData="M886.7,1119.5h1580.5c0.6,0 0,292 0,292H886.7a19.4,19.4 0,0 1,-19.5 -19.5v-253c0,-10.8 8.7,-19.5 19.5,-19.5z"
android:strokeWidth="4"
android:strokeAlpha=".5"
android:strokeColor="#000"
android:strokeLineCap="square"
android:strokeLineJoin="round" />
<path
android:fillAlpha=".2"
android:fillColor="#000"
android:pathData="M1191.2,1173.4v19.9h-88.7v59.3h77v19.9h-77v65.4h89.8v19.9L1079,1357.8v-104.2l-52.3,-52.3 -0.8, -0.2a75.1,75.1 0,0 1,12.7 23,104 104,0 0,1 5.7,34.7v13.5c0,12.7 -2,24.3 -5.7,34.8a72.8,72.8 0,0 1,-41.6 44.4,85.5 85.5,0 0,1 -34.3,6.3L920,1357.8v-6.6l-0.4,6.8 53.8,53.5h622L1416,1232l-58.4,-58.4h-0.3l-58,184.1h -20.4l-44.7,-141.3zM1245.8,1173.5l43,146.5 23.7,-79.7zM972.1,1173.8zM943.8,1192.5v146.1h18.9a62,62 0,0 0,25.8 -5.2,50.3 50.3,0 0,0 18,-14c4.9,-6 8.4,-13 10.7,-21 2.3,-8 3.6,-16.7 3.7,-26v-13.9c-0.1,-9.4 -1.4,-18 -3.7,-26 -2.4,-8 -6,-15 -10.7,-20.7a49.3,49.3 0,0 0,-18 -14,63.4 63.4,0 0,0 -25.8,-5.3zM1022.6,1197.3l2,2.1 -2,-2z"
android:strokeWidth="1.9"
android:strokeColor="#000" />
<path
android:fillColor="#fff"
android:pathData="M920,1357.7v-184.5h42.7q19.3,0.3 34.3,6.6 15.2,6.2 25.6,17.5 10.6,11.2 16,26.9 5.6,15.6 5.7, 34.6v13.5q-0.1,19 -5.7,34.7 -5.4,15.6 -16,26.9 -10.4,11.2 -25.6,17.5 -15,6.2 -34.3,6.3zM943.8,1192.5v146.1h18.9q15, -0.1 25.8,-5.2 11,-5.2 18,-14 7.3,-9 10.7,-21 3.5,-12 3.6,-26v-13.9q0,-14 -3.6,-26t-10.7,-20.7q-7,-9 -18,-14 -10.9,-5 -25.8,-5.3zM1179.5,1272.5h-77v65.4h89.8v19.9L1079,1357.8v-184.6h112.2v20h-88.7v59.4h77zM1288.9, 1320l43.6,-146.8h25l-58.2,184.6h-20.4l-58.3,-184.6h25z"
android:strokeWidth="1"
android:strokeColor="#000" />
</vector>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/> <background android:drawable="@color/colorPrimary" />
<foreground android:drawable="@drawable/ic_launcher_foreground"/> <foreground android:drawable="@drawable/ic_launcher_foreground_dev" />
</adaptive-icon> </adaptive-icon>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/> <background android:drawable="@color/colorPrimary" />
<foreground android:drawable="@drawable/ic_launcher_foreground"/> <foreground android:drawable="@drawable/ic_launcher_foreground_dev" />
</adaptive-icon> </adaptive-icon>

View File

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

View File

@ -43,6 +43,7 @@
android:name=".ui.modules.message.send.SendMessageActivity" android:name=".ui.modules.message.send.SendMessageActivity"
android:configChanges="orientation|screenSize" android:configChanges="orientation|screenSize"
android:label="@string/send_message_title" android:label="@string/send_message_title"
android:windowSoftInputMode="adjustResize"
android:theme="@style/WulkanowyTheme.NoActionBar" /> android:theme="@style/WulkanowyTheme.NoActionBar" />
<activity <activity
android:name=".ui.modules.timetablewidget.TimetableWidgetConfigureActivity" android:name=".ui.modules.timetablewidget.TimetableWidgetConfigureActivity"

View File

@ -5,7 +5,6 @@ import android.util.Log.INFO
import android.util.Log.VERBOSE import android.util.Log.VERBOSE
import androidx.multidex.MultiDex import androidx.multidex.MultiDex
import androidx.work.Configuration import androidx.work.Configuration
import androidx.work.WorkManager
import com.jakewharton.threetenabp.AndroidThreeTen import com.jakewharton.threetenabp.AndroidThreeTen
import dagger.android.AndroidInjector import dagger.android.AndroidInjector
import dagger.android.support.DaggerApplication import dagger.android.support.DaggerApplication
@ -13,6 +12,7 @@ import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.utils.Log import eu.davidea.flexibleadapter.utils.Log
import io.github.wulkanowy.di.DaggerAppComponent import io.github.wulkanowy.di.DaggerAppComponent
import io.github.wulkanowy.services.sync.SyncWorkerFactory import io.github.wulkanowy.services.sync.SyncWorkerFactory
import io.github.wulkanowy.ui.base.ThemeManager
import io.github.wulkanowy.utils.ActivityLifecycleLogger import io.github.wulkanowy.utils.ActivityLifecycleLogger
import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.CrashlyticsTree import io.github.wulkanowy.utils.CrashlyticsTree
@ -24,11 +24,14 @@ import timber.log.Timber
import java.io.IOException import java.io.IOException
import javax.inject.Inject import javax.inject.Inject
class WulkanowyApp : DaggerApplication() { class WulkanowyApp : DaggerApplication(), Configuration.Provider {
@Inject @Inject
lateinit var workerFactory: SyncWorkerFactory lateinit var workerFactory: SyncWorkerFactory
@Inject
lateinit var themeManager: ThemeManager
@Inject @Inject
lateinit var appInfo: AppInfo lateinit var appInfo: AppInfo
@ -41,20 +44,12 @@ class WulkanowyApp : DaggerApplication() {
super.onCreate() super.onCreate()
AndroidThreeTen.init(this) AndroidThreeTen.init(this)
RxJavaPlugins.setErrorHandler(::onError) RxJavaPlugins.setErrorHandler(::onError)
themeManager.applyDefaultTheme()
initWorkManager()
initLogging() initLogging()
initCrashlytics(this, appInfo) initCrashlytics(this, appInfo)
} }
private fun initWorkManager() {
WorkManager.initialize(this,
Configuration.Builder()
.setWorkerFactory(workerFactory)
.setMinimumLoggingLevel(if (appInfo.isDebug) VERBOSE else INFO)
.build())
}
private fun initLogging() { private fun initLogging() {
if (appInfo.isDebug) { if (appInfo.isDebug) {
Timber.plant(DebugLogTree()) Timber.plant(DebugLogTree())
@ -76,4 +71,9 @@ class WulkanowyApp : DaggerApplication() {
override fun applicationInjector(): AndroidInjector<out DaggerApplication> { override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
return DaggerAppComponent.factory().create(this) return DaggerAppComponent.factory().create(this)
} }
override fun getWorkManagerConfiguration() = Configuration.Builder()
.setWorkerFactory(workerFactory)
.setMinimumLoggingLevel(if (appInfo.isDebug) VERBOSE else INFO)
.build()
} }

View File

@ -1,18 +1,15 @@
package io.github.wulkanowy.data.db package io.github.wulkanowy.data.db
import android.annotation.SuppressLint
import android.content.SharedPreferences import android.content.SharedPreferences
import androidx.core.content.edit
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@Singleton @Singleton
@SuppressLint("ApplySharedPref") class SharedPrefProvider @Inject constructor(private val sharedPref: SharedPreferences) {
class SharedPrefHelper @Inject constructor(private val sharedPref: SharedPreferences) {
fun putLong(key: String, value: Long, sync: Boolean = false) { fun putLong(key: String, value: Long, sync: Boolean = false) {
sharedPref.edit().putLong(key, value).apply { sharedPref.edit(sync) { putLong(key, value) }
if (sync) commit() else apply()
}
} }
fun getLong(key: String, defaultValue: Long) = sharedPref.getLong(key, defaultValue) fun getLong(key: String, defaultValue: Long) = sharedPref.getLong(key, defaultValue)

View File

@ -32,10 +32,11 @@ class TimetableRepository @Inject constructor(
.doOnSuccess { old -> .doOnSuccess { old ->
local.deleteTimetable(old.uniqueSubtract(new)) local.deleteTimetable(old.uniqueSubtract(new))
local.saveTimetable(new.uniqueSubtract(old).map { item -> local.saveTimetable(new.uniqueSubtract(old).map { item ->
item.apply { item.also { new ->
old.singleOrNull { this.start == it.start }?.let { old.singleOrNull { new.start == it.start }?.let { old ->
return@map copy( return@map new.copy(
room = if (room.isEmpty()) it.room else room room = if (new.room.isEmpty()) old.room else new.room,
teacher = if (new.teacher.isEmpty() && !new.changes) old.teacher else new.teacher
) )
} }
} }

View File

@ -14,7 +14,7 @@ import javax.inject.Singleton
AppModule::class, AppModule::class,
RepositoryModule::class, RepositoryModule::class,
ServicesModule::class, ServicesModule::class,
BuilderModule::class]) BindingModule::class])
interface AppComponent : AndroidInjector<WulkanowyApp> { interface AppComponent : AndroidInjector<WulkanowyApp> {
@Component.Factory @Component.Factory

View File

@ -6,16 +6,17 @@ import io.github.wulkanowy.di.scopes.PerActivity
import io.github.wulkanowy.ui.modules.login.LoginActivity import io.github.wulkanowy.ui.modules.login.LoginActivity
import io.github.wulkanowy.ui.modules.login.LoginModule import io.github.wulkanowy.ui.modules.login.LoginModule
import io.github.wulkanowy.ui.modules.luckynumberwidget.LuckyNumberWidgetConfigureActivity import io.github.wulkanowy.ui.modules.luckynumberwidget.LuckyNumberWidgetConfigureActivity
import io.github.wulkanowy.ui.modules.luckynumberwidget.LuckyNumberWidgetProvider
import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.main.MainModule import io.github.wulkanowy.ui.modules.main.MainModule
import io.github.wulkanowy.ui.modules.message.send.SendMessageActivity import io.github.wulkanowy.ui.modules.message.send.SendMessageActivity
import io.github.wulkanowy.ui.modules.splash.SplashActivity import io.github.wulkanowy.ui.modules.splash.SplashActivity
import io.github.wulkanowy.ui.modules.luckynumberwidget.LuckyNumberWidgetProvider
import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetConfigureActivity import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetConfigureActivity
import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider
@Suppress("unused")
@Module @Module
internal abstract class BuilderModule { internal abstract class BindingModule {
@PerActivity @PerActivity
@ContributesAndroidInjector @ContributesAndroidInjector

View File

@ -1,8 +1,6 @@
package io.github.wulkanowy.services package io.github.wulkanowy.services
import android.app.NotificationManager
import android.content.Context import android.content.Context
import android.content.Context.NOTIFICATION_SERVICE
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import androidx.work.WorkManager import androidx.work.WorkManager
import com.squareup.inject.assisted.dagger2.AssistedModule import com.squareup.inject.assisted.dagger2.AssistedModule
@ -28,6 +26,7 @@ import io.github.wulkanowy.services.sync.works.Work
import io.github.wulkanowy.services.widgets.TimetableWidgetService import io.github.wulkanowy.services.widgets.TimetableWidgetService
import javax.inject.Singleton import javax.inject.Singleton
@Suppress("unused")
@AssistedModule @AssistedModule
@Module(includes = [AssistedInject_ServicesModule::class]) @Module(includes = [AssistedInject_ServicesModule::class])
abstract class ServicesModule { abstract class ServicesModule {
@ -37,17 +36,12 @@ abstract class ServicesModule {
@JvmStatic @JvmStatic
@Provides @Provides
fun provideWorkManager() = WorkManager.getInstance() fun provideWorkManager(context: Context) = WorkManager.getInstance(context)
@JvmStatic @JvmStatic
@Singleton @Singleton
@Provides @Provides
fun provideNotificationManagerCompat(context: Context) = NotificationManagerCompat.from(context) fun provideNotificationManager(context: Context) = NotificationManagerCompat.from(context)
@JvmStatic
@Singleton
@Provides
fun provideNotificationManager(context: Context) = context.getSystemService(NOTIFICATION_SERVICE) as NotificationManager
} }
@ContributesAndroidInjector @ContributesAndroidInjector

View File

@ -8,9 +8,9 @@ import androidx.work.ExistingPeriodicWorkPolicy.KEEP
import androidx.work.ExistingPeriodicWorkPolicy.REPLACE import androidx.work.ExistingPeriodicWorkPolicy.REPLACE
import androidx.work.NetworkType.CONNECTED import androidx.work.NetworkType.CONNECTED
import androidx.work.NetworkType.UNMETERED import androidx.work.NetworkType.UNMETERED
import androidx.work.PeriodicWorkRequest import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkManager import androidx.work.WorkManager
import io.github.wulkanowy.data.db.SharedPrefHelper import io.github.wulkanowy.data.db.SharedPrefProvider
import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository
import io.github.wulkanowy.services.sync.channels.DebugChannel import io.github.wulkanowy.services.sync.channels.DebugChannel
import io.github.wulkanowy.services.sync.channels.NewEntriesChannel import io.github.wulkanowy.services.sync.channels.NewEntriesChannel
@ -26,14 +26,13 @@ import javax.inject.Singleton
class SyncManager @Inject constructor( class SyncManager @Inject constructor(
private val workManager: WorkManager, private val workManager: WorkManager,
private val preferencesRepository: PreferencesRepository, private val preferencesRepository: PreferencesRepository,
sharedPrefHelper: SharedPrefHelper, sharedPrefProvider: SharedPrefProvider,
newEntriesChannel: NewEntriesChannel, newEntriesChannel: NewEntriesChannel,
debugChannel: DebugChannel, debugChannel: DebugChannel,
appInfo: AppInfo appInfo: AppInfo
) { ) {
companion object { companion object {
private const val APP_VERSION_CODE_KEY = "app_version_code" private const val APP_VERSION_CODE_KEY = "app_version_code"
} }
@ -45,18 +44,18 @@ class SyncManager @Inject constructor(
if (appInfo.isDebug) debugChannel.create() if (appInfo.isDebug) debugChannel.create()
} }
if (sharedPrefHelper.getLong(APP_VERSION_CODE_KEY, -1L) != appInfo.versionCode.toLong()) { if (sharedPrefProvider.getLong(APP_VERSION_CODE_KEY, -1L) != appInfo.versionCode.toLong()) {
startSyncWorker(true) startSyncWorker(true)
sharedPrefHelper.putLong(APP_VERSION_CODE_KEY, appInfo.versionCode.toLong(), true) sharedPrefProvider.putLong(APP_VERSION_CODE_KEY, appInfo.versionCode.toLong(), true)
} }
Timber.i("SyncManager was initialized") Timber.i("SyncManager was initialized")
} }
fun startSyncWorker(restart: Boolean = false) { fun startSyncWorker(restart: Boolean = false) {
if (preferencesRepository.isServiceEnabled && !now().isHolidays) { if (preferencesRepository.isServiceEnabled && !now().isHolidays) {
workManager.enqueueUniquePeriodicWork(SyncWorker::class.java.simpleName, if (restart) REPLACE else KEEP, workManager.enqueueUniquePeriodicWork(SyncWorker::class.java.simpleName, if (restart) REPLACE else KEEP,
PeriodicWorkRequest.Builder(SyncWorker::class.java, preferencesRepository.servicesInterval, MINUTES, 10, MINUTES) PeriodicWorkRequestBuilder<SyncWorker>(preferencesRepository.servicesInterval, MINUTES)
.setInitialDelay(10, MINUTES)
.setBackoffCriteria(EXPONENTIAL, 30, MINUTES) .setBackoffCriteria(EXPONENTIAL, 30, MINUTES)
.setConstraints(Constraints.Builder() .setConstraints(Constraints.Builder()
.setRequiredNetworkType(if (preferencesRepository.isServicesOnlyWifi) UNMETERED else CONNECTED) .setRequiredNetworkType(if (preferencesRepository.isServicesOnlyWifi) UNMETERED else CONNECTED)

View File

@ -64,7 +64,7 @@ class SyncWorker @AssistedInject constructor(
private fun notify(result: Result) { private fun notify(result: Result) {
notificationManager.notify(Random.nextInt(Int.MAX_VALUE), NotificationCompat.Builder(applicationContext, DebugChannel.CHANNEL_ID) notificationManager.notify(Random.nextInt(Int.MAX_VALUE), NotificationCompat.Builder(applicationContext, DebugChannel.CHANNEL_ID)
.setContentTitle("Debug notification") .setContentTitle("Debug notification")
.setSmallIcon(R.drawable.ic_more_settings_24dp) .setSmallIcon(R.drawable.ic_more_settings)
.setAutoCancel(true) .setAutoCancel(true)
.setColor(applicationContext.getCompatColor(R.color.colorPrimary)) .setColor(applicationContext.getCompatColor(R.color.colorPrimary))
.setStyle(BigTextStyle().bigText("${SyncWorker::class.java.simpleName} result: $result")) .setStyle(BigTextStyle().bigText("${SyncWorker::class.java.simpleName} result: $result"))

View File

@ -3,15 +3,15 @@ package io.github.wulkanowy.services.sync.channels
import android.annotation.TargetApi import android.annotation.TargetApi
import android.app.Notification.VISIBILITY_PUBLIC import android.app.Notification.VISIBILITY_PUBLIC
import android.app.NotificationChannel import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.NotificationManager.IMPORTANCE_DEFAULT import android.app.NotificationManager.IMPORTANCE_DEFAULT
import android.content.Context import android.content.Context
import androidx.core.app.NotificationManagerCompat
import io.github.wulkanowy.R import io.github.wulkanowy.R
import javax.inject.Inject import javax.inject.Inject
@TargetApi(26) @TargetApi(26)
class DebugChannel @Inject constructor( class DebugChannel @Inject constructor(
private val notificationManager: NotificationManager, private val notificationManager: NotificationManagerCompat,
private val context: Context private val context: Context
) { ) {
@ -21,8 +21,9 @@ class DebugChannel @Inject constructor(
fun create() { fun create() {
notificationManager.createNotificationChannel( notificationManager.createNotificationChannel(
NotificationChannel(CHANNEL_ID, context.getString(R.string.channel_debug), IMPORTANCE_DEFAULT).apply { NotificationChannel(CHANNEL_ID, context.getString(R.string.channel_debug), IMPORTANCE_DEFAULT)
lockscreenVisibility = VISIBILITY_PUBLIC .apply {
}) lockscreenVisibility = VISIBILITY_PUBLIC
})
} }
} }

View File

@ -3,15 +3,15 @@ package io.github.wulkanowy.services.sync.channels
import android.annotation.TargetApi import android.annotation.TargetApi
import android.app.Notification.VISIBILITY_PUBLIC import android.app.Notification.VISIBILITY_PUBLIC
import android.app.NotificationChannel import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.NotificationManager.IMPORTANCE_HIGH import android.app.NotificationManager.IMPORTANCE_HIGH
import android.content.Context import android.content.Context
import androidx.core.app.NotificationManagerCompat
import io.github.wulkanowy.R import io.github.wulkanowy.R
import javax.inject.Inject import javax.inject.Inject
@TargetApi(26) @TargetApi(26)
class NewEntriesChannel @Inject constructor( class NewEntriesChannel @Inject constructor(
private val notificationManager: NotificationManager, private val notificationManager: NotificationManagerCompat,
private val context: Context private val context: Context
) { ) {
@ -21,10 +21,11 @@ class NewEntriesChannel @Inject constructor(
fun create() { fun create() {
notificationManager.createNotificationChannel( notificationManager.createNotificationChannel(
NotificationChannel(CHANNEL_ID, context.getString(R.string.channel_new_entries), IMPORTANCE_HIGH).apply { NotificationChannel(CHANNEL_ID, context.getString(R.string.channel_new_entries), IMPORTANCE_HIGH)
enableLights(true) .apply {
enableVibration(true) enableLights(true)
lockscreenVisibility = VISIBILITY_PUBLIC enableVibration(true)
}) lockscreenVisibility = VISIBILITY_PUBLIC
})
} }
} }

View File

@ -15,7 +15,7 @@ import io.github.wulkanowy.data.repositories.grade.GradeRepository
import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository
import io.github.wulkanowy.services.sync.channels.NewEntriesChannel import io.github.wulkanowy.services.sync.channels.NewEntriesChannel
import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.main.MainView.MenuView import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.getCompatColor import io.github.wulkanowy.utils.getCompatColor
import io.reactivex.Completable import io.reactivex.Completable
import javax.inject.Inject import javax.inject.Inject
@ -41,14 +41,14 @@ class GradeWork @Inject constructor(
notificationManager.notify(Random.nextInt(Int.MAX_VALUE), NotificationCompat.Builder(context, NewEntriesChannel.CHANNEL_ID) notificationManager.notify(Random.nextInt(Int.MAX_VALUE), NotificationCompat.Builder(context, NewEntriesChannel.CHANNEL_ID)
.setContentTitle(context.resources.getQuantityString(R.plurals.grade_new_items, grades.size, grades.size)) .setContentTitle(context.resources.getQuantityString(R.plurals.grade_new_items, grades.size, grades.size))
.setContentText(context.resources.getQuantityString(R.plurals.grade_notify_new_items, grades.size, grades.size)) .setContentText(context.resources.getQuantityString(R.plurals.grade_notify_new_items, grades.size, grades.size))
.setSmallIcon(R.drawable.ic_stat_notify_grade) .setSmallIcon(R.drawable.ic_stat_grade)
.setAutoCancel(true) .setAutoCancel(true)
.setPriority(PRIORITY_HIGH) .setPriority(PRIORITY_HIGH)
.setDefaults(DEFAULT_ALL) .setDefaults(DEFAULT_ALL)
.setColor(context.getCompatColor(R.color.colorPrimary)) .setColor(context.getCompatColor(R.color.colorPrimary))
.setContentIntent( .setContentIntent(
PendingIntent.getActivity(context, MenuView.GRADE.id, PendingIntent.getActivity(context, MainView.Section.GRADE.id,
MainActivity.getStartIntent(context, MenuView.GRADE, true), FLAG_UPDATE_CURRENT)) MainActivity.getStartIntent(context, MainView.Section.GRADE, true), FLAG_UPDATE_CURRENT))
.setStyle(NotificationCompat.InboxStyle().run { .setStyle(NotificationCompat.InboxStyle().run {
setSummaryText(context.resources.getQuantityString(R.plurals.grade_number_item, grades.size, grades.size)) setSummaryText(context.resources.getQuantityString(R.plurals.grade_number_item, grades.size, grades.size))
grades.forEach { addLine("${it.subject}: ${it.entry}") } grades.forEach { addLine("${it.subject}: ${it.entry}") }

View File

@ -15,7 +15,7 @@ import io.github.wulkanowy.data.repositories.luckynumber.LuckyNumberRepository
import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository
import io.github.wulkanowy.services.sync.channels.NewEntriesChannel import io.github.wulkanowy.services.sync.channels.NewEntriesChannel
import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.main.MainView.MenuView import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.getCompatColor import io.github.wulkanowy.utils.getCompatColor
import io.reactivex.Completable import io.reactivex.Completable
import javax.inject.Inject import javax.inject.Inject
@ -41,14 +41,14 @@ class LuckyNumberWork @Inject constructor(
notificationManager.notify(Random.nextInt(Int.MAX_VALUE), NotificationCompat.Builder(context, NewEntriesChannel.CHANNEL_ID) notificationManager.notify(Random.nextInt(Int.MAX_VALUE), NotificationCompat.Builder(context, NewEntriesChannel.CHANNEL_ID)
.setContentTitle(context.getString(R.string.lucky_number_notify_new_item_title)) .setContentTitle(context.getString(R.string.lucky_number_notify_new_item_title))
.setContentText(context.getString(R.string.lucky_number_notify_new_item, luckyNumber.luckyNumber)) .setContentText(context.getString(R.string.lucky_number_notify_new_item, luckyNumber.luckyNumber))
.setSmallIcon(R.drawable.ic_stat_notify_lucky_number) .setSmallIcon(R.drawable.ic_stat_luckynumber)
.setAutoCancel(true) .setAutoCancel(true)
.setDefaults(DEFAULT_ALL) .setDefaults(DEFAULT_ALL)
.setPriority(PRIORITY_HIGH) .setPriority(PRIORITY_HIGH)
.setColor(context.getCompatColor(R.color.colorPrimary)) .setColor(context.getCompatColor(R.color.colorPrimary))
.setContentIntent( .setContentIntent(
PendingIntent.getActivity(context, MenuView.MESSAGE.id, PendingIntent.getActivity(context, MainView.Section.MESSAGE.id,
MainActivity.getStartIntent(context, MenuView.LUCKY_NUMBER, true), FLAG_UPDATE_CURRENT)) MainActivity.getStartIntent(context, MainView.Section.LUCKY_NUMBER, true), FLAG_UPDATE_CURRENT))
.build()) .build())
} }
} }

View File

@ -16,7 +16,7 @@ import io.github.wulkanowy.data.repositories.message.MessageRepository
import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository
import io.github.wulkanowy.services.sync.channels.NewEntriesChannel import io.github.wulkanowy.services.sync.channels.NewEntriesChannel
import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.main.MainView.MenuView import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.getCompatColor import io.github.wulkanowy.utils.getCompatColor
import io.reactivex.Completable import io.reactivex.Completable
import javax.inject.Inject import javax.inject.Inject
@ -42,14 +42,14 @@ class MessageWork @Inject constructor(
notificationManager.notify(Random.nextInt(Int.MAX_VALUE), NotificationCompat.Builder(context, NewEntriesChannel.CHANNEL_ID) notificationManager.notify(Random.nextInt(Int.MAX_VALUE), NotificationCompat.Builder(context, NewEntriesChannel.CHANNEL_ID)
.setContentTitle(context.resources.getQuantityString(R.plurals.message_new_items, messages.size, messages.size)) .setContentTitle(context.resources.getQuantityString(R.plurals.message_new_items, messages.size, messages.size))
.setContentText(context.resources.getQuantityString(R.plurals.message_notify_new_items, messages.size, messages.size)) .setContentText(context.resources.getQuantityString(R.plurals.message_notify_new_items, messages.size, messages.size))
.setSmallIcon(R.drawable.ic_stat_notify_message) .setSmallIcon(R.drawable.ic_stat_message)
.setAutoCancel(true) .setAutoCancel(true)
.setDefaults(DEFAULT_ALL) .setDefaults(DEFAULT_ALL)
.setPriority(PRIORITY_HIGH) .setPriority(PRIORITY_HIGH)
.setColor(context.getCompatColor(R.color.colorPrimary)) .setColor(context.getCompatColor(R.color.colorPrimary))
.setContentIntent( .setContentIntent(
PendingIntent.getActivity(context, MenuView.MESSAGE.id, PendingIntent.getActivity(context, MainView.Section.MESSAGE.id,
MainActivity.getStartIntent(context, MenuView.MESSAGE, true), FLAG_UPDATE_CURRENT) MainActivity.getStartIntent(context, MainView.Section.MESSAGE, true), FLAG_UPDATE_CURRENT)
) )
.setStyle(NotificationCompat.InboxStyle().run { .setStyle(NotificationCompat.InboxStyle().run {
setSummaryText(context.resources.getQuantityString(R.plurals.message_number_item, messages.size, messages.size)) setSummaryText(context.resources.getQuantityString(R.plurals.message_number_item, messages.size, messages.size))

View File

@ -15,7 +15,7 @@ import io.github.wulkanowy.data.repositories.note.NoteRepository
import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository
import io.github.wulkanowy.services.sync.channels.NewEntriesChannel import io.github.wulkanowy.services.sync.channels.NewEntriesChannel
import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.main.MainView.MenuView import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.getCompatColor import io.github.wulkanowy.utils.getCompatColor
import io.reactivex.Completable import io.reactivex.Completable
import javax.inject.Inject import javax.inject.Inject
@ -41,14 +41,14 @@ class NoteWork @Inject constructor(
notificationManager.notify(Random.nextInt(Int.MAX_VALUE), NotificationCompat.Builder(context, NewEntriesChannel.CHANNEL_ID) notificationManager.notify(Random.nextInt(Int.MAX_VALUE), NotificationCompat.Builder(context, NewEntriesChannel.CHANNEL_ID)
.setContentTitle(context.resources.getQuantityString(R.plurals.note_new_items, notes.size, notes.size)) .setContentTitle(context.resources.getQuantityString(R.plurals.note_new_items, notes.size, notes.size))
.setContentText(context.resources.getQuantityString(R.plurals.note_notify_new_items, notes.size, notes.size)) .setContentText(context.resources.getQuantityString(R.plurals.note_notify_new_items, notes.size, notes.size))
.setSmallIcon(R.drawable.ic_stat_notify_note) .setSmallIcon(R.drawable.ic_stat_note)
.setAutoCancel(true) .setAutoCancel(true)
.setDefaults(DEFAULT_ALL) .setDefaults(DEFAULT_ALL)
.setPriority(PRIORITY_HIGH) .setPriority(PRIORITY_HIGH)
.setColor(context.getCompatColor(R.color.colorPrimary)) .setColor(context.getCompatColor(R.color.colorPrimary))
.setContentIntent( .setContentIntent(
PendingIntent.getActivity(context, MenuView.NOTE.id, PendingIntent.getActivity(context, MainView.Section.NOTE.id,
MainActivity.getStartIntent(context, MenuView.NOTE, true), FLAG_UPDATE_CURRENT)) MainActivity.getStartIntent(context, MainView.Section.NOTE, true), FLAG_UPDATE_CURRENT))
.setStyle(NotificationCompat.InboxStyle().run { .setStyle(NotificationCompat.InboxStyle().run {
setSummaryText(context.resources.getQuantityString(R.plurals.note_number_item, notes.size, notes.size)) setSummaryText(context.resources.getQuantityString(R.plurals.note_number_item, notes.size, notes.size))
notes.forEach { addLine("${it.teacher}: ${it.category}") } notes.forEach { addLine("${it.teacher}: ${it.category}") }

View File

@ -3,7 +3,7 @@ package io.github.wulkanowy.services.widgets
import android.content.Intent import android.content.Intent
import android.widget.RemoteViewsService import android.widget.RemoteViewsService
import dagger.android.AndroidInjection import dagger.android.AndroidInjection
import io.github.wulkanowy.data.db.SharedPrefHelper import io.github.wulkanowy.data.db.SharedPrefProvider
import io.github.wulkanowy.data.repositories.semester.SemesterRepository import io.github.wulkanowy.data.repositories.semester.SemesterRepository
import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.data.repositories.student.StudentRepository
import io.github.wulkanowy.data.repositories.timetable.TimetableRepository import io.github.wulkanowy.data.repositories.timetable.TimetableRepository
@ -23,7 +23,7 @@ class TimetableWidgetService : RemoteViewsService() {
lateinit var semesterRepo: SemesterRepository lateinit var semesterRepo: SemesterRepository
@Inject @Inject
lateinit var sharedPref: SharedPrefHelper lateinit var sharedPref: SharedPrefProvider
@Inject @Inject
lateinit var schedulers: SchedulersProvider lateinit var schedulers: SchedulersProvider

View File

@ -1,5 +1,8 @@
package io.github.wulkanowy.ui.base package io.github.wulkanowy.ui.base
import android.app.ActivityManager
import android.os.Build.VERSION.SDK_INT
import android.os.Build.VERSION_CODES.LOLLIPOP
import android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK import android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
import android.os.Bundle import android.os.Bundle
@ -16,6 +19,7 @@ import dagger.android.HasAndroidInjector
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.ui.modules.login.LoginActivity import io.github.wulkanowy.ui.modules.login.LoginActivity
import io.github.wulkanowy.utils.FragmentLifecycleLogger import io.github.wulkanowy.utils.FragmentLifecycleLogger
import io.github.wulkanowy.utils.getThemeAttrColor
import javax.inject.Inject import javax.inject.Inject
abstract class BaseActivity<T : BasePresenter<out BaseView>> : AppCompatActivity(), BaseView, HasAndroidInjector { abstract class BaseActivity<T : BasePresenter<out BaseView>> : AppCompatActivity(), BaseView, HasAndroidInjector {
@ -35,10 +39,15 @@ abstract class BaseActivity<T : BasePresenter<out BaseView>> : AppCompatActivity
public override fun onCreate(savedInstanceState: Bundle?) { public override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this) AndroidInjection.inject(this)
themeManager.applyTheme(this) themeManager.applyActivityTheme(this)
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleLogger, true) supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleLogger, true)
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true) AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
if (SDK_INT >= LOLLIPOP) {
@Suppress("DEPRECATION")
setTaskDescription(ActivityManager.TaskDescription(null, null, getThemeAttrColor(R.attr.colorSurface)))
}
} }
override fun showError(text: String, error: Throwable) { override fun showError(text: String, error: Throwable) {

View File

@ -4,14 +4,15 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentPagerAdapter import androidx.fragment.app.FragmentPagerAdapter
class BaseFragmentPagerAdapter(private val fragmentManager: FragmentManager) : FragmentPagerAdapter(fragmentManager) { class BaseFragmentPagerAdapter(private val fragmentManager: FragmentManager) :
FragmentPagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
private val pages = mutableMapOf<Fragment, String?>() private val pages = mutableMapOf<Fragment, String?>()
var containerId = 0 var containerId = 0
fun getFragmentInstance(position: Int): Fragment? { fun getFragmentInstance(position: Int): Fragment? {
if (containerId == 0) throw IllegalArgumentException("Container id is 0") require(containerId != 0) { "Container id is 0" }
return fragmentManager.findFragmentByTag("android:switcher:$containerId:$position") return fragmentManager.findFragmentByTag("android:switcher:$containerId:$position")
} }

View File

@ -2,13 +2,13 @@ package io.github.wulkanowy.ui.base
import android.content.ClipData import android.content.ClipData
import android.content.ClipboardManager import android.content.ClipboardManager
import android.content.Context.CLIPBOARD_SERVICE
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Toast import android.widget.Toast
import android.widget.Toast.LENGTH_LONG import android.widget.Toast.LENGTH_LONG
import androidx.core.content.getSystemService
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import io.github.wulkanowy.R import io.github.wulkanowy.R
import kotlinx.android.synthetic.main.dialog_error.* import kotlinx.android.synthetic.main.dialog_error.*
@ -49,7 +49,7 @@ class ErrorDialog : DialogFragment() {
errorDialogContent.text = writer.toString() errorDialogContent.text = writer.toString()
errorDialogCopy.setOnClickListener { errorDialogCopy.setOnClickListener {
ClipData.newPlainText("wulkanowyError", writer.toString()).let { clip -> ClipData.newPlainText("wulkanowyError", writer.toString()).let { clip ->
(activity?.getSystemService(CLIPBOARD_SERVICE) as? ClipboardManager)?.primaryClip = clip activity?.getSystemService<ClipboardManager>()?.primaryClip = clip
} }
Toast.makeText(context, R.string.all_copied, LENGTH_LONG).show() Toast.makeText(context, R.string.all_copied, LENGTH_LONG).show()
} }

View File

@ -2,29 +2,31 @@ package io.github.wulkanowy.ui.base
import android.content.pm.PackageManager.GET_ACTIVITIES import android.content.pm.PackageManager.GET_ACTIVITIES
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_NO import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_NO
import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_YES import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_YES
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class ThemeManager @Inject constructor(private val preferencesRepository: PreferencesRepository) { class ThemeManager @Inject constructor(private val preferencesRepository: PreferencesRepository) {
fun applyTheme(activity: AppCompatActivity) { fun applyActivityTheme(activity: AppCompatActivity) {
if (isThemeApplicable(activity)) { if (isThemeApplicable(activity)) {
activity.delegate.apply { applyDefaultTheme()
when (preferencesRepository.appTheme) { if (preferencesRepository.appTheme == "black") activity.setTheme(R.style.WulkanowyTheme_Black)
"light" -> setLocalNightMode(MODE_NIGHT_NO)
"dark" -> setLocalNightMode(MODE_NIGHT_YES)
"black" -> {
setLocalNightMode(MODE_NIGHT_YES)
activity.setTheme(R.style.WulkanowyTheme_Black)
}
}
}
} }
} }
fun applyDefaultTheme() {
AppCompatDelegate.setDefaultNightMode(
if (preferencesRepository.appTheme == "light") MODE_NIGHT_NO
else MODE_NIGHT_YES
)
}
private fun isThemeApplicable(activity: AppCompatActivity): Boolean { private fun isThemeApplicable(activity: AppCompatActivity): Boolean {
return activity.packageManager.getPackageInfo(activity.packageName, GET_ACTIVITIES) return activity.packageManager.getPackageInfo(activity.packageName, GET_ACTIVITIES)
.activities.singleOrNull { it.name == activity::class.java.canonicalName }?.theme .activities.singleOrNull { it.name == activity::class.java.canonicalName }?.theme

View File

@ -1,19 +1,29 @@
package io.github.wulkanowy.ui.modules.about package io.github.wulkanowy.ui.modules.about
import android.content.Intent import android.content.Intent
import android.content.Intent.ACTION_SENDTO
import android.content.Intent.EXTRA_EMAIL
import android.content.Intent.EXTRA_SUBJECT
import android.content.Intent.EXTRA_TEXT
import android.graphics.drawable.Drawable
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import com.mikepenz.aboutlibraries.LibsBuilder import eu.davidea.flexibleadapter.FlexibleAdapter
import com.mikepenz.aboutlibraries.LibsFragmentCompat import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.about.license.LicenseFragment
import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.getCompatDrawable
import io.github.wulkanowy.utils.openInternetBrowser import io.github.wulkanowy.utils.openInternetBrowser
import io.github.wulkanowy.utils.withOnExtraListener import io.github.wulkanowy.utils.setOnItemClickListener
import kotlinx.android.synthetic.main.fragment_about.*
import javax.inject.Inject import javax.inject.Inject
class AboutFragment : BaseFragment(), AboutView, MainView.TitledView { class AboutFragment : BaseFragment(), AboutView, MainView.TitledView {
@ -22,63 +32,94 @@ class AboutFragment : BaseFragment(), AboutView, MainView.TitledView {
lateinit var presenter: AboutPresenter lateinit var presenter: AboutPresenter
@Inject @Inject
lateinit var fragmentCompat: LibsFragmentCompat lateinit var aboutAdapter: FlexibleAdapter<AbstractFlexibleItem<*>>
@Inject @Inject
lateinit var appInfo: AppInfo lateinit var appInfo: AppInfo
override val versionRes: Triple<String, String, Drawable?>?
get() = context?.run {
Triple(getString(R.string.about_version), "${appInfo.versionName} (${appInfo.versionCode})", getCompatDrawable(R.drawable.ic_all_about))
}
override val feedbackRes: Triple<String, String, Drawable?>?
get() = context?.run {
Triple(getString(R.string.about_feedback), getString(R.string.about_feedback_summary), getCompatDrawable(R.drawable.ic_about_feedback))
}
override val discordRes: Triple<String, String, Drawable?>?
get() = context?.run {
Triple(getString(R.string.about_discord), getString(R.string.about_discord_summary), getCompatDrawable(R.drawable.ic_about_discord))
}
override val homepageRes: Triple<String, String, Drawable?>?
get() = context?.run {
Triple(getString(R.string.about_homepage), getString(R.string.about_homepage_summary), getCompatDrawable(R.drawable.ic_about_homepage))
}
override val licensesRes: Triple<String, String, Drawable?>?
get() = context?.run {
Triple(getString(R.string.about_licenses), getString(R.string.about_licenses_summary), getCompatDrawable(R.drawable.ic_about_licenses))
}
override val privacyRes: Triple<String, String, Drawable?>?
get() = context?.run {
Triple(getString(R.string.about_privacy), getString(R.string.about_privacy_summary), getCompatDrawable(R.drawable.ic_about_privacy))
}
override val titleStringId get() = R.string.about_title
companion object { companion object {
fun newInstance() = AboutFragment() fun newInstance() = AboutFragment()
} }
override val titleStringId: Int
get() = R.string.about_title
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
presenter.onAttachView(this) return inflater.inflate(R.layout.fragment_about, container, false)
return Bundle().apply {
putSerializable("data", LibsBuilder()
.withAboutAppName(getString(R.string.app_name))
.withAboutVersionShown(true)
.withAboutIconShown(true)
.withLicenseShown(true)
.withAboutSpecial1(getString(R.string.about_discord_invite))
.withAboutSpecial2(getString(R.string.about_homepage))
.withAboutSpecial3(getString(R.string.about_feedback))
.withFields(R.string::class.java.fields)
.withCheckCachedDetection(false)
.withExcludedLibraries("fastadapter", "AndroidIconics", "Jsoup", "Retrofit", "okio",
"Butterknife", "CircleImageView")
.withOnExtraListener { presenter.onExtraSelect(it) })
}.let {
fragmentCompat.onCreateView(inflater.context, inflater, container, savedInstanceState, it)
}
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
fragmentCompat.onViewCreated(view, savedInstanceState) presenter.onAttachView(this)
} }
override fun openDiscordInviteView() { override fun initView() {
aboutAdapter.setOnItemClickListener(presenter::onItemSelected)
with(aboutRecycler) {
layoutManager = SmoothScrollLinearLayoutManager(context)
adapter = aboutAdapter
}
}
override fun updateData(header: AboutScrollableHeader, items: List<AboutItem>) {
with(aboutAdapter) {
removeAllScrollableHeaders()
addScrollableHeader(header)
updateDataSet(items)
}
}
override fun openDiscordInvite() {
context?.openInternetBrowser("https://discord.gg/vccAQBr", ::showMessage) context?.openInternetBrowser("https://discord.gg/vccAQBr", ::showMessage)
} }
override fun openHomepageWebView() { override fun openHomepage() {
context?.openInternetBrowser("https://wulkanowy.github.io/", ::showMessage) context?.openInternetBrowser("https://wulkanowy.github.io/", ::showMessage)
} }
override fun openEmailClientView() { override fun openEmailClient() {
val intent = Intent(Intent.ACTION_SENDTO).apply { val intent = Intent(ACTION_SENDTO)
data = Uri.parse("mailto:") .apply {
putExtra(Intent.EXTRA_EMAIL, Array(1) { "wulkanowyinc@gmail.com" }) data = Uri.parse("mailto:")
putExtra(Intent.EXTRA_SUBJECT, "Zgłoszenie błędu") putExtra(EXTRA_EMAIL, arrayOf("wulkanowyinc@gmail.com"))
putExtra(Intent.EXTRA_TEXT, "Tu umieść treść zgłoszenia\n\n" + "-".repeat(40) + "\n" + """ putExtra(EXTRA_SUBJECT, "Zgłoszenie błędu")
Build: ${appInfo.versionCode} putExtra(EXTRA_TEXT, "Tu umieść treść zgłoszenia\n\n${"-".repeat(40)}\n " +
SDK: ${appInfo.systemVersion} """
Device: ${appInfo.systemManufacturer} ${appInfo.systemModel} Build: ${appInfo.versionCode}
""".trimIndent()) SDK: ${appInfo.systemVersion}
} Device: ${appInfo.systemManufacturer} ${appInfo.systemModel}
""".trimIndent())
}
context?.let { context?.let {
if (intent.resolveActivity(it.packageManager) != null) { if (intent.resolveActivity(it.packageManager) != null) {
@ -89,8 +130,15 @@ class AboutFragment : BaseFragment(), AboutView, MainView.TitledView {
} }
} }
override fun openLicenses() {
(activity as? MainActivity)?.pushView(LicenseFragment.newInstance())
}
override fun openPrivacyPolicy() {
context?.openInternetBrowser("https://wulkanowy.github.io/polityka-prywatnosci.html", ::showMessage)
}
override fun onDestroyView() { override fun onDestroyView() {
fragmentCompat.onDestroyView()
presenter.onDetachView() presenter.onDetachView()
super.onDestroyView() super.onDestroyView()
} }

View File

@ -0,0 +1,56 @@
package io.github.wulkanowy.ui.modules.about
import android.graphics.drawable.Drawable
import android.view.View
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import eu.davidea.flexibleadapter.items.IFlexible
import eu.davidea.viewholders.FlexibleViewHolder
import io.github.wulkanowy.R
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.item_about.*
class AboutItem(
val title: String,
private val summary: String,
private val image: Drawable?
) : AbstractFlexibleItem<AboutItem.ViewHolder>() {
override fun getLayoutRes() = R.layout.item_about
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>) = ViewHolder(view, adapter)
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>, holder: ViewHolder, position: Int, payloads: MutableList<Any>) {
with(holder) {
aboutItemImage.setImageDrawable(image)
aboutItemTitle.text = title
aboutItemSummary.text = summary
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as AboutItem
if (title != other.title) return false
if (summary != other.summary) return false
if (image != other.image) return false
return true
}
override fun hashCode(): Int {
var result = title.hashCode()
result = 31 * result + summary.hashCode()
result = 31 * result + (image?.hashCode() ?: 0)
return result
}
class ViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>) : FlexibleViewHolder(view, adapter),
LayoutContainer {
override val containerView: View? get() = contentView
}
}

View File

@ -1,14 +0,0 @@
package io.github.wulkanowy.ui.modules.about
import com.mikepenz.aboutlibraries.LibsFragmentCompat
import dagger.Module
import dagger.Provides
import io.github.wulkanowy.di.scopes.PerFragment
@Module
class AboutModule {
@PerFragment
@Provides
fun provideLibsFragmentCompat() = LibsFragmentCompat()
}

View File

@ -1,9 +1,6 @@
package io.github.wulkanowy.ui.modules.about package io.github.wulkanowy.ui.modules.about
import com.mikepenz.aboutlibraries.Libs import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import com.mikepenz.aboutlibraries.Libs.SpecialButton.SPECIAL1
import com.mikepenz.aboutlibraries.Libs.SpecialButton.SPECIAL2
import com.mikepenz.aboutlibraries.Libs.SpecialButton.SPECIAL3
import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.data.repositories.student.StudentRepository
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
@ -21,28 +18,53 @@ class AboutPresenter @Inject constructor(
override fun onAttachView(view: AboutView) { override fun onAttachView(view: AboutView) {
super.onAttachView(view) super.onAttachView(view)
view.initView()
Timber.i("About view was initialized") Timber.i("About view was initialized")
loadData()
} }
fun onExtraSelect(type: Libs.SpecialButton?) { fun onItemSelected(item: AbstractFlexibleItem<*>) {
if (item !is AboutItem) return
view?.run { view?.run {
when (type) { when (item.title) {
SPECIAL1 -> { feedbackRes?.first -> {
Timber.i("Opening discord invide page") Timber.i("Opening email client ")
analytics.logEvent("open_page", "name" to "discord") openEmailClient()
openDiscordInviteView() analytics.logEvent("about_open", "name" to "feedback")
} }
SPECIAL2 -> { discordRes?.first -> {
Timber.i("Opening home page") Timber.i("Opening discord")
analytics.logEvent("open_page", "name" to "home") openDiscordInvite()
openHomepageWebView() analytics.logEvent("about_open", "name" to "discord")
} }
SPECIAL3 -> { homepageRes?.first -> {
Timber.i("Opening email client") Timber.i("Opening homepage")
analytics.logEvent("open_page", "name" to "email") openHomepage()
openEmailClientView() analytics.logEvent("about_open", "name" to "homepage")
}
licensesRes?.first -> {
Timber.i("Opening licenses view")
openLicenses()
analytics.logEvent("about_open", "name" to "licenses")
}
privacyRes?.first -> {
Timber.i("Opening privacy page ")
openPrivacyPolicy()
analytics.logEvent("about_open", "name" to "privacy")
} }
} }
} }
} }
private fun loadData() {
view?.run {
updateData(AboutScrollableHeader(), listOfNotNull(
versionRes?.let { (title, summary, image) -> AboutItem(title, summary, image) },
feedbackRes?.let { (title, summary, image) -> AboutItem(title, summary, image) },
discordRes?.let { (title, summary, image) -> AboutItem(title, summary, image) },
homepageRes?.let { (title, summary, image) -> AboutItem(title, summary, image) },
licensesRes?.let { (title, summary, image) -> AboutItem(title, summary, image) },
privacyRes?.let { (title, summary, image) -> AboutItem(title, summary, image) }))
}
}
} }

View File

@ -0,0 +1,41 @@
package io.github.wulkanowy.ui.modules.about
import android.view.View
import androidx.core.content.res.ResourcesCompat
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import eu.davidea.flexibleadapter.items.IFlexible
import eu.davidea.viewholders.FlexibleViewHolder
import io.github.wulkanowy.R
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.scrollable_header_about.*
class AboutScrollableHeader : AbstractFlexibleItem<AboutScrollableHeader.ViewHolder>() {
override fun getLayoutRes() = R.layout.scrollable_header_about
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>) = ViewHolder(view, adapter)
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>, holder: ViewHolder, position: Int, payloads: MutableList<Any>) {
with(holder) {
val context = contentView.context
val drawable = ResourcesCompat.getDrawableForDensity(context.resources, context.applicationInfo.icon, 640, null)
aboutScrollableHeaderIcon.setImageDrawable(drawable)
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
return true
}
override fun hashCode() = javaClass.hashCode()
class ViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>) : FlexibleViewHolder(view, adapter),
LayoutContainer {
override val containerView: View? get() = contentView
}
}

View File

@ -1,12 +1,33 @@
package io.github.wulkanowy.ui.modules.about package io.github.wulkanowy.ui.modules.about
import android.graphics.drawable.Drawable
import io.github.wulkanowy.ui.base.BaseView import io.github.wulkanowy.ui.base.BaseView
interface AboutView : BaseView { interface AboutView : BaseView {
fun openDiscordInviteView() val versionRes: Triple<String, String, Drawable?>?
fun openEmailClientView() val feedbackRes: Triple<String, String, Drawable?>?
fun openHomepageWebView() val discordRes: Triple<String, String, Drawable?>?
val homepageRes: Triple<String, String, Drawable?>?
val licensesRes: Triple<String, String, Drawable?>?
val privacyRes: Triple<String, String, Drawable?>?
fun initView()
fun updateData(header: AboutScrollableHeader, items: List<AboutItem>)
fun openDiscordInvite()
fun openEmailClient()
fun openHomepage()
fun openLicenses()
fun openPrivacyPolicy()
} }

View File

@ -0,0 +1,86 @@
package io.github.wulkanowy.ui.modules.about.license
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.View.GONE
import android.view.View.VISIBLE
import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog
import androidx.core.text.parseAsHtml
import com.mikepenz.aboutlibraries.Libs
import com.mikepenz.aboutlibraries.entity.Library
import dagger.Lazy
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.R
import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.setOnItemClickListener
import kotlinx.android.synthetic.main.fragment_license.*
import javax.inject.Inject
class LicenseFragment : BaseFragment(), LicenseView, MainView.TitledView {
@Inject
lateinit var presenter: LicensePresenter
@Inject
lateinit var licenseAdapter: FlexibleAdapter<AbstractFlexibleItem<*>>
@Inject
lateinit var libs: Lazy<Libs>
override val titleStringId get() = R.string.license_title
override val appLibraries: ArrayList<Library>?
get() = context?.let {
libs.get().prepareLibraries(it, emptyArray(), emptyArray(), autoDetect = true, checkCachedDetection = true, sort = true)
}
companion object {
fun newInstance() = LicenseFragment()
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_license, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
presenter.onAttachView(this)
}
override fun initView() {
with(licenseRecycler) {
layoutManager = SmoothScrollLinearLayoutManager(context)
adapter = licenseAdapter
}
licenseAdapter.setOnItemClickListener(presenter::onItemSelected)
}
override fun updateData(data: List<LicenseItem>) {
licenseAdapter.updateDataSet(data)
}
override fun openLicense(licenseHtml: String) {
context?.let {
AlertDialog.Builder(it).apply {
setTitle(R.string.license_dialog_title)
setMessage(licenseHtml.parseAsHtml())
setPositiveButton(android.R.string.ok) { _, _ -> }
show()
}
}
}
override fun showProgress(show: Boolean) {
licenseProgress.visibility = if (show) VISIBLE else GONE
}
override fun onDestroyView() {
presenter.onDetachView()
super.onDestroyView()
}
}

View File

@ -0,0 +1,44 @@
package io.github.wulkanowy.ui.modules.about.license
import android.view.View
import com.mikepenz.aboutlibraries.entity.Library
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import eu.davidea.flexibleadapter.items.IFlexible
import eu.davidea.viewholders.FlexibleViewHolder
import io.github.wulkanowy.R
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.item_license.*
class LicenseItem(val library: Library) : AbstractFlexibleItem<LicenseItem.ViewHolder>() {
override fun getLayoutRes() = R.layout.item_license
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>) = ViewHolder(view, adapter)
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>, holder: ViewHolder, position: Int, payloads: MutableList<Any>) {
with(holder) {
licenseItemName.text = library.libraryName
licenseItemSummary.text = library.license?.licenseName
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as LicenseItem
if (library != other.library) return false
return true
}
override fun hashCode() = library.hashCode()
class ViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>) : FlexibleViewHolder(view, adapter),
LayoutContainer {
override val containerView: View? get() = contentView
}
}

View File

@ -0,0 +1,15 @@
package io.github.wulkanowy.ui.modules.about.license
import android.content.Context
import com.mikepenz.aboutlibraries.Libs
import dagger.Module
import dagger.Provides
import io.github.wulkanowy.di.scopes.PerFragment
@Module
class LicenseModule {
@PerFragment
@Provides
fun provideLibs(context: Context) = Libs(context)
}

View File

@ -0,0 +1,40 @@
package io.github.wulkanowy.ui.modules.about.license
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.data.repositories.student.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.SchedulersProvider
import io.reactivex.Single
import javax.inject.Inject
class LicensePresenter @Inject constructor(
schedulers: SchedulersProvider,
errorHandler: ErrorHandler,
studentRepository: StudentRepository
) : BasePresenter<LicenseView>(errorHandler, studentRepository, schedulers) {
override fun onAttachView(view: LicenseView) {
super.onAttachView(view)
view.initView()
loadData()
}
fun onItemSelected(item: AbstractFlexibleItem<*>) {
if (item !is LicenseItem) return
view?.run { item.library.license?.licenseDescription?.let { openLicense(it) } }
}
private fun loadData() {
disposable.add(Single.fromCallable { view?.appLibraries }
.map {
val exclude = listOf("Android-Iconics", "CircleImageView", "FastAdapter", "Jsoup", "okio", "Retrofit")
it.filter { library -> !exclude.contains(library.libraryName) }
}
.map { it.map { library -> LicenseItem(library) } }
.subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread)
.doOnEvent { _, _ -> view?.showProgress(false) }
.subscribe({ view?.run { updateData(it) } }, { errorHandler.dispatch(it) }))
}
}

View File

@ -0,0 +1,17 @@
package io.github.wulkanowy.ui.modules.about.license
import com.mikepenz.aboutlibraries.entity.Library
import io.github.wulkanowy.ui.base.BaseView
interface LicenseView : BaseView {
val appLibraries: ArrayList<Library>?
fun initView()
fun updateData(data: List<LicenseItem>)
fun openLicense(licenseHtml: String)
fun showProgress(show: Boolean)
}

View File

@ -1,13 +1,16 @@
package io.github.wulkanowy.ui.modules.account package io.github.wulkanowy.ui.modules.account
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.graphics.PorterDuff
import android.view.View import android.view.View
import androidx.core.graphics.ColorUtils
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem 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.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.utils.getThemeAttrColor
import kotlinx.android.extensions.LayoutContainer import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.item_account.* import kotlinx.android.synthetic.main.item_account.*
@ -15,16 +18,19 @@ class AccountItem(val student: Student) : AbstractFlexibleItem<AccountItem.ViewH
override fun getLayoutRes() = R.layout.item_account override fun getLayoutRes() = R.layout.item_account
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>): ViewHolder { override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>) = ViewHolder(view, adapter)
return ViewHolder(view, adapter)
}
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>, holder: ViewHolder, position: Int, payloads: MutableList<Any>?) { override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>, holder: ViewHolder, position: Int, payloads: MutableList<Any>?) {
holder.apply { val context = holder.contentView.context
val colorImage = if (student.isCurrent) context.getThemeAttrColor(R.attr.colorPrimary)
else ColorUtils.setAlphaComponent(context.getThemeAttrColor(R.attr.colorOnSurface), 153)
with(holder) {
accountItemName.text = "${student.studentName} ${student.className}" accountItemName.text = "${student.studentName} ${student.className}"
accountItemSchool.text = student.schoolName accountItemSchool.text = student.schoolName
accountItemImage.setBackgroundResource(if (student.isCurrent) R.drawable.ic_account_circular_border else 0) accountItemImage.setColorFilter(colorImage, PorterDuff.Mode.SRC_IN)
} }
} }
@ -45,8 +51,9 @@ class AccountItem(val student: Student) : AbstractFlexibleItem<AccountItem.ViewH
return result return result
} }
class ViewHolder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter), LayoutContainer { class ViewHolder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter),
override val containerView: View LayoutContainer {
get() = contentView
override val containerView: View? get() = contentView
} }
} }

View File

@ -17,11 +17,13 @@ import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.attendance.summary.AttendanceSummaryFragment import io.github.wulkanowy.ui.modules.attendance.summary.AttendanceSummaryFragment
import io.github.wulkanowy.ui.modules.main.MainActivity 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.dpToPx
import io.github.wulkanowy.utils.setOnItemClickListener import io.github.wulkanowy.utils.setOnItemClickListener
import kotlinx.android.synthetic.main.fragment_attendance.* import kotlinx.android.synthetic.main.fragment_attendance.*
import javax.inject.Inject import javax.inject.Inject
class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MainChildView, MainView.TitledView { class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MainChildView,
MainView.TitledView {
@Inject @Inject
lateinit var presenter: AttendancePresenter lateinit var presenter: AttendancePresenter
@ -35,14 +37,11 @@ class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MainChildVie
fun newInstance() = AttendanceFragment() fun newInstance() = AttendanceFragment()
} }
override val titleStringId: Int override val titleStringId get() = R.string.attendance_title
get() = R.string.attendance_title
override val isViewEmpty: Boolean override val isViewEmpty get() = attendanceAdapter.isEmpty
get() = attendanceAdapter.isEmpty
override val currentStackSize: Int? override val currentStackSize get() = (activity as? MainActivity)?.currentStackSize
get() = (activity as? MainActivity)?.currentStackSize
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -60,21 +59,21 @@ class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MainChildVie
} }
override fun initView() { override fun initView() {
attendanceAdapter.apply { attendanceAdapter.setOnItemClickListener(presenter::onAttendanceItemSelected)
setOnItemClickListener { presenter.onAttendanceItemSelected(it) }
}
attendanceRecycler.run { with(attendanceRecycler) {
layoutManager = SmoothScrollLinearLayoutManager(context) layoutManager = SmoothScrollLinearLayoutManager(context)
adapter = attendanceAdapter adapter = attendanceAdapter
addItemDecoration(FlexibleItemDecoration(context) addItemDecoration(FlexibleItemDecoration(context)
.withDefaultDivider() .withDefaultDivider()
.withDrawDividerOnLastItem(false) .withDrawDividerOnLastItem(false))
)
} }
attendanceSwipe.setOnRefreshListener { presenter.onSwipeRefresh() }
attendanceSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
attendancePreviousButton.setOnClickListener { presenter.onPreviousDay() } attendancePreviousButton.setOnClickListener { presenter.onPreviousDay() }
attendanceNextButton.setOnClickListener { presenter.onNextDay() } attendanceNextButton.setOnClickListener { presenter.onNextDay() }
attendanceNavContainer.setElevationCompat(requireContext().dpToPx(8f))
} }
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {

View File

@ -15,6 +15,7 @@ import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.dpToPx
import io.github.wulkanowy.utils.setOnItemSelectedListener import io.github.wulkanowy.utils.setOnItemSelectedListener
import kotlinx.android.synthetic.main.fragment_attendance_summary.* import kotlinx.android.synthetic.main.fragment_attendance_summary.*
import javax.inject.Inject import javax.inject.Inject
@ -35,11 +36,9 @@ class AttendanceSummaryFragment : BaseFragment(), AttendanceSummaryView, MainVie
fun newInstance() = AttendanceSummaryFragment() fun newInstance() = AttendanceSummaryFragment()
} }
override val titleStringId: Int override val titleStringId get() = R.string.attendance_title
get() = R.string.attendance_title
override val isViewEmpty override val isViewEmpty get() = attendanceSummaryAdapter.isEmpty
get() = attendanceSummaryAdapter.isEmpty
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_attendance_summary, container, false) return inflater.inflate(R.layout.fragment_attendance_summary, container, false)
@ -52,25 +51,26 @@ class AttendanceSummaryFragment : BaseFragment(), AttendanceSummaryView, MainVie
} }
override fun initView() { override fun initView() {
attendanceSummaryRecycler.run { with(attendanceSummaryRecycler) {
layoutManager = SmoothScrollLinearLayoutManager(context) layoutManager = SmoothScrollLinearLayoutManager(context)
adapter = attendanceSummaryAdapter adapter = attendanceSummaryAdapter
} }
attendanceSummarySwipe.setOnRefreshListener { presenter.onSwipeRefresh() }
context?.let { attendanceSummarySwipe.setOnRefreshListener(presenter::onSwipeRefresh)
subjectsAdapter = ArrayAdapter(it, android.R.layout.simple_spinner_item, ArrayList<String>())
subjectsAdapter.setDropDownViewResource(R.layout.item_attendance_summary_subject)
}
attendanceSummarySubjects.run { subjectsAdapter = ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item, mutableListOf())
subjectsAdapter.setDropDownViewResource(R.layout.item_attendance_summary_subject)
with(attendanceSummarySubjects) {
adapter = subjectsAdapter adapter = subjectsAdapter
setOnItemSelectedListener { presenter.onSubjectSelected((it as TextView).text.toString()) } setOnItemSelectedListener<TextView> { presenter.onSubjectSelected(it?.text?.toString()) }
} }
attendanceSummarySubjectsContainer.setElevationCompat(requireContext().dpToPx(1f))
} }
override fun updateSubjects(data: ArrayList<String>) { override fun updateSubjects(data: ArrayList<String>) {
subjectsAdapter.run { with(subjectsAdapter) {
clear() clear()
addAll(data) addAll(data)
notifyDataSetChanged() notifyDataSetChanged()
@ -78,9 +78,9 @@ class AttendanceSummaryFragment : BaseFragment(), AttendanceSummaryView, MainVie
} }
override fun updateDataSet(data: List<AttendanceSummaryItem>, header: AttendanceSummaryScrollableHeader) { override fun updateDataSet(data: List<AttendanceSummaryItem>, header: AttendanceSummaryScrollableHeader) {
attendanceSummaryAdapter.apply { with(attendanceSummaryAdapter) {
updateDataSet(data, true) updateDataSet(data, true)
removeAllScrollableHeaders() removeAllScrollableFooters()
addScrollableHeader(header) addScrollableHeader(header)
} }
} }

View File

@ -46,7 +46,7 @@ class AttendanceSummaryPresenter @Inject constructor(
loadData(currentSubjectId, true) loadData(currentSubjectId, true)
} }
fun onSubjectSelected(name: String) { fun onSubjectSelected(name: String?) {
Timber.i("Select attendance summary subject $name") Timber.i("Select attendance summary subject $name")
view?.run { view?.run {
showContent(false) showContent(false)

View File

@ -16,6 +16,7 @@ import io.github.wulkanowy.data.db.entities.Exam
import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.main.MainActivity 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.dpToPx
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.*
import javax.inject.Inject import javax.inject.Inject
@ -34,11 +35,9 @@ class ExamFragment : BaseFragment(), ExamView, MainView.MainChildView, MainView.
fun newInstance() = ExamFragment() fun newInstance() = ExamFragment()
} }
override val titleStringId: Int override val titleStringId get() = R.string.exam_title
get() = R.string.exam_title
override val isViewEmpty: Boolean override val isViewEmpty get() = examAdapter.isEmpty
get() = examAdapter.isEmpty
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_exam, container, false) return inflater.inflate(R.layout.fragment_exam, container, false)
@ -51,20 +50,21 @@ class ExamFragment : BaseFragment(), ExamView, MainView.MainChildView, MainView.
} }
override fun initView() { override fun initView() {
examAdapter.run { examAdapter.setOnItemClickListener(presenter::onExamItemSelected)
setOnItemClickListener { presenter.onExamItemSelected(it) }
} with(examRecycler) {
examRecycler.run {
layoutManager = SmoothScrollLinearLayoutManager(context) layoutManager = SmoothScrollLinearLayoutManager(context)
adapter = examAdapter adapter = examAdapter
addItemDecoration(FlexibleItemDecoration(context) addItemDecoration(FlexibleItemDecoration(context)
.withDefaultDivider(R.layout.item_exam) .withDefaultDivider(R.layout.item_exam)
.withDrawDividerOnLastItem(false) .withDrawDividerOnLastItem(false))
)
} }
examSwipe.setOnRefreshListener { presenter.onSwipeRefresh() }
examSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
examPreviousButton.setOnClickListener { presenter.onPreviousWeek() } examPreviousButton.setOnClickListener { presenter.onPreviousWeek() }
examNextButton.setOnClickListener { presenter.onNextWeek() } examNextButton.setOnClickListener { presenter.onNextWeek() }
examNavContainer.setElevationCompat(requireContext().dpToPx(8f))
} }
override fun hideRefresh() { override fun hideRefresh() {

View File

@ -17,6 +17,7 @@ import io.github.wulkanowy.ui.modules.grade.details.GradeDetailsFragment
import io.github.wulkanowy.ui.modules.grade.statistics.GradeStatisticsFragment import io.github.wulkanowy.ui.modules.grade.statistics.GradeStatisticsFragment
import io.github.wulkanowy.ui.modules.grade.summary.GradeSummaryFragment import io.github.wulkanowy.ui.modules.grade.summary.GradeSummaryFragment
import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.dpToPx
import io.github.wulkanowy.utils.setOnSelectPageListener import io.github.wulkanowy.utils.setOnSelectPageListener
import kotlinx.android.synthetic.main.fragment_grade.* import kotlinx.android.synthetic.main.fragment_grade.*
import javax.inject.Inject import javax.inject.Inject
@ -37,11 +38,9 @@ class GradeFragment : BaseFragment(), GradeView, MainView.MainChildView, MainVie
fun newInstance() = GradeFragment() fun newInstance() = GradeFragment()
} }
override val titleStringId: Int override val titleStringId get() = R.string.grade_title
get() = R.string.grade_title
override val currentPageIndex: Int override val currentPageIndex get() = gradeViewPager.currentItem
get() = gradeViewPager.currentItem
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -64,7 +63,7 @@ class GradeFragment : BaseFragment(), GradeView, MainView.MainChildView, MainVie
} }
override fun initView() { override fun initView() {
pagerAdapter.apply { with(pagerAdapter) {
containerId = gradeViewPager.id containerId = gradeViewPager.id
addFragmentsWithTitle(mapOf( addFragmentsWithTitle(mapOf(
GradeDetailsFragment.newInstance() to getString(R.string.all_details), GradeDetailsFragment.newInstance() to getString(R.string.all_details),
@ -73,13 +72,18 @@ class GradeFragment : BaseFragment(), GradeView, MainView.MainChildView, MainVie
)) ))
} }
gradeViewPager.run { with(gradeViewPager) {
adapter = pagerAdapter adapter = pagerAdapter
offscreenPageLimit = 3 offscreenPageLimit = 3
setOnSelectPageListener { presenter.onPageSelected(it) } setOnSelectPageListener(presenter::onPageSelected)
} }
gradeTabLayout.setupWithViewPager(gradeViewPager)
gradeSwipe.setOnRefreshListener { presenter.onSwipeRefresh() } with(gradeTabLayout) {
setupWithViewPager(gradeViewPager)
setElevationCompat(context.dpToPx(4f))
}
gradeSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
} }
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
@ -117,19 +121,19 @@ class GradeFragment : BaseFragment(), GradeView, MainView.MainChildView, MainVie
} }
override fun showSemesterDialog(selectedIndex: Int) { override fun showSemesterDialog(selectedIndex: Int) {
arrayOf(getString(R.string.grade_semester, 1), val choices = arrayOf(
getString(R.string.grade_semester, 2)).also { array -> getString(R.string.grade_semester, 1),
context?.let { getString(R.string.grade_semester, 2)
AlertDialog.Builder(it) )
.setSingleChoiceItems(array, selectedIndex) { dialog, which ->
presenter.onSemesterSelected(which) AlertDialog.Builder(requireContext())
dialog.dismiss() .setSingleChoiceItems(choices, selectedIndex) { dialog, which ->
} presenter.onSemesterSelected(which)
.setTitle(R.string.grade_switch_semester) dialog.dismiss()
.setNegativeButton(android.R.string.cancel) { _, _ -> }
.show()
} }
} .setTitle(R.string.grade_switch_semester)
.setNegativeButton(android.R.string.cancel) { _, _ -> }
.show()
} }
fun onChildRefresh() { fun onChildRefresh() {

View File

@ -10,6 +10,7 @@ import io.github.wulkanowy.ui.modules.grade.details.GradeDetailsFragment
import io.github.wulkanowy.ui.modules.grade.statistics.GradeStatisticsFragment import io.github.wulkanowy.ui.modules.grade.statistics.GradeStatisticsFragment
import io.github.wulkanowy.ui.modules.grade.summary.GradeSummaryFragment import io.github.wulkanowy.ui.modules.grade.summary.GradeSummaryFragment
@Suppress("unused")
@Module @Module
abstract class GradeModule { abstract class GradeModule {

View File

@ -19,6 +19,7 @@ import io.github.wulkanowy.data.db.entities.GradeStatistics
import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.base.BaseFragment
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.utils.dpToPx
import io.github.wulkanowy.utils.getThemeAttrColor import io.github.wulkanowy.utils.getThemeAttrColor
import io.github.wulkanowy.utils.setOnItemSelectedListener import io.github.wulkanowy.utils.setOnItemSelectedListener
import kotlinx.android.synthetic.main.fragment_grade_statistics.* import kotlinx.android.synthetic.main.fragment_grade_statistics.*
@ -37,8 +38,7 @@ class GradeStatisticsFragment : BaseFragment(), GradeStatisticsView, GradeView.G
fun newInstance() = GradeStatisticsFragment() fun newInstance() = GradeStatisticsFragment()
} }
override val isViewEmpty override val isViewEmpty get() = gradeStatisticsChart.isEmpty
get() = gradeStatisticsChart.isEmpty
private lateinit var gradeColors: List<Pair<Int, Int>> private lateinit var gradeColors: List<Pair<Int, Int>>
@ -75,32 +75,30 @@ class GradeStatisticsFragment : BaseFragment(), GradeStatisticsView, GradeView.G
} }
override fun initView() { override fun initView() {
gradeStatisticsChart.run { with(gradeStatisticsChart) {
description.isEnabled = false description.isEnabled = false
setHoleColor(context.getThemeAttrColor(android.R.attr.windowBackground)) setHoleColor(context.getThemeAttrColor(android.R.attr.windowBackground))
setCenterTextColor(context.getThemeAttrColor(android.R.attr.textColorPrimary)) setCenterTextColor(context.getThemeAttrColor(android.R.attr.textColorPrimary))
animateXY(1000, 1000) animateXY(1000, 1000)
minAngleForSlices = 25f minAngleForSlices = 25f
legend.apply { legend.textColor = context.getThemeAttrColor(android.R.attr.textColorPrimary)
textColor = context.getThemeAttrColor(android.R.attr.textColorPrimary)
}
} }
context?.let { subjectsAdapter = ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item, mutableListOf())
subjectsAdapter = ArrayAdapter(it, android.R.layout.simple_spinner_item, ArrayList<String>()) subjectsAdapter.setDropDownViewResource(R.layout.item_attendance_summary_subject)
subjectsAdapter.setDropDownViewResource(R.layout.item_attendance_summary_subject)
}
gradeStatisticsSubjects.run { with(gradeStatisticsSubjects) {
adapter = subjectsAdapter adapter = subjectsAdapter
setOnItemSelectedListener { presenter.onSubjectSelected((it as TextView).text.toString()) } setOnItemSelectedListener<TextView> { presenter.onSubjectSelected(it?.text?.toString()) }
} }
gradeStatisticsSwipe.setOnRefreshListener { presenter.onSwipeRefresh() } gradeStatisticsSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
gradeStatisticsSubjectsContainer.setElevationCompat(requireContext().dpToPx(1f))
} }
override fun updateSubjects(data: ArrayList<String>) { override fun updateSubjects(data: ArrayList<String>) {
subjectsAdapter.run { with(subjectsAdapter) {
clear() clear()
addAll(data) addAll(data)
notifyDataSetChanged() notifyDataSetChanged()
@ -204,7 +202,7 @@ class GradeStatisticsFragment : BaseFragment(), GradeStatisticsView, GradeView.G
override fun onSaveInstanceState(outState: Bundle) { override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState) super.onSaveInstanceState(outState)
outState.putBoolean(GradeStatisticsFragment.SAVED_CHART_TYPE, presenter.currentIsSemester) outState.putBoolean(SAVED_CHART_TYPE, presenter.currentIsSemester)
} }
override fun onDestroyView() { override fun onDestroyView() {

View File

@ -62,7 +62,7 @@ class GradeStatisticsPresenter @Inject constructor(
view?.notifyParentRefresh() view?.notifyParentRefresh()
} }
fun onSubjectSelected(name: String) { fun onSubjectSelected(name: String?) {
Timber.i("Select grade stats subject $name") Timber.i("Select grade stats subject $name")
view?.run { view?.run {
showContent(false) showContent(false)
@ -71,8 +71,8 @@ class GradeStatisticsPresenter @Inject constructor(
showEmpty(false) showEmpty(false)
clearView() clearView()
} }
(subjects.singleOrNull { it.name == name }?.name).let { (subjects.singleOrNull { it.name == name }?.name)?.let {
if (it != currentSubjectName) loadData(currentSemesterId, name, currentIsSemester) if (it != currentSubjectName) loadData(currentSemesterId, it, currentIsSemester)
} }
} }

View File

@ -13,6 +13,7 @@ import io.github.wulkanowy.data.db.entities.Homework
import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.main.MainActivity 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.dpToPx
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.*
import javax.inject.Inject import javax.inject.Inject
@ -31,8 +32,7 @@ class HomeworkFragment : BaseFragment(), HomeworkView, MainView.TitledView {
fun newInstance() = HomeworkFragment() fun newInstance() = HomeworkFragment()
} }
override val titleStringId: Int override val titleStringId get() = R.string.homework_title
get() = R.string.homework_title
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_homework, container, false) return inflater.inflate(R.layout.fragment_homework, container, false)
@ -45,21 +45,21 @@ class HomeworkFragment : BaseFragment(), HomeworkView, MainView.TitledView {
} }
override fun initView() { override fun initView() {
homeworkAdapter.run { homeworkAdapter.setOnItemClickListener(presenter::onHomeworkItemSelected)
setOnItemClickListener { presenter.onHomeworkItemSelected(it) }
}
homeworkRecycler.run { with(homeworkRecycler) {
layoutManager = SmoothScrollLinearLayoutManager(context) layoutManager = SmoothScrollLinearLayoutManager(context)
adapter = homeworkAdapter adapter = homeworkAdapter
addItemDecoration(FlexibleItemDecoration(context) addItemDecoration(FlexibleItemDecoration(context)
.withDefaultDivider() .withDefaultDivider()
.withDrawDividerOnLastItem(false) .withDrawDividerOnLastItem(false))
)
} }
homeworkSwipe.setOnRefreshListener { presenter.onSwipeRefresh() }
homeworkSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
homeworkPreviousButton.setOnClickListener { presenter.onPreviousDay() } homeworkPreviousButton.setOnClickListener { presenter.onPreviousDay() }
homeworkNextButton.setOnClickListener { presenter.onNextDay() } homeworkNextButton.setOnClickListener { presenter.onNextDay() }
homeworkNavContainer.setElevationCompat(requireContext().dpToPx(8f))
} }
override fun updateData(data: List<HomeworkItem>) { override fun updateData(data: List<HomeworkItem>) {
@ -110,7 +110,7 @@ class HomeworkFragment : BaseFragment(), HomeworkView, MainView.TitledView {
override fun onSaveInstanceState(outState: Bundle) { override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState) super.onSaveInstanceState(outState)
outState.putLong(HomeworkFragment.SAVED_DATE_KEY, presenter.currentDate.toEpochDay()) outState.putLong(SAVED_DATE_KEY, presenter.currentDate.toEpochDay())
} }
override fun onDestroyView() { override fun onDestroyView() {

View File

@ -10,6 +10,7 @@ import io.github.wulkanowy.ui.modules.login.form.LoginFormFragment
import io.github.wulkanowy.ui.modules.login.studentselect.LoginStudentSelectFragment import io.github.wulkanowy.ui.modules.login.studentselect.LoginStudentSelectFragment
import io.github.wulkanowy.ui.modules.login.symbol.LoginSymbolFragment import io.github.wulkanowy.ui.modules.login.symbol.LoginSymbolFragment
@Suppress("unused")
@Module @Module
internal abstract class LoginModule { internal abstract class LoginModule {

View File

@ -10,6 +10,7 @@ import android.view.ViewGroup
import android.view.inputmethod.EditorInfo.IME_ACTION_DONE import android.view.inputmethod.EditorInfo.IME_ACTION_DONE
import android.view.inputmethod.EditorInfo.IME_NULL import android.view.inputmethod.EditorInfo.IME_NULL
import android.widget.ArrayAdapter import android.widget.ArrayAdapter
import androidx.core.widget.doOnTextChanged
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.base.BaseFragment
@ -17,8 +18,6 @@ import io.github.wulkanowy.ui.modules.login.LoginActivity
import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.hideSoftInput import io.github.wulkanowy.utils.hideSoftInput
import io.github.wulkanowy.utils.openInternetBrowser import io.github.wulkanowy.utils.openInternetBrowser
import io.github.wulkanowy.utils.setOnItemSelectedListener
import io.github.wulkanowy.utils.setOnTextChangedListener
import io.github.wulkanowy.utils.showSoftInput import io.github.wulkanowy.utils.showSoftInput
import kotlinx.android.synthetic.main.fragment_login_form.* import kotlinx.android.synthetic.main.fragment_login_form.*
import javax.inject.Inject import javax.inject.Inject
@ -35,14 +34,16 @@ class LoginFormFragment : BaseFragment(), LoginFormView {
fun newInstance() = LoginFormFragment() fun newInstance() = LoginFormFragment()
} }
override val formNameValue: String override val formNameValue get() = loginFormName.text.toString()
get() = loginFormName.text.toString()
override val formPassValue: String override val formPassValue get() = loginFormPass.text.toString()
get() = loginFormPass.text.toString()
override val formHostValue: String? override val formHostValue
get() = resources.getStringArray(R.array.endpoints_values)[loginFormHost.selectedItemPosition] get() = hostValues.getOrNull(hostKeys.indexOf(loginFormHost.text.toString()))
private lateinit var hostKeys: Array<String>
private lateinit var hostValues: Array<String>
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_login_form, container, false) return inflater.inflate(R.layout.fragment_login_form, container, false)
@ -54,9 +55,12 @@ class LoginFormFragment : BaseFragment(), LoginFormView {
} }
override fun initView() { override fun initView() {
loginFormName.setOnTextChangedListener { presenter.onNameTextChanged() } hostKeys = resources.getStringArray(R.array.endpoints_keys)
loginFormPass.setOnTextChangedListener { presenter.onPassTextChanged() } hostValues = resources.getStringArray(R.array.endpoints_values)
loginFormHost.setOnItemSelectedListener { presenter.onHostSelected() }
loginFormName.doOnTextChanged { _, _, _, _ -> presenter.onNameTextChanged() }
loginFormPass.doOnTextChanged { _, _, _, _ -> presenter.onPassTextChanged() }
loginFormHost.setOnItemClickListener { _, _, _, _ -> presenter.onHostSelected() }
loginFormSignIn.setOnClickListener { presenter.onSignInClick() } loginFormSignIn.setOnClickListener { presenter.onSignInClick() }
loginFormPrivacyLink.setOnClickListener { presenter.onPrivacyLinkClick() } loginFormPrivacyLink.setOnClickListener { presenter.onPrivacyLinkClick() }
@ -64,40 +68,44 @@ class LoginFormFragment : BaseFragment(), LoginFormView {
if (id == IME_ACTION_DONE || id == IME_NULL) loginFormSignIn.callOnClick() else false if (id == IME_ACTION_DONE || id == IME_NULL) loginFormSignIn.callOnClick() else false
} }
context?.let { with(loginFormHost) {
loginFormHost.adapter = ArrayAdapter.createFromResource(it, R.array.endpoints_keys, android.R.layout.simple_spinner_item) //Bug with filter in ExposedDropdownMenu on restoring state
.apply { setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) } isSaveEnabled = false
setText(hostKeys.getOrElse(0) { "" })
setAdapter(ArrayAdapter(context, R.layout.support_simple_spinner_dropdown_item, hostKeys))
keyListener = null
} }
} }
override fun setDefaultCredentials(name: String, pass: String) { override fun setCredentials(name: String, pass: String) {
loginFormName.setText(name) loginFormName.setText(name)
loginFormPass.setText(pass) loginFormPass.setText(pass)
} }
override fun setErrorNameRequired() { override fun setErrorNameRequired() {
loginFormNameLayout.run { with(loginFormNameLayout) {
requestFocus() requestFocus()
error = getString(R.string.login_field_required) error = getString(R.string.login_field_required)
} }
} }
override fun setErrorPassRequired(focus: Boolean) { override fun setErrorPassRequired(focus: Boolean) {
loginFormPassLayout.run { with(loginFormPassLayout) {
if (focus) requestFocus() if (focus) requestFocus()
error = getString(R.string.login_field_required) error = getString(R.string.login_field_required)
} }
} }
override fun setErrorPassInvalid(focus: Boolean) { override fun setErrorPassInvalid(focus: Boolean) {
loginFormPassLayout.run { with(loginFormPassLayout) {
if (focus) requestFocus() if (focus) requestFocus()
error = getString(R.string.login_invalid_password) error = getString(R.string.login_invalid_password)
} }
} }
override fun setErrorPassIncorrect() { override fun setErrorPassIncorrect() {
loginFormPassLayout.run { with(loginFormPassLayout) {
requestFocus() requestFocus()
error = getString(R.string.login_incorrect_password) error = getString(R.string.login_incorrect_password)
} }
@ -129,7 +137,7 @@ class LoginFormFragment : BaseFragment(), LoginFormView {
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
override fun showVersion() { override fun showVersion() {
loginFormVersion.apply { with(loginFormVersion) {
visibility = VISIBLE visibility = VISIBLE
text = "${getString(R.string.app_name)} ${appInfo.versionName}" text = "${getString(R.string.app_name)} ${appInfo.versionName}"
} }
@ -140,11 +148,12 @@ class LoginFormFragment : BaseFragment(), LoginFormView {
} }
override fun notifyParentAccountLogged(students: List<Student>) { override fun notifyParentAccountLogged(students: List<Student>) {
(activity as? LoginActivity)?.onFormFragmentAccountLogged(students, Triple( (activity as? LoginActivity)?.onFormFragmentAccountLogged(students,
loginFormName.text.toString(), Triple(
loginFormPass.text.toString(), loginFormName.text.toString(),
resources.getStringArray(R.array.endpoints_values)[loginFormHost.selectedItemPosition] loginFormPass.text.toString(),
)) resources.getStringArray(R.array.endpoints_values)[1]
))
} }
override fun openPrivacyPolicyPage() { override fun openPrivacyPolicyPage() {

View File

@ -40,7 +40,7 @@ class LoginFormPresenter @Inject constructor(
view?.apply { view?.apply {
clearPassError() clearPassError()
clearNameError() clearNameError()
if (formHostValue?.contains("fakelog") == true) setDefaultCredentials("jan@fakelog.cf", "jan123") if (formHostValue?.contains("fakelog") == true) setCredentials("jan@fakelog.cf", "jan123")
} }
} }

View File

@ -13,7 +13,7 @@ interface LoginFormView : BaseView {
val formHostValue: String? val formHostValue: String?
fun setDefaultCredentials(name: String, pass: String) fun setCredentials(name: String, pass: String)
fun setErrorNameRequired() fun setErrorNameRequired()

View File

@ -48,7 +48,7 @@ class LoginStudentSelectItem(val student: Student) : AbstractFlexibleItem<LoginS
get() = itemView get() = itemView
init { init {
loginItemCheck.setOnClickListener { super.onClick(loginItemContainer) } loginItemCheck.keyListener = null
} }
override fun onClick(view: View?) { override fun onClick(view: View?) {

View File

@ -9,12 +9,12 @@ import android.view.ViewGroup
import android.view.inputmethod.EditorInfo.IME_ACTION_DONE import android.view.inputmethod.EditorInfo.IME_ACTION_DONE
import android.view.inputmethod.EditorInfo.IME_NULL import android.view.inputmethod.EditorInfo.IME_NULL
import android.widget.ArrayAdapter import android.widget.ArrayAdapter
import androidx.core.widget.doOnTextChanged
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.login.LoginActivity import io.github.wulkanowy.ui.modules.login.LoginActivity
import io.github.wulkanowy.utils.hideSoftInput import io.github.wulkanowy.utils.hideSoftInput
import io.github.wulkanowy.utils.setOnTextChangedListener
import io.github.wulkanowy.utils.showSoftInput import io.github.wulkanowy.utils.showSoftInput
import kotlinx.android.synthetic.main.fragment_login_symbol.* import kotlinx.android.synthetic.main.fragment_login_symbol.*
import javax.inject.Inject import javax.inject.Inject
@ -45,7 +45,7 @@ class LoginSymbolFragment : BaseFragment(), LoginSymbolView {
override fun initView() { override fun initView() {
loginSymbolSignIn.setOnClickListener { presenter.attemptLogin(loginSymbolName.text.toString()) } loginSymbolSignIn.setOnClickListener { presenter.attemptLogin(loginSymbolName.text.toString()) }
loginSymbolName.setOnTextChangedListener { presenter.onSymbolTextChanged() } loginSymbolName.doOnTextChanged { _, _, _, _ -> presenter.onSymbolTextChanged() }
loginSymbolName.apply { loginSymbolName.apply {
setOnEditorActionListener { _, id, _ -> setOnEditorActionListener { _, id, _ ->

View File

@ -1,7 +1,7 @@
package io.github.wulkanowy.ui.modules.luckynumberwidget package io.github.wulkanowy.ui.modules.luckynumberwidget
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.data.db.SharedPrefHelper import io.github.wulkanowy.data.db.SharedPrefProvider
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.data.repositories.student.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.BasePresenter
@ -14,7 +14,7 @@ class LuckyNumberWidgetConfigurePresenter @Inject constructor(
schedulers: SchedulersProvider, schedulers: SchedulersProvider,
errorHandler: ErrorHandler, errorHandler: ErrorHandler,
studentRepository: StudentRepository, studentRepository: StudentRepository,
private val sharedPref: SharedPrefHelper private val sharedPref: SharedPrefProvider
) : BasePresenter<LuckyNumberWidgetConfigureView>(errorHandler, studentRepository, schedulers) { ) : BasePresenter<LuckyNumberWidgetConfigureView>(errorHandler, studentRepository, schedulers) {
private var appWidgetId: Int? = null private var appWidgetId: Int? = null

View File

@ -21,13 +21,14 @@ import android.view.View.VISIBLE
import android.widget.RemoteViews import android.widget.RemoteViews
import dagger.android.AndroidInjection import dagger.android.AndroidInjection
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.SharedPrefHelper import io.github.wulkanowy.data.db.SharedPrefProvider
import io.github.wulkanowy.data.db.entities.LuckyNumber import io.github.wulkanowy.data.db.entities.LuckyNumber
import io.github.wulkanowy.data.exceptions.NoCurrentStudentException
import io.github.wulkanowy.data.repositories.luckynumber.LuckyNumberRepository import io.github.wulkanowy.data.repositories.luckynumber.LuckyNumberRepository
import io.github.wulkanowy.data.repositories.semester.SemesterRepository import io.github.wulkanowy.data.repositories.semester.SemesterRepository
import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.data.repositories.student.StudentRepository
import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.main.MainView.MenuView import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.SchedulersProvider import io.github.wulkanowy.utils.SchedulersProvider
import io.reactivex.Maybe import io.reactivex.Maybe
import timber.log.Timber import timber.log.Timber
@ -51,7 +52,7 @@ class LuckyNumberWidgetProvider : BroadcastReceiver() {
lateinit var appWidgetManager: AppWidgetManager lateinit var appWidgetManager: AppWidgetManager
@Inject @Inject
lateinit var sharedPref: SharedPrefHelper lateinit var sharedPref: SharedPrefProvider
companion object { companion object {
fun getStudentWidgetKey(appWidgetId: Int) = "lucky_number_widget_student_$appWidgetId" fun getStudentWidgetKey(appWidgetId: Int) = "lucky_number_widget_student_$appWidgetId"
@ -74,8 +75,8 @@ class LuckyNumberWidgetProvider : BroadcastReceiver() {
getLuckyNumber(sharedPref.getLong(getStudentWidgetKey(appWidgetId), 0), appWidgetId)?.luckyNumber?.toString() ?: "#" getLuckyNumber(sharedPref.getLong(getStudentWidgetKey(appWidgetId), 0), appWidgetId)?.luckyNumber?.toString() ?: "#"
) )
setOnClickPendingIntent(R.id.luckyNumberWidgetContainer, setOnClickPendingIntent(R.id.luckyNumberWidgetContainer,
PendingIntent.getActivity(context, MenuView.LUCKY_NUMBER.id, PendingIntent.getActivity(context, MainView.Section.LUCKY_NUMBER.id,
MainActivity.getStartIntent(context, MenuView.LUCKY_NUMBER, true), FLAG_UPDATE_CURRENT)) MainActivity.getStartIntent(context, MainView.Section.LUCKY_NUMBER, true), FLAG_UPDATE_CURRENT))
}.also { }.also {
setStyles(it, intent) setStyles(it, intent)
appWidgetManager.updateAppWidget(appWidgetId, it) appWidgetManager.updateAppWidget(appWidgetId, it)
@ -114,7 +115,9 @@ class LuckyNumberWidgetProvider : BroadcastReceiver() {
.subscribeOn(schedulers.backgroundThread) .subscribeOn(schedulers.backgroundThread)
.blockingGet() .blockingGet()
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e, "An error has occurred in lucky number provider") if (e.cause !is NoCurrentStudentException) {
Timber.e(e, "An error has occurred in lucky number provider")
}
null null
} }
} }

View File

@ -4,16 +4,21 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK import android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
import android.os.Build.VERSION.SDK_INT
import android.os.Build.VERSION_CODES.LOLLIPOP
import android.os.Bundle import android.os.Bundle
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import androidx.core.content.ContextCompat import androidx.core.graphics.ColorUtils
import androidx.core.view.ViewCompat
import androidx.fragment.app.DialogFragment 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
import com.google.android.material.elevation.ElevationOverlayProvider
import com.ncapdevi.fragnav.FragNavController import com.ncapdevi.fragnav.FragNavController
import com.ncapdevi.fragnav.FragNavController.Companion.HIDE import com.ncapdevi.fragnav.FragNavController.Companion.HIDE
import dagger.Lazy
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.account.AccountDialog import io.github.wulkanowy.ui.modules.account.AccountDialog
@ -26,8 +31,9 @@ import io.github.wulkanowy.ui.modules.message.MessageFragment
import io.github.wulkanowy.ui.modules.more.MoreFragment import io.github.wulkanowy.ui.modules.more.MoreFragment
import io.github.wulkanowy.ui.modules.note.NoteFragment import io.github.wulkanowy.ui.modules.note.NoteFragment
import io.github.wulkanowy.ui.modules.timetable.TimetableFragment import io.github.wulkanowy.ui.modules.timetable.TimetableFragment
import io.github.wulkanowy.utils.dpToPx
import io.github.wulkanowy.utils.getThemeAttrColor import io.github.wulkanowy.utils.getThemeAttrColor
import io.github.wulkanowy.utils.safelyPopFragment import io.github.wulkanowy.utils.safelyPopFragments
import io.github.wulkanowy.utils.setOnViewChangeListener import io.github.wulkanowy.utils.setOnViewChangeListener
import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.activity_main.*
import javax.inject.Inject import javax.inject.Inject
@ -40,10 +46,13 @@ class MainActivity : BaseActivity<MainPresenter>(), MainView {
@Inject @Inject
lateinit var navController: FragNavController lateinit var navController: FragNavController
@Inject
lateinit var overlayProvider: Lazy<ElevationOverlayProvider>
companion object { companion object {
const val EXTRA_START_MENU = "extraStartMenu" const val EXTRA_START_MENU = "extraStartMenu"
fun getStartIntent(context: Context, startMenu: MainView.MenuView? = null, clear: Boolean = false): Intent { fun getStartIntent(context: Context, startMenu: MainView.Section? = null, clear: Boolean = false): Intent {
return Intent(context, MainActivity::class.java) return Intent(context, MainActivity::class.java)
.apply { .apply {
if (clear) flags = FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TASK if (clear) flags = FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TASK
@ -52,24 +61,21 @@ class MainActivity : BaseActivity<MainPresenter>(), MainView {
} }
} }
override val isRootView: Boolean override val isRootView get() = navController.isRootFragment
get() = navController.isRootFragment
override val currentViewTitle: String? override val currentStackSize get() = navController.currentStack?.size
get() = (navController.currentFrag as? MainView.TitledView)?.titleStringId?.let { getString(it) }
override val currentStackSize: Int? override val currentViewTitle get() = (navController.currentFrag as? MainView.TitledView)?.titleStringId?.let { getString(it) }
get() = navController.currentStack?.size
override var startMenuIndex = 0 override var startMenuIndex = 0
override var startMenuMoreIndex = -1 override var startMenuMoreIndex = -1
private val moreMenuFragments = listOf<Fragment>( private val moreMenuFragments = mapOf<Int, Fragment>(
MessageFragment.newInstance(), MainView.Section.MESSAGE.id to MessageFragment.newInstance(),
HomeworkFragment.newInstance(), MainView.Section.HOMEWORK.id to HomeworkFragment.newInstance(),
NoteFragment.newInstance(), MainView.Section.NOTE.id to NoteFragment.newInstance(),
LuckyNumberFragment.newInstance() MainView.Section.LUCKY_NUMBER.id to LuckyNumberFragment.newInstance()
) )
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -78,11 +84,11 @@ class MainActivity : BaseActivity<MainPresenter>(), MainView {
setSupportActionBar(mainToolbar) setSupportActionBar(mainToolbar)
messageContainer = mainFragmentContainer messageContainer = mainFragmentContainer
presenter.onAttachView(this, intent.getSerializableExtra(EXTRA_START_MENU) as? MainView.MenuView) presenter.onAttachView(this, intent.getSerializableExtra(EXTRA_START_MENU) as? MainView.Section)
navController.run { with(navController) {
initialize(startMenuIndex, savedInstanceState) initialize(startMenuIndex, savedInstanceState)
pushFragment(moreMenuFragments.getOrNull(startMenuMoreIndex)) pushFragment(moreMenuFragments[startMenuMoreIndex])
} }
} }
@ -92,30 +98,31 @@ class MainActivity : BaseActivity<MainPresenter>(), MainView {
} }
override fun initView() { override fun initView() {
mainBottomNav.run { with(mainToolbar) {
addItems( if (SDK_INT >= LOLLIPOP) stateListAnimator = null
listOf( setBackgroundColor(overlayProvider.get().getSurfaceColorWithOverlayIfNeeded(dpToPx(4f)))
AHBottomNavigationItem(R.string.grade_title, R.drawable.ic_menu_main_grade_26dp, 0), }
AHBottomNavigationItem(R.string.attendance_title, R.drawable.ic_menu_main_attendance_24dp, 0),
AHBottomNavigationItem(R.string.exam_title, R.drawable.ic_menu_main_exam_24dp, 0), with(mainBottomNav) {
AHBottomNavigationItem(R.string.timetable_title, R.drawable.ic_menu_main_timetable_24dp, 0), addItems(listOf(
AHBottomNavigationItem(R.string.more_title, R.drawable.ic_menu_main_more_24dp, 0) AHBottomNavigationItem(R.string.grade_title, R.drawable.ic_main_grade, 0),
) AHBottomNavigationItem(R.string.attendance_title, R.drawable.ic_main_attendance, 0),
) AHBottomNavigationItem(R.string.exam_title, R.drawable.ic_main_exam, 0),
accentColor = ContextCompat.getColor(context, R.color.colorPrimary) AHBottomNavigationItem(R.string.timetable_title, R.drawable.ic_main_timetable, 0),
inactiveColor = getThemeAttrColor(android.R.attr.textColorSecondary) AHBottomNavigationItem(R.string.more_title, R.drawable.ic_main_more, 0)
defaultBackgroundColor = getThemeAttrColor(R.attr.bottomNavBackground) ))
accentColor = getThemeAttrColor(R.attr.colorPrimary)
inactiveColor = ColorUtils.setAlphaComponent(getThemeAttrColor(R.attr.colorOnSurface), 153)
defaultBackgroundColor = overlayProvider.get().getSurfaceColorWithOverlayIfNeeded(dpToPx(8f))
titleState = ALWAYS_SHOW titleState = ALWAYS_SHOW
currentItem = startMenuIndex currentItem = startMenuIndex
isBehaviorTranslationEnabled = false isBehaviorTranslationEnabled = false
setTitleTextSizeInSp(10f, 10f) setTitleTextSizeInSp(10f, 10f)
setOnTabSelectedListener { position, wasSelected -> setOnTabSelectedListener(presenter::onTabSelected)
presenter.onTabSelected(position, wasSelected)
}
} }
navController.run { with(navController) {
setOnViewChangeListener { presenter.onViewChange() } setOnViewChangeListener(presenter::onViewChange)
fragmentHideStrategy = HIDE fragmentHideStrategy = HIDE
rootFragments = listOf( rootFragments = listOf(
GradeFragment.newInstance(), GradeFragment.newInstance(),
@ -152,6 +159,10 @@ class MainActivity : BaseActivity<MainPresenter>(), MainView {
navController.showDialogFragment(AccountDialog.newInstance()) navController.showDialogFragment(AccountDialog.newInstance())
} }
override fun showActionBarElevation(show: Boolean) {
ViewCompat.setElevation(mainToolbar, if (show) dpToPx(4f) else 0f)
}
override fun notifyMenuViewReselected() { override fun notifyMenuViewReselected() {
(navController.currentStack?.getOrNull(0) as? MainView.MainChildView)?.onFragmentReselected() (navController.currentStack?.getOrNull(0) as? MainView.MainChildView)?.onFragmentReselected()
} }
@ -164,8 +175,8 @@ class MainActivity : BaseActivity<MainPresenter>(), MainView {
navController.pushFragment(fragment) navController.pushFragment(fragment)
} }
override fun popView() { override fun popView(depth: Int) {
navController.safelyPopFragment() navController.safelyPopFragments(depth)
} }
override fun onBackPressed() { override fun onBackPressed() {

View File

@ -1,5 +1,6 @@
package io.github.wulkanowy.ui.modules.main package io.github.wulkanowy.ui.modules.main
import com.google.android.material.elevation.ElevationOverlayProvider
import com.ncapdevi.fragnav.FragNavController import com.ncapdevi.fragnav.FragNavController
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
@ -7,7 +8,8 @@ import dagger.android.ContributesAndroidInjector
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.di.scopes.PerFragment import io.github.wulkanowy.di.scopes.PerFragment
import io.github.wulkanowy.ui.modules.about.AboutFragment import io.github.wulkanowy.ui.modules.about.AboutFragment
import io.github.wulkanowy.ui.modules.about.AboutModule import io.github.wulkanowy.ui.modules.about.license.LicenseFragment
import io.github.wulkanowy.ui.modules.about.license.LicenseModule
import io.github.wulkanowy.ui.modules.account.AccountDialog import io.github.wulkanowy.ui.modules.account.AccountDialog
import io.github.wulkanowy.ui.modules.attendance.AttendanceFragment import io.github.wulkanowy.ui.modules.attendance.AttendanceFragment
import io.github.wulkanowy.ui.modules.attendance.summary.AttendanceSummaryFragment import io.github.wulkanowy.ui.modules.attendance.summary.AttendanceSummaryFragment
@ -19,15 +21,16 @@ import io.github.wulkanowy.ui.modules.luckynumber.LuckyNumberFragment
import io.github.wulkanowy.ui.modules.message.MessageFragment import io.github.wulkanowy.ui.modules.message.MessageFragment
import io.github.wulkanowy.ui.modules.message.MessageModule import io.github.wulkanowy.ui.modules.message.MessageModule
import io.github.wulkanowy.ui.modules.message.preview.MessagePreviewFragment import io.github.wulkanowy.ui.modules.message.preview.MessagePreviewFragment
import io.github.wulkanowy.ui.modules.mobiledevice.token.MobileDeviceTokenDialog
import io.github.wulkanowy.ui.modules.mobiledevice.MobileDeviceFragment import io.github.wulkanowy.ui.modules.mobiledevice.MobileDeviceFragment
import io.github.wulkanowy.ui.modules.mobiledevice.MobileDeviceModule import io.github.wulkanowy.ui.modules.mobiledevice.MobileDeviceModule
import io.github.wulkanowy.ui.modules.mobiledevice.token.MobileDeviceTokenDialog
import io.github.wulkanowy.ui.modules.more.MoreFragment import io.github.wulkanowy.ui.modules.more.MoreFragment
import io.github.wulkanowy.ui.modules.note.NoteFragment import io.github.wulkanowy.ui.modules.note.NoteFragment
import io.github.wulkanowy.ui.modules.settings.SettingsFragment import io.github.wulkanowy.ui.modules.settings.SettingsFragment
import io.github.wulkanowy.ui.modules.timetable.TimetableFragment import io.github.wulkanowy.ui.modules.timetable.TimetableFragment
import io.github.wulkanowy.ui.modules.timetable.completed.CompletedLessonsFragment import io.github.wulkanowy.ui.modules.timetable.completed.CompletedLessonsFragment
@Suppress("unused")
@Module @Module
abstract class MainModule { abstract class MainModule {
@ -39,6 +42,11 @@ abstract class MainModule {
fun provideFragNavController(activity: MainActivity): FragNavController { fun provideFragNavController(activity: MainActivity): FragNavController {
return FragNavController(activity.supportFragmentManager, R.id.mainFragmentContainer) return FragNavController(activity.supportFragmentManager, R.id.mainFragmentContainer)
} }
//In activities must be injected as Lazy
@JvmStatic
@Provides
fun provideElevationOverlayProvider(activity: MainActivity) = ElevationOverlayProvider(activity)
} }
@PerFragment @PerFragment
@ -74,7 +82,7 @@ abstract class MainModule {
abstract fun bindTimetableFragment(): TimetableFragment abstract fun bindTimetableFragment(): TimetableFragment
@PerFragment @PerFragment
@ContributesAndroidInjector(modules = [AboutModule::class]) @ContributesAndroidInjector
abstract fun bindAboutFragment(): AboutFragment abstract fun bindAboutFragment(): AboutFragment
@PerFragment @PerFragment
@ -108,4 +116,8 @@ abstract class MainModule {
@PerFragment @PerFragment
@ContributesAndroidInjector @ContributesAndroidInjector
abstract fun bindMobileDeviceDialog(): MobileDeviceTokenDialog abstract fun bindMobileDeviceDialog(): MobileDeviceTokenDialog
@PerFragment
@ContributesAndroidInjector(modules = [LicenseModule::class])
abstract fun bindLicenseFragment(): LicenseFragment
} }

View File

@ -5,6 +5,8 @@ import io.github.wulkanowy.data.repositories.student.StudentRepository
import io.github.wulkanowy.services.sync.SyncManager import io.github.wulkanowy.services.sync.SyncManager
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 io.github.wulkanowy.ui.modules.main.MainView.Section.GRADE
import io.github.wulkanowy.ui.modules.main.MainView.Section.MESSAGE
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 timber.log.Timber import timber.log.Timber
@ -19,7 +21,7 @@ class MainPresenter @Inject constructor(
private val analytics: FirebaseAnalyticsHelper private val analytics: FirebaseAnalyticsHelper
) : BasePresenter<MainView>(errorHandler, studentRepository, schedulers) { ) : BasePresenter<MainView>(errorHandler, studentRepository, schedulers) {
fun onAttachView(view: MainView, initMenu: MainView.MenuView?) { fun onAttachView(view: MainView, initMenu: MainView.Section?) {
super.onAttachView(view) super.onAttachView(view)
view.apply { view.apply {
getProperViewIndexes(initMenu).let { (main, more) -> getProperViewIndexes(initMenu).let { (main, more) ->
@ -34,8 +36,9 @@ class MainPresenter @Inject constructor(
analytics.logEvent("app_open", "destination" to initMenu?.name) analytics.logEvent("app_open", "destination" to initMenu?.name)
} }
fun onViewChange() { fun onViewChange(section: MainView.Section?) {
view?.apply { view?.apply {
showActionBarElevation(section != GRADE && section != MESSAGE)
currentViewTitle?.let { setViewTitle(it) } currentViewTitle?.let { setViewTitle(it) }
currentStackSize?.let { currentStackSize?.let {
if (it > 1) showHomeArrow(true) if (it > 1) showHomeArrow(true)
@ -77,10 +80,10 @@ class MainPresenter @Inject constructor(
} == true } == true
} }
private fun getProperViewIndexes(initMenu: MainView.MenuView?): Pair<Int, Int> { private fun getProperViewIndexes(initMenu: MainView.Section?): Pair<Int, Int> {
return when { return when (initMenu?.id) {
initMenu?.id in 0..3 -> initMenu!!.id to -1 in 0..3 -> initMenu!!.id to -1
(initMenu?.id ?: 0) > 3 -> 4 to initMenu!!.id - 4 in 4..10 -> 4 to initMenu!!.id
else -> prefRepository.startMenuIndex to -1 else -> prefRepository.startMenuIndex to -1
} }
} }

View File

@ -22,11 +22,13 @@ interface MainView : BaseView {
fun showAccountPicker() fun showAccountPicker()
fun showActionBarElevation(show: Boolean)
fun notifyMenuViewReselected() fun notifyMenuViewReselected()
fun setViewTitle(title: String) fun setViewTitle(title: String)
fun popView() fun popView(depth: Int = 1)
interface MainChildView { interface MainChildView {
@ -38,14 +40,17 @@ interface MainView : BaseView {
val titleStringId: Int val titleStringId: Int
} }
enum class MenuView(val id: Int) { enum class Section(val id: Int) {
GRADE(0), GRADE(0),
ATTENDANCE(1), ATTENDANCE(1),
EXAM(2), EXAM(2),
TIMETABLE(3), TIMETABLE(3),
MESSAGE(4), MORE(4),
HOMEWORK(5), MESSAGE(5),
NOTE(6), HOMEWORK(6),
LUCKY_NUMBER(7), NOTE(7),
LUCKY_NUMBER(8),
SETTINGS(9),
ABOUT(10)
} }
} }

View File

@ -16,6 +16,7 @@ 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.send.SendMessageActivity import io.github.wulkanowy.ui.modules.message.send.SendMessageActivity
import io.github.wulkanowy.ui.modules.message.tab.MessageTabFragment import io.github.wulkanowy.ui.modules.message.tab.MessageTabFragment
import io.github.wulkanowy.utils.dpToPx
import io.github.wulkanowy.utils.setOnSelectPageListener import io.github.wulkanowy.utils.setOnSelectPageListener
import kotlinx.android.synthetic.main.fragment_message.* import kotlinx.android.synthetic.main.fragment_message.*
import javax.inject.Inject import javax.inject.Inject
@ -32,11 +33,9 @@ class MessageFragment : BaseFragment(), MessageView, MainView.TitledView {
fun newInstance() = MessageFragment() fun newInstance() = MessageFragment()
} }
override val titleStringId: Int override val titleStringId get() = R.string.message_title
get() = R.string.message_title
override val currentPageIndex: Int override val currentPageIndex get() = messageViewPager.currentItem
get() = messageViewPager.currentItem
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_message, container, false) return inflater.inflate(R.layout.fragment_message, container, false)
@ -48,7 +47,7 @@ class MessageFragment : BaseFragment(), MessageView, MainView.TitledView {
} }
override fun initView() { override fun initView() {
pagerAdapter.apply { with(pagerAdapter) {
containerId = messageViewPager.id containerId = messageViewPager.id
addFragmentsWithTitle(mapOf( addFragmentsWithTitle(mapOf(
MessageTabFragment.newInstance(RECEIVED) to getString(R.string.message_inbox), MessageTabFragment.newInstance(RECEIVED) to getString(R.string.message_inbox),
@ -57,12 +56,16 @@ class MessageFragment : BaseFragment(), MessageView, MainView.TitledView {
)) ))
} }
messageViewPager.run { with(messageViewPager) {
adapter = pagerAdapter adapter = pagerAdapter
offscreenPageLimit = 2 offscreenPageLimit = 2
setOnSelectPageListener { presenter.onPageSelected(it) } setOnSelectPageListener(presenter::onPageSelected)
}
with(messageTabLayout) {
setupWithViewPager(messageViewPager)
setElevationCompat(context.dpToPx(4f))
} }
messageTabLayout.setupWithViewPager(messageViewPager)
openSendMessageButton.setOnClickListener { presenter.onSendMessageButtonClicked() } openSendMessageButton.setOnClickListener { presenter.onSendMessageButtonClicked() }
} }

View File

@ -8,6 +8,7 @@ import io.github.wulkanowy.di.scopes.PerFragment
import io.github.wulkanowy.ui.base.BaseFragmentPagerAdapter 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
@Suppress("unused")
@Module @Module
abstract class MessageModule { abstract class MessageModule {

View File

@ -1,30 +0,0 @@
package io.github.wulkanowy.ui.modules.message.send
import android.graphics.drawable.Drawable
import android.net.Uri
import com.pchmn.materialchips.model.ChipInterface
import io.github.wulkanowy.data.db.entities.Recipient
class RecipientChip(var recipient: Recipient) : ChipInterface {
override fun getAvatarDrawable(): Drawable? = null
override fun getAvatarUri(): Uri? = null
override fun getId(): Any = recipient.id
override fun getLabel(): String = recipient.name
override fun getInfo(): String {
return recipient.realName.run {
substringBeforeLast("-").let { sub ->
when {
(sub == this) -> this
(sub.indexOf('(') != -1) -> indexOf("(").let { substring(if (it != -1) it else 0) }
(sub.indexOf('[') != -1) -> indexOf("[").let { substring(if (it != -1) it else 0) }
else -> substringAfter('-')
}
}.trim()
}
}
}

View File

@ -0,0 +1,14 @@
package io.github.wulkanowy.ui.modules.message.send
import io.github.wulkanowy.data.db.entities.Recipient
import io.github.wulkanowy.materialchipsinput.ChipItem
data class RecipientChipItem(
override val title: String,
override val summary: String,
val recipient: Recipient
) : ChipItem

View File

@ -2,18 +2,20 @@ package io.github.wulkanowy.ui.modules.message.send
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.graphics.Rect
import android.os.Bundle import android.os.Bundle
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.TouchDelegate
import android.view.View.GONE import android.view.View.GONE
import android.view.View.VISIBLE import android.view.View.VISIBLE
import android.widget.Toast import android.widget.Toast
import android.widget.Toast.LENGTH_LONG import android.widget.Toast.LENGTH_LONG
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.Message
import io.github.wulkanowy.data.db.entities.Recipient
import io.github.wulkanowy.data.db.entities.ReportingUnit import io.github.wulkanowy.data.db.entities.ReportingUnit
import io.github.wulkanowy.ui.base.BaseActivity import io.github.wulkanowy.ui.base.BaseActivity
import io.github.wulkanowy.utils.dpToPx
import io.github.wulkanowy.utils.hideSoftInput import io.github.wulkanowy.utils.hideSoftInput
import io.github.wulkanowy.utils.showSoftInput import io.github.wulkanowy.utils.showSoftInput
import kotlinx.android.synthetic.main.activity_send_message.* import kotlinx.android.synthetic.main.activity_send_message.*
@ -38,14 +40,18 @@ class SendMessageActivity : BaseActivity<SendMessagePresenter>(), SendMessageVie
} }
} }
override val formRecipientsData: List<Recipient> override val isDropdownListVisible: Boolean
get() = (sendMessageRecipientsInput.selectedChipList).map { (it as RecipientChip).recipient } get() = sendMessageTo.isDropdownListVisible
@Suppress("UNCHECKED_CAST")
override val formRecipientsData: List<RecipientChipItem>
get() = sendMessageTo.addedChipItems as List<RecipientChipItem>
override val formSubjectValue: String override val formSubjectValue: String
get() = sendMessageSubjectInput.text.toString() get() = sendMessageSubject.text.toString()
override val formContentValue: String override val formContentValue: String
get() = sendMessageContentInput.text.toString() get() = sendMessageMessageContent.text.toString()
override val messageRequiredRecipients: String override val messageRequiredRecipients: String
get() = getString(R.string.message_required_recipients) get() = getString(R.string.message_required_recipients)
@ -66,6 +72,12 @@ class SendMessageActivity : BaseActivity<SendMessagePresenter>(), SendMessageVie
presenter.onAttachView(this, intent.getSerializableExtra(EXTRA_MESSAGE) as? Message, intent.getSerializableExtra(EXTRA_REPLY) as? Boolean) presenter.onAttachView(this, intent.getSerializableExtra(EXTRA_MESSAGE) as? Message, intent.getSerializableExtra(EXTRA_REPLY) as? Boolean)
} }
override fun initView() {
setUpExtendedHitArea()
sendMessageScroll.setOnTouchListener { _, _ -> presenter.onTouchScroll() }
sendMessageTo.onTextChangeListener = presenter::onRecipientsTextChange
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean { override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.action_menu_send_message, menu) menuInflater.inflate(R.menu.action_menu_send_message, menu)
return true return true
@ -81,15 +93,15 @@ class SendMessageActivity : BaseActivity<SendMessagePresenter>(), SendMessageVie
} }
override fun setReportingUnit(unit: ReportingUnit) { override fun setReportingUnit(unit: ReportingUnit) {
sendMessageFromTextView.setText(unit.senderName) sendMessageFrom.text = unit.senderName
} }
override fun setRecipients(recipients: List<Recipient>) { override fun setRecipients(recipients: List<RecipientChipItem>) {
sendMessageRecipientsInput.filterableList = recipients.map { RecipientChip(it) } sendMessageTo.filterableChipItems = recipients
} }
override fun setSelectedRecipients(recipients: List<Recipient>) { override fun setSelectedRecipients(recipients: List<RecipientChipItem>) {
recipients.map { sendMessageRecipientsInput.addChip(RecipientChip(it)) } sendMessageTo.addChips(recipients)
} }
override fun showProgress(show: Boolean) { override fun showProgress(show: Boolean) {
@ -109,11 +121,11 @@ class SendMessageActivity : BaseActivity<SendMessagePresenter>(), SendMessageVie
} }
override fun setSubject(subject: String) { override fun setSubject(subject: String) {
sendMessageSubjectInput.setText(subject) sendMessageSubject.setText(subject)
} }
override fun setContent(content: String) { override fun setContent(content: String) {
sendMessageContentInput.setText(content) sendMessageMessageContent.setText(content)
} }
override fun showMessage(text: String) { override fun showMessage(text: String) {
@ -124,7 +136,41 @@ class SendMessageActivity : BaseActivity<SendMessagePresenter>(), SendMessageVie
if (show) showSoftInput() else hideSoftInput() if (show) showSoftInput() else hideSoftInput()
} }
override fun hideDropdownList() {
sendMessageTo.hideDropdownList()
}
override fun scrollToRecipients() {
sendMessageScroll.post {
sendMessageScroll.scrollTo(0, sendMessageTo.bottom - dpToPx(53f).toInt())
}
}
override fun popView() { override fun popView() {
onBackPressed() onBackPressed()
} }
private fun setUpExtendedHitArea() {
fun extendHitArea() {
val containerHitRect = Rect().apply {
sendMessageContent.getHitRect(this)
}
val contentHitRect = Rect().apply {
sendMessageMessageContent.getHitRect(this)
}
contentHitRect.top = contentHitRect.bottom
contentHitRect.bottom = containerHitRect.bottom
sendMessageContent.touchDelegate = TouchDelegate(contentHitRect, sendMessageMessageContent)
}
sendMessageMessageContent.post {
sendMessageMessageContent.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
extendHitArea()
}
extendHitArea()
}
}
} }

View File

@ -32,6 +32,7 @@ class SendMessagePresenter @Inject constructor(
fun onAttachView(view: SendMessageView, message: Message?, reply: Boolean?) { fun onAttachView(view: SendMessageView, message: Message?, reply: Boolean?) {
super.onAttachView(view) super.onAttachView(view)
view.initView()
Timber.i("Send message view was initialized") Timber.i("Send message view was initialized")
loadData(message, reply) loadData(message, reply)
view.apply { view.apply {
@ -54,15 +55,47 @@ class SendMessagePresenter @Inject constructor(
} }
} }
fun onTouchScroll(): Boolean {
return view?.run {
if (isDropdownListVisible) {
hideDropdownList()
true
} else false
} == true
}
fun onRecipientsTextChange(text: String) {
if (text.isBlank()) return
view?.scrollToRecipients()
}
fun onUpNavigate(): Boolean { fun onUpNavigate(): Boolean {
view?.popView() view?.popView()
return true return true
} }
fun onSend(): Boolean {
view?.run {
when {
formRecipientsData.isEmpty() -> showMessage(messageRequiredRecipients)
formContentValue.length < 3 -> showMessage(messageContentMinLength)
else -> {
sendMessage(
subject = formSubjectValue,
content = formContentValue,
recipients = formRecipientsData.map { it.recipient }
)
return true
}
}
}
return false
}
private fun loadData(message: Message?, reply: Boolean?) { private fun loadData(message: Message?, reply: Boolean?) {
var reportingUnit: ReportingUnit? = null var reportingUnit: ReportingUnit? = null
var recipients: List<Recipient> = emptyList() var recipientChips: List<RecipientChipItem> = emptyList()
var selectedRecipient: List<Recipient> = emptyList() var selectedRecipientChips: List<RecipientChipItem> = emptyList()
Timber.i("Loading recipients started") Timber.i("Loading recipients started")
disposable.add(studentRepository.getCurrentStudent() disposable.add(studentRepository.getCurrentStudent()
@ -73,14 +106,14 @@ class SendMessagePresenter @Inject constructor(
.flatMap { recipientRepository.getRecipients(student, 2, it).toMaybe() } .flatMap { recipientRepository.getRecipients(student, 2, it).toMaybe() }
.doOnSuccess { .doOnSuccess {
Timber.i("Loading recipients result: Success, fetched %d recipients", it.size) Timber.i("Loading recipients result: Success, fetched %d recipients", it.size)
recipients = it recipientChips = createChips(it)
} }
.flatMapCompletable { .flatMapCompletable {
if (message == null || reply != true) Completable.complete() if (message == null || reply != true) Completable.complete()
else recipientRepository.getMessageRecipients(student, message) else recipientRepository.getMessageRecipients(student, message)
.doOnSuccess { .doOnSuccess {
Timber.i("Loaded message recipients to reply result: Success, fetched %d recipients", it.size) Timber.i("Loaded message recipients to reply result: Success, fetched %d recipients", it.size)
selectedRecipient = it selectedRecipientChips = createChips(it)
} }
.ignoreElement() .ignoreElement()
} }
@ -95,11 +128,11 @@ class SendMessagePresenter @Inject constructor(
} }
.doFinally { view?.run { showProgress(false) } } .doFinally { view?.run { showProgress(false) } }
.subscribe({ .subscribe({
view?.apply { view?.run {
if (reportingUnit !== null) { if (reportingUnit !== null) {
reportingUnit?.let { setReportingUnit(it) } reportingUnit?.let { setReportingUnit(it) }
setRecipients(recipients) setRecipients(recipientChips)
if (selectedRecipient.isNotEmpty()) setSelectedRecipients(selectedRecipient) if (selectedRecipientChips.isNotEmpty()) setSelectedRecipients(selectedRecipientChips)
showContent(true) showContent(true)
} else { } else {
Timber.e("Loading recipients result: Can't find the reporting unit") Timber.e("Loading recipients result: Can't find the reporting unit")
@ -145,21 +178,29 @@ class SendMessagePresenter @Inject constructor(
) )
} }
fun onSend(): Boolean { private fun createChips(recipients: List<Recipient>): List<RecipientChipItem> {
view?.run { fun generateCorrectSummary(recipientRealName: String): String {
when { val substring = recipientRealName.substringBeforeLast("-")
formRecipientsData.isEmpty() -> showMessage(messageRequiredRecipients) return when {
formContentValue.length < 3 -> showMessage(messageContentMinLength) substring == recipientRealName -> recipientRealName
else -> { substring.indexOf("(") != -1 -> {
sendMessage( recipientRealName.indexOf("(")
subject = formSubjectValue, .let { recipientRealName.substring(if (it != -1) it else 0) }
content = formContentValue,
recipients = formRecipientsData
)
return true
} }
} substring.indexOf("[") != -1 -> {
recipientRealName.indexOf("[")
.let { recipientRealName.substring(if (it != -1) it else 0) }
}
else -> recipientRealName.substringAfter("-")
}.trim()
}
return recipients.map {
RecipientChipItem(
title = it.name,
summary = generateCorrectSummary(it.realName),
recipient = it
)
} }
return false
} }
} }

View File

@ -1,12 +1,13 @@
package io.github.wulkanowy.ui.modules.message.send package io.github.wulkanowy.ui.modules.message.send
import io.github.wulkanowy.data.db.entities.Recipient
import io.github.wulkanowy.data.db.entities.ReportingUnit import io.github.wulkanowy.data.db.entities.ReportingUnit
import io.github.wulkanowy.ui.base.BaseView import io.github.wulkanowy.ui.base.BaseView
interface SendMessageView : BaseView { interface SendMessageView : BaseView {
val formRecipientsData: List<Recipient> val isDropdownListVisible: Boolean
val formRecipientsData: List<RecipientChipItem>
val formSubjectValue: String val formSubjectValue: String
@ -18,11 +19,13 @@ interface SendMessageView : BaseView {
val messageSuccess: String val messageSuccess: String
fun initView()
fun setReportingUnit(unit: ReportingUnit) fun setReportingUnit(unit: ReportingUnit)
fun setRecipients(recipients: List<Recipient>) fun setRecipients(recipients: List<RecipientChipItem>)
fun setSelectedRecipients(recipients: List<Recipient>) fun setSelectedRecipients(recipients: List<RecipientChipItem>)
fun showProgress(show: Boolean) fun showProgress(show: Boolean)
@ -38,5 +41,9 @@ interface SendMessageView : BaseView {
fun showSoftInput(show: Boolean) fun showSoftInput(show: Boolean)
fun hideDropdownList()
fun scrollToRecipients()
fun popView() fun popView()
} }

View File

@ -5,7 +5,6 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.content.ContextCompat
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
@ -20,6 +19,7 @@ import io.github.wulkanowy.ui.modules.message.MessageFragment
import io.github.wulkanowy.ui.modules.mobiledevice.MobileDeviceFragment import io.github.wulkanowy.ui.modules.mobiledevice.MobileDeviceFragment
import io.github.wulkanowy.ui.modules.note.NoteFragment import io.github.wulkanowy.ui.modules.note.NoteFragment
import io.github.wulkanowy.ui.modules.settings.SettingsFragment import io.github.wulkanowy.ui.modules.settings.SettingsFragment
import io.github.wulkanowy.utils.getCompatDrawable
import io.github.wulkanowy.utils.setOnItemClickListener import io.github.wulkanowy.utils.setOnItemClickListener
import kotlinx.android.synthetic.main.fragment_more.* import kotlinx.android.synthetic.main.fragment_more.*
import javax.inject.Inject import javax.inject.Inject
@ -39,60 +39,26 @@ class MoreFragment : BaseFragment(), MoreView, MainView.TitledView, MainView.Mai
override val titleStringId: Int override val titleStringId: Int
get() = R.string.more_title get() = R.string.more_title
override val messagesRes: Pair<String, Drawable?>? override val messagesRes: Pair<String, Drawable?>?
get() { get() = context?.run { getString(R.string.message_title) to getCompatDrawable(R.drawable.ic_more_messages) }
return context?.run {
getString(R.string.message_title) to
ContextCompat.getDrawable(this, R.drawable.ic_more_messages_24dp)
}
}
override val homeworkRes: Pair<String, Drawable?>? override val homeworkRes: Pair<String, Drawable?>?
get() { get() = context?.run { getString(R.string.homework_title) to getCompatDrawable(R.drawable.ic_more_homework) }
return context?.run {
getString(R.string.homework_title) to ContextCompat.getDrawable(this, R.drawable.ic_menu_main_homework_24dp)
}
}
override val noteRes: Pair<String, Drawable?>? override val noteRes: Pair<String, Drawable?>?
get() { get() = context?.run { getString(R.string.note_title) to getCompatDrawable(R.drawable.ic_more_note) }
return context?.run {
getString(R.string.note_title) to ContextCompat.getDrawable(this, R.drawable.ic_menu_main_note_24dp)
}
}
override val luckyNumberRes: Pair<String, Drawable?>? override val luckyNumberRes: Pair<String, Drawable?>?
get() { get() = context?.run { getString(R.string.lucky_number_title) to getCompatDrawable(R.drawable.ic_more_lucky_number) }
return context?.run {
getString(R.string.lucky_number_title) to
ContextCompat.getDrawable(this, R.drawable.ic_more_lucky_number_24dp)
}
}
override val mobileDevicesRes: Pair<String, Drawable?>? override val mobileDevicesRes: Pair<String, Drawable?>?
get() { get() = context?.run { getString(R.string.mobile_devices_title) to getCompatDrawable(R.drawable.ic_more_mobile_devices) }
return context?.run {
getString(R.string.mobile_devices_title) to
ContextCompat.getDrawable(this, R.drawable.ic_menu_main_mobile_devices_24dp)
}
}
override val settingsRes: Pair<String, Drawable?>? override val settingsRes: Pair<String, Drawable?>?
get() { get() = context?.run { getString(R.string.settings_title) to getCompatDrawable(R.drawable.ic_more_settings) }
return context?.run {
getString(R.string.settings_title) to
ContextCompat.getDrawable(this, R.drawable.ic_more_settings_24dp)
}
}
override val aboutRes: Pair<String, Drawable?>? override val aboutRes: Pair<String, Drawable?>?
get() { get() = context?.run { getString(R.string.about_title) to getCompatDrawable(R.drawable.ic_all_about) }
return context?.run {
getString(R.string.about_title) to
ContextCompat.getDrawable(this, R.drawable.ic_all_about_24dp)
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_more, container, false) return inflater.inflate(R.layout.fragment_more, container, false)
@ -104,7 +70,7 @@ class MoreFragment : BaseFragment(), MoreView, MainView.TitledView, MainView.Mai
} }
override fun initView() { override fun initView() {
moreAdapter.run { setOnItemClickListener { presenter.onItemSelected(it) } } moreAdapter.setOnItemClickListener { presenter.onItemSelected(it) }
moreRecycler.apply { moreRecycler.apply {
layoutManager = SmoothScrollLinearLayoutManager(context) layoutManager = SmoothScrollLinearLayoutManager(context)
@ -148,8 +114,8 @@ class MoreFragment : BaseFragment(), MoreView, MainView.TitledView, MainView.Mai
(activity as? MainActivity)?.pushView(AboutFragment.newInstance()) (activity as? MainActivity)?.pushView(AboutFragment.newInstance())
} }
override fun popView() { override fun popView(depth: Int) {
(activity as? MainActivity)?.popView() (activity as? MainActivity)?.popView(depth)
} }
override fun onDestroyView() { override fun onDestroyView() {

View File

@ -22,25 +22,24 @@ class MorePresenter @Inject constructor(
} }
fun onItemSelected(item: AbstractFlexibleItem<*>?) { fun onItemSelected(item: AbstractFlexibleItem<*>?) {
if (item is MoreItem) { if (item !is MoreItem) return
Timber.i("Select more item \"${item.title}\"") Timber.i("Select more item \"${item.title}\"")
view?.run { view?.run {
when (item.title) { when (item.title) {
messagesRes?.first -> openMessagesView() messagesRes?.first -> openMessagesView()
homeworkRes?.first -> openHomeworkView() homeworkRes?.first -> openHomeworkView()
noteRes?.first -> openNoteView() noteRes?.first -> openNoteView()
luckyNumberRes?.first -> openLuckyNumberView() luckyNumberRes?.first -> openLuckyNumberView()
mobileDevicesRes?.first -> openMobileDevicesView() mobileDevicesRes?.first -> openMobileDevicesView()
settingsRes?.first -> openSettingsView() settingsRes?.first -> openSettingsView()
aboutRes?.first -> openAboutView() aboutRes?.first -> openAboutView()
}
} }
} }
} }
fun onViewReselected() { fun onViewReselected() {
Timber.i("More view is reselected") Timber.i("More view is reselected")
view?.popView() view?.popView(2)
} }
private fun loadData() { private fun loadData() {

View File

@ -27,7 +27,7 @@ interface MoreView : BaseView {
fun openAboutView() fun openAboutView()
fun popView() fun popView(depth: Int)
fun openMessagesView() fun openMessagesView()

View File

@ -3,7 +3,8 @@ package io.github.wulkanowy.ui.modules.settings
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import android.os.Bundle import android.os.Bundle
import com.takisoft.preferencex.PreferenceFragmentCompat import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import dagger.android.support.AndroidSupportInjection import dagger.android.support.AndroidSupportInjection
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.ui.base.BaseActivity import io.github.wulkanowy.ui.base.BaseActivity
@ -24,8 +25,7 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP
fun newInstance() = SettingsFragment() fun newInstance() = SettingsFragment()
} }
override val titleStringId: Int override val titleStringId get() = R.string.settings_title
get() = R.string.settings_title
override fun onAttach(context: Context) { override fun onAttach(context: Context) {
AndroidSupportInjection.inject(this) AndroidSupportInjection.inject(this)
@ -37,9 +37,9 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP
presenter.onAttachView(this) presenter.onAttachView(this)
} }
override fun onCreatePreferencesFix(savedInstanceState: Bundle?, rootKey: String?) { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.scheme_preferences) setPreferencesFromResource(R.xml.scheme_preferences, rootKey)
findPreference(getString(R.string.pref_key_notification_debug)).isVisible = appInfo.isDebug findPreference<Preference>(getString(R.string.pref_key_notification_debug))?.isVisible = appInfo.isDebug
} }
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) { override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
@ -51,7 +51,7 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP
} }
override fun setServicesSuspended(serviceEnablesKey: String, isHolidays: Boolean) { override fun setServicesSuspended(serviceEnablesKey: String, isHolidays: Boolean) {
findPreference(serviceEnablesKey).run { findPreference<Preference>(serviceEnablesKey)?.apply {
summary = if (isHolidays) getString(R.string.pref_services_suspended) else "" summary = if (isHolidays) getString(R.string.pref_services_suspended) else ""
isEnabled = !isHolidays isEnabled = !isHolidays
} }

View File

@ -31,12 +31,14 @@ class SettingsPresenter @Inject constructor(
fun onSharedPreferenceChanged(key: String) { fun onSharedPreferenceChanged(key: String) {
Timber.i("Change settings $key") Timber.i("Change settings $key")
preferencesRepository.apply {
with(preferencesRepository) {
when (key) { when (key) {
serviceEnableKey -> with(syncManager) { if (isServiceEnabled) startSyncWorker() else stopSyncWorker() } serviceEnableKey -> with(syncManager) { if (isServiceEnabled) startSyncWorker() else stopSyncWorker() }
servicesIntervalKey, servicesOnlyWifiKey -> syncManager.startSyncWorker(true) servicesIntervalKey, servicesOnlyWifiKey -> syncManager.startSyncWorker(true)
isDebugNotificationEnableKey -> chuckCollector.showNotification(isDebugNotificationEnable) isDebugNotificationEnableKey -> chuckCollector.showNotification(isDebugNotificationEnable)
appThemeKey -> view?.recreateView() appThemeKey -> view?.recreateView()
else -> Unit
} }
} }
analytics.logEvent("setting_changed", "name" to key) analytics.logEvent("setting_changed", "name" to key)

View File

@ -17,11 +17,13 @@ import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.main.MainActivity 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.ui.modules.timetable.completed.CompletedLessonsFragment import io.github.wulkanowy.ui.modules.timetable.completed.CompletedLessonsFragment
import io.github.wulkanowy.utils.dpToPx
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.*
import javax.inject.Inject import javax.inject.Inject
class TimetableFragment : BaseFragment(), TimetableView, MainView.MainChildView, MainView.TitledView { class TimetableFragment : BaseFragment(), TimetableView, MainView.MainChildView,
MainView.TitledView {
@Inject @Inject
lateinit var presenter: TimetablePresenter lateinit var presenter: TimetablePresenter
@ -35,17 +37,13 @@ class TimetableFragment : BaseFragment(), TimetableView, MainView.MainChildView,
fun newInstance() = TimetableFragment() fun newInstance() = TimetableFragment()
} }
override val titleStringId: Int override val titleStringId get() = R.string.timetable_title
get() = R.string.timetable_title
override val roomString: String override val roomString get() = getString(R.string.timetable_room)
get() = getString(R.string.timetable_room)
override val isViewEmpty: Boolean override val isViewEmpty get() = timetableAdapter.isEmpty
get() = timetableAdapter.isEmpty
override val currentStackSize: Int? override val currentStackSize get() = (activity as? MainActivity)?.currentStackSize
get() = (activity as? MainActivity)?.currentStackSize
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -63,11 +61,9 @@ class TimetableFragment : BaseFragment(), TimetableView, MainView.MainChildView,
} }
override fun initView() { override fun initView() {
timetableAdapter.run { timetableAdapter.setOnItemClickListener(presenter::onTimetableItemSelected)
setOnItemClickListener { presenter.onTimetableItemSelected(it) }
}
timetableRecycler.run { with(timetableRecycler) {
layoutManager = SmoothScrollLinearLayoutManager(context) layoutManager = SmoothScrollLinearLayoutManager(context)
adapter = timetableAdapter adapter = timetableAdapter
addItemDecoration(FlexibleItemDecoration(context) addItemDecoration(FlexibleItemDecoration(context)
@ -75,9 +71,12 @@ class TimetableFragment : BaseFragment(), TimetableView, MainView.MainChildView,
.withDrawDividerOnLastItem(false) .withDrawDividerOnLastItem(false)
) )
} }
timetableSwipe.setOnRefreshListener { presenter.onSwipeRefresh() }
timetableSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
timetablePreviousButton.setOnClickListener { presenter.onPreviousDay() } timetablePreviousButton.setOnClickListener { presenter.onPreviousDay() }
timetableNextButton.setOnClickListener { presenter.onNextDay() } timetableNextButton.setOnClickListener { presenter.onNextDay() }
timetableNavContainer.setElevationCompat(requireContext().dpToPx(8f))
} }
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {

View File

@ -75,7 +75,7 @@ class TimetablePresenter @Inject constructor(
fun onTimetableItemSelected(item: AbstractFlexibleItem<*>?) { fun onTimetableItemSelected(item: AbstractFlexibleItem<*>?) {
if (item is TimetableItem) { if (item is TimetableItem) {
Timber.i("Select exam item ${item.lesson.id}") Timber.i("Select timetable item ${item.lesson.id}")
view?.showTimetableDialog(item.lesson) view?.showTimetableDialog(item.lesson)
} }
} }

View File

@ -4,7 +4,6 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.content.ContextCompat
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
@ -13,6 +12,8 @@ import io.github.wulkanowy.data.db.entities.CompletedLesson
import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.main.MainActivity 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.dpToPx
import io.github.wulkanowy.utils.getCompatDrawable
import io.github.wulkanowy.utils.setOnItemClickListener import io.github.wulkanowy.utils.setOnItemClickListener
import kotlinx.android.synthetic.main.fragment_timetable_completed.* import kotlinx.android.synthetic.main.fragment_timetable_completed.*
import javax.inject.Inject import javax.inject.Inject
@ -31,11 +32,9 @@ class CompletedLessonsFragment : BaseFragment(), CompletedLessonsView, MainView.
fun newInstance() = CompletedLessonsFragment() fun newInstance() = CompletedLessonsFragment()
} }
override val titleStringId: Int override val titleStringId get() = R.string.completed_lessons_title
get() = R.string.completed_lessons_title
override val isViewEmpty override val isViewEmpty get() = completedLessonsAdapter.isEmpty
get() = completedLessonsAdapter.isEmpty
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_timetable_completed, container, false) return inflater.inflate(R.layout.fragment_timetable_completed, container, false)
@ -48,17 +47,18 @@ class CompletedLessonsFragment : BaseFragment(), CompletedLessonsView, MainView.
} }
override fun initView() { override fun initView() {
completedLessonsAdapter.run { completedLessonsAdapter.setOnItemClickListener(presenter::onCompletedLessonsItemSelected)
setOnItemClickListener { presenter.onCompletedLessonsItemSelected(it) }
}
completedLessonsRecycler.run { with(completedLessonsRecycler) {
layoutManager = SmoothScrollLinearLayoutManager(context) layoutManager = SmoothScrollLinearLayoutManager(context)
adapter = completedLessonsAdapter adapter = completedLessonsAdapter
} }
completedLessonsSwipe.setOnRefreshListener { presenter.onSwipeRefresh() }
completedLessonsSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
completedLessonsPreviousButton.setOnClickListener { presenter.onPreviousDay() } completedLessonsPreviousButton.setOnClickListener { presenter.onPreviousDay() }
completedLessonsNextButton.setOnClickListener { presenter.onNextDay() } completedLessonsNextButton.setOnClickListener { presenter.onNextDay() }
completedLessonsNavContainer.setElevationCompat(requireContext().dpToPx(8f))
} }
override fun updateData(data: List<CompletedLessonItem>) { override fun updateData(data: List<CompletedLessonItem>) {
@ -82,10 +82,8 @@ class CompletedLessonsFragment : BaseFragment(), CompletedLessonsView, MainView.
} }
override fun showFeatureDisabled() { override fun showFeatureDisabled() {
context?.let { completedLessonsInfo.text = getString(R.string.error_feature_disabled)
completedLessonsInfo.text = getString(R.string.error_feature_disabled) completedLessonsInfoImage.setImageDrawable(requireContext().getCompatDrawable(R.drawable.ic_all_close_circle))
completedLessonsInfoImage.setImageDrawable(ContextCompat.getDrawable(it, R.drawable.ic_all_close_circle_24dp))
}
} }
override fun showProgress(show: Boolean) { override fun showProgress(show: Boolean) {
@ -114,7 +112,7 @@ class CompletedLessonsFragment : BaseFragment(), CompletedLessonsView, MainView.
override fun onSaveInstanceState(outState: Bundle) { override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState) super.onSaveInstanceState(outState)
outState.putLong(CompletedLessonsFragment.SAVED_DATE_KEY, presenter.currentDate.toEpochDay()) outState.putLong(SAVED_DATE_KEY, presenter.currentDate.toEpochDay())
} }
override fun onDestroyView() { override fun onDestroyView() {

View File

@ -1,7 +1,7 @@
package io.github.wulkanowy.ui.modules.timetablewidget package io.github.wulkanowy.ui.modules.timetablewidget
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.data.db.SharedPrefHelper import io.github.wulkanowy.data.db.SharedPrefProvider
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.data.repositories.student.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.BasePresenter
@ -14,7 +14,7 @@ class TimetableWidgetConfigurePresenter @Inject constructor(
schedulers: SchedulersProvider, schedulers: SchedulersProvider,
errorHandler: ErrorHandler, errorHandler: ErrorHandler,
studentRepository: StudentRepository, studentRepository: StudentRepository,
private val sharedPref: SharedPrefHelper private val sharedPref: SharedPrefProvider
) : BasePresenter<TimetableWidgetConfigureView>(errorHandler, studentRepository, schedulers) { ) : BasePresenter<TimetableWidgetConfigureView>(errorHandler, studentRepository, schedulers) {
private var appWidgetId: Int? = null private var appWidgetId: Int? = null

View File

@ -11,7 +11,7 @@ import android.widget.AdapterView.INVALID_POSITION
import android.widget.RemoteViews import android.widget.RemoteViews
import android.widget.RemoteViewsService import android.widget.RemoteViewsService
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.SharedPrefHelper import io.github.wulkanowy.data.db.SharedPrefProvider
import io.github.wulkanowy.data.db.entities.Timetable import io.github.wulkanowy.data.db.entities.Timetable
import io.github.wulkanowy.data.repositories.semester.SemesterRepository import io.github.wulkanowy.data.repositories.semester.SemesterRepository
import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.data.repositories.student.StudentRepository
@ -28,7 +28,7 @@ class TimetableWidgetFactory(
private val timetableRepository: TimetableRepository, private val timetableRepository: TimetableRepository,
private val studentRepository: StudentRepository, private val studentRepository: StudentRepository,
private val semesterRepository: SemesterRepository, private val semesterRepository: SemesterRepository,
private val sharedPref: SharedPrefHelper, private val sharedPref: SharedPrefProvider,
private val schedulers: SchedulersProvider, private val schedulers: SchedulersProvider,
private val context: Context, private val context: Context,
private val intent: Intent? private val intent: Intent?
@ -60,8 +60,6 @@ class TimetableWidgetFactory(
.filter { true } .filter { true }
.flatMap { studentRepository.getSavedStudents().toMaybe() } .flatMap { studentRepository.getSavedStudents().toMaybe() }
.flatMap { .flatMap {
if (studentId == 0L) throw IllegalArgumentException("Student id is 0")
it.singleOrNull { student -> student.id == studentId } it.singleOrNull { student -> student.id == studentId }
.let { student -> .let { student ->
if (student != null) Maybe.just(student) if (student != null) Maybe.just(student)

View File

@ -1,5 +1,6 @@
package io.github.wulkanowy.ui.modules.timetablewidget package io.github.wulkanowy.ui.modules.timetablewidget
import android.annotation.SuppressLint
import android.app.PendingIntent import android.app.PendingIntent
import android.app.PendingIntent.FLAG_UPDATE_CURRENT import android.app.PendingIntent.FLAG_UPDATE_CURRENT
import android.appwidget.AppWidgetManager import android.appwidget.AppWidgetManager
@ -15,12 +16,13 @@ import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
import android.widget.RemoteViews import android.widget.RemoteViews
import dagger.android.AndroidInjection import dagger.android.AndroidInjection
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.SharedPrefHelper import io.github.wulkanowy.data.db.SharedPrefProvider
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.exceptions.NoCurrentStudentException
import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.data.repositories.student.StudentRepository
import io.github.wulkanowy.services.widgets.TimetableWidgetService import io.github.wulkanowy.services.widgets.TimetableWidgetService
import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.main.MainView.MenuView import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.SchedulersProvider import io.github.wulkanowy.utils.SchedulersProvider
import io.github.wulkanowy.utils.nextOrSameSchoolDay import io.github.wulkanowy.utils.nextOrSameSchoolDay
@ -43,7 +45,7 @@ class TimetableWidgetProvider : BroadcastReceiver() {
lateinit var studentRepository: StudentRepository lateinit var studentRepository: StudentRepository
@Inject @Inject
lateinit var sharedPref: SharedPrefHelper lateinit var sharedPref: SharedPrefProvider
@Inject @Inject
lateinit var schedulers: SchedulersProvider lateinit var schedulers: SchedulersProvider
@ -110,6 +112,7 @@ class TimetableWidgetProvider : BroadcastReceiver() {
} }
} }
@SuppressLint("DefaultLocale")
private fun updateWidget(context: Context, appWidgetId: Int, date: LocalDate, student: Student?) { private fun updateWidget(context: Context, appWidgetId: Int, date: LocalDate, student: Student?) {
RemoteViews(context.packageName, R.layout.widget_timetable).apply { RemoteViews(context.packageName, R.layout.widget_timetable).apply {
setEmptyView(R.id.timetableWidgetList, R.id.timetableWidgetEmpty) setEmptyView(R.id.timetableWidgetList, R.id.timetableWidgetEmpty)
@ -130,8 +133,8 @@ class TimetableWidgetProvider : BroadcastReceiver() {
putExtra(EXTRA_FROM_PROVIDER, true) putExtra(EXTRA_FROM_PROVIDER, true)
}, FLAG_UPDATE_CURRENT)) }, FLAG_UPDATE_CURRENT))
setPendingIntentTemplate(R.id.timetableWidgetList, setPendingIntentTemplate(R.id.timetableWidgetList,
PendingIntent.getActivity(context, MenuView.TIMETABLE.id, PendingIntent.getActivity(context, MainView.Section.TIMETABLE.id,
MainActivity.getStartIntent(context, MenuView.TIMETABLE, true), FLAG_UPDATE_CURRENT)) MainActivity.getStartIntent(context, MainView.Section.TIMETABLE, true), FLAG_UPDATE_CURRENT))
}.also { }.also {
sharedPref.putLong(getDateWidgetKey(appWidgetId), date.toEpochDay(), true) sharedPref.putLong(getDateWidgetKey(appWidgetId), date.toEpochDay(), true)
appWidgetManager.apply { appWidgetManager.apply {
@ -173,7 +176,9 @@ class TimetableWidgetProvider : BroadcastReceiver() {
.subscribeOn(schedulers.backgroundThread) .subscribeOn(schedulers.backgroundThread)
.blockingGet() .blockingGet()
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e, "An error has occurred in timetable widget provider") if (e.cause !is NoCurrentStudentException) {
Timber.e(e, "An error has occurred in timetable widget provider")
}
null null
} }
} }

View File

@ -1,14 +1,13 @@
package io.github.wulkanowy.ui.modules.grade package io.github.wulkanowy.ui.widgets
import android.content.Context import android.content.Context
import android.util.AttributeSet import android.util.AttributeSet
import android.view.ViewGroup import android.view.ViewGroup
import com.google.android.material.tabs.TabLayout
/** /**
* @see <a href="https://stackoverflow.com/a/50382854">Tabs don't fit to screen with tabmode=scrollable, Even with a Custom Tab Layout</a> * @see <a href="https://stackoverflow.com/a/50382854">Tabs don't fit to screen with tabmode=scrollable, Even with a Custom Tab Layout</a>
*/ */
class CustomTabLayout : TabLayout { class FittedScrollableTabLayout : MaterialTabLayout {
constructor(context: Context) : super(context) constructor(context: Context) : super(context)

View File

@ -0,0 +1,39 @@
package io.github.wulkanowy.ui.widgets
import android.content.Context
import android.os.Build.VERSION.SDK_INT
import android.os.Build.VERSION_CODES.LOLLIPOP
import android.util.AttributeSet
import android.widget.LinearLayout
import androidx.core.view.ViewCompat
import com.google.android.material.elevation.ElevationOverlayProvider
import com.google.android.material.shape.MaterialShapeDrawable
class MaterialLinearLayout : LinearLayout {
constructor(context: Context) : super(context)
constructor(context: Context, attr: AttributeSet) : super(context, attr)
constructor(context: Context, attr: AttributeSet, defStyleAttr: Int) : super(context, attr, defStyleAttr)
init {
val drawable = MaterialShapeDrawable.createWithElevationOverlay(context, ViewCompat.getElevation(this))
ViewCompat.setBackground(this, drawable)
}
override fun setElevation(elevation: Float) {
super.setElevation(elevation)
if (background is MaterialShapeDrawable) {
(background as MaterialShapeDrawable).elevation = elevation
}
}
fun setElevationCompat(elevation: Float) {
if (SDK_INT >= LOLLIPOP) {
setElevation(elevation)
} else {
setBackgroundColor(ElevationOverlayProvider(context).getSurfaceColorWithOverlayIfNeeded(elevation))
}
}
}

View File

@ -0,0 +1,25 @@
package io.github.wulkanowy.ui.widgets
import android.content.Context
import android.os.Build.VERSION.SDK_INT
import android.os.Build.VERSION_CODES.LOLLIPOP
import android.util.AttributeSet
import com.google.android.material.elevation.ElevationOverlayProvider
import com.google.android.material.tabs.TabLayout
open class MaterialTabLayout : TabLayout {
constructor(context: Context) : super(context)
constructor(context: Context, attr: AttributeSet) : super(context, attr)
constructor(context: Context, attr: AttributeSet, defStyleAttr: Int) : super(context, attr, defStyleAttr)
fun setElevationCompat(elevation: Float) {
if (SDK_INT >= LOLLIPOP) {
setElevation(elevation)
} else {
setBackgroundColor(ElevationOverlayProvider(context).getSurfaceColorWithOverlayIfNeeded(elevation))
}
}
}

View File

@ -1,17 +1,15 @@
package io.github.wulkanowy.utils package io.github.wulkanowy.utils
import android.app.Activity import android.app.Activity
import android.content.Context.INPUT_METHOD_SERVICE
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
import androidx.core.content.getSystemService
fun Activity.showSoftInput() { fun Activity.showSoftInput() {
(getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager?)?.run { getSystemService<InputMethodManager>()?.let { manager ->
if (currentFocus != null) showSoftInput(currentFocus, 0) currentFocus?.let { manager.showSoftInput(it, 0) }
} }
} }
fun Activity.hideSoftInput() { fun Activity.hideSoftInput() {
(getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager?)?.run { getSystemService<InputMethodManager>()?.hideSoftInputFromWindow(window.decorView.applicationWindowToken, 0)
hideSoftInputFromWindow(window.decorView.applicationWindowToken, 0)
}
} }

View File

@ -2,9 +2,11 @@ package io.github.wulkanowy.utils
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.util.DisplayMetrics.DENSITY_DEFAULT
import androidx.annotation.AttrRes import androidx.annotation.AttrRes
import androidx.annotation.ColorInt import androidx.annotation.ColorInt
import androidx.annotation.ColorRes import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
@ColorInt @ColorInt
@ -20,9 +22,13 @@ fun Context.getThemeAttrColor(@AttrRes colorAttr: Int): Int {
@ColorInt @ColorInt
fun Context.getCompatColor(@ColorRes colorRes: Int) = ContextCompat.getColor(this, colorRes) fun Context.getCompatColor(@ColorRes colorRes: Int) = ContextCompat.getColor(this, colorRes)
fun Context.getCompatDrawable(@DrawableRes drawableRes: Int) = ContextCompat.getDrawable(this, drawableRes)
fun Context.openInternetBrowser(uri: String, onActivityNotFound: (uri: String) -> Unit) { fun Context.openInternetBrowser(uri: String, onActivityNotFound: (uri: String) -> Unit) {
Intent.parseUri(uri, 0).let { Intent.parseUri(uri, 0).let {
if (it.resolveActivity(packageManager) != null) startActivity(it) if (it.resolveActivity(packageManager) != null) startActivity(it)
else onActivityNotFound(uri) else onActivityNotFound(uri)
} }
} }
fun Context.dpToPx(dp: Float) = dp * resources.displayMetrics.densityDpi / DENSITY_DEFAULT

View File

@ -1,16 +0,0 @@
package io.github.wulkanowy.utils
import android.text.Editable
import android.text.TextWatcher
import android.widget.EditText
inline fun EditText.setOnTextChangedListener(crossinline listener: () -> Unit) {
addTextChangedListener(object : TextWatcher {
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
listener()
}
override fun afterTextChanged(s: Editable?) {}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
})
}

View File

@ -2,19 +2,20 @@ package io.github.wulkanowy.utils
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import com.ncapdevi.fragnav.FragNavController import com.ncapdevi.fragnav.FragNavController
import io.github.wulkanowy.ui.modules.main.MainView
inline fun FragNavController.setOnViewChangeListener(crossinline listener: (fragment: Fragment?) -> Unit) { inline fun FragNavController.setOnViewChangeListener(crossinline listener: (section: MainView.Section?) -> Unit) {
transactionListener = object : FragNavController.TransactionListener { transactionListener = object : FragNavController.TransactionListener {
override fun onFragmentTransaction(fragment: Fragment?, transactionType: FragNavController.TransactionType) { override fun onFragmentTransaction(fragment: Fragment?, transactionType: FragNavController.TransactionType) {
listener(fragment) listener(fragment?.toSection())
} }
override fun onTabTransaction(fragment: Fragment?, index: Int) { override fun onTabTransaction(fragment: Fragment?, index: Int) {
listener(fragment) listener(fragment?.toSection())
} }
} }
} }
fun FragNavController.safelyPopFragment() { fun FragNavController.safelyPopFragments(depth: Int) {
if (!isRootFragment) popFragment() if (!isRootFragment) popFragments(depth)
} }

View File

@ -0,0 +1,32 @@
package io.github.wulkanowy.utils
import androidx.fragment.app.Fragment
import io.github.wulkanowy.ui.modules.about.AboutFragment
import io.github.wulkanowy.ui.modules.attendance.AttendanceFragment
import io.github.wulkanowy.ui.modules.exam.ExamFragment
import io.github.wulkanowy.ui.modules.grade.GradeFragment
import io.github.wulkanowy.ui.modules.homework.HomeworkFragment
import io.github.wulkanowy.ui.modules.luckynumber.LuckyNumberFragment
import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.ui.modules.message.MessageFragment
import io.github.wulkanowy.ui.modules.more.MoreFragment
import io.github.wulkanowy.ui.modules.note.NoteFragment
import io.github.wulkanowy.ui.modules.settings.SettingsFragment
import io.github.wulkanowy.ui.modules.timetable.TimetableFragment
fun Fragment.toSection(): MainView.Section? {
return when (this) {
is GradeFragment -> MainView.Section.GRADE
is AttendanceFragment -> MainView.Section.ATTENDANCE
is ExamFragment -> MainView.Section.EXAM
is TimetableFragment -> MainView.Section.TIMETABLE
is MoreFragment -> MainView.Section.MORE
is MessageFragment -> MainView.Section.MESSAGE
is HomeworkFragment -> MainView.Section.HOMEWORK
is NoteFragment -> MainView.Section.NOTE
is LuckyNumberFragment -> MainView.Section.LUCKY_NUMBER
is SettingsFragment -> MainView.Section.SETTINGS
is AboutFragment -> MainView.Section.ABOUT
else -> null
}
}

View File

@ -1,16 +0,0 @@
package io.github.wulkanowy.utils
import android.view.View
import com.mikepenz.aboutlibraries.Libs
import com.mikepenz.aboutlibraries.LibsBuilder
import com.mikepenz.aboutlibraries.LibsConfiguration
inline fun LibsBuilder.withOnExtraListener(crossinline listener: (Libs.SpecialButton?) -> Unit): LibsBuilder {
withListener(object : LibsConfiguration.LibsListenerImpl() {
override fun onExtraClicked(v: View?, specialButton: Libs.SpecialButton?): Boolean {
listener(specialButton)
return true
}
})
return this
}

View File

@ -7,14 +7,15 @@ import android.widget.Spinner
/** /**
* @see <a href="https://stackoverflow.com/a/29602298">How to keep onItemSelected from firing off on a newly instantiated Spinner?</a> * @see <a href="https://stackoverflow.com/a/29602298">How to keep onItemSelected from firing off on a newly instantiated Spinner?</a>
*/ */
inline fun Spinner.setOnItemSelectedListener(crossinline listener: (view: View?) -> Unit) { @Suppress("UNCHECKED_CAST")
inline fun <T : View> Spinner.setOnItemSelectedListener(crossinline listener: (view: T?) -> Unit) {
onItemSelectedListener = object : AdapterView.OnItemSelectedListener { onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onNothingSelected(parent: AdapterView<*>?) {} override fun onNothingSelected(parent: AdapterView<*>?) {}
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
onItemSelectedListener = object : AdapterView.OnItemSelectedListener { onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onNothingSelected(parent: AdapterView<*>?) {} override fun onNothingSelected(parent: AdapterView<*>?) {}
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
listener(view) listener(view as T?)
} }
} }
} }

View File

@ -1,6 +1,7 @@
Wersja 0.9.4 Wersja 0.10.0
- naprawienie nawigacji we frekwencji, sprawdzianach, planie lekcji na wakacjach - odświeżyliśmy wygląd aplikacji
- wyświetlanie szczęśliwego numerka po ostatniej aktualizacji dziennika VULCAN (19.06) - poprawiliśmy wyświetlanie nauczycieli w planie lekcji
- naprawiliśmy szczęsliwy numerek
Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases

View File

@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="28.26087"
android:viewportHeight="28.26087">
<group
android:translateX="1.1304348"
android:translateY="1.1304348">
<path
android:fillColor="#FFF"
android:pathData="M13 11h2v2h-2m0 2h2a2 2 0 0 0 2-2v-2a2 2 0 0 0-2-2h-2V7h4V5h-4a2 2 0 0 0-2 2v6c0 1.1 0.9 2 2 2m8 2H7V3h14m0-2H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V3a2 2 0 0 0-2-2M3 5H1v16a2 2 0 0 0 2 2h16v-2H3V5z" />
</group>
</vector>

View File

@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="26.086956"
android:viewportHeight="26.086956">
<group
android:translateX="1.0434783"
android:translateY="1.0434783">
<path
android:fillColor="#FFF"
android:pathData="M12,11.18C15.3,8.18 17,6.64 17,4.69C17,3.19 15.75,2 14.25,2C13.39,2 12.57,2.36 12,3C11.43,2.36 10.61,2 9.69,2C8.19,2 7,3.25 7,4.75C7,6.64 8.7,8.18 12,11.18M11.18,12C8.18,8.7 6.64,7 4.69,7C3.19,7 2,8.25 2, 9.75C2,10.61 2.36,11.43 3,12C2.36,12.57 2,13.39 2,14.31C2,15.81 3.25,17 4.75,17C6.64,17 8.18,15.3 11.18,12M12.83, 12C15.82,15.3 17.36,17 19.31,17C20.81,17 22,15.75 22,14.25C22,13.39 21.64,12.57 21,12C21.64,11.43 22,10.61 22,9.69C22, 8.19 20.75,7 19.25,7C17.36,7 15.82,8.7 12.83,12M12,12.82C8.7,15.82 7,17.36 7,19.31C7,20.81 8.25,22 9.75,22C10.61,22 11.43,21.64 12,21C12.57,21.64 13.39,22 14.31,22C15.81,22 17,20.75 17,19.25C17,17.36 15.3,15.82 12,12.82Z" />
</group>
</vector>

View File

@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="26.086956"
android:viewportHeight="26.086956">
<group
android:translateX="1.0434783"
android:translateY="1.0434783">
<path
android:fillColor="#FFF"
android:pathData="M20,2L4,2c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM18,14L6,14v-2h12v2zM18,11L6,11L6,9h12v2zM18,8L6,8L6,6h12v2z" />
</group>
</vector>

View File

@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="26.086956"
android:viewportHeight="26.086956">
<group
android:translateX="1.0434783"
android:translateY="1.0434783">
<path
android:fillColor="#FFF"
android:pathData="M20.2,2H19.5H18C17.1,2 16,3 16,4H8C8,3 6.9,2 6,2H4.5H3.8H2V11C2,12 3,13 4,13H6.2C6.6,15 7.9,16.7 11,17V19.1C8.8,19.3 8,20.4 8,21.7V22H16V21.7C16,20.4 15.2,19.3 13,19.1V17C16.1,16.7 17.4,15 17.8,13H20C21,13 22,12 22,11V2H20.2M4,11V4H6V6V11C5.1,11 4.3,11 4,11M20,11C19.7,11 18.9,11 18,11V6V4H20V11Z" />
</group>
</vector>

View File

@ -1,14 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="26.086956"
android:viewportHeight="26.086956"
android:tint="#FFFFFF">
<group
android:translateX="1.0434783"
android:translateY="1.0434783">
<path
android:fillColor="#FFFFFF"
android:pathData="M12,11.18C15.3,8.18 17,6.64 17,4.69C17,3.19 15.75,2 14.25,2C13.39,2 12.57,2.36 12,3C11.43,2.36 10.61,2 9.69,2C8.19,2 7,3.25 7,4.75C7,6.64 8.7,8.18 12,11.18M11.18,12C8.18,8.7 6.64,7 4.69,7C3.19,7 2,8.25 2,9.75C2,10.61 2.36,11.43 3,12C2.36,12.57 2,13.39 2,14.31C2,15.81 3.25,17 4.75,17C6.64,17 8.18,15.3 11.18,12M12.83,12C15.82,15.3 17.36,17 19.31,17C20.81,17 22,15.75 22,14.25C22,13.39 21.64,12.57 21,12C21.64,11.43 22,10.61 22,9.69C22,8.19 20.75,7 19.25,7C17.36,7 15.82,8.7 12.83,12M12,12.82C8.7,15.82 7,17.36 7,19.31C7,20.81 8.25,22 9.75,22C10.61,22 11.43,21.64 12,21C12.57,21.64 13.39,22 14.31,22C15.81,22 17,20.75 17,19.25C17,17.36 15.3,15.82 12,12.82Z" />
</group>
</vector>

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