diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/local/StudentLocalTest.kt b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/local/StudentLocalTest.kt index 85ff2319..67fe2161 100644 --- a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/local/StudentLocalTest.kt +++ b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/local/StudentLocalTest.kt @@ -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)) - .blockingAwait() + .blockingGet() assert(studentLocal.isStudentSaved) val student = studentLocal.getCurrentStudent().blockingGet() diff --git a/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt b/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt index ca717a67..e7d15ceb 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt @@ -4,6 +4,7 @@ import android.content.Context import androidx.room.Database import androidx.room.Room import androidx.room.RoomDatabase +import androidx.room.RoomDatabase.JournalMode.TRUNCATE import androidx.room.TypeConverters import io.github.wulkanowy.data.db.dao.AttendanceDao import io.github.wulkanowy.data.db.dao.ExamDao @@ -47,6 +48,7 @@ abstract class AppDatabase : RoomDatabase() { companion object { fun newInstance(context: Context): AppDatabase { return Room.databaseBuilder(context, AppDatabase::class.java, "wulkanowy_database") + .setJournalMode(TRUNCATE) .build() } } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/AttendanceDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/AttendanceDao.kt index 287b7d96..17a0bc74 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/AttendanceDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/AttendanceDao.kt @@ -7,7 +7,9 @@ import androidx.room.Query import io.github.wulkanowy.data.db.entities.Attendance import io.reactivex.Maybe import org.threeten.bp.LocalDate +import javax.inject.Singleton +@Singleton @Dao interface AttendanceDao { diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/ExamDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/ExamDao.kt index 60c053c2..fdd3eae2 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/ExamDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/ExamDao.kt @@ -7,7 +7,9 @@ import androidx.room.Query import io.github.wulkanowy.data.db.entities.Exam import io.reactivex.Maybe import org.threeten.bp.LocalDate +import javax.inject.Singleton +@Singleton @Dao interface ExamDao { diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeDao.kt index 531db4ff..17011bd1 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeDao.kt @@ -7,7 +7,9 @@ import androidx.room.Query import androidx.room.Update import io.github.wulkanowy.data.db.entities.Grade import io.reactivex.Maybe +import javax.inject.Singleton +@Singleton @Dao interface GradeDao { diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeSummaryDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeSummaryDao.kt index 92748148..658ba7a8 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeSummaryDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeSummaryDao.kt @@ -6,7 +6,9 @@ import androidx.room.Insert import androidx.room.Query import io.github.wulkanowy.data.db.entities.GradeSummary import io.reactivex.Maybe +import javax.inject.Singleton +@Singleton @Dao interface GradeSummaryDao { diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/HomeworkDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/HomeworkDao.kt index 56ebf88e..bb4841db 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/HomeworkDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/HomeworkDao.kt @@ -7,7 +7,9 @@ import androidx.room.Query import io.github.wulkanowy.data.db.entities.Homework import io.reactivex.Maybe import org.threeten.bp.LocalDate +import javax.inject.Singleton +@Singleton @Dao interface HomeworkDao { diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/NoteDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/NoteDao.kt index 1b94d4d0..27decfbf 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/NoteDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/NoteDao.kt @@ -7,7 +7,9 @@ import androidx.room.Query import androidx.room.Update import io.github.wulkanowy.data.db.entities.Note import io.reactivex.Maybe +import javax.inject.Singleton +@Singleton @Dao interface NoteDao { diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/SemesterDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/SemesterDao.kt index eab24e85..7eef2d0e 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/SemesterDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/SemesterDao.kt @@ -6,19 +6,21 @@ import androidx.room.OnConflictStrategy.IGNORE import androidx.room.Query import io.github.wulkanowy.data.db.entities.Semester import io.reactivex.Maybe +import javax.inject.Singleton +@Singleton @Dao interface SemesterDao { @Insert(onConflict = IGNORE) fun insertAll(semester: List) - @Query("UPDATE Semesters SET is_current = 1 WHERE semester_id = :semesterId AND diary_id = :diaryId") - fun update(semesterId: Int, diaryId: Int) - @Query("SELECT * FROM Semesters WHERE student_id = :studentId") fun load(studentId: Int): Maybe> + @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) } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/StudentDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/StudentDao.kt index 8fa30e90..76e29539 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/StudentDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/StudentDao.kt @@ -5,18 +5,16 @@ import androidx.room.Delete import androidx.room.Insert import androidx.room.OnConflictStrategy.FAIL import androidx.room.Query -import androidx.room.Update import io.github.wulkanowy.data.db.entities.Student import io.reactivex.Maybe +import javax.inject.Singleton +@Singleton @Dao interface StudentDao { @Insert(onConflict = FAIL) - fun insert(student: Student) - - @Update - fun update(student: Student) + fun insert(student: Student): Long @Delete fun delete(student: Student) @@ -27,6 +25,9 @@ interface StudentDao { @Query("SELECT * FROM Students") fun loadAll(): Maybe> + @Query("UPDATE Students SET is_current = 1 WHERE student_id = :studentId") + fun updateCurrent(studentId: Int) + @Query("UPDATE Students SET is_current = 0") fun resetCurrent() } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/TimetableDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/TimetableDao.kt index 9253993c..8c835c52 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/TimetableDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/TimetableDao.kt @@ -7,7 +7,9 @@ import androidx.room.Query import io.github.wulkanowy.data.db.entities.Timetable import io.reactivex.Maybe import org.threeten.bp.LocalDate +import javax.inject.Singleton +@Singleton @Dao interface TimetableDao { diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/StudentRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/StudentRepository.kt index 04d3e621..014eb7f1 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/StudentRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/StudentRepository.kt @@ -44,7 +44,7 @@ class StudentRepository @Inject constructor( return local.getCurrentStudent().toSingle() } - fun saveStudent(student: Student): Completable { + fun saveStudent(student: Student): Single { return local.saveStudent(student) } @@ -52,7 +52,7 @@ class StudentRepository @Inject constructor( return local.setCurrentStudent(student) } - fun logoutCurrentStudent(): Completable { - return local.logoutCurrentStudent() + fun logoutStudent(student: Student): Completable { + return local.logoutStudent(student) } } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/local/SemesterLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/local/SemesterLocal.kt index 45b4c700..0e084d91 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/local/SemesterLocal.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/local/SemesterLocal.kt @@ -21,7 +21,7 @@ class SemesterLocal @Inject constructor(private val semesterDb: SemesterDao) { fun setCurrentSemester(semester: Semester) { semesterDb.run { resetCurrent(semester.studentId) - update(semester.semesterId, semester.diaryId) + updateCurrent(semester.semesterId, semester.diaryId) } } } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/local/StudentLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/local/StudentLocal.kt index 1df90120..c6ad3b43 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/local/StudentLocal.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/local/StudentLocal.kt @@ -8,6 +8,7 @@ import io.github.wulkanowy.utils.security.decrypt import io.github.wulkanowy.utils.security.encrypt import io.reactivex.Completable import io.reactivex.Maybe +import io.reactivex.Single import javax.inject.Inject import javax.inject.Singleton @@ -25,36 +26,32 @@ class StudentLocal @Inject constructor( val isStudentSaved get() = sharedPref.getBoolean(STUDENT_SAVED_KEY, false) - fun saveStudent(student: Student): Completable { - return Completable.fromCallable { - studentDb.run { - resetCurrent() - studentDb.insert(student.copy(password = encrypt(student.password, context))) - } - }.doOnComplete { sharedPref.putBoolean(STUDENT_SAVED_KEY, true) } - } - - fun getCurrentStudent(): Maybe { - return studentDb.loadCurrent().map { it.apply { password = decrypt(password) } } + fun saveStudent(student: Student): Single { + return Single.fromCallable { studentDb.insert(student.copy(password = encrypt(student.password, context))) } + .doOnSuccess { sharedPref.putBoolean(STUDENT_SAVED_KEY, true) } } fun getStudents(): Maybe> { return studentDb.loadAll() } + fun getCurrentStudent(): Maybe { + return studentDb.loadCurrent().map { it.apply { password = decrypt(password) } } + } + fun setCurrentStudent(student: Student): Completable { return Completable.fromCallable { studentDb.run { resetCurrent() - update(student.apply { isCurrent = true }) + updateCurrent(student.studentId) } }.doOnComplete { sharedPref.putBoolean(STUDENT_SAVED_KEY, true) } } - fun logoutCurrentStudent(): Completable { - return studentDb.loadCurrent().doOnSuccess { - studentDb.delete(it) - sharedPref.putBoolean(STUDENT_SAVED_KEY, false) - }.ignoreElement() + fun logoutStudent(student: Student): Completable { + return Completable.fromCallable { + studentDb.delete(student) + if (student.isCurrent) sharedPref.putBoolean(STUDENT_SAVED_KEY, false) + } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountDialog.kt index 3c3c0a7a..32458f27 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountDialog.kt @@ -1,5 +1,7 @@ package io.github.wulkanowy.ui.modules.account +import android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK +import android.content.Intent.FLAG_ACTIVITY_NEW_TASK import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -77,6 +79,13 @@ class AccountDialog : DaggerAppCompatDialogFragment(), AccountView { } } + override fun openClearLoginView() { + activity?.also { + startActivity(LoginActivity.getStartIntent(it) + .apply { addFlags(FLAG_ACTIVITY_CLEAR_TASK or FLAG_ACTIVITY_NEW_TASK) }) + } + } + override fun showConfirmDialog() { context?.let { AlertDialog.Builder(it) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountPresenter.kt index 8a276492..74c35015 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountPresenter.kt @@ -29,7 +29,8 @@ class AccountPresenter @Inject constructor( } fun onLogoutConfirm() { - disposable.add(studentRepository.logoutCurrentStudent() + disposable.add(studentRepository.getCurrentStudent() + .flatMapCompletable { studentRepository.logoutStudent(it) } .andThen(studentRepository.getSavedStudents()) .flatMap { if (it.isNotEmpty()) studentRepository.switchStudent(it[0]).toSingle { it } @@ -40,7 +41,7 @@ class AccountPresenter @Inject constructor( .doFinally { view?.dismissView() } .subscribe({ view?.apply { - if (it.isEmpty()) openLoginView() + if (it.isEmpty()) openClearLoginView() else recreateView() } }, { errorHandler.proceed(it) })) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountView.kt index e5b8ed6c..ca74998a 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountView.kt @@ -14,6 +14,8 @@ interface AccountView : BaseView { fun openLoginView() + fun openClearLoginView() + fun recreateView() } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/options/LoginOptionsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/options/LoginOptionsPresenter.kt index eb9d981f..a9fbb24f 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/options/LoginOptionsPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/options/LoginOptionsPresenter.kt @@ -41,9 +41,11 @@ class LoginOptionsPresenter @Inject constructor( } private fun registerStudent(student: Student) { - disposable.add(studentRepository.saveStudent(student.apply { isCurrent = true }) - .andThen(semesterRepository.getSemesters(student, true)) - .onErrorResumeNext { studentRepository.logoutCurrentStudent().andThen(Single.error(it)) } + disposable.add(studentRepository.saveStudent(student) + .map { student.apply { id = it } } + .flatMap { semesterRepository.getSemesters(student, true) } + .onErrorResumeNext { studentRepository.logoutStudent(student).andThen(Single.error(it)) } + .flatMapCompletable { studentRepository.switchStudent(student) } .subscribeOn(schedulers.backgroundThread) .observeOn(schedulers.mainThread) .doOnSubscribe { diff --git a/app/src/main/java/io/github/wulkanowy/utils/security/Scrambler.kt b/app/src/main/java/io/github/wulkanowy/utils/security/Scrambler.kt index 6c7f242c..295882e9 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/security/Scrambler.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/security/Scrambler.kt @@ -9,9 +9,17 @@ import android.os.Build.VERSION_CODES.JELLY_BEAN_MR2 import android.os.Build.VERSION_CODES.M import android.security.KeyPairGeneratorSpec import android.security.keystore.KeyGenParameterSpec -import android.security.keystore.KeyProperties.* +import android.security.keystore.KeyProperties.DIGEST_SHA256 +import android.security.keystore.KeyProperties.DIGEST_SHA512 +import android.security.keystore.KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1 +import android.security.keystore.KeyProperties.PURPOSE_DECRYPT +import android.security.keystore.KeyProperties.PURPOSE_ENCRYPT +import android.security.keystore.KeyProperties.SIGNATURE_PADDING_RSA_PKCS1 import android.util.Base64 -import android.util.Base64.* +import android.util.Base64.DEFAULT +import android.util.Base64.decode +import android.util.Base64.encode +import android.util.Base64.encodeToString import timber.log.Timber import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream @@ -20,7 +28,7 @@ import java.nio.charset.Charset import java.security.KeyPairGenerator import java.security.KeyStore import java.security.PrivateKey -import java.util.* +import java.util.Calendar import java.util.Calendar.YEAR import javax.crypto.Cipher import javax.crypto.Cipher.DECRYPT_MODE @@ -28,7 +36,6 @@ import javax.crypto.Cipher.ENCRYPT_MODE import javax.crypto.CipherInputStream import javax.crypto.CipherOutputStream import javax.security.auth.x500.X500Principal -import kotlin.collections.ArrayList private const val KEY_ALIAS = "USER_PASSWORD" @@ -80,7 +87,6 @@ fun encrypt(plainText: String, context: Context): String { Timber.e(exception, "An error occurred while encrypting text") String(encode(plainText.toByteArray(KEY_CHARSET), DEFAULT), KEY_CHARSET) } - } fun decrypt(cipherText: String): String { diff --git a/app/src/test/java/io/github/wulkanowy/ui/modules/login/options/LoginOptionsPresenterTest.kt b/app/src/test/java/io/github/wulkanowy/ui/modules/login/options/LoginOptionsPresenterTest.kt index c7c87ded..21632770 100644 --- a/app/src/test/java/io/github/wulkanowy/ui/modules/login/options/LoginOptionsPresenterTest.kt +++ b/app/src/test/java/io/github/wulkanowy/ui/modules/login/options/LoginOptionsPresenterTest.kt @@ -68,8 +68,9 @@ class LoginOptionsPresenterTest { @Test fun onSelectedStudentTest() { - doReturn(Completable.complete()).`when`(studentRepository).saveStudent(testStudent) + doReturn(Single.just(1L)).`when`(studentRepository).saveStudent(testStudent) doReturn(Single.just(emptyList())).`when`(semesterRepository).getSemesters(testStudent, true) + doReturn(Completable.complete()).`when`(studentRepository).switchStudent(testStudent) presenter.onItemSelected(LoginOptionsItem(testStudent)) verify(loginOptionsView).showContent(false) verify(loginOptionsView).showProgress(true) @@ -78,9 +79,9 @@ class LoginOptionsPresenterTest { @Test fun onSelectedStudentErrorTest() { - doReturn(Completable.error(testException)).`when`(studentRepository).saveStudent(testStudent) + doReturn(Single.error(testException)).`when`(studentRepository).saveStudent(testStudent) doReturn(Single.just(emptyList())).`when`(semesterRepository).getSemesters(testStudent, true) - doReturn(Completable.complete()).`when`(studentRepository).logoutCurrentStudent() + doReturn(Completable.complete()).`when`(studentRepository).logoutStudent(testStudent) presenter.onItemSelected(LoginOptionsItem(testStudent)) verify(loginOptionsView).showContent(false) verify(loginOptionsView).showProgress(true)