1
0
mirror of https://github.com/wulkanowy/wulkanowy.git synced 2024-09-20 01:49:09 -05:00

Merge branch '0.7.x'

This commit is contained in:
Mikołaj Pich 2019-03-30 20:19:34 +01:00
commit f2855d598d
No known key found for this signature in database
GPG Key ID: F62B26E36D4C4BAA
29 changed files with 2962 additions and 26 deletions

View File

@ -16,8 +16,8 @@ android {
testApplicationId "io.github.tests.wulkanowy"
minSdkVersion 15
targetSdkVersion 28
versionCode 29
versionName "0.7.3"
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('com.github.wulkanowy:api:master-SNAPSHOT') { exclude module: "threetenbp" }
implementation('com.github.wulkanowy:api:44323f6') { exclude module: "threetenbp" }
implementation "androidx.legacy:legacy-support-v4:1.0.0"
implementation "androidx.appcompat:appcompat:1.0.2"
@ -86,8 +95,8 @@ dependencies {
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.multidex:multidex:2.0.1'
implementation "androidx.work:work-runtime:2.0.0"
implementation "androidx.work:work-rxjava2:2.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"
@ -137,6 +146,7 @@ dependencies {
androidTestImplementation 'androidx.test.ext:junit:1.1.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

@ -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

@ -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

@ -42,6 +42,7 @@ 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
@ -74,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 = 11
const val VERSION_SCHEMA = 12
fun newInstance(context: Context): AppDatabase {
return Room.databaseBuilder(context, AppDatabase::class.java, "wulkanowy_database")
@ -97,7 +98,8 @@ abstract class AppDatabase : RoomDatabase() {
Migration8(),
Migration9(),
Migration10(),
Migration11()
Migration11(),
Migration12()
)
.build()
}

View File

@ -18,6 +18,6 @@ interface SemesterDao {
@Delete
fun deleteAll(semester: List<Semester>)
@Query("SELECT * FROM Semesters WHERE student_id = :studentId")
fun loadAll(studentId: Int): Maybe<List<Semester>>
@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

@ -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,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

@ -19,6 +19,6 @@ class SemesterLocal @Inject constructor(private val semesterDb: SemesterDao) {
}
fun getSemesters(student: Student): Maybe<List<Semester>> {
return semesterDb.loadAll(student.studentId).filter { !it.isEmpty() }
return semesterDb.loadAll(student.studentId, student.classId).filter { !it.isEmpty() }
}
}

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

@ -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

@ -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

@ -1,6 +1,11 @@
Wersja 0.7.3
Wersja 0.7.4
Naprawiliśmy:
- naprawiono problemy ze stabilnością podczas odświeżania danych
- 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.3
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

@ -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

@ -22,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

@ -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") }