diff --git a/.circleci/config.yml b/.circleci/config.yml
index f1f6a4c15..789123fa4 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -38,7 +38,7 @@ jobs:
command: ./gradlew build -x test -x lint -x fabricGenerateResourcesRelease -x packageRelease --no-daemon --stacktrace --console=plain -PdisablePreDex
- run:
name: Run FOSSA
- command: fossa --no-ansi
+ command: fossa --no-ansi || true
- persist_to_workspace:
root: *workspace_root
paths:
@@ -113,7 +113,7 @@ jobs:
adb shell input keyevent 82
- run:
name: Run instrumented tests
- command: ./gradlew clean createDebugCoverageReport jacocoTestReport --no-daemon --stacktrace --console=plain -PdisablePreDex
+ command: ./gradlew clean createDebugCoverageReport jacocoTestReport --no-daemon --stacktrace --console=plain -PdisablePreDex -PdisableCrashlytics
- run:
name: Collect logs from emulator
command: adb logcat -d > ./app/build/reports/logcat_emulator.txt
diff --git a/.gitignore b/.gitignore
index 8ad04ebf0..3eb9aa654 100644
--- a/.gitignore
+++ b/.gitignore
@@ -33,7 +33,6 @@ local.properties
.idea/vcs.xml
.idea/workspace.xml
.idea/caches/
-.idea/codeStyles/
*.iml
# OS-specific files
@@ -44,7 +43,7 @@ local.properties
.Trashes
ehthumbs.db
Thumbs.db
-.idea/codeStyles/
.idea/caches/
./app/key.p12
./app/upload-key.jks
+*.log
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
new file mode 100644
index 000000000..89f657f27
--- /dev/null
+++ b/.idea/codeStyles/Project.xml
@@ -0,0 +1,174 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ xmlns:android
+ ^$
+
+
+
+
+
+
+
+
+ xmlns:.*
+ ^$
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*:id
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ .*:name
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ name
+ ^$
+
+
+
+
+
+
+
+
+ style
+ ^$
+
+
+
+
+
+
+
+
+ .*
+ ^$
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*
+ http://schemas.android.com/apk/res/android
+
+
+ ANDROID_ATTRIBUTE_ORDER
+
+
+
+
+
+
+ .*
+ .*
+
+
+ BY_NAME
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 000000000..0f7bc519d
--- /dev/null
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/app/build.gradle b/app/build.gradle
index c8d99fa22..b7f5face2 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -7,6 +7,8 @@ apply plugin: 'com.github.triplet.play'
apply from: 'jacoco.gradle'
apply from: 'sonarqube.gradle'
+def fabricApiKey = System.getenv("FABRIC_API_KEY") ?: "null"
+
android {
compileSdkVersion 28
buildToolsVersion '28.0.3'
@@ -29,9 +31,7 @@ android {
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
playAccountConfig = playAccountConfigs.defaultAccountConfig
- manifestPlaceholders = [
- fabricApiKey: System.getenv("FABRIC_API_KEY") ?: "null"
- ]
+ manifestPlaceholders = [ fabricApiKey: fabricApiKey ]
}
signingConfigs {
@@ -45,18 +45,24 @@ android {
buildTypes {
release {
+ buildConfigField "boolean", "FABRIC_ENABLED", "true"
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release
}
debug {
+ buildConfigField "boolean", "FABRIC_ENABLED", fabricApiKey == "null" ? "false" : "true"
applicationIdSuffix ".dev"
versionNameSuffix "-dev"
testCoverageEnabled = true
- ext.enableCrashlytics = false
+ ext.enableCrashlytics = fabricApiKey != "null" && !project.hasProperty("disableCrashlytics")
multiDexKeepProguard file('proguard-multidex-rules.pro')
}
}
+
+ lintOptions {
+ disable 'HardwareIds'
+ }
}
androidExtensions {
@@ -72,7 +78,7 @@ ext.androidx_version = "1.0.0"
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
- implementation('com.github.wulkanowy:api:1400211f0d') { exclude module: "threetenbp" }
+ implementation('com.github.wulkanowy:api:e829b094de') { exclude module: "threetenbp" }
implementation "androidx.legacy:legacy-support-v4:$androidx_version"
implementation "androidx.appcompat:appcompat:$androidx_version"
@@ -89,12 +95,13 @@ dependencies {
kapt "com.google.dagger:dagger-compiler:2.16"
kapt "com.google.dagger:dagger-android-processor:2.16"
- implementation "androidx.room:room-runtime:2.1.0-alpha01"
- implementation "androidx.room:room-rxjava2:2.1.0-alpha01"
- kapt "androidx.room:room-compiler:2.1.0-alpha01"
+ implementation "androidx.room:room-runtime:2.1.0-alpha02"
+ implementation "androidx.room:room-rxjava2:2.1.0-alpha02"
+ kapt "androidx.room:room-compiler:2.1.0-alpha02"
implementation "eu.davidea:flexible-adapter:5.1.0"
implementation "eu.davidea:flexible-adapter-ui:1.0.0"
+
implementation "com.aurelhubert:ahbottomnavigation:2.2.0"
implementation 'com.ncapdevi:frag-nav:3.0.0-RC3'
@@ -120,9 +127,10 @@ dependencies {
testImplementation "org.mockito:mockito-inline:2.23.0"
testImplementation 'org.threeten:threetenbp:1.3.7'
- androidTestImplementation 'androidx.test:core:1.0.0-beta02'
- androidTestImplementation 'androidx.test:runner:1.1.0-beta02'
- androidTestImplementation 'androidx.test.ext:junit:1.0.0-beta02'
+ androidTestImplementation 'androidx.test:core:1.0.0'
+ 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"
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index f661c2c6c..b5c6e5a2b 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -40,6 +40,14 @@
android:launchMode="singleTop"
android:theme="@style/WulkanowyTheme.NoActionBar" />
+
+
+
+
+
+
diff --git a/app/src/main/java/io/github/wulkanowy/WulkanowyApp.kt b/app/src/main/java/io/github/wulkanowy/WulkanowyApp.kt
index 84885edcc..5aadf5f28 100644
--- a/app/src/main/java/io/github/wulkanowy/WulkanowyApp.kt
+++ b/app/src/main/java/io/github/wulkanowy/WulkanowyApp.kt
@@ -39,7 +39,7 @@ class WulkanowyApp : DaggerApplication() {
private fun initializeFabric() {
Fabric.with(Fabric.Builder(this)
.kits(Crashlytics.Builder()
- .core(CrashlyticsCore.Builder().disabled(BuildConfig.DEBUG).build())
+ .core(CrashlyticsCore.Builder().disabled(BuildConfig.DEBUG || !BuildConfig.FABRIC_ENABLED).build())
.build(),
Answers())
.debuggable(BuildConfig.DEBUG)
diff --git a/app/src/main/java/io/github/wulkanowy/data/ErrorHandler.kt b/app/src/main/java/io/github/wulkanowy/data/ErrorHandler.kt
index b4ba5a7f3..3ba1cd351 100644
--- a/app/src/main/java/io/github/wulkanowy/data/ErrorHandler.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/ErrorHandler.kt
@@ -14,7 +14,7 @@ open class ErrorHandler @Inject constructor(private val resources: Resources) {
var showErrorMessage: (String) -> Unit = {}
open fun proceed(error: Throwable) {
- Timber.i(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)
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/SharedPrefHelper.kt b/app/src/main/java/io/github/wulkanowy/data/db/SharedPrefHelper.kt
index 6adb79eb0..d4f9bb5ba 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/SharedPrefHelper.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/SharedPrefHelper.kt
@@ -14,4 +14,12 @@ class SharedPrefHelper @Inject constructor(private val sharedPref: SharedPrefere
fun getLong(key: String, defaultValue: Long): Long {
return sharedPref.getLong(key, defaultValue)
}
-}
\ No newline at end of file
+
+ fun getBoolean(key: String, defaultValue: Boolean): Boolean {
+ return sharedPref.getBoolean(key, defaultValue)
+ }
+
+ fun getString(key: String, defaultValue: String): String {
+ return sharedPref.getString(key, defaultValue) ?: defaultValue
+ }
+}
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeDao.kt
index 6212b5b0c..e5a153a6c 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeDao.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeDao.kt
@@ -13,9 +13,15 @@ interface GradeDao {
@Update
fun update(grade: Grade)
+ @Update
+ fun updateAll(grade: List)
+
@Delete
fun deleteAll(grades: List)
@Query("SELECT * FROM Grades WHERE semester_id = :semesterId AND student_id = :studentId")
fun getGrades(semesterId: Int, studentId: Int): Maybe>
+
+ @Query("SELECT * FROM Grades WHERE is_read = 0 AND semester_id = :semesterId AND student_id = :studentId")
+ fun getNewGrades(semesterId: Int, studentId: Int): Maybe>
}
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeSummaryDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeSummaryDao.kt
index dbbfa4df7..3a9b9d816 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeSummaryDao.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeSummaryDao.kt
@@ -11,7 +11,7 @@ import io.reactivex.Maybe
@Dao
interface GradeSummaryDao {
- @Insert(onConflict = REPLACE)
+ @Insert
fun insertAll(gradesSummary: List)
@Delete
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/SemesterDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/SemesterDao.kt
index e8e89406d..135e65e5b 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/dao/SemesterDao.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/SemesterDao.kt
@@ -14,4 +14,10 @@ interface SemesterDao {
@Query("SELECT * FROM Semesters WHERE student_id = :studentId")
fun getSemester(studentId: Int): Single>
+
+ @Query("UPDATE Semesters SET is_current = 0")
+ fun resetCurrentSemester()
+
+ @Query("UPDATE Semesters SET is_current = 1 WHERE semester_id = :semesterId")
+ fun setCurrentSemester(semesterId: Int)
}
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/Grade.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/Grade.kt
index 44c161035..d665f9d20 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/entities/Grade.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/Grade.kt
@@ -9,41 +9,44 @@ import java.io.Serializable
@Entity(tableName = "Grades")
data class Grade(
- @ColumnInfo(name = "semester_id")
- var semesterId: Int,
+ @ColumnInfo(name = "semester_id")
+ var semesterId: Int,
- @ColumnInfo(name = "student_id")
- var studentId: Int,
+ @ColumnInfo(name = "student_id")
+ var studentId: Int,
- var subject: String,
+ var subject: String,
- var entry: String,
+ var entry: String,
- var value: Int,
+ var value: Int,
- var modifier: Double,
+ var modifier: Double,
- var comment: String,
+ var comment: String,
- var color: String,
+ var color: String,
- @ColumnInfo(name = "grade_symbol")
- var gradeSymbol: String,
+ @ColumnInfo(name = "grade_symbol")
+ var gradeSymbol: String,
- var description: String,
+ var description: String,
- var weight: String,
+ var weight: String,
- var weightValue: Int,
+ var weightValue: Int,
- var date: LocalDate,
+ var date: LocalDate,
- var teacher: String
+ var teacher: String
) : Serializable {
@PrimaryKey(autoGenerate = true)
var id: Long = 0
- @ColumnInfo(name = "is_new")
- var isNew: Boolean = false
+ @ColumnInfo(name = "is_read")
+ var isRead: Boolean = true
+
+ @ColumnInfo(name = "is_notified")
+ var isNotified: Boolean = true
}
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/GradeRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/GradeRepository.kt
index 9e4e2ab89..42266955e 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/GradeRepository.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/GradeRepository.kt
@@ -14,29 +14,39 @@ import javax.inject.Singleton
@Singleton
class GradeRepository @Inject constructor(
- private val settings: InternetObservingSettings,
- private val local: GradeLocal,
- private val remote: GradeRemote
+ private val settings: InternetObservingSettings,
+ private val local: GradeLocal,
+ private val remote: GradeRemote
) {
- fun getGrades(semester: Semester, forceRefresh: Boolean = false): Single> {
+ fun getGrades(semester: Semester, forceRefresh: Boolean = false, notify: Boolean = false): Single> {
return local.getGrades(semester).filter { !forceRefresh }
- .switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings)
- .flatMap {
- if (it) remote.getGrades(semester)
- else Single.error(UnknownHostException())
- }.flatMap { newGrades ->
- local.getGrades(semester).toSingle(emptyList())
- .doOnSuccess { oldGrades ->
- local.deleteGrades(oldGrades - newGrades)
- local.saveGrades((newGrades - oldGrades)
- .onEach { if (oldGrades.isNotEmpty()) it.isNew = true })
- }
- }.flatMap { local.getGrades(semester).toSingle(emptyList()) })
+ .switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings)
+ .flatMap {
+ if (it) remote.getGrades(semester)
+ else Single.error(UnknownHostException())
+ }.flatMap { newGrades ->
+ local.getGrades(semester).toSingle(emptyList())
+ .doOnSuccess { oldGrades ->
+ local.deleteGrades(oldGrades - newGrades)
+ local.saveGrades((newGrades - oldGrades)
+ .onEach {
+ if (oldGrades.isNotEmpty()) it.isRead = false
+ if (notify) it.isNotified = false
+ })
+ }
+ }.flatMap { local.getGrades(semester).toSingle(emptyList()) })
+ }
+ fun getNewGrades(semester: Semester): Single> {
+ return local.getNewGrades(semester).toSingle(emptyList())
}
fun updateGrade(grade: Grade): Completable {
return local.updateGrade(grade)
}
+
+ fun updateGrades(grades: List): Completable {
+ return local.updateGrades(grades)
+ }
}
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/PreferencesRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/PreferencesRepository.kt
index 0da419405..2c4a1242e 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/PreferencesRepository.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/PreferencesRepository.kt
@@ -1,19 +1,39 @@
package io.github.wulkanowy.data.repositories
+import android.content.Context
import android.content.SharedPreferences
+import io.github.wulkanowy.R
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
-class PreferencesRepository @Inject constructor(private val sharedPref: SharedPreferences) {
+class PreferencesRepository @Inject constructor(
+ private val sharedPref: SharedPreferences,
+ val context: Context
+) {
val startMenuIndex: Int
- get() = sharedPref.getString("start_menu", "0")?.toInt() ?: 0
+ get() = sharedPref.getString(context.getString(R.string.pref_key_start_menu), "0")?.toInt() ?: 0
val showPresent: Boolean
- get() = sharedPref.getBoolean("attendance_present", true)
+ get() = sharedPref.getBoolean(context.getString(R.string.pref_key_attendance_present), true)
+ val currentThemeKey: String = context.getString(R.string.pref_key_theme)
val currentTheme: Int
- get() = sharedPref.getString("theme", "1")?.toInt() ?: 1
-}
+ get() = sharedPref.getString(currentThemeKey, "1")?.toInt() ?: 1
+ val serviceEnablesKey: String = context.getString(R.string.pref_key_services_enable)
+ val serviceEnabled: Boolean
+ get() = sharedPref.getBoolean(serviceEnablesKey, true)
+
+ val servicesIntervalKey: String = context.getString(R.string.pref_key_services_interval)
+ val servicesInterval: Int
+ get() = sharedPref.getString(servicesIntervalKey, "60")?.toInt() ?: 60
+
+ val servicesOnlyWifiKey: String = context.getString(R.string.pref_key_services_wifi_only)
+ val servicesOnlyWifi: Boolean
+ get() = sharedPref.getBoolean(servicesOnlyWifiKey, true)
+
+ val notificationsEnable: Boolean
+ get() = sharedPref.getBoolean(context.getString(R.string.pref_key_notifications_enable), true)
+}
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/SessionRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/SessionRepository.kt
index 7b0ea4c49..93c8e2b70 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/SessionRepository.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/SessionRepository.kt
@@ -14,9 +14,10 @@ import javax.inject.Singleton
@Singleton
class SessionRepository @Inject constructor(
- private val local: SessionLocal,
- private val remote: SessionRemote,
- private val settings: InternetObservingSettings) {
+ private val local: SessionLocal,
+ private val remote: SessionRemote,
+ private val settings: InternetObservingSettings
+) {
val isSessionSaved
get() = local.isSessionSaved
@@ -26,24 +27,39 @@ class SessionRepository @Inject constructor(
fun getConnectedStudents(email: String, password: String, symbol: String, endpoint: String): Single> {
cachedStudents = ReactiveNetwork.checkInternetConnectivity(settings)
- .flatMap { isConnected ->
- if (isConnected) remote.getConnectedStudents(email, password, symbol, endpoint)
- else Single.error>(UnknownHostException("No internet connection"))
- }.doOnSuccess { cachedStudents = Single.just(it) }
+ .flatMap { isConnected ->
+ if (isConnected) remote.getConnectedStudents(email, password, symbol, endpoint)
+ else Single.error>(UnknownHostException("No internet connection"))
+ }.doOnSuccess { cachedStudents = Single.just(it) }
return cachedStudents
}
- fun getSemesters(): Single> {
- return local.getLastStudent()
- .flatMapSingle {
- remote.initApi(it, true)
- local.getSemesters(it)
- }
+ fun saveStudent(student: Student): Completable {
+ return remote.getSemesters(student)
+ .flatMapCompletable { local.saveSemesters(it) }
+ .concatWith(local.saveStudent(student))
}
- fun saveStudent(student: Student): Completable {
- return remote.getSemesters(student).flatMapCompletable { local.saveSemesters(it) }
- .concatWith(local.saveStudent(student))
+ fun getSemesters(forceRefresh: Boolean = false): Single> {
+ 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)
+ })
+ }
}
fun clearCache() {
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/local/GradeLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/local/GradeLocal.kt
index 5689ee4bb..4110bc3a2 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/local/GradeLocal.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/local/GradeLocal.kt
@@ -15,6 +15,10 @@ class GradeLocal @Inject constructor(private val gradeDb: GradeDao) {
return gradeDb.getGrades(semester.semesterId, semester.studentId).filter { !it.isEmpty() }
}
+ fun getNewGrades(semester: Semester): Maybe> {
+ return gradeDb.getNewGrades(semester.semesterId, semester.studentId)
+ }
+
fun saveGrades(grades: List) {
gradeDb.insertAll(grades)
}
@@ -23,6 +27,10 @@ class GradeLocal @Inject constructor(private val gradeDb: GradeDao) {
return Completable.fromCallable { gradeDb.update(grade) }
}
+ fun updateGrades(grade: List): Completable {
+ return Completable.fromCallable { gradeDb.updateAll(grade) }
+ }
+
fun deleteGrades(grades: List) {
gradeDb.deleteAll(grades)
}
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/local/SessionLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/local/SessionLocal.kt
index b26ce6371..008cd8e3f 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/local/SessionLocal.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/local/SessionLocal.kt
@@ -46,4 +46,10 @@ class SessionLocal @Inject constructor(
fun getSemesters(student: Student): Single> {
return semesterDb.getSemester(student.studentId)
}
+
+ fun setCurrentSemester(semesterId: Int): Completable {
+ return Single.fromCallable { semesterDb.resetCurrentSemester() }.ignoreElement().andThen {
+ semesterDb.setCurrentSemester(semesterId)
+ }
+ }
}
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/remote/SessionRemote.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/remote/SessionRemote.kt
index a02b0ada6..f01c677ba 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/remote/SessionRemote.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/remote/SessionRemote.kt
@@ -12,37 +12,46 @@ import javax.inject.Singleton
class SessionRemote @Inject constructor(private val api: Api) {
fun getConnectedStudents(email: String, password: String, symbol: String, endpoint: String): Single> {
- return Single.just(initApi(Student(email = email, password = password, symbol = symbol, endpoint = endpoint, loginType = "AUTO")))
- .flatMap { _ ->
- api.getPupils().map { students ->
- students.map {
- Student(
- email = email,
- password = password,
- symbol = it.symbol,
- studentId = it.studentId,
- studentName = it.studentName,
- schoolSymbol = it.schoolSymbol,
- schoolName = it.schoolName,
- endpoint = endpoint,
- loginType = it.loginType.name
- )
- }
- }
+ 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> {
- return Single.just(initApi(student)).flatMap { _ ->
+ return Single.just(initApi(student)).flatMap {
api.getSemesters().map { semesters ->
- semesters.map {
+ semesters.map { semester ->
Semester(
- studentId = student.studentId,
- diaryId = it.diaryId,
- diaryName = it.diaryName,
- semesterId = it.semesterId,
- semesterName = it.semesterNumber,
- current = it.current
+ studentId = student.studentId,
+ diaryId = semester.diaryId,
+ diaryName = semester.diaryName,
+ semesterId = semester.semesterId,
+ semesterName = semester.semesterNumber,
+ current = semester.current
)
}
@@ -50,8 +59,21 @@ class SessionRemote @Inject constructor(private val api: Api) {
}
}
- fun initApi(student: Student, checkInit: Boolean = false) {
- if (if (checkInit) 0 == api.studentId else true) {
+ fun getCurrentSemester(student: Student): Single {
+ 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 {
email = student.email
password = student.password
diff --git a/app/src/main/java/io/github/wulkanowy/di/AppComponent.kt b/app/src/main/java/io/github/wulkanowy/di/AppComponent.kt
index 053e46976..21c193e5a 100644
--- a/app/src/main/java/io/github/wulkanowy/di/AppComponent.kt
+++ b/app/src/main/java/io/github/wulkanowy/di/AppComponent.kt
@@ -14,6 +14,7 @@ import javax.inject.Singleton
RepositoryModule::class,
BuilderModule::class])
interface AppComponent : AndroidInjector {
+
@Component.Builder
abstract class Builder : AndroidInjector.Builder()
}
diff --git a/app/src/main/java/io/github/wulkanowy/di/AppModule.kt b/app/src/main/java/io/github/wulkanowy/di/AppModule.kt
index e6a8b6492..430b5c293 100644
--- a/app/src/main/java/io/github/wulkanowy/di/AppModule.kt
+++ b/app/src/main/java/io/github/wulkanowy/di/AppModule.kt
@@ -1,6 +1,8 @@
package io.github.wulkanowy.di
import android.content.Context
+import com.firebase.jobdispatcher.FirebaseJobDispatcher
+import com.firebase.jobdispatcher.GooglePlayDriver
import dagger.Module
import dagger.Provides
import eu.davidea.flexibleadapter.FlexibleAdapter
@@ -22,4 +24,10 @@ internal class AppModule {
@Provides
fun provideFlexibleAdapter() = FlexibleAdapter>(null, null, true)
+
+ @Singleton
+ @Provides
+ fun provideJobDispatcher(context: Context): FirebaseJobDispatcher {
+ return FirebaseJobDispatcher(GooglePlayDriver(context))
+ }
}
diff --git a/app/src/main/java/io/github/wulkanowy/di/BuilderModule.kt b/app/src/main/java/io/github/wulkanowy/di/BuilderModule.kt
index 66adcd554..694580f49 100644
--- a/app/src/main/java/io/github/wulkanowy/di/BuilderModule.kt
+++ b/app/src/main/java/io/github/wulkanowy/di/BuilderModule.kt
@@ -3,6 +3,7 @@ package io.github.wulkanowy.di
import dagger.Module
import dagger.android.ContributesAndroidInjector
import io.github.wulkanowy.di.scopes.PerActivity
+import io.github.wulkanowy.services.job.SyncWorker
import io.github.wulkanowy.ui.modules.login.LoginActivity
import io.github.wulkanowy.ui.modules.login.LoginModule
import io.github.wulkanowy.ui.modules.main.MainActivity
@@ -23,4 +24,7 @@ internal abstract class BuilderModule {
@PerActivity
@ContributesAndroidInjector(modules = [MainModule::class])
abstract fun bindMainActivity(): MainActivity
+
+ @ContributesAndroidInjector
+ abstract fun bindSyncJob(): SyncWorker
}
diff --git a/app/src/main/java/io/github/wulkanowy/services/job/ServiceHelper.kt b/app/src/main/java/io/github/wulkanowy/services/job/ServiceHelper.kt
new file mode 100644
index 000000000..ab88edd86
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/services/job/ServiceHelper.kt
@@ -0,0 +1,57 @@
+package io.github.wulkanowy.services.job
+
+import com.firebase.jobdispatcher.Constraint.ON_ANY_NETWORK
+import com.firebase.jobdispatcher.Constraint.ON_UNMETERED_NETWORK
+import com.firebase.jobdispatcher.FirebaseJobDispatcher
+import com.firebase.jobdispatcher.Lifetime.FOREVER
+import com.firebase.jobdispatcher.RetryStrategy.DEFAULT_EXPONENTIAL
+import com.firebase.jobdispatcher.Trigger.executionWindow
+import io.github.wulkanowy.data.repositories.PreferencesRepository
+import io.github.wulkanowy.utils.isHolidays
+import org.threeten.bp.LocalDate
+import timber.log.Timber
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class ServiceHelper @Inject constructor(
+ private val prefRepository: PreferencesRepository,
+ private val dispatcher: FirebaseJobDispatcher
+) {
+
+ fun reloadFullSyncService() {
+ startFullSyncService(true)
+ }
+
+ fun startFullSyncService(replaceCurrent: Boolean = false) {
+ if (LocalDate.now().isHolidays || !prefRepository.serviceEnabled) {
+ Timber.d("Services disabled or it's holidays")
+ return
+ }
+
+ dispatcher.mustSchedule(
+ dispatcher.newJobBuilder()
+ .setLifetime(FOREVER)
+ .setRecurring(true)
+ .setService(SyncWorker::class.java)
+ .setTag(SyncWorker.WORK_TAG)
+ .setTrigger(
+ executionWindow(
+ prefRepository.servicesInterval * 60,
+ (prefRepository.servicesInterval + 10) * 60
+ )
+ )
+ .setConstraints(if (prefRepository.servicesOnlyWifi) ON_UNMETERED_NETWORK else ON_ANY_NETWORK)
+ .setReplaceCurrent(replaceCurrent)
+ .setRetryStrategy(DEFAULT_EXPONENTIAL)
+ .build()
+ )
+
+ Timber.d("Services started")
+ }
+
+ fun stopFullSyncService() {
+ dispatcher.cancel(SyncWorker.WORK_TAG)
+ Timber.d("Services stopped")
+ }
+}
diff --git a/app/src/main/java/io/github/wulkanowy/services/job/SyncWorker.kt b/app/src/main/java/io/github/wulkanowy/services/job/SyncWorker.kt
new file mode 100644
index 000000000..e9f446ac1
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/services/job/SyncWorker.kt
@@ -0,0 +1,110 @@
+package io.github.wulkanowy.services.job
+
+import com.firebase.jobdispatcher.JobParameters
+import com.firebase.jobdispatcher.SimpleJobService
+import dagger.android.AndroidInjection
+import io.github.wulkanowy.data.repositories.AttendanceRepository
+import io.github.wulkanowy.data.repositories.ExamRepository
+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.TimetableRepository
+import io.github.wulkanowy.services.notification.GradeNotification
+import io.github.wulkanowy.utils.friday
+import io.github.wulkanowy.utils.isHolidays
+import io.github.wulkanowy.utils.monday
+import io.reactivex.Single
+import io.reactivex.disposables.CompositeDisposable
+import org.threeten.bp.LocalDate
+import timber.log.Timber
+import javax.inject.Inject
+
+class SyncWorker : SimpleJobService() {
+
+ @Inject
+ lateinit var session: SessionRepository
+
+ @Inject
+ lateinit var gradesDetails: GradeRepository
+
+ @Inject
+ lateinit var gradesSummary: GradeSummaryRepository
+
+ @Inject
+ lateinit var attendance: AttendanceRepository
+
+ @Inject
+ lateinit var exam: ExamRepository
+
+ @Inject
+ lateinit var timetable: TimetableRepository
+
+ @Inject
+ lateinit var prefRepository: PreferencesRepository
+
+ private val disposable = CompositeDisposable()
+
+ companion object {
+ const val WORK_TAG = "FULL_SYNC"
+ }
+
+ override fun onCreate() {
+ super.onCreate()
+ AndroidInjection.inject(this)
+ }
+
+ override fun onRunJob(job: JobParameters?): Int {
+ Timber.d("Synchronization started")
+
+ val start = LocalDate.now().monday
+ val end = LocalDate.now().friday
+
+ if (start.isHolidays) return RESULT_FAIL_NORETRY
+
+ var error: Throwable? = null
+
+ disposable.add(session.getSemesters(true)
+ .map { it.single { semester -> semester.current } }
+ .flatMapPublisher {
+ Single.merge(
+ listOf(
+ gradesDetails.getGrades(it, true, true),
+ gradesSummary.getGradesSummary(it, true),
+ attendance.getAttendance(it, start, end, true),
+ exam.getExams(it, start, end, true),
+ timetable.getTimetable(it, start, end, true)
+ )
+ )
+ }
+ .subscribe({}, { error = it }))
+
+ return if (null === error) {
+ if (prefRepository.notificationsEnable) sendNotifications()
+ Timber.d("Synchronization successful")
+ RESULT_SUCCESS
+ } else {
+ Timber.e(error, "Synchronization failed")
+ RESULT_FAIL_RETRY
+ }
+ }
+
+ private fun sendNotifications() {
+ disposable.add(session.getSemesters(true)
+ .map { it.single { semester -> semester.current } }
+ .flatMap { gradesDetails.getNewGrades(it) }
+ .map { it.filter { grade -> !grade.isNotified } }
+ .subscribe({
+ if (it.isNotEmpty()) {
+ Timber.d("Found ${it.size} unread grades")
+ GradeNotification(applicationContext).sendNotification(it)
+ gradesDetails.updateGrades(it.map { grade -> grade.apply { isNotified = true } }).subscribe()
+ }
+ }) { Timber.e("Notifications sending failed") })
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ disposable.clear()
+ }
+}
diff --git a/app/src/main/java/io/github/wulkanowy/services/notification/BaseNotification.kt b/app/src/main/java/io/github/wulkanowy/services/notification/BaseNotification.kt
new file mode 100644
index 000000000..945d0b153
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/services/notification/BaseNotification.kt
@@ -0,0 +1,34 @@
+package io.github.wulkanowy.services.notification
+
+import android.app.Notification
+import android.app.NotificationManager
+import android.content.Context
+import android.content.Context.NOTIFICATION_SERVICE
+import android.os.Build.VERSION.SDK_INT
+import android.os.Build.VERSION_CODES.O
+import androidx.core.app.NotificationCompat
+import timber.log.Timber
+import kotlin.random.Random
+
+abstract class BaseNotification(protected val context: Context) {
+
+ protected val notificationManager: NotificationManager by lazy {
+ context.getSystemService(NOTIFICATION_SERVICE) as NotificationManager
+ }
+
+ fun notify(notification: Notification) {
+ notificationManager.notify(Random.nextInt(1000), notification)
+ }
+
+ fun notificationBuilder(channelId: String): NotificationCompat.Builder {
+ if (SDK_INT >= O) createChannel(channelId)
+ return NotificationCompat.Builder(context, channelId)
+ }
+
+ fun cancelAll() {
+ notificationManager.cancelAll()
+ Timber.d("Notifications canceled")
+ }
+
+ abstract fun createChannel(channelId: String)
+}
diff --git a/app/src/main/java/io/github/wulkanowy/services/notification/GradeNotification.kt b/app/src/main/java/io/github/wulkanowy/services/notification/GradeNotification.kt
new file mode 100644
index 000000000..8eec6a20e
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/services/notification/GradeNotification.kt
@@ -0,0 +1,60 @@
+package io.github.wulkanowy.services.notification
+
+import android.annotation.TargetApi
+import android.app.Notification.VISIBILITY_PUBLIC
+import android.app.NotificationChannel
+import android.app.NotificationManager.IMPORTANCE_HIGH
+import android.app.PendingIntent
+import android.app.PendingIntent.FLAG_UPDATE_CURRENT
+import android.content.Context
+import androidx.core.app.NotificationCompat
+import androidx.core.content.ContextCompat
+import io.github.wulkanowy.R
+import io.github.wulkanowy.data.db.entities.Grade
+import io.github.wulkanowy.ui.modules.main.MainActivity
+import io.github.wulkanowy.ui.modules.main.MainActivity.Companion.EXTRA_CARD_ID_KEY
+import timber.log.Timber
+
+class GradeNotification(context: Context) : BaseNotification(context) {
+
+ private val channelId = "Grade_Notify"
+
+ @TargetApi(26)
+ override fun createChannel(channelId: String) {
+ notificationManager.createNotificationChannel(NotificationChannel(
+ channelId, context.getString(R.string.notify_grade_channel), IMPORTANCE_HIGH
+ ).apply {
+ enableLights(true)
+ enableVibration(true)
+ lockscreenVisibility = VISIBILITY_PUBLIC
+ })
+ }
+
+ fun sendNotification(items: List) {
+ notify(notificationBuilder(channelId)
+ .setContentTitle(context.resources.getQuantityString(R.plurals.grade_new_items, items.size, items.size))
+ .setContentText(context.resources.getQuantityString(R.plurals.notify_grade_new_items, items.size, items.size))
+ .setSmallIcon(R.drawable.ic_stat_notify_grade)
+ .setAutoCancel(true)
+ .setDefaults(NotificationCompat.DEFAULT_ALL)
+ .setPriority(NotificationCompat.PRIORITY_HIGH)
+ .setColor(ContextCompat.getColor(context, R.color.colorPrimary))
+ .setContentIntent(
+ PendingIntent.getActivity(context, 0,
+ MainActivity.getStartIntent(context).putExtra(EXTRA_CARD_ID_KEY, 0),
+ FLAG_UPDATE_CURRENT
+ )
+ )
+ .setStyle(NotificationCompat.InboxStyle().run {
+ setSummaryText(context.resources.getQuantityString(R.plurals.grade_number_item, items.size, items.size))
+ items.forEach {
+ addLine("${it.subject}: ${it.entry}")
+ }
+ this
+ })
+ .build()
+ )
+
+ Timber.d("Notification sent")
+ }
+}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BasePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/base/BasePresenter.kt
index 55816e409..9a0b5eda0 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/base/BasePresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/base/BasePresenter.kt
@@ -1,6 +1,7 @@
package io.github.wulkanowy.ui.base
import io.github.wulkanowy.data.ErrorHandler
+import io.github.wulkanowy.ui.modules.main.MainView
import io.reactivex.disposables.CompositeDisposable
open class BasePresenter(private val errorHandler: ErrorHandler) {
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutModule.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutModule.kt
index 675026659..cc5ba7cf3 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutModule.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutModule.kt
@@ -12,4 +12,3 @@ class AboutModule {
@Provides
fun provideLibsFragmentCompat() = LibsFragmentCompat()
}
-
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutView.kt
index 1e05bfc54..a3ae2ada8 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutView.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutView.kt
@@ -7,4 +7,4 @@ interface AboutView : BaseView {
fun openSourceWebView()
fun openIssuesWebView()
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeModule.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeModule.kt
index 186ab4db6..eb032f795 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeModule.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeModule.kt
@@ -29,4 +29,3 @@ abstract class GradeModule {
@ContributesAndroidInjector
abstract fun binGradeSummaryFragment(): GradeSummaryFragment
}
-
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsDialog.kt
index 323db812b..19b462b1b 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsDialog.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsDialog.kt
@@ -75,6 +75,4 @@ class GradeDetailsDialog : DialogFragment() {
gradeDialogClose.setOnClickListener { dismiss() }
}
-
}
-
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsItem.kt
index bb3480c02..18c2656d6 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsItem.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsItem.kt
@@ -34,7 +34,7 @@ class GradeDetailsItem(val grade: Grade, private val weightString: String, priva
gradeItemDescription.text = if (grade.description.isNotBlank()) grade.description else grade.gradeSymbol
gradeItemDate.text = grade.date.toFormattedString()
gradeItemWeight.text = "$weightString: ${grade.weight}"
- gradeItemNote.visibility = if (grade.isNew) VISIBLE else GONE
+ gradeItemNote.visibility = if (!grade.isRead) VISIBLE else GONE
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt
index 20e1a7f83..5e58b2f4f 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt
@@ -9,6 +9,7 @@ import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.utils.SchedulersProvider
import io.github.wulkanowy.utils.calcAverage
import io.github.wulkanowy.utils.valueColor
+import timber.log.Timber
import javax.inject.Inject
class GradeDetailsPresenter @Inject constructor(
@@ -51,8 +52,8 @@ class GradeDetailsPresenter @Inject constructor(
if (item is GradeDetailsItem) {
view?.apply {
showGradeDialog(item.grade)
- if (item.grade.isNew) {
- item.grade.isNew = false
+ if (!item.grade.isRead) {
+ item.grade.isRead = true
updateItem(item)
getHeaderOfItem(item)?.let { header ->
if (header is GradeDetailsHeader) {
@@ -94,7 +95,7 @@ class GradeDetailsPresenter @Inject constructor(
subject = it.key,
average = formatAverage(average),
number = view?.getGradeNumberString(it.value.size).orEmpty(),
- newGrades = it.value.filter { grade -> grade.isNew }.size
+ newGrades = it.value.filter { grade -> !grade.isRead }.size
).apply {
subItems = it.value.map { item ->
GradeDetailsItem(
@@ -120,5 +121,6 @@ class GradeDetailsPresenter @Inject constructor(
.subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread)
.subscribe({}) { error -> errorHandler.proceed(error) })
+ Timber.d("Grade ${grade.id} updated")
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginErrorHandler.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginErrorHandler.kt
index ab12479c2..418dbed93 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginErrorHandler.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginErrorHandler.kt
@@ -20,4 +20,3 @@ class LoginErrorHandler(resources: Resources) : ErrorHandler(resources) {
doOnBadCredentials = {}
}
}
-
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginSwitchListener.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginSwitchListener.kt
index 564d1df95..1e6d82c8d 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginSwitchListener.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginSwitchListener.kt
@@ -3,4 +3,4 @@ package io.github.wulkanowy.ui.modules.login
interface LoginSwitchListener {
fun switchFragment(position: Int)
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt
index f6b0cf3cc..13c11d288 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt
@@ -10,6 +10,7 @@ import com.aurelhubert.ahbottomnavigation.AHBottomNavigationItem
import com.ncapdevi.fragnav.FragNavController
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.attendance.AttendanceFragment
import io.github.wulkanowy.ui.modules.exam.ExamFragment
@@ -31,6 +32,7 @@ class MainActivity : BaseActivity(), MainView {
lateinit var navController: FragNavController
companion object {
+ const val EXTRA_CARD_ID_KEY = "cardId"
fun getStartIntent(context: Context) = Intent(context, MainActivity::class.java)
}
@@ -51,7 +53,7 @@ class MainActivity : BaseActivity(), MainView {
setSupportActionBar(mainToolbar)
messageContainer = mainFragmentContainer
- presenter.onAttachView(this)
+ presenter.onAttachView(this, intent.getIntExtra(EXTRA_CARD_ID_KEY, -1))
navController.initialize(startMenuIndex, savedInstanceState)
}
@@ -66,13 +68,15 @@ class MainActivity : BaseActivity(), MainView {
override fun initView() {
mainBottomNav.run {
- addItems(mutableListOf(
+ addItems(
+ mutableListOf(
AHBottomNavigationItem(R.string.grade_title, R.drawable.ic_menu_main_grade_26dp, 0),
AHBottomNavigationItem(R.string.attendance_title, R.drawable.ic_menu_main_attendance_24dp, 0),
AHBottomNavigationItem(R.string.exam_title, R.drawable.ic_menu_main_exam_24dp, 0),
AHBottomNavigationItem(R.string.timetable_title, R.drawable.ic_menu_main_timetable_24dp, 0),
AHBottomNavigationItem(R.string.more_title, R.drawable.ic_menu_main_more_24dp, 0)
- ))
+ )
+ )
accentColor = ContextCompat.getColor(context, R.color.colorPrimary)
inactiveColor = getThemeAttrColor(android.R.attr.textColorSecondary)
defaultBackgroundColor = getThemeAttrColor(R.attr.bottomNavBackground)
@@ -89,11 +93,11 @@ class MainActivity : BaseActivity(), MainView {
setOnViewChangeListener { presenter.onViewStart() }
fragmentHideStrategy = HIDE
rootFragments = listOf(
- GradeFragment.newInstance(),
- AttendanceFragment.newInstance(),
- ExamFragment.newInstance(),
- TimetableFragment.newInstance(),
- MoreFragment.newInstance()
+ GradeFragment.newInstance(),
+ AttendanceFragment.newInstance(),
+ ExamFragment.newInstance(),
+ TimetableFragment.newInstance(),
+ MoreFragment.newInstance()
)
}
}
@@ -126,6 +130,10 @@ class MainActivity : BaseActivity(), MainView {
presenter.onBackPressed { super.onBackPressed() }
}
+ override fun cancelNotifications() {
+ GradeNotification(applicationContext).cancelAll()
+ }
+
override fun onSaveInstanceState(outState: Bundle?) {
super.onSaveInstanceState(outState)
navController.onSaveInstanceState(outState)
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainModule.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainModule.kt
index 959f482da..278c3f0b1 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainModule.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainModule.kt
@@ -14,6 +14,7 @@ import io.github.wulkanowy.ui.modules.exam.ExamFragment
import io.github.wulkanowy.ui.modules.grade.GradeFragment
import io.github.wulkanowy.ui.modules.grade.GradeModule
import io.github.wulkanowy.ui.modules.more.MoreFragment
+import io.github.wulkanowy.ui.modules.settings.SettingsFragment
import io.github.wulkanowy.ui.modules.timetable.TimetableFragment
@Module
@@ -53,4 +54,7 @@ abstract class MainModule {
@PerFragment
@ContributesAndroidInjector(modules = [AboutModule::class])
abstract fun bindAboutFragment(): AboutFragment
+
+ @ContributesAndroidInjector
+ abstract fun bindSettingsFragment(): SettingsFragment
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt
index 4cb716272..aa696f189 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt
@@ -2,20 +2,26 @@ package io.github.wulkanowy.ui.modules.main
import io.github.wulkanowy.data.ErrorHandler
import io.github.wulkanowy.data.repositories.PreferencesRepository
+import io.github.wulkanowy.services.job.ServiceHelper
import io.github.wulkanowy.ui.base.BasePresenter
import javax.inject.Inject
class MainPresenter @Inject constructor(
- errorHandler: ErrorHandler,
- private val prefRepository: PreferencesRepository)
- : BasePresenter(errorHandler) {
+ errorHandler: ErrorHandler,
+ private val prefRepository: PreferencesRepository,
+ private val serviceHelper: ServiceHelper
+) : BasePresenter(errorHandler) {
- override fun onAttachView(view: MainView) {
+ fun onAttachView(view: MainView, init: Int) {
super.onAttachView(view)
+
view.run {
- startMenuIndex = prefRepository.startMenuIndex
+ cancelNotifications()
+ startMenuIndex = if (init != -1) init else prefRepository.startMenuIndex
initView()
}
+
+ serviceHelper.startFullSyncService()
}
fun onViewStart() {
@@ -33,7 +39,6 @@ class MainPresenter @Inject constructor(
return true
}
-
fun onBackPressed(default: () -> Unit) {
view?.run {
if (isRootView) default()
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainView.kt
index 9a5121d53..8848f2f06 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainView.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainView.kt
@@ -24,6 +24,8 @@ interface MainView : BaseView {
fun popView()
+ fun cancelNotifications()
+
interface MainChildView {
fun onFragmentReselected()
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsFragment.kt
index 4061c4b33..7a24a10d0 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsFragment.kt
@@ -1,14 +1,21 @@
package io.github.wulkanowy.ui.modules.settings
+import android.content.Context
import android.content.SharedPreferences
import android.os.Bundle
+import android.widget.Toast
import androidx.appcompat.app.AppCompatDelegate
import com.takisoft.preferencex.PreferenceFragmentCompat
+import dagger.android.support.AndroidSupportInjection
import io.github.wulkanowy.R
import io.github.wulkanowy.ui.modules.main.MainView
+import javax.inject.Inject
class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedPreferenceChangeListener,
- MainView.TitledView {
+ MainView.TitledView, SettingsView {
+
+ @Inject
+ lateinit var presenter: SettingsPresenter
companion object {
fun newInstance() = SettingsFragment()
@@ -17,19 +24,40 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP
override val titleStringId: Int
get() = R.string.settings_title
+ override fun onAttach(context: Context) {
+ AndroidSupportInjection.inject(this)
+ super.onAttach(context)
+ }
+
+ override fun onActivityCreated(savedInstanceState: Bundle?) {
+ super.onActivityCreated(savedInstanceState)
+ presenter.onAttachView(this)
+ }
+
override fun onCreatePreferencesFix(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.scheme_preferences)
}
- override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String) {
- when(key) {
- "theme" -> {
- AppCompatDelegate.setDefaultNightMode(sharedPreferences?.getString("theme", "1")?.toInt() ?: 1)
- activity?.recreate()
- }
+ override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
+ presenter.onSharedPreferenceChanged(key)
+ }
+
+ override fun setTheme(theme: Int) {
+ AppCompatDelegate.setDefaultNightMode(theme)
+ activity?.recreate()
+ }
+
+ override fun setServicesSuspended(serviceEnablesKey: String, isHolidays: Boolean) {
+ findPreference(serviceEnablesKey).run {
+ summary = if (isHolidays) getString(R.string.pref_services_suspended) else ""
+ isEnabled = !isHolidays
}
}
+ override fun showMessage(text: String) {
+ Toast.makeText(context, text, Toast.LENGTH_SHORT).show()
+ }
+
override fun onResume() {
super.onResume()
preferenceScreen.sharedPreferences.registerOnSharedPreferenceChangeListener(this)
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsPresenter.kt
new file mode 100644
index 000000000..d3aa027c9
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsPresenter.kt
@@ -0,0 +1,40 @@
+package io.github.wulkanowy.ui.modules.settings
+
+import io.github.wulkanowy.data.ErrorHandler
+import io.github.wulkanowy.data.repositories.PreferencesRepository
+import io.github.wulkanowy.services.job.ServiceHelper
+import io.github.wulkanowy.ui.base.BasePresenter
+import io.github.wulkanowy.utils.isHolidays
+import org.threeten.bp.LocalDate.now
+import javax.inject.Inject
+
+class SettingsPresenter @Inject constructor(
+ errorHandler: ErrorHandler,
+ private val preferencesRepository: PreferencesRepository,
+ private val serviceHelper: ServiceHelper
+) : BasePresenter(errorHandler) {
+
+ override fun onAttachView(view: SettingsView) {
+ super.onAttachView(view)
+
+ view.run {
+ setServicesSuspended(preferencesRepository.serviceEnablesKey, now().isHolidays)
+ }
+ }
+
+ fun onSharedPreferenceChanged(key: String) {
+ when (key) {
+ preferencesRepository.serviceEnablesKey -> {
+ if (preferencesRepository.serviceEnabled) serviceHelper.startFullSyncService()
+ else serviceHelper.stopFullSyncService()
+ }
+ preferencesRepository.servicesIntervalKey,
+ preferencesRepository.servicesOnlyWifiKey -> {
+ serviceHelper.reloadFullSyncService()
+ }
+ preferencesRepository.currentThemeKey -> {
+ view?.setTheme(preferencesRepository.currentTheme)
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsView.kt
new file mode 100644
index 000000000..0b3c2f70c
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsView.kt
@@ -0,0 +1,10 @@
+package io.github.wulkanowy.ui.modules.settings
+
+import io.github.wulkanowy.ui.base.BaseView
+
+interface SettingsView : BaseView {
+
+ fun setTheme(theme: Int)
+
+ fun setServicesSuspended(serviceEnablesKey: String, isHolidays: Boolean)
+}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/splash/SplashActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/splash/SplashActivity.kt
index be481b9fd..0b6e86bb0 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/splash/SplashActivity.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/splash/SplashActivity.kt
@@ -1,6 +1,7 @@
package io.github.wulkanowy.ui.modules.splash
import android.os.Bundle
+import io.github.wulkanowy.services.notification.GradeNotification
import io.github.wulkanowy.ui.base.BaseActivity
import io.github.wulkanowy.ui.modules.login.LoginActivity
import io.github.wulkanowy.ui.modules.main.MainActivity
diff --git a/app/src/main/java/io/github/wulkanowy/utils/security/Scrambler.kt b/app/src/main/java/io/github/wulkanowy/utils/security/Scrambler.kt
index 6e64c2b7c..6c7f242ce 100644
--- a/app/src/main/java/io/github/wulkanowy/utils/security/Scrambler.kt
+++ b/app/src/main/java/io/github/wulkanowy/utils/security/Scrambler.kt
@@ -140,4 +140,3 @@ private fun generateKeyPair(context: Context) {
}
Timber.i("A new KeyPair has been generated")
}
-
diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml
index fb6d913d2..4a7ee27a9 100644
--- a/app/src/main/res/values-pl/strings.xml
+++ b/app/src/main/res/values-pl/strings.xml
@@ -62,6 +62,16 @@
+
+ Nowe oceny
+
+ - Dostałeś %1$d ocenę
+ - "Dostałeś %1$d oceny
+ - Dostałeś %1$d ocen
+ - Dostałeś %1$d ocen
+
+
+
Lekcja
Sala
@@ -128,24 +138,12 @@
Powiadomienia
Pokazuj powiadomienia
- Usługi
+ Synchronizacja
Automatyczna aktualizacja
- Zawieszone na wakacjach
+ Zawieszona na wakacjach
Interwał aktualizacji
Tylko WiFi
- Wymagany restart
-
-
-
- Nowe oceny
-
- - Dostałeś %1$d ocenę
- - "Dostałeś %1$d oceny
- - Dostałeś %1$d ocen
- - Dostałeś %1$d ocen
-
-
Czarny
diff --git a/app/src/main/res/values-pl/value_prefernces.xml b/app/src/main/res/values-pl/value_prefernces.xml
index 3731be408..611add4b2 100644
--- a/app/src/main/res/values-pl/value_prefernces.xml
+++ b/app/src/main/res/values-pl/value_prefernces.xml
@@ -1,9 +1,9 @@
- - 10 minut
+ - 15 minut
- 30 minut
- - 1 godzinę
+ - 1 godzina
- 2 godziny
- 6 godzin
- 12 godzin
diff --git a/app/src/main/res/values/preferences_keys.xml b/app/src/main/res/values/preferences_keys.xml
new file mode 100644
index 000000000..974d82424
--- /dev/null
+++ b/app/src/main/res/values/preferences_keys.xml
@@ -0,0 +1,10 @@
+
+
+ start_menu
+ attendance_present
+ theme
+ services_enable
+ services_interval
+ services_disable_wifi_only
+ notifications_enable
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 4dc703fba..61e45d6ca 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -57,6 +57,13 @@
- New grades
+
+ New grades
+
+ - You received %1$d grade
+ - You received %1$d grades
+
+
Lesson
@@ -124,22 +131,12 @@
Notifications
Show notifications
- Services
+ Synchronization
Automatic update
Suspended on holiday
Updates interval
Only WiFi
- Restart required
-
-
-
- New grades
-
- - You received %1$d grade
- - You received %1$d grades
-
-
Black
diff --git a/app/src/main/res/values/value_prefernces.xml b/app/src/main/res/values/value_prefernces.xml
index b855e24c5..bf07b5158 100644
--- a/app/src/main/res/values/value_prefernces.xml
+++ b/app/src/main/res/values/value_prefernces.xml
@@ -27,7 +27,7 @@
- - 10 minutes
+ - 15 minutes
- 30 minutes
- 1 hour
- 2 hours
@@ -36,7 +36,7 @@
- 24 hours
- - 10
+ - 15
- 30
- 60
- 120
diff --git a/app/src/main/res/xml/scheme_preferences.xml b/app/src/main/res/xml/scheme_preferences.xml
index 36180c94b..e39125555 100644
--- a/app/src/main/res/xml/scheme_preferences.xml
+++ b/app/src/main/res/xml/scheme_preferences.xml
@@ -1,29 +1,63 @@
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+ android:title="@string/pref_view_header"
+ app:iconSpaceReserved="false">
+ android:defaultValue="0"
+ android:entries="@array/startup_tab_entries"
+ android:entryValues="@array/startup_tab_value"
+ android:key="@string/pref_key_start_menu"
+ android:summary="%s"
+ android:title="@string/pref_view_list"
+ app:iconSpaceReserved="false" />
+
+ android:defaultValue="true"
+ android:key="@string/pref_key_attendance_present"
+ android:title="@string/pref_view_present"
+ app:iconSpaceReserved="false" />
+
+
+
+ android:defaultValue="60"
+ android:dependency="services_enable"
+ android:entries="@array/services_interval_entries"
+ android:entryValues="@array/services_interval_value"
+ android:key="@string/pref_key_services_interval"
+ android:summary="%s"
+ android:title="@string/pref_services_interval"
+ app:iconSpaceReserved="false" />
+
+
+
+
diff --git a/app/src/test/java/io/github/wulkanowy/ui/modules/main/MainPresenterTest.kt b/app/src/test/java/io/github/wulkanowy/ui/modules/main/MainPresenterTest.kt
index 7071fa27e..9d65f1882 100644
--- a/app/src/test/java/io/github/wulkanowy/ui/modules/main/MainPresenterTest.kt
+++ b/app/src/test/java/io/github/wulkanowy/ui/modules/main/MainPresenterTest.kt
@@ -2,6 +2,7 @@ package io.github.wulkanowy.ui.modules.main
import io.github.wulkanowy.data.ErrorHandler
import io.github.wulkanowy.data.repositories.PreferencesRepository
+import io.github.wulkanowy.services.job.ServiceHelper
import org.junit.Before
import org.junit.Test
import org.mockito.Mock
@@ -17,6 +18,9 @@ class MainPresenterTest {
@Mock
lateinit var prefRepository: PreferencesRepository
+ @Mock
+ lateinit var serviceHelper: ServiceHelper
+
@Mock
lateinit var mainView: MainView
@@ -27,8 +31,8 @@ class MainPresenterTest {
MockitoAnnotations.initMocks(this)
clearInvocations(mainView)
- presenter = MainPresenter(errorHandler, prefRepository)
- presenter.onAttachView(mainView)
+ presenter = MainPresenter(errorHandler, prefRepository, serviceHelper)
+ presenter.onAttachView(mainView, -1)
}
@Test
diff --git a/build.gradle b/build.gradle
index d8960364a..1b4538bb6 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,5 +1,5 @@
buildscript {
- ext.kotlin_version = '1.2.71'
+ ext.kotlin_version = '1.3.0'
repositories {
mavenCentral()
google()