Add account manager (#183)

This commit is contained in:
Rafał Borcz 2018-11-24 18:51:41 +01:00 committed by Mikołaj Pich
parent 1f30deb36e
commit 7a3c0de7ad
106 changed files with 1140 additions and 585 deletions

View File

@ -52,7 +52,7 @@ script:
- ./gradlew createDebugCoverageReport --stacktrace -PdisableCrashlytics --daemon
- ./gradlew jacocoTestReport --stacktrace --daemon
- if [ "$TRAVIS_PULL_REQUEST" != "false" ] || [ "$TRAVIS_BRANCH" == "master" ]; then
./gradlew sonarqube -x test -x lint -x fabricGenerateResourcesRelease -Dsonar.host.url=$SONAR_HOST -Dsonar.organization=$SONAR_ORG -Dsonar.login=$SONAR_KEY -Dsonar.branch.name=$TRAVIS_BRANCH -PdisableCrashlytics --stacktrace --daemon;
./gradlew sonarqube -x test -x lint -x fabricGenerateResourcesRelease -Dsonar.host.url=$SONAR_HOST -Dsonar.organization=$SONAR_ORG -Dsonar.login=$SONAR_KEY -Dsonar.branch.name=${TRAVIS_PULL_REQUEST_BRANCH:-$TRAVIS_BRANCH} -PdisableCrashlytics --stacktrace --daemon;
fi
- |
if [ $TRAVIS_TAG ]; then

View File

@ -31,7 +31,7 @@ android {
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
playAccountConfig = playAccountConfigs.defaultAccountConfig
manifestPlaceholders = [ fabricApiKey: fabricApiKey ]
manifestPlaceholders = [fabricApiKey: fabricApiKey]
}
signingConfigs {
@ -74,16 +74,18 @@ play {
uploadImages = true
}
ext.androidx_version = "1.0.0"
configurations.all {
resolutionStrategy.force "com.squareup.okhttp3:okhttp-urlconnection:3.11.0"
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation('com.github.wulkanowy:api:a80b8e5') { exclude module: "threetenbp" }
implementation('com.github.wulkanowy:api:0ac961607b') { exclude module: "threetenbp" }
implementation "androidx.legacy:legacy-support-v4:$androidx_version"
implementation "androidx.appcompat:appcompat:$androidx_version"
implementation "androidx.cardview:cardview:$androidx_version"
implementation "com.google.android.material:material:$androidx_version"
implementation "androidx.legacy:legacy-support-v4:1.0.0"
implementation "androidx.appcompat:appcompat:1.0.2"
implementation "androidx.cardview:cardview:1.0.0"
implementation "com.google.android.material:material:1.0.0"
implementation 'androidx.multidex:multidex:2.0.0'
implementation 'com.takisoft.preferencex:preferencex:1.0.0'
@ -113,7 +115,6 @@ dependencies {
implementation "com.jakewharton.timber:timber:4.7.1"
implementation "at.favre.lib:slf4j-timber:1.0.1"
implementation 'com.akaita.java:rxjava2-debug:1.3.0'
implementation("com.crashlytics.sdk.android:crashlytics:2.9.5@aar") {
transitive = true
}
@ -132,6 +133,5 @@ dependencies {
androidTestImplementation 'androidx.test:runner:1.1.0'
androidTestImplementation 'androidx.test.ext:junit:1.0.0'
androidTestImplementation "org.mockito:mockito-android:2.23.0"
androidTestImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
}

View File

@ -23,7 +23,7 @@ class AttendanceLocalTest {
@Before
fun createDb() {
testDb = Room.inMemoryDatabaseBuilder(ApplicationProvider.getApplicationContext(), AppDatabase::class.java).build()
attendanceLocal = AttendanceLocal(testDb.attendanceDao())
attendanceLocal = AttendanceLocal(testDb.attendanceDao)
}
@After

View File

@ -23,7 +23,7 @@ class ExamLocalTest {
@Before
fun createDb() {
testDb = Room.inMemoryDatabaseBuilder(ApplicationProvider.getApplicationContext(), AppDatabase::class.java).build()
examLocal = ExamLocal(testDb.examsDao())
examLocal = ExamLocal(testDb.examsDao)
}
@After

View File

@ -14,9 +14,9 @@ import org.junit.runner.RunWith
import kotlin.test.assertEquals
@RunWith(AndroidJUnit4::class)
class SessionLocalTest {
class StudentLocalTest {
private lateinit var sessionLocal: SessionLocal
private lateinit var studentLocal: StudentLocal
private lateinit var testDb: AppDatabase
@ -26,9 +26,9 @@ class SessionLocalTest {
fun createDb() {
val context = ApplicationProvider.getApplicationContext<Context>()
testDb = Room.inMemoryDatabaseBuilder(context, AppDatabase::class.java)
.build()
.build()
sharedHelper = SharedPrefHelper(context.getSharedPreferences("TEST", Context.MODE_PRIVATE))
sessionLocal = SessionLocal(testDb.studentDao(), testDb.semesterDao(), sharedHelper, context)
studentLocal = StudentLocal(testDb.studentDao, sharedHelper, context)
}
@After
@ -38,12 +38,11 @@ class SessionLocalTest {
@Test
fun saveAndReadTest() {
sessionLocal.saveStudent(Student(email = "test", password = "test123", schoolSymbol = "23", endpoint = "fakelog.cf", loginType = "AUTO")).blockingAwait()
assert(sharedHelper.getLong(SessionLocal.LAST_USER_KEY, 0) == 1L)
studentLocal.saveStudent(Student(email = "test", password = "test123", schoolSymbol = "23", endpoint = "fakelog.cf", loginType = "AUTO", isCurrent = true))
.blockingAwait()
assert(studentLocal.isStudentSaved)
assert(sessionLocal.isSessionSaved)
val student = sessionLocal.getLastStudent().blockingGet()
val student = studentLocal.getCurrentStudent().blockingGet()
assertEquals("23", student.schoolSymbol)
}
}

View File

@ -24,7 +24,7 @@ class TimetableLocalTest {
@Before
fun createDb() {
testDb = Room.inMemoryDatabaseBuilder(ApplicationProvider.getApplicationContext(), AppDatabase::class.java).build()
timetableDb = TimetableLocal(testDb.timetableDao())
timetableDb = TimetableLocal(testDb.timetableDao)
}
@After

View File

@ -38,7 +38,6 @@
android:name=".ui.modules.main.MainActivity"
android:configChanges="orientation|screenSize"
android:label="@string/main_title"
android:launchMode="singleTop"
android:theme="@style/WulkanowyTheme.NoActionBar" />
<service

View File

@ -3,7 +3,6 @@ package io.github.wulkanowy
import android.content.Context
import androidx.appcompat.app.AppCompatDelegate
import androidx.multidex.MultiDex
import com.akaita.java.rxjava2debug.RxJava2Debug
import com.crashlytics.android.Crashlytics
import com.crashlytics.android.answers.Answers
import com.crashlytics.android.core.CrashlyticsCore
@ -36,7 +35,6 @@ class WulkanowyApp : DaggerApplication() {
AndroidThreeTen.init(this)
initializeFabric()
if (DEBUG) enableDebugLog()
RxJava2Debug.enableRxJava2AssemblyTracking(arrayOf(BuildConfig.APPLICATION_ID))
AppCompatDelegate.setDefaultNightMode(prefRepository.currentTheme)
}

View File

@ -0,0 +1,27 @@
package io.github.wulkanowy.data
import io.github.wulkanowy.api.Api
import io.github.wulkanowy.data.db.entities.Student
import java.net.URL
import javax.inject.Inject
class ApiHelper @Inject constructor(private val api: Api) {
fun initApi(student: Student) {
api.apply {
email = student.email
password = student.password
symbol = student.symbol
schoolSymbol = student.schoolSymbol
studentId = student.studentId
host = URL(student.endpoint).run { host + ":$port".removeSuffix(":-1") }
ssl = student.endpoint.startsWith("https")
loginType = Api.LoginType.valueOf(student.loginType)
}
}
fun initApi(email: String, password: String, symbol: String, endpoint: String) {
initApi(Student(email = email, password = password, symbol = symbol, endpoint = endpoint, loginType = "AUTO"))
}
}

View File

@ -1,7 +1,6 @@
package io.github.wulkanowy.data
import android.content.res.Resources
import com.akaita.java.rxjava2debug.RxJava2Debug
import io.github.wulkanowy.R
import io.github.wulkanowy.api.login.NotLoggedInException
import timber.log.Timber
@ -10,12 +9,12 @@ import java.net.SocketTimeoutException
import java.net.UnknownHostException
import javax.inject.Inject
open class ErrorHandler @Inject constructor(private val resources: Resources) {
open class ErrorHandler @Inject constructor(protected val resources: Resources) {
var showErrorMessage: (String) -> Unit = {}
open fun proceed(error: Throwable) {
Timber.e(RxJava2Debug.getEnhancedStackTrace(error), "An exception occurred while the Wulkanowy was running")
Timber.e(error, "An exception occurred while the Wulkanowy was running")
showErrorMessage((when (error) {
is UnknownHostException -> resources.getString(R.string.all_no_internet)

View File

@ -9,6 +9,10 @@ import dagger.Module
import dagger.Provides
import io.github.wulkanowy.api.Api
import io.github.wulkanowy.data.db.AppDatabase
import okhttp3.logging.HttpLoggingInterceptor
import okhttp3.logging.HttpLoggingInterceptor.Level.BASIC
import okhttp3.logging.HttpLoggingInterceptor.Level.NONE
import timber.log.Timber
import javax.inject.Singleton
@Module
@ -18,14 +22,19 @@ internal class RepositoryModule {
@Provides
fun provideInternetObservingSettings(): InternetObservingSettings {
return InternetObservingSettings.builder()
.strategy(SocketInternetObservingStrategy())
.host("www.google.com")
.build()
.strategy(SocketInternetObservingStrategy())
.host("www.google.com")
.build()
}
@Singleton
@Provides
fun provideApi() = Api()
fun provideApi(): Api {
return Api().apply {
logLevel = NONE
setInterceptor(HttpLoggingInterceptor(HttpLoggingInterceptor.Logger { Timber.d(it) }).setLevel(BASIC))
}
}
@Singleton
@Provides
@ -36,43 +45,41 @@ internal class RepositoryModule {
@Singleton
@Provides
fun provideSharedPref(context: Context): SharedPreferences {
return PreferenceManager.getDefaultSharedPreferences(context)
}
fun provideSharedPref(context: Context): SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
@Singleton
@Provides
fun provideStudentDao(database: AppDatabase) = database.studentDao()
fun provideStudentDao(database: AppDatabase) = database.studentDao
@Singleton
@Provides
fun provideSemesterDao(database: AppDatabase) = database.semesterDao()
fun provideSemesterDao(database: AppDatabase) = database.semesterDao
@Singleton
@Provides
fun provideGradeDao(database: AppDatabase) = database.gradeDao()
fun provideGradeDao(database: AppDatabase) = database.gradeDao
@Singleton
@Provides
fun provideGradeSummaryDao(database: AppDatabase) = database.gradeSummaryDao()
fun provideGradeSummaryDao(database: AppDatabase) = database.gradeSummaryDao
@Singleton
@Provides
fun provideExamDao(database: AppDatabase) = database.examsDao()
fun provideExamDao(database: AppDatabase) = database.examsDao
@Singleton
@Provides
fun provideAttendanceDao(database: AppDatabase) = database.attendanceDao()
fun provideAttendanceDao(database: AppDatabase) = database.attendanceDao
@Singleton
@Provides
fun provideTimetableDao(database: AppDatabase) = database.timetableDao()
fun provideTimetableDao(database: AppDatabase) = database.timetableDao
@Singleton
@Provides
fun provideNoteDao(database: AppDatabase) = database.noteDao()
fun provideNoteDao(database: AppDatabase) = database.noteDao
@Singleton
@Provides
fun provideHomeworkDao(database: AppDatabase) = database.homeworkDao()
fun provideHomeworkDao(database: AppDatabase) = database.homeworkDao
}

View File

@ -51,21 +51,21 @@ abstract class AppDatabase : RoomDatabase() {
}
}
abstract fun studentDao(): StudentDao
abstract val studentDao: StudentDao
abstract fun semesterDao(): SemesterDao
abstract val semesterDao: SemesterDao
abstract fun examsDao(): ExamDao
abstract val examsDao: ExamDao
abstract fun timetableDao(): TimetableDao
abstract val timetableDao: TimetableDao
abstract fun attendanceDao(): AttendanceDao
abstract val attendanceDao: AttendanceDao
abstract fun gradeDao(): GradeDao
abstract val gradeDao: GradeDao
abstract fun gradeSummaryDao(): GradeSummaryDao
abstract val gradeSummaryDao: GradeSummaryDao
abstract fun noteDao(): NoteDao
abstract val noteDao: NoteDao
abstract fun homeworkDao(): HomeworkDao
abstract val homeworkDao: HomeworkDao
}

View File

@ -19,6 +19,14 @@ class SharedPrefHelper @Inject constructor(private val sharedPref: SharedPrefere
return sharedPref.getLong(key, defaultValue)
}
fun putBoolean(key: String, value: Boolean) {
sharedPref.edit().putBoolean(key, value).apply()
}
fun getBoolean(key: String, defaultValue: Boolean): Boolean {
return sharedPref.getBoolean(key, defaultValue)
}
fun delete(key: String) {
sharedPref.edit().remove(key).apply()
}

View File

@ -18,5 +18,5 @@ interface AttendanceDao {
fun deleteAll(exams: List<Attendance>)
@Query("SELECT * FROM Attendance WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :from AND date <= :end")
fun getExams(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): Maybe<List<Attendance>>
fun load(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): Maybe<List<Attendance>>
}

View File

@ -18,5 +18,5 @@ interface ExamDao {
fun deleteAll(exams: List<Exam>)
@Query("SELECT * FROM Exams WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :from AND date <= :end")
fun getExams(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): Maybe<List<Exam>>
fun load(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): Maybe<List<Exam>>
}

View File

@ -1,6 +1,10 @@
package io.github.wulkanowy.data.db.dao
import androidx.room.*
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update
import io.github.wulkanowy.data.db.entities.Grade
import io.reactivex.Maybe
@ -20,8 +24,8 @@ interface GradeDao {
fun deleteAll(grades: List<Grade>)
@Query("SELECT * FROM Grades WHERE semester_id = :semesterId AND student_id = :studentId")
fun getGrades(semesterId: Int, studentId: Int): Maybe<List<Grade>>
fun load(semesterId: Int, studentId: Int): Maybe<List<Grade>>
@Query("SELECT * FROM Grades WHERE is_read = 0 AND semester_id = :semesterId AND student_id = :studentId")
fun getNewGrades(semesterId: Int, studentId: Int): Maybe<List<Grade>>
fun loadNew(semesterId: Int, studentId: Int): Maybe<List<Grade>>
}

View File

@ -3,7 +3,6 @@ package io.github.wulkanowy.data.db.dao
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy.REPLACE
import androidx.room.Query
import io.github.wulkanowy.data.db.entities.GradeSummary
import io.reactivex.Maybe
@ -18,5 +17,5 @@ interface GradeSummaryDao {
fun deleteAll(gradesSummary: List<GradeSummary>)
@Query("SELECT * FROM grades_summary WHERE student_id = :studentId AND semester_id = :semesterId")
fun getGradesSummary(semesterId: Int, studentId: Int): Maybe<List<GradeSummary>>
fun load(semesterId: Int, studentId: Int): Maybe<List<GradeSummary>>
}

View File

@ -18,5 +18,5 @@ interface HomeworkDao {
fun deleteAll(homework: List<Homework>)
@Query("SELECT * FROM Homework WHERE semester_id = :semesterId AND student_id = :studentId AND date = :date")
fun getHomework(semesterId: Int, studentId: Int, date: LocalDate): Maybe<List<Homework>>
fun load(semesterId: Int, studentId: Int, date: LocalDate): Maybe<List<Homework>>
}

View File

@ -24,8 +24,8 @@ interface NoteDao {
fun deleteAll(notes: List<Note>)
@Query("SELECT * FROM Notes WHERE semester_id = :semesterId AND student_id = :studentId")
fun getNotes(semesterId: Int, studentId: Int): Maybe<List<Note>>
fun load(semesterId: Int, studentId: Int): Maybe<List<Note>>
@Query("SELECT * FROM Notes WHERE is_read = 0 AND semester_id = :semesterId AND student_id = :studentId")
fun getNewNotes(semesterId: Int, studentId: Int): Maybe<List<Note>>
fun loadNew(semesterId: Int, studentId: Int): Maybe<List<Note>>
}

View File

@ -2,22 +2,23 @@ package io.github.wulkanowy.data.db.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy.IGNORE
import androidx.room.Query
import io.github.wulkanowy.data.db.entities.Semester
import io.reactivex.Single
import io.reactivex.Maybe
@Dao
interface SemesterDao {
@Insert
@Insert(onConflict = IGNORE)
fun insertAll(semester: List<Semester>)
@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 getSemester(studentId: Int): Single<List<Semester>>
fun load(studentId: Int): Maybe<List<Semester>>
@Query("UPDATE Semesters SET is_current = 0")
fun resetCurrentSemester()
@Query("UPDATE Semesters SET is_current = 1 WHERE semester_id = :semesterId")
fun setCurrentSemester(semesterId: Int)
@Query("UPDATE Semesters SET is_current = 0 WHERE student_id = :studentId")
fun resetCurrent(studentId: Int)
}

View File

@ -1,17 +1,32 @@
package io.github.wulkanowy.data.db.dao
import androidx.room.Dao
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
@Dao
interface StudentDao {
@Insert
fun insert(student: Student): Long
@Insert(onConflict = FAIL)
fun insert(student: Student)
@Query("SELECT * FROM Students WHERE id = :id")
fun load(id: Long): Maybe<Student>
@Update
fun update(student: Student)
@Delete
fun delete(student: Student)
@Query("SELECT * FROM Students WHERE is_current = 1")
fun loadCurrent(): Maybe<Student>
@Query("SELECT * FROM Students")
fun loadAll(): Maybe<List<Student>>
@Query("UPDATE Students SET is_current = 0")
fun resetCurrent()
}

View File

@ -18,5 +18,5 @@ interface TimetableDao {
fun deleteAll(exams: List<Timetable>)
@Query("SELECT * FROM Timetable WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :from AND date <= :end")
fun getTimetable(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): Maybe<List<Timetable>>
fun load(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): Maybe<List<Timetable>>
}

View File

@ -2,29 +2,30 @@ package io.github.wulkanowy.data.db.entities
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.Index
import androidx.room.PrimaryKey
@Entity(tableName = "Semesters")
@Entity(tableName = "Semesters", indices = [Index(value = ["student_id", "diary_id", "semester_id"], unique = true)])
data class Semester(
@PrimaryKey(autoGenerate = true)
var id: Long = 0,
@PrimaryKey(autoGenerate = true)
var id: Long = 0,
@ColumnInfo(name = "student_id")
var studentId: Int,
@ColumnInfo(name = "student_id")
var studentId: Int,
@ColumnInfo(name = "diary_id")
var diaryId: Int,
@ColumnInfo(name = "diary_id")
var diaryId: Int,
@ColumnInfo(name = "diary_name")
var diaryName: String,
@ColumnInfo(name = "diary_name")
var diaryName: String,
@ColumnInfo(name = "semester_id")
var semesterId: Int,
@ColumnInfo(name = "semester_id")
var semesterId: Int,
@ColumnInfo(name = "semester_name")
var semesterName: Int,
@ColumnInfo(name = "semester_name")
var semesterName: Int,
@ColumnInfo(name = "is_current")
var current: Boolean = false
@ColumnInfo(name = "is_current")
var isCurrent: Boolean = false
)

View File

@ -2,33 +2,37 @@ package io.github.wulkanowy.data.db.entities
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.Index
import androidx.room.PrimaryKey
@Entity(tableName = "Students")
@Entity(tableName = "Students", indices = [Index(value = ["email", "symbol", "student_id", "school_id"], unique = true)])
data class Student(
@PrimaryKey(autoGenerate = true)
var id: Long = 0,
@PrimaryKey(autoGenerate = true)
var id: Long = 0,
var endpoint: String,
var endpoint: String,
var loginType: String,
var loginType: String,
var email: String,
var email: String,
var password: String,
var password: String,
var symbol: String = "",
var symbol: String = "",
@ColumnInfo(name = "student_id")
var studentId: Int = 0,
@ColumnInfo(name = "student_id")
var studentId: Int = 0,
@ColumnInfo(name = "student_name")
var studentName: String = "",
@ColumnInfo(name = "student_name")
var studentName: String = "",
@ColumnInfo(name = "school_id")
var schoolSymbol: String = "",
@ColumnInfo(name = "school_id")
var schoolSymbol: String = "",
@ColumnInfo(name = "school_name")
var schoolName: String = ""
@ColumnInfo(name = "school_name")
var schoolName: String = "",
@ColumnInfo(name = "is_current")
var isCurrent: Boolean = false
)

View File

@ -0,0 +1,42 @@
package io.github.wulkanowy.data.repositories
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.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.local.SemesterLocal
import io.github.wulkanowy.data.repositories.remote.SemesterRemote
import io.reactivex.Maybe
import io.reactivex.Single
import java.net.UnknownHostException
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class SemesterRepository @Inject constructor(
private val remote: SemesterRemote,
private val local: SemesterLocal,
private val settings: InternetObservingSettings,
private val apiHelper: ApiHelper
) {
fun getSemesters(student: Student, forceRefresh: Boolean = false): Single<List<Semester>> {
return Maybe.just(apiHelper.initApi(student))
.flatMap { local.getSemesters(student).filter { !forceRefresh } }
.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 { local.getSemesters(student).toSingle(emptyList()) })
}
fun getCurrentSemester(student: Student, forceRefresh: Boolean = false): Single<Semester> {
return getSemesters(student, forceRefresh).map { item -> item.single { it.isCurrent } }
}
}

View File

@ -1,64 +0,0 @@
package io.github.wulkanowy.data.repositories
import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork
import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.local.SessionLocal
import io.github.wulkanowy.data.repositories.remote.SessionRemote
import io.reactivex.Completable
import io.reactivex.Single
import java.net.UnknownHostException
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class SessionRepository @Inject constructor(
private val local: SessionLocal,
private val remote: SessionRemote,
private val settings: InternetObservingSettings
) {
val isSessionSaved
get() = local.isSessionSaved
lateinit var cachedStudents: Single<List<Student>>
private set
fun getConnectedStudents(email: String, password: String, symbol: String, endpoint: String): Single<List<Student>> {
cachedStudents = ReactiveNetwork.checkInternetConnectivity(settings)
.flatMap { isConnected ->
if (isConnected) remote.getConnectedStudents(email, password, symbol, endpoint)
else Single.error<List<Student>>(UnknownHostException("No internet connection"))
}.doOnSuccess { cachedStudents = Single.just(it) }
return cachedStudents
}
fun saveStudent(student: Student): Completable {
return remote.getSemesters(student)
.flatMapCompletable { local.saveSemesters(it) }
.concatWith(local.saveStudent(student))
}
fun getSemesters(forceRefresh: Boolean = false): Single<List<Semester>> {
return local.getLastStudent()
.flatMapSingle { student ->
remote.initApi(student)
local.getSemesters(student).filter { !forceRefresh }
.switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings).flatMap {
if (it) remote.getCurrentSemester(student)
else Single.error(UnknownHostException())
}.flatMap { current ->
local.getSemesters(student).doOnSuccess { semesters ->
if (semesters.single { it.current }.semesterId != current.semesterId) {
local.saveSemesters(listOf(current)).andThen {
local.setCurrentSemester(current.semesterId)
}
}
}
}.flatMap {
local.getSemesters(student)
})
}
}
}

View File

@ -0,0 +1,58 @@
package io.github.wulkanowy.data.repositories
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.repositories.local.StudentLocal
import io.github.wulkanowy.data.repositories.remote.StudentRemote
import io.reactivex.Completable
import io.reactivex.Single
import java.net.UnknownHostException
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class StudentRepository @Inject constructor(
private val local: StudentLocal,
private val remote: StudentRemote,
private val settings: InternetObservingSettings,
private val apiHelper: ApiHelper
) {
val isStudentSaved
get() = local.isStudentSaved
lateinit var cachedStudents: Single<List<Student>>
private set
fun getStudents(email: String, password: String, symbol: String, endpoint: String): Single<List<Student>> {
cachedStudents = ReactiveNetwork.checkInternetConnectivity(settings)
.flatMap {
apiHelper.initApi(email, password, symbol, endpoint)
if (it) remote.getStudents(email, password, endpoint)
else Single.error(UnknownHostException("No internet connection"))
}.doOnSuccess { cachedStudents = Single.just(it) }
return cachedStudents
}
fun getSavedStudents(): Single<List<Student>> {
return local.getStudents().toSingle(emptyList())
}
fun getCurrentStudent(): Single<Student> {
return local.getCurrentStudent().toSingle()
}
fun saveStudent(student: Student): Completable {
return local.saveStudent(student)
}
fun switchStudent(student: Student): Completable {
return local.setCurrentStudent(student)
}
fun logoutCurrentStudent(): Completable {
return local.logoutCurrentStudent()
}
}

View File

@ -10,7 +10,7 @@ import javax.inject.Inject
class AttendanceLocal @Inject constructor(private val attendanceDb: AttendanceDao) {
fun getAttendance(semester: Semester, startDate: LocalDate, endDate: LocalDate): Maybe<List<Attendance>> {
return attendanceDb.getExams(semester.diaryId, semester.studentId, startDate, endDate)
return attendanceDb.load(semester.diaryId, semester.studentId, startDate, endDate)
.filter { !it.isEmpty() }
}

View File

@ -10,7 +10,7 @@ import javax.inject.Inject
class ExamLocal @Inject constructor(private val examDb: ExamDao) {
fun getExams(semester: Semester, startDate: LocalDate, endDate: LocalDate): Maybe<List<Exam>> {
return examDb.getExams(semester.diaryId, semester.studentId, startDate, endDate)
return examDb.load(semester.diaryId, semester.studentId, startDate, endDate)
.filter { !it.isEmpty() }
}

View File

@ -12,11 +12,11 @@ import javax.inject.Singleton
class GradeLocal @Inject constructor(private val gradeDb: GradeDao) {
fun getGrades(semester: Semester): Maybe<List<Grade>> {
return gradeDb.getGrades(semester.semesterId, semester.studentId).filter { !it.isEmpty() }
return gradeDb.load(semester.semesterId, semester.studentId).filter { !it.isEmpty() }
}
fun getNewGrades(semester: Semester): Maybe<List<Grade>> {
return gradeDb.getNewGrades(semester.semesterId, semester.studentId)
return gradeDb.loadNew(semester.semesterId, semester.studentId)
}
fun saveGrades(grades: List<Grade>) {

View File

@ -11,7 +11,7 @@ import javax.inject.Singleton
class GradeSummaryLocal @Inject constructor(private val gradeSummaryDb: GradeSummaryDao) {
fun getGradesSummary(semester: Semester): Maybe<List<GradeSummary>> {
return gradeSummaryDb.getGradesSummary(semester.semesterId, semester.studentId)
return gradeSummaryDb.load(semester.semesterId, semester.studentId)
.filter { !it.isEmpty() }
}

View File

@ -12,7 +12,7 @@ import javax.inject.Singleton
class HomeworkLocal @Inject constructor(private val homeworkDb: HomeworkDao) {
fun getHomework(semester: Semester, date: LocalDate): Maybe<List<Homework>> {
return homeworkDb.getHomework(semester.semesterId, semester.studentId, date).filter { !it.isEmpty() }
return homeworkDb.load(semester.semesterId, semester.studentId, date).filter { !it.isEmpty() }
}
fun saveHomework(homework: List<Homework>) {

View File

@ -12,11 +12,11 @@ import javax.inject.Singleton
class NoteLocal @Inject constructor(private val noteDb: NoteDao) {
fun getNotes(semester: Semester): Maybe<List<Note>> {
return noteDb.getNotes(semester.semesterId, semester.studentId).filter { !it.isEmpty() }
return noteDb.load(semester.semesterId, semester.studentId).filter { !it.isEmpty() }
}
fun getNewNotes(semester: Semester): Maybe<List<Note>> {
return noteDb.getNewNotes(semester.semesterId, semester.studentId)
return noteDb.loadNew(semester.semesterId, semester.studentId)
}
fun saveNotes(notes: List<Note>) {

View File

@ -0,0 +1,27 @@
package io.github.wulkanowy.data.repositories.local
import io.github.wulkanowy.data.db.dao.SemesterDao
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.reactivex.Maybe
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class SemesterLocal @Inject constructor(private val semesterDb: SemesterDao) {
fun saveSemesters(semesters: List<Semester>) {
return semesterDb.insertAll(semesters)
}
fun getSemesters(student: Student): Maybe<List<Semester>> {
return semesterDb.load(student.studentId).filter { !it.isEmpty() }
}
fun setCurrentSemester(semester: Semester) {
semesterDb.run {
resetCurrent(semester.studentId)
update(semester.semesterId, semester.diaryId)
}
}
}

View File

@ -1,55 +0,0 @@
package io.github.wulkanowy.data.repositories.local
import android.content.Context
import io.github.wulkanowy.data.db.SharedPrefHelper
import io.github.wulkanowy.data.db.dao.SemesterDao
import io.github.wulkanowy.data.db.dao.StudentDao
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
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
@Singleton
class SessionLocal @Inject constructor(
private val studentDb: StudentDao,
private val semesterDb: SemesterDao,
private val sharedPref: SharedPrefHelper,
private val context: Context) {
companion object {
const val LAST_USER_KEY: String = "last_user_id"
}
val isSessionSaved
get() = sharedPref.getLong(LAST_USER_KEY, defaultValue = 0L) != 0L
fun saveStudent(student: Student): Completable {
return Single.fromCallable { studentDb.insert(student.copy(password = encrypt(student.password, context))) }
.map { sharedPref.putLong(LAST_USER_KEY, it) }
.ignoreElement()
}
fun getLastStudent(): Maybe<Student> {
return studentDb.load(sharedPref.getLong(LAST_USER_KEY, defaultValue = 0))
.map { it.apply { password = decrypt(password) } }
}
fun saveSemesters(semesters: List<Semester>): Completable {
return Single.fromCallable { semesterDb.insertAll(semesters) }.ignoreElement()
}
fun getSemesters(student: Student): Single<List<Semester>> {
return semesterDb.getSemester(student.studentId)
}
fun setCurrentSemester(semesterId: Int): Completable {
return Single.fromCallable { semesterDb.resetCurrentSemester() }.ignoreElement().andThen {
semesterDb.setCurrentSemester(semesterId)
}
}
}

View File

@ -0,0 +1,60 @@
package io.github.wulkanowy.data.repositories.local
import android.content.Context
import io.github.wulkanowy.data.db.SharedPrefHelper
import io.github.wulkanowy.data.db.dao.StudentDao
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.utils.security.decrypt
import io.github.wulkanowy.utils.security.encrypt
import io.reactivex.Completable
import io.reactivex.Maybe
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class StudentLocal @Inject constructor(
private val studentDb: StudentDao,
private val sharedPref: SharedPrefHelper,
private val context: Context
) {
companion object {
const val STUDENT_SAVED_KEY: String = "is_student_saved"
}
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<Student> {
return studentDb.loadCurrent().map { it.apply { password = decrypt(password) } }
}
fun getStudents(): Maybe<List<Student>> {
return studentDb.loadAll()
}
fun setCurrentStudent(student: Student): Completable {
return Completable.fromCallable {
studentDb.run {
resetCurrent()
update(student.apply { isCurrent = true })
}
}.doOnComplete { sharedPref.putBoolean(STUDENT_SAVED_KEY, true) }
}
fun logoutCurrentStudent(): Completable {
return studentDb.loadCurrent().doOnSuccess {
studentDb.delete(it)
sharedPref.putBoolean(STUDENT_SAVED_KEY, false)
}.ignoreElement()
}
}

View File

@ -10,8 +10,8 @@ import javax.inject.Inject
class TimetableLocal @Inject constructor(private val timetableDb: TimetableDao) {
fun getTimetable(semester: Semester, startDate: LocalDate, endDate: LocalDate): Maybe<List<Timetable>> {
return timetableDb.getTimetable(semester.diaryId, semester.studentId, startDate, endDate)
.filter { !it.isEmpty() }
return timetableDb.load(semester.diaryId, semester.studentId, startDate, endDate)
.filter { !it.isEmpty() }
}
fun saveTimetable(timetables: List<Timetable>) {

View File

@ -11,14 +11,10 @@ import javax.inject.Inject
class AttendanceRemote @Inject constructor(private val api: Api) {
fun getAttendance(semester: Semester, startDate: LocalDate, endDate: LocalDate): Single<List<Attendance>> {
return Single.just(api.run {
if (diaryId != semester.diaryId) {
diaryId = semester.diaryId
notifyDataChanged()
}
}).flatMap { api.getAttendance(startDate, endDate) }.map { attendance ->
attendance.map {
Attendance(
return Single.just(api.apply { diaryId = semester.diaryId })
.flatMap { it.getAttendance(startDate, endDate) }.map { attendance ->
attendance.map {
Attendance(
studentId = semester.studentId,
diaryId = semester.diaryId,
date = it.date.toLocalDate(),
@ -31,8 +27,8 @@ class AttendanceRemote @Inject constructor(private val api: Api) {
lateness = it.lateness,
excused = it.excused,
deleted = it.deleted
)
)
}
}
}
}
}

View File

@ -11,14 +11,10 @@ import javax.inject.Inject
class ExamRemote @Inject constructor(private val api: Api) {
fun getExams(semester: Semester, startDate: LocalDate, endDate: LocalDate): Single<List<Exam>> {
return Single.just(api.run {
if (diaryId != semester.diaryId) {
diaryId = semester.diaryId
notifyDataChanged()
}
}).flatMap { api.getExams(startDate, endDate) }.map { exams ->
exams.map {
Exam(
return Single.just(api.apply { diaryId = semester.diaryId })
.flatMap { it.getExams(startDate, endDate) }.map { exams ->
exams.map {
Exam(
studentId = semester.studentId,
diaryId = semester.diaryId,
date = it.date.toLocalDate(),
@ -29,8 +25,8 @@ class ExamRemote @Inject constructor(private val api: Api) {
description = it.description,
teacher = it.teacher,
teacherSymbol = it.teacherSymbol
)
)
}
}
}
}
}

View File

@ -12,31 +12,27 @@ import javax.inject.Singleton
class GradeRemote @Inject constructor(private val api: Api) {
fun getGrades(semester: Semester): Single<List<Grade>> {
return Single.just(api.run {
if (diaryId != semester.diaryId) {
diaryId = semester.diaryId
notifyDataChanged()
}
}).flatMap { api.getGrades(semester.semesterId) }
.map { grades ->
grades.map {
Grade(
semesterId = semester.semesterId,
studentId = semester.studentId,
subject = it.subject,
entry = it.entry,
value = it.value,
modifier = it.modifier,
comment = it.comment,
color = it.color,
gradeSymbol = it.symbol,
description = it.description,
weight = it.weight,
weightValue = it.weightValue,
date = it.date.toLocalDate(),
teacher = it.teacher
)
}
return Single.just(api.apply { diaryId = semester.diaryId })
.flatMap { it.getGrades(semester.semesterId) }
.map { grades ->
grades.map {
Grade(
semesterId = semester.semesterId,
studentId = semester.studentId,
subject = it.subject,
entry = it.entry,
value = it.value,
modifier = it.modifier,
comment = it.comment,
color = it.color,
gradeSymbol = it.symbol,
description = it.description,
weight = it.weight,
weightValue = it.weightValue,
date = it.date.toLocalDate(),
teacher = it.teacher
)
}
}
}
}

View File

@ -11,22 +11,18 @@ import javax.inject.Singleton
class GradeSummaryRemote @Inject constructor(private val api: Api) {
fun getGradeSummary(semester: Semester): Single<List<GradeSummary>> {
return Single.just(api.run {
if (diaryId != semester.diaryId) {
diaryId = semester.diaryId
notifyDataChanged()
}
}).flatMap { api.getGradesSummary(semester.semesterId) }
.map { gradesSummary ->
gradesSummary.map {
GradeSummary(
semesterId = semester.semesterId,
studentId = semester.studentId,
subject = it.name,
predictedGrade = it.predicted,
finalGrade = it.final
)
}
return Single.just(api.apply { diaryId = semester.diaryId })
.flatMap { it.getGradesSummary(semester.semesterId) }
.map { gradesSummary ->
gradesSummary.map {
GradeSummary(
semesterId = semester.semesterId,
studentId = semester.studentId,
subject = it.name,
predictedGrade = it.predicted,
finalGrade = it.final
)
}
}
}
}

View File

@ -13,12 +13,8 @@ import javax.inject.Singleton
class HomeworkRemote @Inject constructor(private val api: Api) {
fun getHomework(semester: Semester, date: LocalDate): Single<List<Homework>> {
return Single.just(api.run {
if (diaryId != semester.diaryId) {
diaryId = semester.diaryId
notifyDataChanged()
}
}).flatMap { api.getHomework(date, date) }
return Single.just(api.apply { diaryId = semester.diaryId })
.flatMap { it.getHomework(date, date) }
.map { homework ->
homework.map {
Homework(

View File

@ -12,12 +12,8 @@ import javax.inject.Singleton
class NoteRemote @Inject constructor(private val api: Api) {
fun getNotes(semester: Semester): Single<List<Note>> {
return Single.just(api.run {
if (diaryId != semester.diaryId) {
diaryId = semester.diaryId
notifyDataChanged()
}
}).flatMap { api.getNotes() }
return Single.just(api.apply { diaryId = semester.diaryId })
.flatMap { it.getNotes() }
.map { notes ->
notes.map {
Note(

View File

@ -0,0 +1,30 @@
package io.github.wulkanowy.data.repositories.remote
import io.github.wulkanowy.api.Api
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.reactivex.Single
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class SemesterRemote @Inject constructor(private val api: Api) {
fun getSemesters(student: Student): Single<List<Semester>> {
return api.getSemesters().map { semesters ->
semesters.map { semester ->
Semester(
studentId = student.studentId,
diaryId = semester.diaryId,
diaryName = semester.diaryName,
semesterId = semester.semesterId,
semesterName = semester.semesterNumber,
isCurrent = semester.current
)
}
}
}
}

View File

@ -1,96 +0,0 @@
package io.github.wulkanowy.data.repositories.remote
import io.github.wulkanowy.api.Api
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.reactivex.Single
import okhttp3.logging.HttpLoggingInterceptor
import timber.log.Timber
import java.net.URL
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class SessionRemote @Inject constructor(private val api: Api) {
fun getConnectedStudents(email: String, password: String, symbol: String, endpoint: String): Single<List<Student>> {
return Single.just(
initApi(
Student(
email = email,
password = password,
symbol = symbol,
endpoint = endpoint,
loginType = "AUTO"
), true
)
).flatMap {
api.getPupils().map { students ->
students.map { pupil ->
Student(
email = email,
password = password,
symbol = pupil.symbol,
studentId = pupil.studentId,
studentName = pupil.studentName,
schoolSymbol = pupil.schoolSymbol,
schoolName = pupil.schoolName,
endpoint = endpoint,
loginType = pupil.loginType.name
)
}
}
}
}
fun getSemesters(student: Student): Single<List<Semester>> {
return Single.just(initApi(student)).flatMap {
api.getSemesters().map { semesters ->
semesters.map { semester ->
Semester(
studentId = student.studentId,
diaryId = semester.diaryId,
diaryName = semester.diaryName,
semesterId = semester.semesterId,
semesterName = semester.semesterNumber,
current = semester.current
)
}
}
}
}
fun getCurrentSemester(student: Student): Single<Semester> {
return api.getCurrentSemester().map {
Semester(
studentId = student.studentId,
diaryId = it.diaryId,
diaryName = it.diaryName,
semesterId = it.semesterId,
semesterName = it.semesterNumber,
current = it.current
)
}
}
fun initApi(student: Student, reInitialize: Boolean = false) {
if (if (reInitialize) true else 0 == api.studentId) {
api.run {
logLevel = HttpLoggingInterceptor.Level.NONE
email = student.email
password = student.password
symbol = student.symbol
host = URL(student.endpoint).run { host + ":$port".removeSuffix(":-1") }
ssl = student.endpoint.startsWith("https")
schoolSymbol = student.schoolSymbol
studentId = student.studentId
loginType = Api.LoginType.valueOf(student.loginType)
notifyDataChanged()
setInterceptor(HttpLoggingInterceptor(HttpLoggingInterceptor.Logger {
Timber.d(it)
}).setLevel(HttpLoggingInterceptor.Level.BASIC))
}
}
}
}

View File

@ -0,0 +1,29 @@
package io.github.wulkanowy.data.repositories.remote
import io.github.wulkanowy.api.Api
import io.github.wulkanowy.data.db.entities.Student
import io.reactivex.Single
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class StudentRemote @Inject constructor(private val api: Api) {
fun getStudents(email: String, password: String, endpoint: String): Single<List<Student>> {
return api.getPupils().map { students ->
students.map { pupil ->
Student(
email = email,
password = password,
symbol = pupil.symbol,
studentId = pupil.studentId,
studentName = pupil.studentName,
schoolSymbol = pupil.schoolSymbol,
schoolName = pupil.schoolName,
endpoint = endpoint,
loginType = pupil.loginType.name
)
}
}
}
}

View File

@ -12,14 +12,11 @@ import javax.inject.Inject
class TimetableRemote @Inject constructor(private val api: Api) {
fun getTimetable(semester: Semester, startDate: LocalDate, endDate: LocalDate): Single<List<Timetable>> {
return Single.just(api.run {
if (diaryId != semester.diaryId) {
diaryId = semester.diaryId
notifyDataChanged()
}
}).flatMap { api.getTimetable(startDate, endDate) }.map { lessons ->
lessons.map {
Timetable(
return Single.just(api.apply { diaryId = semester.diaryId })
.flatMap { it.getTimetable(startDate, endDate) }
.map { lessons ->
lessons.map {
Timetable(
studentId = semester.studentId,
diaryId = semester.diaryId,
number = it.number,
@ -33,8 +30,8 @@ class TimetableRemote @Inject constructor(private val api: Api) {
info = it.info,
changes = it.changes,
canceled = it.canceled
)
)
}
}
}
}
}

View File

@ -10,7 +10,8 @@ import io.github.wulkanowy.data.repositories.GradeSummaryRepository
import io.github.wulkanowy.data.repositories.HomeworkRepository
import io.github.wulkanowy.data.repositories.NoteRepository
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.data.repositories.SessionRepository
import io.github.wulkanowy.data.repositories.SemesterRepository
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.data.repositories.TimetableRepository
import io.github.wulkanowy.services.notification.GradeNotification
import io.github.wulkanowy.services.notification.NoteNotification
@ -26,7 +27,10 @@ import javax.inject.Inject
class SyncWorker : SimpleJobService() {
@Inject
lateinit var session: SessionRepository
lateinit var student: StudentRepository
@Inject
lateinit var semester: SemesterRepository
@Inject
lateinit var gradesDetails: GradeRepository
@ -73,8 +77,8 @@ class SyncWorker : SimpleJobService() {
var error: Throwable? = null
disposable.add(session.getSemesters(true)
.map { it.single { semester -> semester.current } }
disposable.add(student.getCurrentStudent()
.flatMap { semester.getCurrentSemester(it, true) }
.flatMapPublisher {
Single.merge(
listOf(
@ -107,8 +111,8 @@ class SyncWorker : SimpleJobService() {
}
private fun sendGradeNotifications() {
disposable.add(session.getSemesters()
.map { it.single { semester -> semester.current } }
disposable.add(student.getCurrentStudent()
.flatMap { semester.getCurrentSemester(it, true) }
.flatMap { gradesDetails.getNewGrades(it) }
.map { it.filter { grade -> !grade.isNotified } }
.subscribe({
@ -121,8 +125,8 @@ class SyncWorker : SimpleJobService() {
}
private fun sendNoteNotification() {
disposable.add(session.getSemesters()
.map { it.single { semester -> semester.current } }
disposable.add(student.getCurrentStudent()
.flatMap { semester.getCurrentSemester(it, true) }
.flatMap { note.getNewNotes(it) }
.map { it.filter { note -> !note.isNotified } }
.subscribe({

View File

@ -4,7 +4,8 @@ import android.content.Intent
import android.widget.RemoteViewsService
import dagger.android.AndroidInjection
import io.github.wulkanowy.data.db.SharedPrefHelper
import io.github.wulkanowy.data.repositories.SessionRepository
import io.github.wulkanowy.data.repositories.SemesterRepository
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.data.repositories.TimetableRepository
import io.github.wulkanowy.ui.widgets.timetable.TimetableWidgetFactory
import javax.inject.Inject
@ -12,16 +13,19 @@ import javax.inject.Inject
class TimetableWidgetService : RemoteViewsService() {
@Inject
lateinit var timetableRepository: TimetableRepository
lateinit var timetableRepo: TimetableRepository
@Inject
lateinit var sessionRepository: SessionRepository
lateinit var studentRepo: StudentRepository
@Inject
lateinit var semesterRepo: SemesterRepository
@Inject
lateinit var sharedPref: SharedPrefHelper
override fun onGetViewFactory(intent: Intent?): RemoteViewsFactory {
AndroidInjection.inject(this)
return TimetableWidgetFactory(timetableRepository, sessionRepository, sharedPref, applicationContext, intent)
return TimetableWidgetFactory(timetableRepo, studentRepo, semesterRepo, sharedPref, applicationContext, intent)
}
}

View File

@ -0,0 +1,99 @@
package io.github.wulkanowy.ui.modules.account
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import android.widget.Toast.LENGTH_LONG
import androidx.appcompat.app.AlertDialog
import dagger.android.support.DaggerAppCompatDialogFragment
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.R
import io.github.wulkanowy.ui.modules.login.LoginActivity
import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.utils.setOnItemClickListener
import kotlinx.android.synthetic.main.dialog_account.*
import javax.inject.Inject
class AccountDialog : DaggerAppCompatDialogFragment(), AccountView {
@Inject
lateinit var presenter: AccountPresenter
@Inject
lateinit var accountAdapter: FlexibleAdapter<AbstractFlexibleItem<*>>
companion object {
fun newInstance() = AccountDialog()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NO_TITLE, 0)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.dialog_account, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
presenter.onAttachView(this)
}
override fun initView() {
accountAdapter.setOnItemClickListener { presenter.onItemSelected(it) }
accountDialogAdd.setOnClickListener { presenter.onAddSelected() }
accountDialogRemove.setOnClickListener { presenter.onRemoveSelected() }
accountDialogRecycler.apply {
layoutManager = SmoothScrollLinearLayoutManager(context)
adapter = accountAdapter
}
}
override fun updateData(data: List<AccountItem>) {
accountAdapter.updateDataSet(data)
}
override fun showMessage(text: String) {
Toast.makeText(context, text, LENGTH_LONG).show()
}
override fun dismissView() {
dismiss()
}
override fun openLoginView() {
activity?.also {
startActivity(LoginActivity.getStartIntent(it))
}
}
override fun showConfirmDialog() {
context?.let {
AlertDialog.Builder(it)
.setTitle(R.string.account_logout_student)
.setMessage(R.string.account_confirm)
.setPositiveButton(R.string.account_logout) { _, _ -> presenter.onLogoutConfirm() }
.setNegativeButton(android.R.string.cancel) { _, _ -> }
.show()
}
}
override fun recreateView() {
activity?.also {
startActivity(MainActivity.getStartIntent(it))
it.finish()
}
}
override fun onDestroy() {
presenter.onDetachView()
super.onDestroy()
}
}

View File

@ -0,0 +1,51 @@
package io.github.wulkanowy.ui.modules.account
import android.view.View
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import eu.davidea.flexibleadapter.items.IFlexible
import eu.davidea.viewholders.FlexibleViewHolder
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Student
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.item_account.*
class AccountItem(val student: Student) : AbstractFlexibleItem<AccountItem.ViewHolder>() {
override fun getLayoutRes() = R.layout.item_account
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 {
accountItemName.text = student.studentName
accountItemSchool.text = student.schoolName
accountItemImage.setBackgroundResource(if (student.isCurrent) R.drawable.ic_account_circular_border else 0)
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as AccountItem
if (student != other.student) return false
return true
}
override fun hashCode(): Int {
return student.hashCode()
}
class ViewHolder(view: View?, adapter: FlexibleAdapter<IFlexible<*>>?) : FlexibleViewHolder(view, adapter),
LayoutContainer {
override val containerView: View?
get() = contentView
}
}

View File

@ -0,0 +1,70 @@
package io.github.wulkanowy.ui.modules.account
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.data.ErrorHandler
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.utils.SchedulersProvider
import io.reactivex.Single
import javax.inject.Inject
class AccountPresenter @Inject constructor(
private val errorHandler: ErrorHandler,
private val studentRepository: StudentRepository,
private val schedulers: SchedulersProvider
) : BasePresenter<AccountView>(errorHandler) {
override fun onAttachView(view: AccountView) {
super.onAttachView(view)
view.initView()
loadData()
}
fun onAddSelected() {
view?.openLoginView()
}
fun onRemoveSelected() {
view?.showConfirmDialog()
}
fun onLogoutConfirm() {
disposable.add(studentRepository.logoutCurrentStudent()
.andThen(studentRepository.getSavedStudents())
.flatMap {
if (it.isNotEmpty()) studentRepository.switchStudent(it[0]).toSingle { it }
else Single.just(it)
}
.subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread)
.doFinally { view?.dismissView() }
.subscribe({
view?.apply {
if (it.isEmpty()) openLoginView()
else recreateView()
}
}, { errorHandler.proceed(it) }))
}
fun onItemSelected(item: AbstractFlexibleItem<*>) {
if (item is AccountItem) {
if (item.student.isCurrent) {
view?.dismissView()
} else {
disposable.add(studentRepository.switchStudent(item.student)
.subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread)
.subscribe({ view?.recreateView() }, { errorHandler.proceed(it) }))
}
}
}
private fun loadData() {
disposable.add(studentRepository.getSavedStudents()
.map { it.map { item -> AccountItem(item) } }
.subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread)
.subscribe({ view?.updateData(it) }, { errorHandler.proceed(it) }))
}
}

View File

@ -0,0 +1,19 @@
package io.github.wulkanowy.ui.modules.account
import io.github.wulkanowy.ui.base.BaseView
interface AccountView : BaseView {
fun initView()
fun updateData(data: List<AccountItem>)
fun dismissView()
fun showConfirmDialog()
fun openLoginView()
fun recreateView()
}

View File

@ -47,7 +47,7 @@ class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MainChildVie
override fun initView() {
attendanceAdapter.apply {
setOnItemClickListener { presenter.onAttendanceItemSelected(getItem(it)) }
setOnItemClickListener { presenter.onAttendanceItemSelected(it) }
}
attendanceRecycler.run {

View File

@ -4,7 +4,8 @@ import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.data.ErrorHandler
import io.github.wulkanowy.data.repositories.AttendanceRepository
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.data.repositories.SessionRepository
import io.github.wulkanowy.data.repositories.SemesterRepository
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.utils.SchedulersProvider
import io.github.wulkanowy.utils.isHolidays
@ -23,7 +24,8 @@ class AttendancePresenter @Inject constructor(
private val errorHandler: ErrorHandler,
private val schedulers: SchedulersProvider,
private val attendanceRepository: AttendanceRepository,
private val sessionRepository: SessionRepository,
private val studentRepository: StudentRepository,
private val semesterRepository: SemesterRepository,
private val prefRepository: PreferencesRepository
) : BasePresenter<AttendanceView>(errorHandler) {
@ -66,9 +68,9 @@ class AttendancePresenter @Inject constructor(
currentDate = date
disposable.apply {
clear()
add(sessionRepository.getSemesters()
add(studentRepository.getCurrentStudent()
.delay(200, MILLISECONDS)
.map { it.single { semester -> semester.current } }
.flatMap { semesterRepository.getCurrentSemester(it) }
.flatMap { attendanceRepository.getAttendance(it, date, date, forceRefresh) }
.map { list ->
if (prefRepository.isShowPresent) list

View File

@ -3,7 +3,9 @@ package io.github.wulkanowy.ui.modules.exam
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.View.*
import android.view.View.GONE
import android.view.View.INVISIBLE
import android.view.View.VISIBLE
import android.view.ViewGroup
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
@ -48,7 +50,7 @@ class ExamFragment : BaseFragment(), ExamView, MainView.MainChildView, MainView.
override fun initView() {
examAdapter.run {
setOnItemClickListener { presenter.onExamItemSelected(getItem(it)) }
setOnItemClickListener { presenter.onExamItemSelected(it) }
}
examRecycler.run {
layoutManager = SmoothScrollLinearLayoutManager(context)

View File

@ -4,7 +4,8 @@ import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.data.ErrorHandler
import io.github.wulkanowy.data.db.entities.Exam
import io.github.wulkanowy.data.repositories.ExamRepository
import io.github.wulkanowy.data.repositories.SessionRepository
import io.github.wulkanowy.data.repositories.SemesterRepository
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.utils.SchedulersProvider
import io.github.wulkanowy.utils.friday
@ -23,7 +24,8 @@ class ExamPresenter @Inject constructor(
private val errorHandler: ErrorHandler,
private val schedulers: SchedulersProvider,
private val examRepository: ExamRepository,
private val sessionRepository: SessionRepository
private val studentRepository: StudentRepository,
private val semesterRepository: SemesterRepository
) : BasePresenter<ExamView>(errorHandler) {
lateinit var currentDate: LocalDate
@ -65,9 +67,9 @@ class ExamPresenter @Inject constructor(
currentDate = date
disposable.apply {
clear()
add(sessionRepository.getSemesters()
add(studentRepository.getCurrentStudent()
.delay(200, MILLISECONDS)
.map { it.single { semester -> semester.current } }
.flatMap { semesterRepository.getCurrentSemester(it) }
.flatMap {
examRepository.getExams(it, currentDate.monday, currentDate.friday, forceRefresh)
}.map { it.groupBy { exam -> exam.date }.toSortedMap() }

View File

@ -1,9 +1,14 @@
package io.github.wulkanowy.ui.modules.grade
import android.os.Bundle
import android.view.*
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.View.INVISIBLE
import android.view.View.VISIBLE
import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog
import io.github.wulkanowy.R
import io.github.wulkanowy.ui.base.BaseFragment
@ -55,8 +60,8 @@ class GradeFragment : BaseFragment(), GradeView, MainView.MainChildView, MainVie
override fun initView() {
pagerAdapter.fragments.putAll(mapOf(
getString(R.string.all_details) to GradeDetailsFragment.newInstance(),
getString(R.string.grade_menu_summary) to GradeSummaryFragment.newInstance()
getString(R.string.all_details) to GradeDetailsFragment.newInstance(),
getString(R.string.grade_menu_summary) to GradeSummaryFragment.newInstance()
))
gradeViewPager.run {
adapter = pagerAdapter
@ -85,16 +90,16 @@ class GradeFragment : BaseFragment(), GradeView, MainView.MainChildView, MainVie
override fun showSemesterDialog(selectedIndex: Int) {
arrayOf(getString(R.string.grade_semester, 1),
getString(R.string.grade_semester, 2)).also { array ->
getString(R.string.grade_semester, 2)).also { array ->
context?.let {
AlertDialog.Builder(it)
.setSingleChoiceItems(array, selectedIndex) { dialog, which ->
presenter.onSemesterSelected(which)
dialog.dismiss()
}
.setTitle(R.string.grade_switch_semester)
.setNegativeButton(R.string.all_cancel) { dialog, _ -> dialog.dismiss() }
.show()
.setSingleChoiceItems(array, selectedIndex) { dialog, which ->
presenter.onSemesterSelected(which)
dialog.dismiss()
}
.setTitle(R.string.grade_switch_semester)
.setNegativeButton(android.R.string.cancel) { _, _ -> }
.show()
}
}
}

View File

@ -2,19 +2,21 @@ package io.github.wulkanowy.ui.modules.grade
import io.github.wulkanowy.data.ErrorHandler
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.repositories.SessionRepository
import io.github.wulkanowy.data.repositories.SemesterRepository
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.utils.SchedulersProvider
import io.github.wulkanowy.utils.logEvent
import io.reactivex.Completable
import timber.log.Timber
import java.util.concurrent.TimeUnit.MILLISECONDS
import javax.inject.Inject
class GradePresenter @Inject constructor(
private val errorHandler: ErrorHandler,
private val schedulers: SchedulersProvider,
private val sessionRepository: SessionRepository) : BasePresenter<GradeView>(errorHandler) {
private val errorHandler: ErrorHandler,
private val schedulers: SchedulersProvider,
private val studentRepository: StudentRepository,
private val semesterRepository: SemesterRepository
) : BasePresenter<GradeView>(errorHandler) {
var selectedIndex = 0
private set
@ -26,11 +28,11 @@ class GradePresenter @Inject constructor(
fun onAttachView(view: GradeView, savedIndex: Int?) {
super.onAttachView(view)
disposable.add(Completable.timer(150, MILLISECONDS, schedulers.mainThread)
.subscribe {
selectedIndex = savedIndex ?: 0
view.initView()
loadData()
})
.subscribe {
selectedIndex = savedIndex ?: 0
view.initView()
loadData()
})
}
fun onViewReselected() {
@ -71,15 +73,16 @@ class GradePresenter @Inject constructor(
}
private fun loadData() {
disposable.add(sessionRepository.getSemesters()
.doOnSuccess {
it.first { item -> item.current }.also { current ->
selectedIndex = if (selectedIndex == 0) current.semesterName else selectedIndex
semesters = it.filter { semester -> semester.diaryId == current.diaryId }
}
disposable.add(studentRepository.getCurrentStudent()
.flatMap { semesterRepository.getSemesters(it) }
.doOnSuccess {
it.first { item -> item.isCurrent }.also { current ->
selectedIndex = if (selectedIndex == 0) current.semesterName else selectedIndex
semesters = it.filter { semester -> semester.diaryId == current.diaryId }
}
.subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread)
}
.subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread)
.subscribe({ view?.run { loadChild(currentPageIndex) } }) { errorHandler.proceed(it) })
}

View File

@ -59,7 +59,7 @@ class GradeDetailsFragment : BaseFragment(), GradeDetailsView, GradeView.GradeCh
gradeDetailsAdapter.run {
isAutoCollapseOnExpand = true
isAutoScrollOnExpand = true
setOnItemClickListener { presenter.onGradeItemSelected(getItem(it)) }
setOnItemClickListener { presenter.onGradeItemSelected(it) }
}
gradeDetailsRecycler.run {

View File

@ -5,7 +5,8 @@ import io.github.wulkanowy.data.ErrorHandler
import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.data.repositories.GradeRepository
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.data.repositories.SessionRepository
import io.github.wulkanowy.data.repositories.SemesterRepository
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.utils.SchedulersProvider
import io.github.wulkanowy.utils.calcAverage
@ -19,7 +20,8 @@ class GradeDetailsPresenter @Inject constructor(
private val errorHandler: ErrorHandler,
private val schedulers: SchedulersProvider,
private val gradeRepository: GradeRepository,
private val sessionRepository: SessionRepository,
private val studentRepository: StudentRepository,
private val semesterRepository: SemesterRepository,
private val preferencesRepository: PreferencesRepository
) : BasePresenter<GradeDetailsView>(errorHandler) {
@ -29,7 +31,8 @@ class GradeDetailsPresenter @Inject constructor(
}
fun onParentViewLoadData(semesterId: Int, forceRefresh: Boolean) {
disposable.add(sessionRepository.getSemesters()
disposable.add(studentRepository.getCurrentStudent()
.flatMap { semesterRepository.getSemesters(it) }
.flatMap { gradeRepository.getGrades(it.first { item -> item.semesterId == semesterId }, forceRefresh) }
.map { it.map { item -> item.changeModifier(preferencesRepository.gradeModifier) } }
.map { createGradeItems(it.groupBy { grade -> grade.subject }.toSortedMap()) }

View File

@ -3,7 +3,9 @@ package io.github.wulkanowy.ui.modules.grade.summary
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.View.*
import android.view.View.GONE
import android.view.View.INVISIBLE
import android.view.View.VISIBLE
import android.view.ViewGroup
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
@ -56,7 +58,7 @@ class GradeSummaryFragment : BaseFragment(), GradeSummaryView, GradeView.GradeCh
gradeSummarySwipe.setOnRefreshListener { presenter.onSwipeRefresh() }
}
override fun updateDataSet(data: List<GradeSummaryItem>, header: GradeSummaryScrollableHeader) {
override fun updateData(data: List<GradeSummaryItem>, header: GradeSummaryScrollableHeader) {
gradeSummaryAdapter.apply {
updateDataSet(data, true)
removeAllScrollableHeaders()

View File

@ -5,7 +5,8 @@ import io.github.wulkanowy.data.db.entities.GradeSummary
import io.github.wulkanowy.data.repositories.GradeRepository
import io.github.wulkanowy.data.repositories.GradeSummaryRepository
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.data.repositories.SessionRepository
import io.github.wulkanowy.data.repositories.SemesterRepository
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.utils.SchedulersProvider
import io.github.wulkanowy.utils.calcAverage
@ -19,7 +20,8 @@ class GradeSummaryPresenter @Inject constructor(
private val errorHandler: ErrorHandler,
private val gradeSummaryRepository: GradeSummaryRepository,
private val gradeRepository: GradeRepository,
private val sessionRepository: SessionRepository,
private val studentRepository: StudentRepository,
private val semesterRepository: SemesterRepository,
private val preferencesRepository: PreferencesRepository,
private val schedulers: SchedulersProvider
) : BasePresenter<GradeSummaryView>(errorHandler) {
@ -30,7 +32,8 @@ class GradeSummaryPresenter @Inject constructor(
}
fun onParentViewLoadData(semesterId: Int, forceRefresh: Boolean) {
disposable.add(sessionRepository.getSemesters()
disposable.add(studentRepository.getCurrentStudent()
.flatMap { semesterRepository.getSemesters(it) }
.map { semester -> semester.first { it.semesterId == semesterId } }
.flatMap {
gradeSummaryRepository.getGradesSummary(it, forceRefresh)
@ -63,7 +66,7 @@ class GradeSummaryPresenter @Inject constructor(
view?.run {
showEmpty(it.first.isEmpty())
showContent(it.first.isNotEmpty())
updateDataSet(it.first, it.second)
updateData(it.first, it.second)
}
logEvent("Grade summary load", mapOf("items" to it.first.size, "forceRefresh" to forceRefresh))
}) {

View File

@ -12,7 +12,7 @@ interface GradeSummaryView : BaseView {
fun initView()
fun updateDataSet(data: List<GradeSummaryItem>, header: GradeSummaryScrollableHeader)
fun updateData(data: List<GradeSummaryItem>, header: GradeSummaryScrollableHeader)
fun resetView()

View File

@ -44,7 +44,7 @@ class HomeworkFragment : BaseFragment(), HomeworkView, MainView.TitledView {
override fun initView() {
homeworkAdapter.run {
setOnItemClickListener { presenter.onHomeworkItemSelected(getItem(it)) }
setOnItemClickListener { presenter.onHomeworkItemSelected(it) }
}
homeworkRecycler.run {

View File

@ -3,7 +3,8 @@ package io.github.wulkanowy.ui.modules.homework
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.data.ErrorHandler
import io.github.wulkanowy.data.repositories.HomeworkRepository
import io.github.wulkanowy.data.repositories.SessionRepository
import io.github.wulkanowy.data.repositories.SemesterRepository
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.utils.SchedulersProvider
import io.github.wulkanowy.utils.isHolidays
@ -20,7 +21,8 @@ class HomeworkPresenter @Inject constructor(
private val errorHandler: ErrorHandler,
private val schedulers: SchedulersProvider,
private val homeworkRepository: HomeworkRepository,
private val sessionRepository: SessionRepository
private val studentRepository: StudentRepository,
private val semesterRepository: SemesterRepository
) : BasePresenter<HomeworkView>(errorHandler) {
lateinit var currentDate: LocalDate
@ -57,9 +59,9 @@ class HomeworkPresenter @Inject constructor(
currentDate = date
disposable.apply {
clear()
add(sessionRepository.getSemesters()
add(studentRepository.getCurrentStudent()
.delay(200, TimeUnit.MILLISECONDS)
.map { it.single { semester -> semester.current } }
.flatMap { semesterRepository.getCurrentSemester(it) }
.flatMap { homeworkRepository.getHomework(it, currentDate, forceRefresh) }
.map { items -> items.map { HomeworkItem(it) } }
.subscribeOn(schedulers.backgroundThread)

View File

@ -1,22 +1,25 @@
package io.github.wulkanowy.ui.modules.login
import android.content.res.Resources
import android.database.sqlite.SQLiteConstraintException
import io.github.wulkanowy.R
import io.github.wulkanowy.api.login.BadCredentialsException
import io.github.wulkanowy.data.ErrorHandler
class LoginErrorHandler(resources: Resources) : ErrorHandler(resources) {
var doOnBadCredentials: () -> Unit = {}
var onBadCredentials: () -> Unit = {}
override fun proceed(error: Throwable) {
when (error) {
is BadCredentialsException -> doOnBadCredentials()
is BadCredentialsException -> onBadCredentials()
is SQLiteConstraintException -> showErrorMessage(resources.getString(R.string.login_duplicate_student))
else -> super.proceed(error)
}
}
override fun clear() {
super.clear()
doOnBadCredentials = {}
onBadCredentials = {}
}
}

View File

@ -1,6 +1,6 @@
package io.github.wulkanowy.ui.modules.login.form
import io.github.wulkanowy.data.repositories.SessionRepository
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.modules.login.LoginErrorHandler
import io.github.wulkanowy.utils.SchedulersProvider
@ -12,7 +12,7 @@ import javax.inject.Inject
class LoginFormPresenter @Inject constructor(
private val schedulers: SchedulersProvider,
private val errorHandler: LoginErrorHandler,
private val sessionRepository: SessionRepository
private val studentRepository: StudentRepository
) : BasePresenter<LoginFormView>(errorHandler) {
private var wasEmpty = false
@ -21,7 +21,7 @@ class LoginFormPresenter @Inject constructor(
super.onAttachView(view)
view.run {
initView()
errorHandler.doOnBadCredentials = {
errorHandler.onBadCredentials = {
setErrorPassIncorrect()
showSoftKeyboard()
Timber.i("Entered wrong username or password")
@ -32,7 +32,7 @@ class LoginFormPresenter @Inject constructor(
fun attemptLogin(email: String, password: String, symbol: String, endpoint: String) {
if (!validateCredentials(email, password, symbol)) return
disposable.add(sessionRepository.getConnectedStudents(email, password, symbol, endpoint)
disposable.add(studentRepository.getStudents(email, password, symbol, endpoint)
.subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread)
.doOnSubscribe {

View File

@ -1,5 +1,7 @@
package io.github.wulkanowy.ui.modules.login.options
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
@ -39,7 +41,7 @@ class LoginOptionsFragment : BaseFragment(), LoginOptionsView {
}
override fun initView() {
loginAdapter.apply { setOnItemClickListener { presenter.onSelectItem(getItem(it)) } }
loginAdapter.apply { setOnItemClickListener { presenter.onItemSelected(it) } }
loginOptionsRecycler.apply {
adapter = loginAdapter
@ -57,8 +59,8 @@ class LoginOptionsFragment : BaseFragment(), LoginOptionsView {
override fun openMainView() {
activity?.let {
startActivity(MainActivity.getStartIntent(it))
it.finish()
startActivity(MainActivity.getStartIntent(it)
.apply { addFlags(FLAG_ACTIVITY_CLEAR_TASK or FLAG_ACTIVITY_NEW_TASK) })
}
}

View File

@ -1,17 +1,20 @@
package io.github.wulkanowy.ui.modules.login.options
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.data.ErrorHandler
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.SessionRepository
import io.github.wulkanowy.data.repositories.SemesterRepository
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.modules.login.LoginErrorHandler
import io.github.wulkanowy.utils.SchedulersProvider
import io.github.wulkanowy.utils.logRegister
import io.reactivex.Single
import javax.inject.Inject
class LoginOptionsPresenter @Inject constructor(
private val errorHandler: ErrorHandler,
private val repository: SessionRepository,
private val errorHandler: LoginErrorHandler,
private val studentRepository: StudentRepository,
private val semesterRepository: SemesterRepository,
private val schedulers: SchedulersProvider
) : BasePresenter<LoginOptionsView>(errorHandler) {
@ -21,25 +24,27 @@ class LoginOptionsPresenter @Inject constructor(
}
fun onParentViewLoadData() {
disposable.add(repository.cachedStudents
disposable.add(studentRepository.cachedStudents
.observeOn(schedulers.mainThread)
.subscribeOn(schedulers.backgroundThread)
.doOnSubscribe { view?.showActionBar(true) }
.subscribe({ view?.updateData(it.map { student -> LoginOptionsItem(student) }) }, { errorHandler.proceed(it) }))
}
fun onSelectItem(item: AbstractFlexibleItem<*>?) {
fun onItemSelected(item: AbstractFlexibleItem<*>?) {
if (item is LoginOptionsItem) {
registerStudent(item.student)
}
}
private fun registerStudent(student: Student) {
disposable.add(repository.saveStudent(student)
disposable.add(studentRepository.saveStudent(student.apply { isCurrent = true })
.andThen(semesterRepository.getSemesters(student, true))
.onErrorResumeNext { studentRepository.logoutCurrentStudent().andThen(Single.error(it)) }
.subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread)
.doOnSubscribe {
view?.run {
view?.apply {
showProgress(true)
showContent(false)
showActionBar(false)
@ -48,6 +53,13 @@ class LoginOptionsPresenter @Inject constructor(
.subscribe({
logRegister("Success", true, student.symbol, student.endpoint)
view?.openMainView()
}, { errorHandler.proceed(it) }))
}, {
errorHandler.proceed(it)
view?.apply {
showProgress(false)
showContent(true)
showActionBar(true)
}
}))
}
}

View File

@ -3,6 +3,8 @@ package io.github.wulkanowy.ui.modules.main
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import com.aurelhubert.ahbottomnavigation.AHBottomNavigation.TitleState.ALWAYS_SHOW
@ -12,6 +14,7 @@ import com.ncapdevi.fragnav.FragNavController.Companion.HIDE
import io.github.wulkanowy.R
import io.github.wulkanowy.services.notification.GradeNotification
import io.github.wulkanowy.ui.base.BaseActivity
import io.github.wulkanowy.ui.modules.account.AccountDialog
import io.github.wulkanowy.ui.modules.attendance.AttendanceFragment
import io.github.wulkanowy.ui.modules.exam.ExamFragment
import io.github.wulkanowy.ui.modules.grade.GradeFragment
@ -58,15 +61,16 @@ class MainActivity : BaseActivity(), MainView {
navController.initialize(startMenuIndex, savedInstanceState)
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.action_menu_main, menu)
return true
}
override fun onStart() {
super.onStart()
presenter.onViewStart()
}
override fun onSupportNavigateUp(): Boolean {
return presenter.onUpNavigate()
}
override fun initView() {
mainBottomNav.run {
addItems(
@ -103,6 +107,15 @@ class MainActivity : BaseActivity(), MainView {
}
}
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
return if (item?.itemId == R.id.mainMenuAccount) presenter.onAccountManagerSelected()
else false
}
override fun onSupportNavigateUp(): Boolean {
return presenter.onUpNavigate()
}
override fun switchMenuView(position: Int) {
navController.switchTab(position)
}
@ -115,6 +128,10 @@ class MainActivity : BaseActivity(), MainView {
supportActionBar?.setDisplayHomeAsUpEnabled(show)
}
override fun showAccountPicker() {
navController.showDialogFragment(AccountDialog.newInstance())
}
override fun notifyMenuViewReselected() {
(navController.currentStack?.get(0) as? MainView.MainChildView)?.onFragmentReselected()
}

View File

@ -9,6 +9,7 @@ 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
import io.github.wulkanowy.ui.modules.account.AccountDialog
import io.github.wulkanowy.ui.modules.attendance.AttendanceFragment
import io.github.wulkanowy.ui.modules.exam.ExamFragment
import io.github.wulkanowy.ui.modules.grade.GradeFragment
@ -57,12 +58,19 @@ abstract class MainModule {
@ContributesAndroidInjector(modules = [AboutModule::class])
abstract fun bindAboutFragment(): AboutFragment
@PerFragment
@ContributesAndroidInjector
abstract fun bindSettingsFragment(): SettingsFragment
@PerFragment
@ContributesAndroidInjector
abstract fun bindNoteFragment(): NoteFragment
@PerFragment
@ContributesAndroidInjector
abstract fun bindHomeworkFragment(): HomeworkFragment
@PerFragment
@ContributesAndroidInjector
abstract fun bindsAccountDialog(): AccountDialog
}

View File

@ -41,6 +41,11 @@ class MainPresenter @Inject constructor(
}
}
fun onAccountManagerSelected(): Boolean {
view?.showAccountPicker()
return true
}
fun onUpNavigate(): Boolean {
view?.popView()
return true

View File

@ -18,6 +18,8 @@ interface MainView : BaseView {
fun showHomeArrow(show: Boolean)
fun showAccountPicker()
fun notifyMenuViewReselected()
fun setViewTitle(title: String)

View File

@ -76,7 +76,7 @@ class MoreFragment : BaseFragment(), MoreView, MainView.TitledView, MainView.Mai
}
override fun initView() {
moreAdapter.run { setOnItemClickListener { presenter.onItemSelected(getItem(it)) } }
moreAdapter.run { setOnItemClickListener { presenter.onItemSelected(it) } }
moreRecycler.apply {
layoutManager = SmoothScrollLinearLayoutManager(context)

View File

@ -10,8 +10,7 @@ import io.github.wulkanowy.R
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.item_more.*
class MoreItem(val title: String, private val drawable: Drawable?)
: AbstractFlexibleItem<MoreItem.ViewHolder>() {
class MoreItem(val title: String, private val drawable: Drawable?) : AbstractFlexibleItem<MoreItem.ViewHolder>() {
override fun getLayoutRes() = R.layout.item_more
@ -19,8 +18,7 @@ class MoreItem(val title: String, private val drawable: Drawable?)
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 {
moreItemTitle.text = title
moreItemImage.setImageDrawable(drawable)
@ -42,8 +40,7 @@ class MoreItem(val title: String, private val drawable: Drawable?)
return title.hashCode()
}
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

@ -46,7 +46,7 @@ class NoteFragment : BaseFragment(), NoteView, MainView.TitledView {
override fun initView() {
noteAdapter.run {
setOnItemClickListener { presenter.onNoteItemSelected(getItem(it)) }
setOnItemClickListener { presenter.onNoteItemSelected(it) }
}
noteRecycler.run {

View File

@ -4,7 +4,8 @@ import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.data.ErrorHandler
import io.github.wulkanowy.data.db.entities.Note
import io.github.wulkanowy.data.repositories.NoteRepository
import io.github.wulkanowy.data.repositories.SessionRepository
import io.github.wulkanowy.data.repositories.SemesterRepository
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.utils.SchedulersProvider
import io.github.wulkanowy.utils.logEvent
@ -14,8 +15,9 @@ import javax.inject.Inject
class NotePresenter @Inject constructor(
private val errorHandler: ErrorHandler,
private val schedulers: SchedulersProvider,
private val sessionRepository: SessionRepository,
private val noteRepository: NoteRepository
private val studentRepository: StudentRepository,
private val noteRepository: NoteRepository,
private val semesterRepository: SemesterRepository
) : BasePresenter<NoteView>(errorHandler) {
override fun onAttachView(view: NoteView) {
@ -29,8 +31,8 @@ class NotePresenter @Inject constructor(
}
private fun loadData(forceRefresh: Boolean = false) {
disposable.add(sessionRepository.getSemesters()
.map { it.single { semester -> semester.current } }
disposable.add(studentRepository.getCurrentStudent()
.flatMap { semesterRepository.getCurrentSemester(it) }
.flatMap { noteRepository.getNotes(it, forceRefresh) }
.map { items -> items.map { NoteItem(it) } }
.map { items -> items.sortedByDescending { it.note.date } }

View File

@ -1,20 +1,20 @@
package io.github.wulkanowy.ui.modules.splash
import io.github.wulkanowy.data.ErrorHandler
import io.github.wulkanowy.data.repositories.SessionRepository
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.utils.logLogin
import javax.inject.Inject
class SplashPresenter @Inject constructor(
private val sessionRepository: SessionRepository,
private val studentRepository: StudentRepository,
errorHandler: ErrorHandler
) : BasePresenter<SplashView>(errorHandler) {
override fun onAttachView(view: SplashView) {
super.onAttachView(view)
view.run {
if (sessionRepository.isSessionSaved) {
if (studentRepository.isStudentSaved) {
logLogin("Open app")
openMainView()
} else openLoginView()

View File

@ -47,7 +47,7 @@ class TimetableFragment : BaseFragment(), TimetableView, MainView.MainChildView,
override fun initView() {
timetableAdapter.run {
setOnItemClickListener { presenter.onTimetableItemSelected(getItem(it)) }
setOnItemClickListener { presenter.onTimetableItemSelected(it) }
}
timetableRecycler.run {

View File

@ -2,7 +2,8 @@ package io.github.wulkanowy.ui.modules.timetable
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.data.ErrorHandler
import io.github.wulkanowy.data.repositories.SessionRepository
import io.github.wulkanowy.data.repositories.SemesterRepository
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.data.repositories.TimetableRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.utils.SchedulersProvider
@ -22,7 +23,8 @@ class TimetablePresenter @Inject constructor(
private val errorHandler: ErrorHandler,
private val schedulers: SchedulersProvider,
private val timetableRepository: TimetableRepository,
private val sessionRepository: SessionRepository
private val studentRepository: StudentRepository,
private val semesterRepository: SemesterRepository
) : BasePresenter<TimetableView>(errorHandler) {
lateinit var currentDate: LocalDate
@ -64,9 +66,9 @@ class TimetablePresenter @Inject constructor(
currentDate = date
disposable.apply {
clear()
add(sessionRepository.getSemesters()
add(studentRepository.getCurrentStudent()
.flatMap { semesterRepository.getCurrentSemester(it) }
.delay(200, MILLISECONDS)
.map { it.single { semester -> semester.current } }
.flatMap { timetableRepository.getTimetable(it, currentDate, currentDate, forceRefresh) }
.map { items -> items.map { TimetableItem(it, view?.roomString.orEmpty()) } }
.map { items -> items.sortedBy { it.lesson.number } }

View File

@ -12,7 +12,8 @@ import android.widget.RemoteViewsService
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.SharedPrefHelper
import io.github.wulkanowy.data.db.entities.Timetable
import io.github.wulkanowy.data.repositories.SessionRepository
import io.github.wulkanowy.data.repositories.SemesterRepository
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.data.repositories.TimetableRepository
import io.github.wulkanowy.utils.toFormattedString
import io.reactivex.disposables.CompositeDisposable
@ -21,7 +22,8 @@ import timber.log.Timber
class TimetableWidgetFactory(
private val timetableRepository: TimetableRepository,
private val sessionRepository: SessionRepository,
private val studentRepository: StudentRepository,
private val semesterRepository: SemesterRepository,
private val sharedPref: SharedPrefHelper,
private val context: Context,
private val intent: Intent?
@ -46,9 +48,9 @@ class TimetableWidgetFactory(
override fun onDataSetChanged() {
intent?.action?.let { LocalDate.ofEpochDay(sharedPref.getLong(it, 0)) }
?.let { date ->
if (sessionRepository.isSessionSaved) {
disposable.add(sessionRepository.getSemesters()
.map { it.single { item -> item.current } }
if (studentRepository.isStudentSaved) {
disposable.add(studentRepository.getCurrentStudent()
.flatMap { semesterRepository.getCurrentSemester(it) }
.flatMap { timetableRepository.getTimetable(it, date, date) }
.map { item -> item.sortedBy { it.number } }
.subscribe({ lessons = it })

View File

@ -1,10 +1,11 @@
package io.github.wulkanowy.utils
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
inline fun FlexibleAdapter<*>.setOnItemClickListener(crossinline listener: (position: Int) -> Unit) {
inline fun FlexibleAdapter<*>.setOnItemClickListener(crossinline listener: (item: AbstractFlexibleItem<*>) -> Unit) {
addListener(FlexibleAdapter.OnItemClickListener { _, position ->
listener(position)
listener(getItem(position) as AbstractFlexibleItem<*>)
true
})
}

View File

@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="#FFF"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM17,13h-4v4h-2v-4L7,
13v-2h4L11,7h2v4h4v2z" />
</vector>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@android:color/transparent" />
<stroke
android:width="3dp"
android:color="@color/colorPrimary" />
</shape>

View File

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="#FFF"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2
12,2zM12,5c1.66,0 3,1.34 3,3s-1.34,3 -3,3 -3,-1.34 -3,-3 1.34,-3 3,-3zM12,19.2c-2.5,0 -4.71,-1.28 -6,-3.22
0.03,-1.99 4,-3.08 6,-3.08 1.99,0 5.97,1.09 6,3.08 -1.29,1.94 -3.5,3.22 -6,3.22z" />
</vector>

View File

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="280dp"
android:layout_height="wrap_content">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/accountDialogTitle"
android:layout_width="match_parent"
android:layout_height="64dp"
android:paddingStart="24dp"
android:paddingLeft="24dp"
android:paddingEnd="24dp"
android:paddingRight="24dp"
android:text="@string/account_title"
android:textSize="20sp"
android:textStyle="bold"
app:firstBaselineToTopHeight="40dp" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/accountDialogRecycler"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/accountDialogTitle"
android:overScrollMode="never"
tools:itemCount="3"
tools:listitem="@layout/item_account" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/accountDialogAdd"
style="@style/Widget.AppCompat.Button.Borderless"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/accountDialogRecycler"
android:layout_margin="8dp"
android:text="@string/account_add_new" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/accountDialogRemove"
style="@style/Widget.AppCompat.Button.Borderless"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/accountDialogRecycler"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_margin="8dp"
android:text="@string/account_logout"
android:textColor="@color/colorPrimary" />
</RelativeLayout>

View File

@ -10,7 +10,6 @@
android:padding="20dp">
<TextView
android:id="@+id/attendance_dialog_details"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"

View File

@ -10,7 +10,6 @@
android:padding="20dp">
<TextView
android:id="@+id/exam_dialog_details"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"

View File

@ -10,7 +10,6 @@
android:padding="20dp">
<TextView
android:id="@+id/homework_dialog_details"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"

View File

@ -10,7 +10,6 @@
android:padding="20dp">
<TextView
android:id="@+id/note_dialog_details"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"

View File

@ -10,7 +10,6 @@
android:padding="20dp">
<TextView
android:id="@+id/exam_dialog_details"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"

View File

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="56dp"
android:background="?attr/selectableItemBackground"
android:orientation="horizontal"
android:paddingStart="24dp"
android:paddingLeft="24dp"
android:paddingEnd="24dp"
android:paddingRight="24dp">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/accountItemImage"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_centerVertical="true"
android:background="@drawable/ic_account_circular_border"
android:tint="#7E7E7E"
app:srcCompat="@drawable/ic_all_account_24dp" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/accountItemName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@+id/accountItemImage"
android:layout_marginStart="20dp"
android:layout_marginLeft="20dp"
android:layout_toEndOf="@id/accountItemImage"
android:layout_toRightOf="@id/accountItemImage"
android:text="@string/app_name"
android:textSize="16sp" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/accountItemSchool"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/accountItemName"
android:layout_marginStart="20dp"
android:layout_marginLeft="20dp"
android:layout_marginTop="3dp"
android:layout_toEndOf="@id/accountItemImage"
android:layout_toRightOf="@id/accountItemImage"
android:ellipsize="end"
android:maxLines="1"
android:text="@string/app_name"
android:textSize="12sp" />
</RelativeLayout>

View File

@ -16,7 +16,7 @@
android:layout_height="24dp"
android:layout_gravity="center_vertical"
app:srcCompat="@drawable/ic_more_settings_24dp"
android:tint="?android:attr/android:textColorSecondary"/>
app:tint="?android:attr/android:textColorSecondary" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/moreItemTitle"
@ -27,5 +27,4 @@
android:layout_marginLeft="32dp"
android:text="@string/app_name"
android:textSize="16sp" />
</LinearLayout>

View File

@ -1,12 +1,10 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context="io.github.wulkanowy.timetable.MainActivity">
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/gradeMenuSemester"
android:icon="@drawable/ic_menu_grade_semester_24dp"
android:orderInCategory="2"
android:orderInCategory="1"
android:title="@string/grade_switch_semester"
app:showAsAction="ifRoom" />
</menu>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/mainMenuAccount"
android:icon="@drawable/ic_all_account_24dp"
android:orderInCategory="2"
android:title="@string/main_account_picker"
app:showAsAction="always" />
</menu>

View File

@ -13,9 +13,10 @@
<string name="about_title">O aplikacji</string>
<string name="note_title">Uwagi i osiągnięcia</string>
<string name="homework_title">Zadania domowe</string>
<string name="account_title">Wybierz konto</string>
<!--Login form-->
<!--Login-->
<string name="login_header_default">Zaloguj się za pomocą konta ucznia lub rodzica</string>
<string name="login_header_symbol">Podaj symbol dziennika VULCAN</string>
<string name="login_nickname_hint">Email lub nick</string>
@ -29,6 +30,11 @@
<string name="login_incorrect_password">To hasło jest niepoprawne</string>
<string name="login_incorrect_symbol">Nie znaleziono ucznia. Sprwadź symbol</string>
<string name="login_field_required">To pole jest wymagane</string>
<string name="login_duplicate_student">Ten student jest już zalogowany</string>
<!--Main-->
<string name="main_account_picker">Menadżer kont</string>
<!--Grade-->
@ -140,6 +146,13 @@
<string name="homework_no_items">Brak zadań domowych</string>
<!--Account-->
<string name="account_add_new">Dodaj konto</string>
<string name="account_logout">Wyloguj</string>
<string name="account_confirm">Czy chcesz wylogować aktualnego ucznia?</string>
<string name="account_logout_student">Wylogowanie ucznia</string>
<!--Generic-->
<string name="all_content">Treść</string>
<string name="all_description">Opis</string>
@ -151,7 +164,6 @@
<string name="all_details">Szczegóły</string>
<string name="all_category">Kategoria</string>
<string name="all_close">Zamknij</string>
<string name="all_cancel">Anuluj</string>
<string name="all_no_data">Brak danych</string>
<string name="all_subject">Przedmiot</string>
<string name="all_prev">Poprzedni</string>

View File

@ -13,9 +13,10 @@
<string name="about_title">About</string>
<string name="note_title">Notes and achievements</string>
<string name="homework_title">Homework</string>
<string name="account_title">Choose account</string>
<!--Login form-->
<!--Login-->
<string name="login_header_default">Sign in with the student or parent account</string>
<string name="login_header_symbol">Enter the VULCAN diary symbol</string>
<string name="login_nickname_hint">Email or nick</string>
@ -29,6 +30,11 @@
<string name="login_incorrect_password">This password is incorrect</string>
<string name="login_incorrect_symbol">Student not found. Check the symbol</string>
<string name="login_field_required">This field is required</string>
<string name="login_duplicate_student">This student has already been logged in</string>
<!--Main-->
<string name="main_account_picker">Account manager</string>
<!--Grade-->
@ -125,6 +131,13 @@
<string name="homework_no_items">No info about homework</string>
<!--Account-->
<string name="account_add_new">Add account</string>
<string name="account_logout">Logout</string>
<string name="account_confirm">Do you want to log out of an active student?</string>
<string name="account_logout_student">Student logout</string>
<!--Generic-->
<string name="all_content">Content</string>
<string name="all_description">Description</string>
@ -136,7 +149,6 @@
<string name="all_details">Details</string>
<string name="all_category">Category</string>
<string name="all_close">Close</string>
<string name="all_cancel">Cancel</string>
<string name="all_no_data">No data</string>
<string name="all_subject">Subject</string>
<string name="all_prev">Prev</string>

View File

@ -14,7 +14,6 @@
<item name="subtitleTextColor">@android:color/primary_text_dark</item>
<item name="android:colorBackground">@android:color/white</item>
<item name="bottomNavBackground">@color/bottom_nav_background_inverse</item>
<item name="android:windowAnimationStyle">@null</item>
<!-- AboutLibraries specific values -->
<item name="about_libraries_window_background">@color/about_libraries_window_background</item>

View File

@ -6,6 +6,7 @@ import io.github.wulkanowy.data.db.entities.Semester
import io.mockk.MockKAnnotations
import io.mockk.every
import io.mockk.impl.annotations.MockK
import io.mockk.impl.annotations.SpyK
import io.reactivex.Single
import org.junit.Assert.assertEquals
import org.junit.Before
@ -15,8 +16,8 @@ import java.sql.Date
class AttendanceRemoteTest {
@MockK
private lateinit var mockApi: Api
@SpyK
private var mockApi = Api()
@MockK
private lateinit var semesterMock: Semester
@ -27,7 +28,7 @@ class AttendanceRemoteTest {
}
@Test
fun getExamsTest() {
fun getAttendanceTest() {
every { mockApi.getAttendance(
LocalDate.of(2018, 9, 10),
LocalDate.of(2018, 9, 15)

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