1
0

Compare commits

...

28 Commits
0.7.0 ... 0.7.4

Author SHA1 Message Date
297a2909ba Version 0.7.4 2019-03-30 19:45:57 +01:00
935bec3f5b Add 0,75 grade modifier (#313) 2019-03-30 19:26:19 +01:00
e71dd55066 Fix more than one current semester (#307) 2019-03-30 18:28:37 +01:00
4e3864f26f Add opening login view on no current student (#312) 2019-03-30 09:31:30 +01:00
8601093725 Again fix rejected execution in sync worker (#310) 2019-03-28 23:30:30 +01:00
b97b94ae29 Fix more than one current student (#311)
Fix #309
2019-03-28 23:07:59 +01:00
c6c7357623 Fix black spinner in login form (#308) 2019-03-28 18:27:17 +01:00
c2ab53cfeb Version 0.7.3 2019-03-26 19:46:59 +01:00
87268b3ef6 Fix rejected execution in sync worker (#305) 2019-03-26 16:47:14 +01:00
b3cd7e8ac1 Fix undeliverable network exceptions (#306)
* Remove unnecessary this
2019-03-26 14:32:23 +01:00
5a997dacb7 Version 0.7.2 2019-03-24 22:42:09 +01:00
ed9458d9a5 Fix more than one current semester in database (#304) 2019-03-24 20:21:05 +01:00
3656d3161f Fix crash on duplicate items (#303) 2019-03-24 17:31:39 +01:00
d178c15d2f Update dependencies (#302) 2019-03-24 16:03:51 +01:00
1f65b8465e Add logging to sync worker (#300) 2019-03-23 18:35:56 +01:00
6bb03b3be8 Fix day navigation unevenition (#301) 2019-03-23 17:29:34 +01:00
68b9847927 Fix reselecting root fragments (#299) 2019-03-23 16:35:33 +01:00
e1a83927c4 Fix reset button in timetable widget (#298) 2019-03-23 14:44:52 +01:00
fc9981aa5d Fix issues when loading lucky number (#297) 2019-03-23 13:01:01 +01:00
2d6610e05c Set max concurrency in sync worker (#296) 2019-03-23 01:12:17 +01:00
316cd2f7f9 Add checking current student in background services (#295) 2019-03-23 00:37:13 +01:00
36785f019a Fix restoring the grade fragment (#293) 2019-03-22 23:54:58 +01:00
4b78862486 Remove retry sync work when completed lessons is disabled (#294) 2019-03-22 23:41:41 +01:00
20d0abba29 Fix empty container id in grade fragemnt adapter (#289) 2019-03-21 22:34:41 +01:00
5add95ece1 Version 0.7.1 2019-03-20 21:02:09 +01:00
575e244b3a Add swipe refresh to grade fragment (#287) 2019-03-20 20:45:26 +01:00
8db73e9459 Fix the application finish after selecting an account (#286) 2019-03-19 18:14:55 +01:00
040857ba20 Change grade weightValue type to double (#285) 2019-03-19 13:23:52 +01:00
81 changed files with 3381 additions and 281 deletions

View File

@ -14,6 +14,7 @@ cache:
#branches:
# only:
# - master
# - 0.7.x
android:
licenses:

View File

@ -16,8 +16,8 @@ android {
testApplicationId "io.github.tests.wulkanowy"
minSdkVersion 15
targetSdkVersion 28
versionCode 26
versionName "0.7.0"
versionCode 30
versionName "0.7.4"
multiDexEnabled true
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
@ -25,6 +25,15 @@ android {
fabric_api_key: System.getenv("FABRIC_API_KEY") ?: "null",
crashlytics_enabled: project.hasProperty("enableCrashlytics")
]
javaCompileOptions {
annotationProcessorOptions {
arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]
}
}
}
sourceSets {
androidTest.assets.srcDirs += files("$projectDir/schemas".toString())
}
signingConfigs {
@ -77,7 +86,7 @@ play {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation('io.github.wulkanowy:api:0.7.0') { exclude module: "threetenbp" }
implementation('io.github.wulkanowy:api:0.7.4') { exclude module: "threetenbp" }
implementation "androidx.legacy:legacy-support-v4:1.0.0"
implementation "androidx.appcompat:appcompat:1.0.2"
@ -86,10 +95,14 @@ dependencies {
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.multidex:multidex:2.0.1'
implementation 'com.takisoft.preferencex:preferencex:1.0.0'
implementation "android.arch.work:work-rxjava2:1.0.0"
implementation "android.arch.work:work-runtime:1.0.0"
implementation "android.arch.work:work-rxjava2:1.0.0"
implementation "androidx.room:room-runtime:2.1.0-alpha06"
implementation "androidx.room:room-rxjava2:2.1.0-alpha06"
kapt "androidx.room:room-compiler:2.1.0-alpha06"
implementation 'com.takisoft.preferencex:preferencex:1.0.0'
implementation 'com.squareup.inject:assisted-inject-annotations-dagger2:0.3.3'
kapt 'com.squareup.inject:assisted-inject-processor-dagger2:0.3.3'
@ -98,17 +111,13 @@ dependencies {
kapt "com.google.dagger:dagger-compiler:2.21"
kapt "com.google.dagger:dagger-android-processor:2.21"
implementation "androidx.room:room-runtime:2.1.0-alpha05"
implementation "androidx.room:room-rxjava2:2.1.0-alpha05"
kapt "androidx.room:room-compiler:2.1.0-alpha05"
implementation "eu.davidea:flexible-adapter:5.1.0"
implementation "eu.davidea:flexible-adapter-ui:1.0.0"
implementation "com.aurelhubert:ahbottomnavigation:2.3.4"
implementation 'com.ncapdevi:frag-nav:3.1.0'
implementation 'com.ncapdevi:frag-nav:3.2.0'
implementation 'com.github.wulkanowy:MaterialChipsInput:b72fd0ee6f'
implementation 'com.github.PhilJay:MPAndroidChart:971640b29d'
implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0'
implementation 'com.github.pwittchen:reactivenetwork-rx2:3.0.2'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
@ -128,15 +137,16 @@ dependencies {
debugImplementation "com.amitshekhar.android:debug-db:1.0.6"
testImplementation "junit:junit:4.12"
testImplementation "io.mockk:mockk:1.9.1"
testImplementation "org.mockito:mockito-inline:2.25.0"
testImplementation "io.mockk:mockk:1.9.2"
testImplementation "org.mockito:mockito-inline:2.25.1"
testImplementation 'org.threeten:threetenbp:1.3.8'
androidTestImplementation "io.mockk:mockk-android:1.9.1"
androidTestImplementation 'androidx.test:core:1.1.0'
androidTestImplementation 'androidx.test:runner:1.1.1'
androidTestImplementation 'androidx.test.ext:junit:1.1.0'
androidTestImplementation 'org.mockito:mockito-android:2.25.0'
androidTestImplementation "io.mockk:mockk-android:1.9.2"
androidTestImplementation 'org.mockito:mockito-android:2.25.1'
androidTestImplementation "androidx.room:room-testing:2.1.0-alpha06"
androidTestImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,159 @@
package io.github.wulkanowy.data.db.migrations
import android.content.ContentValues
import android.database.sqlite.SQLiteDatabase.CONFLICT_FAIL
import androidx.room.Room
import androidx.room.testing.MigrationTestHelper
import androidx.sqlite.db.SupportSQLiteDatabase
import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import io.github.wulkanowy.data.db.AppDatabase
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import kotlin.test.assertEquals
@RunWith(AndroidJUnit4::class)
class Migration12Test {
private val dbName = "migration-test"
@get:Rule
val helper: MigrationTestHelper = MigrationTestHelper(
InstrumentationRegistry.getInstrumentation(),
AppDatabase::class.java.canonicalName,
FrameworkSQLiteOpenHelperFactory()
)
@Test
fun migrate11To12_twoNotRelatedStudents() {
helper.createDatabase(dbName, 11).apply {
// user 1
createStudent(this, 1, true)
createSemester(this, 1, false, 5, 1)
createSemester(this, 1, true, 5, 2)
// user 2
createStudent(this, 2, true)
createSemester(this, 2, false, 6, 1)
createSemester(this, 2, true, 6, 2)
close()
}
helper.runMigrationsAndValidate(dbName, 12, true, Migration12())
val db = getMigratedRoomDatabase()
val students = db.studentDao.loadAll().blockingGet()
assertEquals(2, students.size)
students[0].run {
assertEquals(1, studentId)
assertEquals(5, classId)
}
students[1].run {
assertEquals(2, studentId)
assertEquals(6, classId)
}
}
@Test
fun migrate11To12_removeStudentsWithoutClassId() {
helper.createDatabase(dbName, 11).apply {
// user 1
createStudent(this, 1, true)
createSemester(this, 1, false, 0, 2)
createStudent(this, 2, true)
createSemester(this, 2, true, 1, 2)
close()
}
helper.runMigrationsAndValidate(dbName, 12, true, Migration12())
val db = getMigratedRoomDatabase()
val students = db.studentDao.loadAll().blockingGet()
assertEquals(1, students.size)
students[0].run {
assertEquals(2, studentId)
assertEquals(1, classId)
}
}
@Test
fun migrate11To12_ensureThereIsOnlyOneCurrentStudent() {
helper.createDatabase(dbName, 11).apply {
// user 1
createStudent(this, 1, true)
createSemester(this, 1, true, 5, 2)
createStudent(this, 2, true)
createSemester(this, 2, true, 6, 2)
createStudent(this, 3, true)
createSemester(this, 3, false, 7, 2)
close()
}
helper.runMigrationsAndValidate(dbName, 12, true, Migration12())
val db = getMigratedRoomDatabase()
val students = db.studentDao.loadAll().blockingGet()
assertEquals(3, students.size)
students[0].run {
assertEquals(studentId, 1)
assertEquals(false, isCurrent)
}
students[1].run {
assertEquals(studentId, 2)
assertEquals(false, isCurrent)
}
students[2].run {
assertEquals(studentId, 3)
assertEquals(true, isCurrent)
}
}
private fun createStudent(db: SupportSQLiteDatabase, studentId: Int, isCurrent: Boolean) {
db.insert("Students", CONFLICT_FAIL, ContentValues().apply {
put("endpoint", "https://fakelog.cf")
put("loginType", "STANDARD")
put("email", "jan@fakelog.cf")
put("password", "******")
put("symbol", "Default")
put("student_id", studentId)
put("student_name", "Jan Kowalski")
put("school_id", "000123")
put("school_name", "")
put("is_current", isCurrent)
put("registration_date", "0")
})
}
private fun createSemester(db: SupportSQLiteDatabase, studentId: Int, isCurrent: Boolean, classId: Int, diaryId: Int) {
db.insert("Semesters", CONFLICT_FAIL, ContentValues().apply {
put("student_id", studentId)
put("diary_id", diaryId)
put("diary_name", "IA")
put("semester_id", diaryId * 5)
put("semester_name", "1")
put("is_current", isCurrent)
put("class_id", classId)
put("unit_id", "99")
})
}
private fun getMigratedRoomDatabase(): AppDatabase {
val database = Room.databaseBuilder(ApplicationProvider.getApplicationContext(),
AppDatabase::class.java, dbName)
.addMigrations(Migration12())
.build()
// close the database and release any stream resources when the test finishes
helper.closeWhenFinished(database)
return database
}
}

View File

@ -34,9 +34,9 @@ class GradeLocalTest {
@Test
fun saveAndReadTest() {
gradeLocal.saveGrades(listOf(
createGradeLocal(5, 3, LocalDate.of(2018, 9, 10), "", 1),
createGradeLocal(4, 4, LocalDate.of(2019, 2, 27), "", 2),
createGradeLocal(3, 5, LocalDate.of(2019, 2, 28), "", 2)
createGradeLocal(5, 3.0, LocalDate.of(2018, 9, 10), "", 1),
createGradeLocal(4, 4.0, LocalDate.of(2019, 2, 27), "", 2),
createGradeLocal(3, 5.0, LocalDate.of(2019, 2, 28), "", 2)
))
val grades = gradeLocal

View File

@ -71,10 +71,10 @@ class GradeRepositoryTest {
@Test
fun markOlderThanRegisterDateAsRead() {
every { mockApi.getGrades(1) } returns Single.just(listOf(
createGradeApi(5, 4, of(2019, 2, 25), "Ocena pojawiła się"),
createGradeApi(5, 4, of(2019, 2, 26), "przed zalogowanie w aplikacji"),
createGradeApi(5, 4, of(2019, 2, 27), "Ocena z dnia logowania"),
createGradeApi(5, 4, of(2019, 2, 28), "Ocena jeszcze nowsza")
createGradeApi(5, 4.0, of(2019, 2, 25), "Ocena pojawiła się"),
createGradeApi(5, 4.0, of(2019, 2, 26), "przed zalogowanie w aplikacji"),
createGradeApi(5, 4.0, of(2019, 2, 27), "Ocena z dnia logowania"),
createGradeApi(5, 4.0, of(2019, 2, 28), "Ocena jeszcze nowsza")
))
val grades = GradeRepository(settings, gradeLocal, gradeRemote)
@ -89,16 +89,16 @@ class GradeRepositoryTest {
@Test
fun mitigateOldGradesNotifications() {
gradeLocal.saveGrades(listOf(
createGradeLocal(5, 3, of(2019, 2, 25), "Jedna ocena"),
createGradeLocal(4, 4, of(2019, 2, 26), "Druga"),
createGradeLocal(3, 5, of(2019, 2, 27), "Trzecia")
createGradeLocal(5, 3.0, of(2019, 2, 25), "Jedna ocena"),
createGradeLocal(4, 4.0, of(2019, 2, 26), "Druga"),
createGradeLocal(3, 5.0, of(2019, 2, 27), "Trzecia")
))
every { mockApi.getGrades(1) } returns Single.just(listOf(
createGradeApi(5, 2, of(2019, 2, 25), "Ocena ma datę, jest inna, ale nie zostanie powiadomiona"),
createGradeApi(4, 3, of(2019, 2, 26), "starszą niż ostatnia lokalnie"),
createGradeApi(3, 4, of(2019, 2, 27), "Ta jest z tego samego dnia co ostatnia lokalnie"),
createGradeApi(2, 5, of(2019, 2, 28), "Ta jest już w ogóle nowa")
createGradeApi(5, 2.0, of(2019, 2, 25), "Ocena ma datę, jest inna, ale nie zostanie powiadomiona"),
createGradeApi(4, 3.0, of(2019, 2, 26), "starszą niż ostatnia lokalnie"),
createGradeApi(3, 4.0, of(2019, 2, 27), "Ta jest z tego samego dnia co ostatnia lokalnie"),
createGradeApi(2, 5.0, of(2019, 2, 28), "Ta jest już w ogóle nowa")
))
val grades = GradeRepository(settings, gradeLocal, gradeRemote)

View File

@ -5,7 +5,7 @@ import org.threeten.bp.LocalDate
import io.github.wulkanowy.api.grades.Grade as GradeRemote
import io.github.wulkanowy.data.db.entities.Grade as GradeLocal
fun createGradeLocal(value: Int, weight: Int, date: LocalDate, desc: String, semesterId: Int = 1): GradeLocal {
fun createGradeLocal(value: Int, weight: Double, date: LocalDate, desc: String, semesterId: Int = 1): GradeLocal {
return GradeLocal(
semesterId = semesterId,
studentId = 1,
@ -24,7 +24,7 @@ fun createGradeLocal(value: Int, weight: Int, date: LocalDate, desc: String, sem
)
}
fun createGradeApi(value: Int, weight: Int, date: LocalDate, desc: String): GradeRemote {
fun createGradeApi(value: Int, weight: Double, date: LocalDate, desc: String): GradeRemote {
return GradeRemote().apply {
this.value = value
this.weightValue = weight

View File

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

View File

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

View File

@ -39,18 +39,12 @@
android:name=".ui.modules.main.MainActivity"
android:configChanges="orientation|screenSize"
android:label="@string/main_title"
android:launchMode="singleTop"
android:theme="@style/WulkanowyTheme.NoActionBar" />
<activity
android:name=".ui.modules.message.send.SendMessageActivity"
android:configChanges="orientation|screenSize"
android:label="@string/send_message_title"
android:parentActivityName=".ui.modules.main.MainActivity"
android:theme="@style/WulkanowyTheme.NoActionBar">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".ui.modules.main.MainActivity" />
</activity>
android:theme="@style/WulkanowyTheme.NoActionBar" />
<service
android:name=".services.widgets.TimetableWidgetService"
@ -58,6 +52,7 @@
<receiver
android:name=".ui.widgets.timetable.TimetableWidgetProvider"
android:exported="true"
android:label="@string/timetable_title">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />

View File

@ -19,7 +19,11 @@ import io.github.wulkanowy.di.DaggerAppComponent
import io.github.wulkanowy.services.sync.SyncWorkerFactory
import io.github.wulkanowy.utils.CrashlyticsTree
import io.github.wulkanowy.utils.DebugLogTree
import io.reactivex.exceptions.UndeliverableException
import io.reactivex.plugins.RxJavaPlugins
import timber.log.Timber
import java.io.IOException
import java.lang.Exception
import javax.inject.Inject
class WulkanowyApp : DaggerApplication() {
@ -42,6 +46,7 @@ class WulkanowyApp : DaggerApplication() {
if (DEBUG) enableDebugLog()
AppCompatDelegate.setDefaultNightMode(prefRepository.currentTheme)
WorkManager.initialize(this, Configuration.Builder().setWorkerFactory(workerFactory).build())
RxJavaPlugins.setErrorHandler(::onError)
}
private fun enableDebugLog() {
@ -56,6 +61,12 @@ class WulkanowyApp : DaggerApplication() {
Timber.plant(CrashlyticsTree())
}
private fun onError(t: Throwable) {
if (t is UndeliverableException && t.cause is IOException || t.cause is InterruptedException) {
Timber.e(t.cause, "An undeliverable error occurred")
} else throw t
}
override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
return DaggerAppComponent.builder().create(this)
}

View File

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

View File

@ -41,6 +41,8 @@ import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.db.entities.Subject
import io.github.wulkanowy.data.db.entities.Timetable
import io.github.wulkanowy.data.db.migrations.Migration10
import io.github.wulkanowy.data.db.migrations.Migration11
import io.github.wulkanowy.data.db.migrations.Migration12
import io.github.wulkanowy.data.db.migrations.Migration2
import io.github.wulkanowy.data.db.migrations.Migration3
import io.github.wulkanowy.data.db.migrations.Migration4
@ -73,13 +75,13 @@ import javax.inject.Singleton
Recipient::class
],
version = AppDatabase.VERSION_SCHEMA,
exportSchema = false
exportSchema = true
)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
companion object {
const val VERSION_SCHEMA = 10
const val VERSION_SCHEMA = 12
fun newInstance(context: Context): AppDatabase {
return Room.databaseBuilder(context, AppDatabase::class.java, "wulkanowy_database")
@ -95,7 +97,9 @@ abstract class AppDatabase : RoomDatabase() {
Migration7(),
Migration8(),
Migration9(),
Migration10()
Migration10(),
Migration11(),
Migration12()
)
.build()
}

View File

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

View File

@ -25,8 +25,8 @@ interface StudentDao {
@Query("SELECT * FROM Students")
fun loadAll(): Maybe<List<Student>>
@Query("UPDATE Students SET is_current = 1 WHERE student_id = :studentId")
fun updateCurrent(studentId: Int)
@Query("UPDATE Students SET is_current = 1 WHERE id = :id")
fun updateCurrent(id: Long)
@Query("UPDATE Students SET is_current = 0")
fun resetCurrent()

View File

@ -34,7 +34,7 @@ data class Grade(
val weight: String,
val weightValue: Int,
val weightValue: Double,
val date: LocalDate,

View File

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

View File

@ -0,0 +1,34 @@
package io.github.wulkanowy.data.db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
class Migration11 : Migration(10, 11) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("""
CREATE TABLE IF NOT EXISTS Grades_temp (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
is_read INTEGER NOT NULL,
is_notified INTEGER NOT NULL,
semester_id INTEGER NOT NULL,
student_id INTEGER NOT NULL,
subject TEXT NOT NULL,
entry TEXT NOT NULL,
value INTEGER NOT NULL,
modifier REAL NOT NULL,
comment TEXT NOT NULL,
color TEXT NOT NULL,
grade_symbol TEXT NOT NULL,
description TEXT NOT NULL,
weight TEXT NOT NULL,
weightValue REAL NOT NULL,
date INTEGER NOT NULL,
teacher TEXT NOT NULL
)
""")
database.execSQL("INSERT INTO Grades_temp SELECT * FROM Grades")
database.execSQL("DROP TABLE Grades")
database.execSQL("ALTER TABLE Grades_temp RENAME TO Grades")
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -35,7 +35,7 @@ class StudentLocal @Inject constructor(
return Completable.fromCallable {
studentDb.run {
resetCurrent()
updateCurrent(student.studentId)
updateCurrent(student.id)
}
}
}

View File

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

View File

@ -4,6 +4,7 @@ import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork
import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings
import io.github.wulkanowy.data.ApiHelper
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.exceptions.NoCurrentStudentException
import io.reactivex.Completable
import io.reactivex.Maybe
import io.reactivex.Single
@ -36,7 +37,7 @@ class StudentRepository @Inject constructor(
fun getCurrentStudent(decryptPass: Boolean = true): Single<Student> {
return local.getCurrentStudent(decryptPass)
.switchIfEmpty(Maybe.error(NoSuchElementException("No current student")))
.switchIfEmpty(Maybe.error(NoCurrentStudentException()))
.toSingle()
}

View File

@ -3,7 +3,6 @@ package io.github.wulkanowy.di
import dagger.Module
import dagger.android.ContributesAndroidInjector
import io.github.wulkanowy.di.scopes.PerActivity
import io.github.wulkanowy.services.widgets.TimetableWidgetService
import io.github.wulkanowy.ui.modules.login.LoginActivity
import io.github.wulkanowy.ui.modules.login.LoginModule
import io.github.wulkanowy.ui.modules.main.MainActivity
@ -30,9 +29,6 @@ internal abstract class BuilderModule {
@ContributesAndroidInjector
abstract fun bindMessageSendActivity(): SendMessageActivity
@ContributesAndroidInjector
abstract fun bindTimetableWidgetService(): TimetableWidgetService
@ContributesAndroidInjector
abstract fun bindTimetableWidgetProvider(): TimetableWidgetProvider
}

View File

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

View File

@ -40,7 +40,7 @@ class SyncManager @Inject constructor(
fun startSyncWorker(restart: Boolean = false) {
if (preferencesRepository.isServiceEnabled && !now().isHolidays) {
workManager.enqueueUniquePeriodicWork(SyncWorker::class.java.simpleName, if (restart) REPLACE else KEEP,
PeriodicWorkRequest.Builder(SyncWorker::class.java, preferencesRepository.servicesInterval, MINUTES)
PeriodicWorkRequest.Builder(SyncWorker::class.java, preferencesRepository.servicesInterval, MINUTES, 10, MINUTES)
.setBackoffCriteria(EXPONENTIAL, 30, MINUTES)
.setConstraints(Constraints.Builder()
.setRequiredNetworkType(if (preferencesRepository.isServicesOnlyWifi) METERED else UNMETERED)

View File

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

View File

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

View File

@ -1,6 +1,9 @@
package io.github.wulkanowy.ui.base.session
import android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.login.LoginActivity
import io.github.wulkanowy.ui.modules.main.MainActivity
open class BaseSessionFragment : BaseFragment(), BaseSessionView {
@ -8,4 +11,11 @@ open class BaseSessionFragment : BaseFragment(), BaseSessionView {
override fun showExpiredDialog() {
(activity as? MainActivity)?.showExpiredDialog()
}
override fun openLoginView() {
activity?.also {
startActivity(LoginActivity.getStartIntent(it)
.apply { addFlags(FLAG_ACTIVITY_CLEAR_TASK or FLAG_ACTIVITY_NEW_TASK) })
}
}
}

View File

@ -7,6 +7,9 @@ open class BaseSessionPresenter<T : BaseSessionView>(private val errorHandler: S
override fun onAttachView(view: T) {
super.onAttachView(view)
errorHandler.onDecryptionFail = { view.showExpiredDialog() }
errorHandler.apply {
onDecryptionFail = { view.showExpiredDialog() }
onNoCurrentStudent = { view.openLoginView() }
}
}
}

View File

@ -5,4 +5,6 @@ import io.github.wulkanowy.ui.base.BaseView
interface BaseSessionView : BaseView {
fun showExpiredDialog()
fun openLoginView()
}

View File

@ -2,6 +2,7 @@ package io.github.wulkanowy.ui.base.session
import android.content.res.Resources
import com.readystatesoftware.chuck.api.ChuckCollector
import io.github.wulkanowy.data.exceptions.NoCurrentStudentException
import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.security.ScramblerException
import javax.inject.Inject
@ -13,9 +14,12 @@ open class SessionErrorHandler @Inject constructor(
var onDecryptionFail: () -> Unit = {}
var onNoCurrentStudent: () -> Unit = {}
override fun proceed(error: Throwable) {
when (error) {
is ScramblerException -> onDecryptionFail()
is NoCurrentStudentException -> onNoCurrentStudent()
else -> super.proceed(error)
}
}
@ -23,5 +27,6 @@ open class SessionErrorHandler @Inject constructor(
override fun clear() {
super.clear()
onDecryptionFail = {}
onNoCurrentStudent = {}
}
}

View File

@ -43,7 +43,8 @@ class AboutFragment : BaseFragment(), AboutView, MainView.TitledView {
.withAboutSpecial3(getString(R.string.about_feedback))
.withFields(R.string::class.java.fields)
.withCheckCachedDetection(false)
.withExcludedLibraries("fastadapter", "AndroidIconics", "gson", "Jsoup", "Retrofit", "okio", "OkHttp")
.withExcludedLibraries("fastadapter", "AndroidIconics", "Jsoup", "Retrofit", "okio",
"OkHttp", "Butterknife", "CircleImageView")
.withOnExtraListener { presenter.onExtraSelect(it) })
}.let {
fragmentCompat.onCreateView(inflater.context, inflater, container, savedInstanceState, it)
@ -68,7 +69,7 @@ class AboutFragment : BaseFragment(), AboutView, MainView.TitledView {
data = Uri.parse("mailto:")
putExtra(Intent.EXTRA_EMAIL, Array(1) { "wulkanowyinc@gmail.com" })
putExtra(Intent.EXTRA_SUBJECT, "Zgłoszenie błędu")
putExtra(Intent.EXTRA_TEXT,"Tu umieść treść zgłoszenia\n\n" + "-".repeat(40) + "\n" + """
putExtra(Intent.EXTRA_TEXT, "Tu umieść treść zgłoszenia\n\n" + "-".repeat(40) + "\n" + """
Build: ${BuildConfig.VERSION_CODE}
SDK: ${android.os.Build.VERSION.SDK_INT}
Device: ${android.os.Build.MANUFACTURER} ${android.os.Build.MODEL}

View File

@ -14,12 +14,12 @@ class AccountItem(val student: Student) : AbstractFlexibleItem<AccountItem.ViewH
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 {
return ViewHolder(view, adapter)
}
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>?, holder: ViewHolder?, position: Int, payloads: MutableList<Any>?) {
holder?.apply {
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>, holder: ViewHolder, position: Int, payloads: MutableList<Any>?) {
holder.apply {
accountItemName.text = student.studentName
accountItemSchool.text = student.schoolName
accountItemImage.setBackgroundResource(if (student.isCurrent) R.drawable.ic_account_circular_border else 0)
@ -38,14 +38,13 @@ class AccountItem(val student: Student) : AbstractFlexibleItem<AccountItem.ViewH
}
override fun hashCode(): Int {
return student.hashCode()
var result = student.hashCode()
result = 31 * result + student.id.toInt()
return result
}
class ViewHolder(view: View?, adapter: FlexibleAdapter<IFlexible<*>>?) : FlexibleViewHolder(view, adapter),
LayoutContainer {
override val containerView: View?
class ViewHolder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter), LayoutContainer {
override val containerView: View
get() = contentView
}
}

View File

@ -14,14 +14,13 @@ import kotlinx.android.synthetic.main.item_attendance.*
class AttendanceItem(val attendance: Attendance) : AbstractFlexibleItem<AttendanceItem.ViewHolder>() {
override fun getLayoutRes() = R.layout.item_attendance
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>): ViewHolder {
return ViewHolder(view, adapter)
}
override fun getLayoutRes(): Int = R.layout.item_attendance
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 {
attendanceItemNumber.text = attendance.number.toString()
attendanceItemSubject.text = attendance.subject
@ -37,16 +36,17 @@ class AttendanceItem(val attendance: Attendance) : AbstractFlexibleItem<Attendan
other as AttendanceItem
if (attendance != other.attendance) return false
return true
}
override fun hashCode(): Int {
return attendance.hashCode()
var result = attendance.hashCode()
result = 31 * result + attendance.id.toInt()
return result
}
class ViewHolder(val view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter),
LayoutContainer {
class ViewHolder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter), LayoutContainer {
override val containerView: View
get() = contentView
}

View File

@ -135,7 +135,7 @@ class AttendancePresenter @Inject constructor(
clearData()
showNextButton(!currentDate.plusDays(1).isHolidays)
showPreButton(!currentDate.minusDays(1).isHolidays)
updateNavigationDay(currentDate.toFormattedString("EEEE \n dd.MM.YYYY").capitalize())
updateNavigationDay(currentDate.toFormattedString("EEEE\ndd.MM.YYYY").capitalize())
}
}
}

View File

@ -23,12 +23,12 @@ class AttendanceSummaryItem(
override fun getLayoutRes() = R.layout.item_attendance_summary
override fun createViewHolder(view: View?, adapter: FlexibleAdapter<IFlexible<*>>?): ViewHolder {
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>): ViewHolder {
return ViewHolder(view, adapter)
}
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>?, holder: ViewHolder?, position: Int, payloads: MutableList<Any>?) {
holder?.apply {
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>, holder: ViewHolder, position: Int, payloads: MutableList<Any>?) {
holder.apply {
attendanceSummaryMonth.text = month
attendanceSummaryPercentage.text = percentage
attendanceSummaryPresent.text = present
@ -73,10 +73,8 @@ class AttendanceSummaryItem(
return result
}
class ViewHolder(view: View?, adapter: FlexibleAdapter<IFlexible<*>>?) : FlexibleViewHolder(view, adapter),
LayoutContainer {
override val containerView: View?
class ViewHolder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter), LayoutContainer {
override val containerView: View
get() = contentView
}
}

View File

@ -18,8 +18,7 @@ class ExamItem(header: ExamHeader, val exam: Exam) : AbstractSectionableItem<Exa
return ViewHolder(view, adapter)
}
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>, holder: ViewHolder,
position: Int, payloads: MutableList<Any>?) {
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>, holder: ViewHolder, position: Int, payloads: MutableList<Any>?) {
holder.run {
examItemSubject.text = exam.subject
examItemTeacher.text = exam.teacher
@ -39,12 +38,12 @@ class ExamItem(header: ExamHeader, val exam: Exam) : AbstractSectionableItem<Exa
}
override fun hashCode(): Int {
return exam.hashCode()
var result = exam.hashCode()
result = 31 * result + exam.id.toInt()
return result
}
class ViewHolder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter),
LayoutContainer {
class ViewHolder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter), LayoutContainer {
override val containerView: View
get() = contentView
}

View File

@ -29,6 +29,8 @@ class GradeFragment : BaseSessionFragment(), GradeView, MainView.MainChildView,
@Inject
lateinit var pagerAdapter: BaseFragmentPagerAdapter
private var semesterSwitchMenu: MenuItem? = null
companion object {
private const val SAVED_SEMESTER_KEY = "CURRENT_SEMESTER"
@ -57,6 +59,8 @@ class GradeFragment : BaseSessionFragment(), GradeView, MainView.MainChildView,
override fun onCreateOptionsMenu(menu: Menu?, inflater: MenuInflater?) {
inflater?.inflate(R.menu.action_menu_grade, menu)
semesterSwitchMenu = menu?.findItem(R.id.gradeMenuSemester)
presenter.onCreateMenu()
}
override fun initView() {
@ -75,6 +79,7 @@ class GradeFragment : BaseSessionFragment(), GradeView, MainView.MainChildView,
setOnSelectPageListener { presenter.onPageSelected(it) }
}
gradeTabLayout.setupWithViewPager(gradeViewPager)
gradeSwipe.setOnRefreshListener { presenter.onSwipeRefresh() }
}
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
@ -95,8 +100,20 @@ class GradeFragment : BaseSessionFragment(), GradeView, MainView.MainChildView,
gradeProgress.visibility = if (show) VISIBLE else INVISIBLE
}
override fun showEmpty() {
gradeEmpty.visibility = VISIBLE
override fun showEmpty(show: Boolean) {
gradeEmpty.visibility = if (show) VISIBLE else INVISIBLE
}
override fun showRefresh(show: Boolean) {
gradeSwipe.isRefreshing = show
}
override fun showSemesterSwitch(show: Boolean) {
semesterSwitchMenu?.isVisible = show
}
override fun enableSwipe(enable: Boolean) {
gradeSwipe.isEnabled = enable
}
override fun showSemesterDialog(selectedIndex: Int) {

View File

@ -7,9 +7,7 @@ import io.github.wulkanowy.ui.base.session.BaseSessionPresenter
import io.github.wulkanowy.ui.base.session.SessionErrorHandler
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.SchedulersProvider
import io.reactivex.Completable
import timber.log.Timber
import java.util.concurrent.TimeUnit.MILLISECONDS
import javax.inject.Inject
class GradePresenter @Inject constructor(
@ -30,12 +28,16 @@ class GradePresenter @Inject constructor(
fun onAttachView(view: GradeView, savedIndex: Int?) {
super.onAttachView(view)
Timber.i("Grade view is attached")
disposable.add(Completable.timer(150, MILLISECONDS, schedulers.mainThread)
.subscribe {
selectedIndex = savedIndex ?: 0
view.initView()
loadData()
})
selectedIndex = savedIndex ?: 0
view.run {
initView()
enableSwipe(false)
}
loadData()
}
fun onCreateMenu() {
if (semesters.isEmpty()) view?.showSemesterSwitch(false)
}
fun onViewReselected() {
@ -69,12 +71,17 @@ class GradePresenter @Inject constructor(
view?.apply {
showContent(true)
showProgress(false)
showEmpty(false)
loadedSemesterId[currentPageIndex] = semesterId
}
}
fun onPageSelected(index: Int) {
loadChild(index)
if (semesters.isNotEmpty()) loadChild(index)
}
fun onSwipeRefresh() {
loadData()
}
private fun loadData() {
@ -89,17 +96,21 @@ class GradePresenter @Inject constructor(
}
.subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread)
.doFinally { view?.showRefresh(false) }
.subscribe({
view?.run {
Timber.i("Loading grade result: Attempt load index $currentPageIndex")
loadChild(currentPageIndex)
enableSwipe(false)
showSemesterSwitch(true)
}
}) {
Timber.i("Loading grade result: An exception occurred")
errorHandler.dispatch(it)
view?.run {
showProgress(false)
showEmpty()
showEmpty(true)
enableSwipe(true)
}
})
}

View File

@ -12,10 +12,16 @@ interface GradeView : BaseSessionView {
fun showProgress(show: Boolean)
fun showEmpty()
fun showEmpty(show: Boolean)
fun showRefresh(show: Boolean)
fun showSemesterSwitch(show: Boolean)
fun showSemesterDialog(selectedIndex: Int)
fun enableSwipe(enable: Boolean)
fun notifyChildLoadData(index: Int, semesterId: Int, forceRefresh: Boolean)
fun notifyChildParentReselected(index: Int)

View File

@ -28,10 +28,7 @@ class GradeDetailsItem(
}
@SuppressLint("SetTextI18n")
override fun bindViewHolder(
adapter: FlexibleAdapter<IFlexible<*>>, holder: ViewHolder,
position: Int, payloads: MutableList<Any>?
) {
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>, holder: ViewHolder, position: Int, payloads: MutableList<Any>?) {
holder.run {
gradeItemValue.run {
text = grade.entry
@ -70,9 +67,7 @@ class GradeDetailsItem(
return result
}
class ViewHolder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter),
LayoutContainer {
class ViewHolder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter), LayoutContainer {
override val containerView: View
get() = contentView
}

View File

@ -63,7 +63,7 @@ class GradeStatisticsPresenter @Inject constructor(
}
fun onSubjectSelected(name: String) {
Timber.i("Select attendance stats subject $name")
Timber.i("Select grade stats subject $name")
view?.run {
showContent(false)
showProgress(true)
@ -77,7 +77,7 @@ class GradeStatisticsPresenter @Inject constructor(
}
fun onTypeChange(isSemester: Boolean) {
Timber.i("Select attendance stats semester: $isSemester")
Timber.i("Select grade stats semester: $isSemester")
disposable.clear()
view?.run {
showContent(false)

View File

@ -18,15 +18,12 @@ class GradeSummaryItem(
override fun getLayoutRes() = R.layout.item_grade_summary
override fun createViewHolder(view: View?, adapter: FlexibleAdapter<IFlexible<*>>?): ViewHolder {
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>): ViewHolder {
return ViewHolder(view, adapter)
}
override fun bindViewHolder(
adapter: FlexibleAdapter<IFlexible<*>>?, holder: ViewHolder?,
position: Int, payloads: MutableList<Any>?
) {
holder?.run {
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>, holder: ViewHolder, position: Int, payloads: MutableList<Any>?) {
holder.run {
gradeSummaryItemTitle.text = title
gradeSummaryItemAverage.text = average
gradeSummaryItemPredicted.text = predictedGrade
@ -56,10 +53,8 @@ class GradeSummaryItem(
return result
}
class ViewHolder(view: View?, adapter: FlexibleAdapter<IFlexible<*>>?) : FlexibleViewHolder(view, adapter),
LayoutContainer {
override val containerView: View?
class ViewHolder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter), LayoutContainer {
override val containerView: View
get() = contentView
}
}

View File

@ -10,9 +10,8 @@ import io.github.wulkanowy.data.db.entities.Homework
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.item_homework.*
class HomeworkItem(
header: HomeworkHeader, val homework: Homework
) : AbstractSectionableItem<HomeworkItem.ViewHolder, HomeworkHeader>(header) {
class HomeworkItem(header: HomeworkHeader, val homework: Homework) :
AbstractSectionableItem<HomeworkItem.ViewHolder, HomeworkHeader>(header) {
override fun getLayoutRes() = R.layout.item_homework
@ -20,10 +19,7 @@ class HomeworkItem(
return ViewHolder(view, adapter)
}
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 {
homeworkItemSubject.text = homework.subject
homeworkItemTeacher.text = homework.teacher
@ -42,12 +38,12 @@ class HomeworkItem(
}
override fun hashCode(): Int {
return homework.hashCode()
var result = homework.hashCode()
result = 31 * result + homework.id.toInt()
return result
}
class ViewHolder(val view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter),
LayoutContainer {
class ViewHolder(val view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter), LayoutContainer {
override val containerView: View
get() = contentView
}

View File

@ -8,19 +8,21 @@ import eu.davidea.viewholders.FlexibleViewHolder
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Student
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.item_login_student_select.view.*
import kotlinx.android.synthetic.main.item_login_student_select.*
class LoginStudentSelectItem(val student: Student) : AbstractFlexibleItem<LoginStudentSelectItem.ItemViewHolder>() {
override fun getLayoutRes(): Int = R.layout.item_login_student_select
override fun createViewHolder(view: View?, adapter: FlexibleAdapter<IFlexible<*>>?): ItemViewHolder {
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>): ItemViewHolder {
return ItemViewHolder(view, adapter)
}
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>?, holder: ItemViewHolder?,
position: Int, payloads: MutableList<Any>?) {
holder?.bind(student)
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>, holder: ItemViewHolder, position: Int, payloads: MutableList<Any>?) {
holder.run {
loginItemName.text = student.studentName
loginItemSchool.text = student.schoolName
}
}
override fun equals(other: Any?): Boolean {
@ -38,17 +40,8 @@ class LoginStudentSelectItem(val student: Student) : AbstractFlexibleItem<LoginS
return student.hashCode()
}
class ItemViewHolder(view: View?, adapter: FlexibleAdapter<*>?)
: FlexibleViewHolder(view, adapter), LayoutContainer {
override val containerView: View?
class ItemViewHolder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter), LayoutContainer {
override val containerView: View
get() = itemView
fun bind(item: Student) {
itemView.run {
loginItemName.text = item.studentName
loginItemSchool.text = item.schoolName
}
}
}
}

View File

@ -22,7 +22,11 @@ class LuckyNumberPresenter @Inject constructor(
override fun onAttachView(view: LuckyNumberView) {
super.onAttachView(view)
Timber.i("Lucky number view is attached")
view.initView()
view.run {
initView()
showContent(false)
enableSwipe(false)
}
loadData()
}
@ -35,12 +39,6 @@ class LuckyNumberPresenter @Inject constructor(
.flatMapMaybe { luckyNumberRepository.getLuckyNumber(it, forceRefresh) }
.subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread)
.doOnSubscribe {
view?.run {
showContent(false)
enableSwipe(false)
}
}
.doFinally {
view?.run {
hideRefresh()

View File

@ -2,7 +2,10 @@ package io.github.wulkanowy.ui.modules.main
import android.content.Context
import android.content.Intent
import android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
import android.os.Bundle
import android.os.Handler
import android.view.Menu
import android.view.MenuItem
import androidx.appcompat.app.AlertDialog
@ -68,11 +71,6 @@ class MainActivity : BaseActivity(), MainView {
return true
}
override fun onStart() {
super.onStart()
presenter.onViewChange()
}
override fun initView() {
mainBottomNav.run {
addItems(
@ -144,7 +142,9 @@ class MainActivity : BaseActivity(), MainView {
}
override fun notifyMenuViewReselected() {
(navController.currentStack?.get(0) as? MainView.MainChildView)?.onFragmentReselected()
Handler().postDelayed({
(navController.currentStack?.get(0) as? MainView.MainChildView)?.onFragmentReselected()
}, 250)
}
fun showDialogFragment(dialog: DialogFragment) {
@ -165,7 +165,7 @@ class MainActivity : BaseActivity(), MainView {
override fun openLoginView() {
startActivity(LoginActivity.getStartIntent(this)
.apply { addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK) })
.apply { addFlags(FLAG_ACTIVITY_CLEAR_TASK or FLAG_ACTIVITY_NEW_TASK) })
}
override fun onSaveInstanceState(outState: Bundle?) {

View File

@ -5,7 +5,6 @@ import dagger.Module
import dagger.Provides
import dagger.android.ContributesAndroidInjector
import io.github.wulkanowy.R
import io.github.wulkanowy.di.scopes.PerActivity
import io.github.wulkanowy.di.scopes.PerFragment
import io.github.wulkanowy.ui.modules.about.AboutFragment
import io.github.wulkanowy.ui.modules.about.AboutModule
@ -33,7 +32,6 @@ abstract class MainModule {
companion object {
@JvmStatic
@PerActivity
@Provides
fun provideFragNavController(activity: MainActivity): FragNavController {
return FragNavController(activity.supportFragmentManager, R.id.mainFragmentContainer)

View File

@ -24,9 +24,9 @@ class MainPresenter @Inject constructor(
fun onAttachView(view: MainView, initMenuIndex: Int) {
super.onAttachView(view)
Timber.i("Main view is attached with $initMenuIndex menu index")
view.run {
startMenuIndex = if (initMenuIndex != -1) initMenuIndex else prefRepository.startMenuIndex
Timber.i("Main view is attached with $startMenuIndex menu index")
initView()
}

View File

@ -20,12 +20,9 @@ class MessageItem(val message: Message, private val noSubjectString: String) :
return ViewHolder(view, adapter)
}
override fun getLayoutRes(): Int = R.layout.item_message
override fun getLayoutRes() = R.layout.item_message
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 style = if (message.unread) BOLD else NORMAL
@ -58,9 +55,7 @@ class MessageItem(val message: Message, private val noSubjectString: String) :
return message.hashCode()
}
class ViewHolder(val view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter),
LayoutContainer {
class ViewHolder(val view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter), LayoutContainer {
override val containerView: View
get() = contentView
}

View File

@ -72,6 +72,10 @@ class SendMessageActivity : BaseActivity(), SendMessageView {
else false
}
override fun onSupportNavigateUp(): Boolean {
return presenter.onUpNavigate()
}
override fun setReportingUnit(unit: ReportingUnit) {
sendMessageFromTextView.setText(unit.senderName)
}

View File

@ -47,6 +47,11 @@ class SendMessagePresenter @Inject constructor(
}
}
fun onUpNavigate(): Boolean {
view?.popView()
return true
}
private fun loadData(message: Message?) {
var reportingUnit: ReportingUnit? = null
var recipients: List<Recipient> = emptyList()

View File

@ -14,12 +14,12 @@ class MoreItem(val title: String, private val drawable: Drawable?) : AbstractFle
override fun getLayoutRes() = R.layout.item_more
override fun createViewHolder(view: View?, adapter: FlexibleAdapter<IFlexible<*>>?): ViewHolder {
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>): ViewHolder {
return ViewHolder(view, adapter)
}
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>?, holder: ViewHolder?, position: Int, payloads: MutableList<Any>?) {
holder?.apply {
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>, holder: ViewHolder, position: Int, payloads: MutableList<Any>?) {
holder.apply {
moreItemTitle.text = title
moreItemImage.setImageDrawable(drawable)
}
@ -40,9 +40,8 @@ class MoreItem(val title: String, private val drawable: Drawable?) : AbstractFle
return title.hashCode()
}
class ViewHolder(view: View?, adapter: FlexibleAdapter<*>?) : FlexibleViewHolder(view, adapter), LayoutContainer {
override val containerView: View?
class ViewHolder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter), LayoutContainer {
override val containerView: View
get() = contentView
}
}

View File

@ -15,16 +15,13 @@ import kotlinx.android.synthetic.main.item_note.*
class NoteItem(val note: Note) : AbstractFlexibleItem<NoteItem.ViewHolder>() {
override fun getLayoutRes() = R.layout.item_note
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>): NoteItem.ViewHolder {
return NoteItem.ViewHolder(view, adapter)
}
override fun getLayoutRes(): Int = R.layout.item_note
override fun bindViewHolder(
adapter: FlexibleAdapter<IFlexible<*>>,
holder: NoteItem.ViewHolder, position: Int, payloads: MutableList<Any>?
) {
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>, holder: NoteItem.ViewHolder, position: Int, payloads: MutableList<Any>?) {
holder.apply {
noteItemDate.apply {
text = note.date.toFormattedString()
@ -57,7 +54,6 @@ class NoteItem(val note: Note) : AbstractFlexibleItem<NoteItem.ViewHolder>() {
}
class ViewHolder(val view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter), LayoutContainer {
override val containerView: View
get() = contentView
}

View File

@ -15,18 +15,17 @@ import io.github.wulkanowy.utils.toFormattedString
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.item_timetable.*
class TimetableItem(val lesson: Timetable, private val roomText: String)
: AbstractFlexibleItem<TimetableItem.ViewHolder>() {
class TimetableItem(val lesson: Timetable, private val roomText: String) :
AbstractFlexibleItem<TimetableItem.ViewHolder>() {
override fun getLayoutRes(): Int = R.layout.item_timetable
override fun getLayoutRes() = R.layout.item_timetable
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>): ViewHolder {
return ViewHolder(view, adapter)
}
@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 {
timetableItemNumber.text = lesson.number.toString()
timetableItemSubject.text = lesson.subject
@ -34,8 +33,8 @@ class TimetableItem(val lesson: Timetable, private val roomText: String)
timetableItemTime.text = "${lesson.start.toFormattedString("HH:mm")} - ${lesson.end.toFormattedString("HH:mm")}"
timetableItemAlert.visibility = if (lesson.changes || lesson.canceled) VISIBLE else GONE
timetableItemSubject.paintFlags =
if (lesson.canceled) timetableItemSubject.paintFlags or Paint.STRIKE_THRU_TEXT_FLAG
else timetableItemSubject.paintFlags and Paint.STRIKE_THRU_TEXT_FLAG.inv()
if (lesson.canceled) timetableItemSubject.paintFlags or Paint.STRIKE_THRU_TEXT_FLAG
else timetableItemSubject.paintFlags and Paint.STRIKE_THRU_TEXT_FLAG.inv()
}
}
@ -50,12 +49,12 @@ class TimetableItem(val lesson: Timetable, private val roomText: String)
}
override fun hashCode(): Int {
return lesson.hashCode()
var result = lesson.hashCode()
result = 31 * result + lesson.id.toInt()
return result
}
class ViewHolder(val view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter),
LayoutContainer {
class ViewHolder(val view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter), LayoutContainer {
override val containerView: View
get() = contentView
}

View File

@ -128,7 +128,7 @@ class TimetablePresenter @Inject constructor(
clearData()
showNextButton(!currentDate.plusDays(1).isHolidays)
showPreButton(!currentDate.minusDays(1).isHolidays)
updateNavigationDay(currentDate.toFormattedString("EEEE \n dd.MM.YYYY").capitalize())
updateNavigationDay(currentDate.toFormattedString("EEEE\ndd.MM.YYYY").capitalize())
}
}
}

View File

@ -112,7 +112,7 @@ class CompletedLessonsPresenter @Inject constructor(
clearData()
showNextButton(!currentDate.plusDays(1).isHolidays)
showPreButton(!currentDate.minusDays(1).isHolidays)
updateNavigationDay(currentDate.toFormattedString("EEEE \n dd.MM.YYYY").capitalize())
updateNavigationDay(currentDate.toFormattedString("EEEE\ndd.MM.YYYY").capitalize())
}
}
}

View File

@ -17,6 +17,7 @@ import io.github.wulkanowy.data.repositories.student.StudentRepository
import io.github.wulkanowy.data.repositories.timetable.TimetableRepository
import io.github.wulkanowy.utils.SchedulersProvider
import io.github.wulkanowy.utils.toFormattedString
import io.reactivex.Single
import org.threeten.bp.LocalDate
import timber.log.Timber
@ -51,9 +52,13 @@ class TimetableWidgetFactory(
?.let { date ->
try {
lessons = studentRepository.isStudentSaved()
.flatMap { studentRepository.getCurrentStudent() }
.flatMap { semesterRepository.getCurrentSemester(it) }
.flatMap { timetableRepository.getTimetable(it, date, date) }
.flatMap { isSaved ->
if (isSaved) {
studentRepository.getCurrentStudent()
.flatMap { semesterRepository.getCurrentSemester(it) }
.flatMap { timetableRepository.getTimetable(it, date, date) }
} else Single.just(emptyList())
}
.map { item -> item.sortedBy { it.number } }
.subscribeOn(schedulers.backgroundThread)
.blockingGet()

View File

@ -62,7 +62,7 @@ class TimetableWidgetProvider : BroadcastReceiver() {
private fun onUpdate(context: Context, intent: Intent) {
if (intent.getStringExtra(EXTRA_BUTTON_TYPE) === null) {
intent.getIntArrayExtra(EXTRA_APPWIDGET_IDS).forEach { appWidgetId ->
intent.getIntArrayExtra(EXTRA_APPWIDGET_IDS)?.forEach { appWidgetId ->
updateWidget(context, appWidgetId, now().nextOrSameSchoolDay)
}
} else {
@ -95,7 +95,7 @@ class TimetableWidgetProvider : BroadcastReceiver() {
.apply { action = createWidgetKey(appWidgetId) })
setOnClickPendingIntent(R.id.timetableWidgetNext, createNavIntent(context, appWidgetId, appWidgetId, BUTTON_NEXT))
setOnClickPendingIntent(R.id.timetableWidgetPrev, createNavIntent(context, -appWidgetId, appWidgetId, BUTTON_PREV))
createNavIntent(context, Int.MAX_VALUE, appWidgetId, BUTTON_RESET).also {
createNavIntent(context, Int.MAX_VALUE - appWidgetId, appWidgetId, BUTTON_RESET).also {
setOnClickPendingIntent(R.id.timetableWidgetDate, it)
setOnClickPendingIntent(R.id.timetableWidgetDay, it)
}

View File

@ -1,20 +1,11 @@
Wersja 0.7.0
Dodaliśmy:
- szczęśliwy numerek
- lekcje zrealizowane
- wysyłanie wiadomości
- ucznia na tle klasy
- opcję zmiany kolorów ocen
Zmieniliśmy:
- wygląd ekranu logowania
- sposób wyświetlania zastępstw
- widok zadań domowych na tygodniowy
Wersja 0.7.4
Naprawiliśmy:
- automatyczne przełączanie widżetu na nowy dzień
- wyświetlanie powiadomień o starych ocenach
- znikające numery sal w planie lekcji
- problem ze stabilnością na androidach 4 i 5
- problem z przełączaniem kont, jeśli zalogowany był jednocześnie uczeń i rodzic
- problem z odświeżaniem danych, jeśli uczeń przeniósł się z klasy do klasy w bieżącym roku szkolnym
Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases/tag/0.7.0
Dodaliśmy:
- nową opcję zmiany wartości plusa i minusa na 0.75
Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases/tag/0.7.4

View File

@ -1,4 +1,5 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@null" />
<stroke
android:width="1dip"
android:color="#61000000" />

View File

@ -17,13 +17,19 @@
app:tabTextColor="@android:color/white"
tools:visibility="visible" />
<androidx.viewpager.widget.ViewPager
android:id="@+id/gradeViewPager"
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/gradeSwipe"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="48dp"
android:visibility="invisible"
tools:visibility="visible" />
android:layout_marginTop="48dp">
<androidx.viewpager.widget.ViewPager
android:id="@+id/gradeViewPager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="invisible"
tools:visibility="visible" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<ProgressBar
android:id="@+id/gradeProgress"

View File

@ -22,6 +22,7 @@
<item>0,25</item>
<item>0,33</item>
<item>0,5</item>
<item>0,75</item>
</string-array>
<string-array name="grade_color_scheme_entries">

View File

@ -50,12 +50,14 @@
<item>0,25</item>
<item>0,33</item>
<item>0,5</item>
<item>0,75</item>
</string-array>
<string-array name="grade_modifier_value" translatable="false">
<item>0.0</item>
<item>0.25</item>
<item>0.33</item>
<item>0.5</item>
<item>0.75</item>
</string-array>
<string-array name="grade_color_scheme_entries">

View File

@ -0,0 +1,19 @@
package io.github.wulkanowy.data.repositories
import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingStrategy
import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.error.ErrorHandler
import io.reactivex.Observable
import io.reactivex.Single
class UnitTestInternetObservingStrategy : InternetObservingStrategy {
override fun checkInternetConnectivity(host: String?, port: Int, timeoutInMs: Int, httpResponse: Int, errorHandler: ErrorHandler?): Single<Boolean> {
return Single.just(true)
}
override fun observeInternetConnectivity(initialIntervalInMs: Int, intervalInMs: Int, host: String?, port: Int, timeoutInMs: Int, httpResponse: Int, errorHandler: ErrorHandler?): Observable<Boolean> {
return Observable.just(true)
}
override fun getDefaultPingHost() = "localhost"
}

View File

@ -1,9 +1,8 @@
package io.github.wulkanowy.data.repositories.remote
package io.github.wulkanowy.data.repositories.attendance
import io.github.wulkanowy.api.Api
import io.github.wulkanowy.api.attendance.Attendance
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.repositories.attendance.AttendanceRemote
import io.mockk.MockKAnnotations
import io.mockk.every
import io.mockk.impl.annotations.MockK

View File

@ -1,9 +1,8 @@
package io.github.wulkanowy.data.repositories.remote
package io.github.wulkanowy.data.repositories.completedlessons
import io.github.wulkanowy.api.Api
import io.github.wulkanowy.api.timetable.CompletedLesson
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.repositories.completedlessons.CompletedLessonsRemote
import io.mockk.MockKAnnotations
import io.mockk.every
import io.mockk.impl.annotations.MockK

View File

@ -1,9 +1,8 @@
package io.github.wulkanowy.data.repositories.remote
package io.github.wulkanowy.data.repositories.exam
import io.github.wulkanowy.api.Api
import io.github.wulkanowy.api.exams.Exam
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.repositories.exam.ExamRemote
import io.mockk.MockKAnnotations
import io.mockk.every
import io.mockk.impl.annotations.MockK

View File

@ -1,9 +1,8 @@
package io.github.wulkanowy.data.repositories.remote
package io.github.wulkanowy.data.repositories.gradestatistics
import io.github.wulkanowy.api.Api
import io.github.wulkanowy.api.grades.GradeStatistics
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.repositories.gradestatistics.GradeStatisticsRemote
import io.mockk.MockKAnnotations
import io.mockk.every
import io.mockk.impl.annotations.MockK

View File

@ -1,4 +1,4 @@
package io.github.wulkanowy.data.repositories.remote
package io.github.wulkanowy.data.repositories.luckynumber
import io.github.wulkanowy.api.Api
import io.github.wulkanowy.data.db.entities.Semester

View File

@ -0,0 +1,74 @@
package io.github.wulkanowy.data.repositories.semester
import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings
import io.github.wulkanowy.data.ApiHelper
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.UnitTestInternetObservingStrategy
import io.reactivex.Maybe
import io.reactivex.Single
import org.junit.Before
import org.junit.Test
import org.mockito.Mock
import org.mockito.Mockito.doNothing
import org.mockito.Mockito.doReturn
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
class SemesterRepositoryTest {
@Mock
private lateinit var semesterRemote: SemesterRemote
@Mock
private lateinit var semesterLocal: SemesterLocal
@Mock
private lateinit var apiHelper: ApiHelper
@Mock
private lateinit var student: Student
private lateinit var semesterRepository: SemesterRepository
private val settings = InternetObservingSettings.builder()
.strategy(UnitTestInternetObservingStrategy())
.build()
@Before
fun initTest() {
MockitoAnnotations.initMocks(this)
semesterRepository = SemesterRepository(semesterRemote, semesterLocal, settings, apiHelper)
}
@Test
fun singleCurrentSemesterTest() {
val semesters = listOf(
createSemesterEntity(false),
createSemesterEntity(true)
)
doNothing().`when`(apiHelper).initApi(student)
doReturn(Maybe.empty<Semester>()).`when`(semesterLocal).getSemesters(student)
doReturn(Single.just(semesters)).`when`(semesterRemote).getSemesters(student)
semesterRepository.getSemesters(student).blockingGet()
verify(semesterLocal).deleteSemesters(emptyList())
verify(semesterLocal).saveSemesters(semesters)
}
@Test(expected = IllegalArgumentException::class)
fun twoCurrentSemesterTest() {
val semesters = listOf(
createSemesterEntity(true),
createSemesterEntity(true)
)
doNothing().`when`(apiHelper).initApi(student)
doReturn(Maybe.empty<Semester>()).`when`(semesterLocal).getSemesters(student)
doReturn(Single.just(semesters)).`when`(semesterRemote).getSemesters(student)
semesterRepository.getSemesters(student).blockingGet()
}
}

View File

@ -0,0 +1,16 @@
package io.github.wulkanowy.data.repositories.semester
import io.github.wulkanowy.data.db.entities.Semester
fun createSemesterEntity(current: Boolean): Semester {
return Semester(
studentId = 0,
diaryId = 0,
semesterId = 0,
diaryName = "",
classId = 0,
isCurrent = current,
semesterName = 0,
unitId = 0
)
}

View File

@ -1,8 +1,7 @@
package io.github.wulkanowy.data.repositories.remote
package io.github.wulkanowy.data.repositories.student
import io.github.wulkanowy.api.Api
import io.github.wulkanowy.api.register.Student
import io.github.wulkanowy.data.repositories.student.StudentRemote
import io.reactivex.Single
import org.junit.Assert.assertEquals
import org.junit.Before
@ -23,7 +22,7 @@ class StudentRemoteTest {
@Test
fun testRemoteAll() {
doReturn(Single.just(listOf(Student("", "", 1, "test", "", "", Api.LoginType.AUTO))))
doReturn(Single.just(listOf(Student("", "", 1, "test", "", "", 1, Api.LoginType.AUTO))))
.`when`(mockApi).getStudents()
val students = StudentRemote(mockApi).getStudents("", "", "").blockingGet()

View File

@ -1,4 +1,4 @@
package io.github.wulkanowy.data.repositories.remote
package io.github.wulkanowy.data.repositories.timetable
import io.github.wulkanowy.api.Api
import io.github.wulkanowy.api.timetable.Timetable

View File

@ -86,7 +86,7 @@ class LoginFormPresenterTest {
@Test
fun loginTest() {
val studentTest = Student(email = "test@", password = "123", endpoint = "https://fakelog.cf", loginType = "AUTO", studentName = "", schoolSymbol = "", schoolName = "", studentId = 0, isCurrent = false, symbol = "", registrationDate = now())
val studentTest = Student(email = "test@", password = "123", endpoint = "https://fakelog.cf", loginType = "AUTO", studentName = "", schoolSymbol = "", schoolName = "", studentId = 0, classId = 1, isCurrent = false, symbol = "", registrationDate = now())
doReturn(Single.just(listOf(studentTest)))
.`when`(repository).getStudents(anyString(), anyString(), anyString(), anyString())

View File

@ -32,7 +32,7 @@ class LoginStudentSelectPresenterTest {
private lateinit var presenter: LoginStudentSelectPresenter
private val testStudent by lazy { Student(email = "test", password = "test123", endpoint = "https://fakelog.cf", loginType = "AUTO", symbol = "", isCurrent = false, studentId = 0, schoolName = "", schoolSymbol = "", studentName = "", registrationDate = now()) }
private val testStudent by lazy { Student(email = "test", password = "test123", endpoint = "https://fakelog.cf", loginType = "AUTO", symbol = "", isCurrent = false, studentId = 0, schoolName = "", schoolSymbol = "", classId = 1, studentName = "", registrationDate = now()) }
private val testException by lazy { RuntimeException("Problem") }

View File

@ -22,11 +22,11 @@ class GradeExtensionTest {
@Test
fun calcWeightedAverage() {
assertEquals(3.47, listOf(
createGrade(5, 6, 0.33),
createGrade(5, 5, -0.33),
createGrade(4, 1, 0.0),
createGrade(1, 9, 0.5),
createGrade(0, 0, 0.0)
createGrade(5, 6.0, 0.33),
createGrade(5, 5.0, -0.33),
createGrade(4, 1.0, 0.0),
createGrade(1, 9.0, 0.5),
createGrade(0, .0, 0.0)
).calcAverage(), 0.005)
}
@ -42,23 +42,23 @@ class GradeExtensionTest {
@Test
fun changeModifier_default() {
assertEquals(.33, createGrade(5, 0, .33).changeModifier(.0, .0).modifier, .0)
assertEquals(-.33, createGrade(5, 0, -.33).changeModifier(.0, .0).modifier, .0)
assertEquals(.33, createGrade(5, .0, .33).changeModifier(.0, .0).modifier, .0)
assertEquals(-.33, createGrade(5, .0, -.33).changeModifier(.0, .0).modifier, .0)
}
@Test
fun changeModifier_plus() {
assertEquals(.33, createGrade(5, 0, .25).changeModifier(.33, .50).modifier, .0)
assertEquals(.25, createGrade(5, 0, .33).changeModifier(.25, .0).modifier, .0)
assertEquals(.33, createGrade(5, .0, .25).changeModifier(.33, .50).modifier, .0)
assertEquals(.25, createGrade(5, .0, .33).changeModifier(.25, .0).modifier, .0)
}
@Test
fun changeModifier_minus() {
assertEquals(-.33, createGrade(5, 0, -.25).changeModifier(.25, .33).modifier, .0)
assertEquals(-.25, createGrade(5, 0, -.33).changeModifier(.0, .25).modifier, .0)
assertEquals(-.33, createGrade(5, .0, -.25).changeModifier(.25, .33).modifier, .0)
assertEquals(-.25, createGrade(5, .0, -.33).changeModifier(.0, .25).modifier, .0)
}
private fun createGrade(value: Int, weightValue: Int = 0, modifier: Double = 0.25): Grade {
private fun createGrade(value: Int, weightValue: Double = .0, modifier: Double = 0.25): Grade {
return Grade(
semesterId = 1,
studentId = 1,