mirror of
https://github.com/wulkanowy/wulkanowy.git
synced 2025-01-18 13:26:44 -06:00
Services refactor (#168)
This commit is contained in:
parent
ab71dd3fde
commit
70879945f2
@ -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
|
||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -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
|
||||
|
174
.idea/codeStyles/Project.xml
generated
Normal file
174
.idea/codeStyles/Project.xml
generated
Normal file
@ -0,0 +1,174 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<code_scheme name="Project" version="173">
|
||||
<option name="LINE_SEPARATOR" value=" " />
|
||||
<AndroidXmlCodeStyleSettings>
|
||||
<option name="USE_CUSTOM_SETTINGS" value="true" />
|
||||
</AndroidXmlCodeStyleSettings>
|
||||
<JetCodeStyleSettings>
|
||||
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
|
||||
<value>
|
||||
<package name="kotlinx.android.synthetic" withSubpackages="true" static="false" />
|
||||
</value>
|
||||
</option>
|
||||
<option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="2147483647" />
|
||||
<option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="2147483647" />
|
||||
<option name="CONTINUATION_INDENT_IN_PARAMETER_LISTS" value="false" />
|
||||
<option name="CONTINUATION_INDENT_IN_ARGUMENT_LISTS" value="false" />
|
||||
<option name="CONTINUATION_INDENT_FOR_EXPRESSION_BODIES" value="false" />
|
||||
<option name="CONTINUATION_INDENT_FOR_CHAINED_CALLS" value="false" />
|
||||
<option name="CONTINUATION_INDENT_IN_SUPERTYPE_LISTS" value="false" />
|
||||
<option name="CONTINUATION_INDENT_IN_IF_CONDITIONS" value="false" />
|
||||
<option name="CONTINUATION_INDENT_IN_ELVIS" value="false" />
|
||||
<option name="WRAP_EXPRESSION_BODY_FUNCTIONS" value="1" />
|
||||
</JetCodeStyleSettings>
|
||||
<Objective-C-extensions>
|
||||
<file>
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Import" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Macro" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Typedef" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Enum" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Constant" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Global" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Struct" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="FunctionPredecl" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Function" />
|
||||
</file>
|
||||
<class>
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Property" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Synthesize" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InitMethod" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="StaticMethod" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InstanceMethod" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="DeallocMethod" />
|
||||
</class>
|
||||
<extensions>
|
||||
<pair source="cpp" header="h" fileNamingConvention="NONE" />
|
||||
<pair source="c" header="h" fileNamingConvention="NONE" />
|
||||
</extensions>
|
||||
</Objective-C-extensions>
|
||||
<XML>
|
||||
<option name="XML_KEEP_LINE_BREAKS" value="false" />
|
||||
<option name="XML_ALIGN_ATTRIBUTES" value="false" />
|
||||
<option name="XML_SPACE_INSIDE_EMPTY_TAG" value="true" />
|
||||
</XML>
|
||||
<codeStyleSettings language="XML">
|
||||
<option name="FORCE_REARRANGE_MODE" value="1" />
|
||||
<indentOptions>
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
</indentOptions>
|
||||
<arrangement>
|
||||
<rules>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>xmlns:android</NAME>
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>xmlns:.*</NAME>
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:id</NAME>
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:name</NAME>
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>name</NAME>
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>style</NAME>
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>ANDROID_ATTRIBUTE_ORDER</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_NAMESPACE>.*</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
</rules>
|
||||
</arrangement>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="kotlin">
|
||||
<option name="KEEP_BLANK_LINES_IN_DECLARATIONS" value="1" />
|
||||
<option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
|
||||
<option name="KEEP_BLANK_LINES_BEFORE_RBRACE" value="0" />
|
||||
<option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
|
||||
<option name="METHOD_PARAMETERS_WRAP" value="5" />
|
||||
<option name="METHOD_PARAMETERS_LPAREN_ON_NEXT_LINE" value="true" />
|
||||
<option name="METHOD_PARAMETERS_RPAREN_ON_NEXT_LINE" value="true" />
|
||||
<option name="EXTENDS_LIST_WRAP" value="1" />
|
||||
<option name="METHOD_CALL_CHAIN_WRAP" value="1" />
|
||||
<option name="ASSIGNMENT_WRAP" value="1" />
|
||||
<indentOptions>
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
</code_scheme>
|
||||
</component>
|
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
@ -0,0 +1,5 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||
</state>
|
||||
</component>
|
@ -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"
|
||||
}
|
||||
|
@ -40,6 +40,14 @@
|
||||
android:launchMode="singleTop"
|
||||
android:theme="@style/WulkanowyTheme.NoActionBar" />
|
||||
|
||||
<service
|
||||
android:name=".services.job.SyncWorker"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="com.firebase.jobdispatcher.ACTION_EXECUTE" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<meta-data
|
||||
android:name="io.fabric.ApiKey"
|
||||
android:value="${fabricApiKey}" />
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -14,4 +14,12 @@ class SharedPrefHelper @Inject constructor(private val sharedPref: SharedPrefere
|
||||
fun getLong(key: String, defaultValue: Long): Long {
|
||||
return sharedPref.getLong(key, defaultValue)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
@ -13,9 +13,15 @@ interface GradeDao {
|
||||
@Update
|
||||
fun update(grade: Grade)
|
||||
|
||||
@Update
|
||||
fun updateAll(grade: List<Grade>)
|
||||
|
||||
@Delete
|
||||
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>>
|
||||
|
||||
@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>>
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ import io.reactivex.Maybe
|
||||
@Dao
|
||||
interface GradeSummaryDao {
|
||||
|
||||
@Insert(onConflict = REPLACE)
|
||||
@Insert
|
||||
fun insertAll(gradesSummary: List<GradeSummary>)
|
||||
|
||||
@Delete
|
||||
|
@ -14,4 +14,10 @@ interface SemesterDao {
|
||||
|
||||
@Query("SELECT * FROM Semesters WHERE student_id = :studentId")
|
||||
fun getSemester(studentId: Int): Single<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)
|
||||
}
|
||||
|
@ -44,6 +44,9 @@ data class Grade(
|
||||
@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
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ class GradeRepository @Inject constructor(
|
||||
private val remote: GradeRemote
|
||||
) {
|
||||
|
||||
fun getGrades(semester: Semester, forceRefresh: Boolean = false): Single<List<Grade>> {
|
||||
fun getGrades(semester: Semester, forceRefresh: Boolean = false, notify: Boolean = false): Single<List<Grade>> {
|
||||
return local.getGrades(semester).filter { !forceRefresh }
|
||||
.switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings)
|
||||
.flatMap {
|
||||
@ -30,13 +30,23 @@ class GradeRepository @Inject constructor(
|
||||
.doOnSuccess { oldGrades ->
|
||||
local.deleteGrades(oldGrades - newGrades)
|
||||
local.saveGrades((newGrades - oldGrades)
|
||||
.onEach { if (oldGrades.isNotEmpty()) it.isNew = true })
|
||||
.onEach {
|
||||
if (oldGrades.isNotEmpty()) it.isRead = false
|
||||
if (notify) it.isNotified = false
|
||||
})
|
||||
}
|
||||
}.flatMap { local.getGrades(semester).toSingle(emptyList()) })
|
||||
}
|
||||
|
||||
fun getNewGrades(semester: Semester): Single<List<Grade>> {
|
||||
return local.getNewGrades(semester).toSingle(emptyList())
|
||||
}
|
||||
|
||||
fun updateGrade(grade: Grade): Completable {
|
||||
return local.updateGrade(grade)
|
||||
}
|
||||
|
||||
fun updateGrades(grades: List<Grade>): Completable {
|
||||
return local.updateGrades(grades)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -16,7 +16,8 @@ import javax.inject.Singleton
|
||||
class SessionRepository @Inject constructor(
|
||||
private val local: SessionLocal,
|
||||
private val remote: SessionRemote,
|
||||
private val settings: InternetObservingSettings) {
|
||||
private val settings: InternetObservingSettings
|
||||
) {
|
||||
|
||||
val isSessionSaved
|
||||
get() = local.isSessionSaved
|
||||
@ -33,17 +34,32 @@ class SessionRepository @Inject constructor(
|
||||
return cachedStudents
|
||||
}
|
||||
|
||||
fun getSemesters(): Single<List<Semester>> {
|
||||
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<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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fun clearCache() {
|
||||
|
@ -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<List<Grade>> {
|
||||
return gradeDb.getNewGrades(semester.semesterId, semester.studentId)
|
||||
}
|
||||
|
||||
fun saveGrades(grades: List<Grade>) {
|
||||
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<Grade>): Completable {
|
||||
return Completable.fromCallable { gradeDb.updateAll(grade) }
|
||||
}
|
||||
|
||||
fun deleteGrades(grades: List<Grade>) {
|
||||
gradeDb.deleteAll(grades)
|
||||
}
|
||||
|
@ -46,4 +46,10 @@ class SessionLocal @Inject constructor(
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,20 +12,29 @@ import javax.inject.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")))
|
||||
.flatMap { _ ->
|
||||
api.getPupils().map { students ->
|
||||
students.map {
|
||||
return Single.just(
|
||||
initApi(
|
||||
Student(
|
||||
email = email,
|
||||
password = password,
|
||||
symbol = it.symbol,
|
||||
studentId = it.studentId,
|
||||
studentName = it.studentName,
|
||||
schoolSymbol = it.schoolSymbol,
|
||||
schoolName = it.schoolName,
|
||||
symbol = symbol,
|
||||
endpoint = endpoint,
|
||||
loginType = it.loginType.name
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -33,9 +42,25 @@ class SessionRemote @Inject constructor(private val api: Api) {
|
||||
}
|
||||
|
||||
fun getSemesters(student: Student): Single<List<Semester>> {
|
||||
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 = 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,
|
||||
@ -45,13 +70,10 @@ class SessionRemote @Inject constructor(private val api: Api) {
|
||||
current = it.current
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun initApi(student: Student, checkInit: Boolean = false) {
|
||||
if (if (checkInit) 0 == api.studentId else true) {
|
||||
fun initApi(student: Student, reInitialize: Boolean = false) {
|
||||
if (if (reInitialize) true else 0 == api.studentId) {
|
||||
api.run {
|
||||
email = student.email
|
||||
password = student.password
|
||||
|
@ -14,6 +14,7 @@ import javax.inject.Singleton
|
||||
RepositoryModule::class,
|
||||
BuilderModule::class])
|
||||
interface AppComponent : AndroidInjector<WulkanowyApp> {
|
||||
|
||||
@Component.Builder
|
||||
abstract class Builder : AndroidInjector.Builder<WulkanowyApp>()
|
||||
}
|
||||
|
@ -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<AbstractFlexibleItem<*>>(null, null, true)
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideJobDispatcher(context: Context): FirebaseJobDispatcher {
|
||||
return FirebaseJobDispatcher(GooglePlayDriver(context))
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
110
app/src/main/java/io/github/wulkanowy/services/job/SyncWorker.kt
Normal file
110
app/src/main/java/io/github/wulkanowy/services/job/SyncWorker.kt
Normal file
@ -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()
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
@ -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<Grade>) {
|
||||
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")
|
||||
}
|
||||
}
|
@ -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<T : BaseView>(private val errorHandler: ErrorHandler) {
|
||||
|
@ -12,4 +12,3 @@ class AboutModule {
|
||||
@Provides
|
||||
fun provideLibsFragmentCompat() = LibsFragmentCompat()
|
||||
}
|
||||
|
||||
|
@ -29,4 +29,3 @@ abstract class GradeModule {
|
||||
@ContributesAndroidInjector
|
||||
abstract fun binGradeSummaryFragment(): GradeSummaryFragment
|
||||
}
|
||||
|
||||
|
@ -75,6 +75,4 @@ class GradeDetailsDialog : DialogFragment() {
|
||||
|
||||
gradeDialogClose.setOnClickListener { dismiss() }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
@ -20,4 +20,3 @@ class LoginErrorHandler(resources: Resources) : ErrorHandler(resources) {
|
||||
doOnBadCredentials = {}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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<MainView>(errorHandler) {
|
||||
private val prefRepository: PreferencesRepository,
|
||||
private val serviceHelper: ServiceHelper
|
||||
) : BasePresenter<MainView>(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()
|
||||
|
@ -24,6 +24,8 @@ interface MainView : BaseView {
|
||||
|
||||
fun popView()
|
||||
|
||||
fun cancelNotifications()
|
||||
|
||||
interface MainChildView {
|
||||
|
||||
fun onFragmentReselected()
|
||||
|
@ -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)
|
||||
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)
|
||||
|
@ -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<SettingsView>(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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
@ -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
|
||||
|
@ -140,4 +140,3 @@ private fun generateKeyPair(context: Context) {
|
||||
}
|
||||
Timber.i("A new KeyPair has been generated")
|
||||
}
|
||||
|
||||
|
@ -62,6 +62,16 @@
|
||||
</plurals>
|
||||
|
||||
|
||||
<!--Grade notify-->
|
||||
<string name="notify_grade_channel">Nowe oceny</string>
|
||||
<plurals name="notify_grade_new_items">
|
||||
<item quantity="one">Dostałeś %1$d ocenę</item>
|
||||
<item quantity="few">"Dostałeś %1$d oceny</item>
|
||||
<item quantity="many">Dostałeś %1$d ocen</item>
|
||||
<item quantity="other">Dostałeś %1$d ocen</item>
|
||||
</plurals>
|
||||
|
||||
|
||||
<!--Timetable-->
|
||||
<string name="timetable_lesson">Lekcja</string>
|
||||
<string name="timetable_room">Sala</string>
|
||||
@ -128,24 +138,12 @@
|
||||
<string name="pref_notify_header">Powiadomienia</string>
|
||||
<string name="pref_notify_switch">Pokazuj powiadomienia</string>
|
||||
|
||||
<string name="pref_services_header">Usługi</string>
|
||||
<string name="pref_services_header">Synchronizacja</string>
|
||||
<string name="pref_services_switch">Automatyczna aktualizacja</string>
|
||||
<string name="pref_services_suspended">Zawieszone na wakacjach</string>
|
||||
<string name="pref_services_suspended">Zawieszona na wakacjach</string>
|
||||
<string name="pref_services_interval">Interwał aktualizacji</string>
|
||||
<string name="pref_services_wifi">Tylko WiFi</string>
|
||||
|
||||
<string name="pref_restart">Wymagany restart</string>
|
||||
|
||||
|
||||
<!--Grade notify-->
|
||||
<string name="notify_grade_chanel">Nowe oceny</string>
|
||||
<plurals name="notify_grade_new_items">
|
||||
<item quantity="one">Dostałeś %1$d ocenę</item>
|
||||
<item quantity="few">"Dostałeś %1$d oceny</item>
|
||||
<item quantity="many">Dostałeś %1$d ocen</item>
|
||||
<item quantity="other">Dostałeś %1$d ocen</item>
|
||||
</plurals>
|
||||
|
||||
|
||||
<!--Colors-->
|
||||
<string name="all_black">Czarny</string>
|
||||
|
@ -1,9 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string-array name="services_interval_entries">
|
||||
<item>10 minut</item>
|
||||
<item>15 minut</item>
|
||||
<item>30 minut</item>
|
||||
<item>1 godzinę</item>
|
||||
<item>1 godzina</item>
|
||||
<item>2 godziny</item>
|
||||
<item>6 godzin</item>
|
||||
<item>12 godzin</item>
|
||||
|
10
app/src/main/res/values/preferences_keys.xml
Normal file
10
app/src/main/res/values/preferences_keys.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
|
||||
<string name="pref_key_start_menu">start_menu</string>
|
||||
<string name="pref_key_attendance_present">attendance_present</string>
|
||||
<string name="pref_key_theme">theme</string>
|
||||
<string name="pref_key_services_enable">services_enable</string>
|
||||
<string name="pref_key_services_interval">services_interval</string>
|
||||
<string name="pref_key_services_wifi_only">services_disable_wifi_only</string>
|
||||
<string name="pref_key_notifications_enable">notifications_enable</string>
|
||||
</resources>
|
@ -57,6 +57,13 @@
|
||||
<item quantity="other">New grades</item>
|
||||
</plurals>
|
||||
|
||||
<!--Grade notify-->
|
||||
<string name="notify_grade_channel">New grades</string>
|
||||
<plurals name="notify_grade_new_items">
|
||||
<item quantity="one">You received %1$d grade</item>
|
||||
<item quantity="other">You received %1$d grades</item>
|
||||
</plurals>
|
||||
|
||||
|
||||
<!--Timetable-->
|
||||
<string name="timetable_lesson">Lesson</string>
|
||||
@ -124,22 +131,12 @@
|
||||
<string name="pref_notify_header">Notifications</string>
|
||||
<string name="pref_notify_switch">Show notifications</string>
|
||||
|
||||
<string name="pref_services_header">Services</string>
|
||||
<string name="pref_services_header">Synchronization</string>
|
||||
<string name="pref_services_switch">Automatic update</string>
|
||||
<string name="pref_services_suspended">Suspended on holiday</string>
|
||||
<string name="pref_services_interval">Updates interval</string>
|
||||
<string name="pref_services_wifi">Only WiFi</string>
|
||||
|
||||
<string name="pref_restart">Restart required</string>
|
||||
|
||||
|
||||
<!--Grade notify-->
|
||||
<string name="notify_grade_chanel">New grades</string>
|
||||
<plurals name="notify_grade_new_items">
|
||||
<item quantity="one">You received %1$d grade</item>
|
||||
<item quantity="other">You received %1$d grades</item>
|
||||
</plurals>
|
||||
|
||||
|
||||
<!--Colors-->
|
||||
<string name="all_black">Black</string>
|
||||
|
@ -27,7 +27,7 @@
|
||||
</string-array>
|
||||
|
||||
<string-array name="services_interval_entries">
|
||||
<item>10 minutes</item>
|
||||
<item>15 minutes</item>
|
||||
<item>30 minutes</item>
|
||||
<item>1 hour</item>
|
||||
<item>2 hours</item>
|
||||
@ -36,7 +36,7 @@
|
||||
<item>24 hours</item>
|
||||
</string-array>
|
||||
<string-array name="services_interval_value" translatable="false">
|
||||
<item>10</item>
|
||||
<item>15</item>
|
||||
<item>30</item>
|
||||
<item>60</item>
|
||||
<item>120</item>
|
||||
|
@ -8,15 +8,10 @@
|
||||
android:defaultValue="0"
|
||||
android:entries="@array/startup_tab_entries"
|
||||
android:entryValues="@array/startup_tab_value"
|
||||
android:key="start_menu"
|
||||
android:key="@string/pref_key_start_menu"
|
||||
android:summary="%s"
|
||||
android:title="@string/pref_view_list"
|
||||
app:iconSpaceReserved="false" />
|
||||
<SwitchPreference
|
||||
android:defaultValue="true"
|
||||
android:key="attendance_present"
|
||||
android:title="@string/pref_view_present"
|
||||
app:iconSpaceReserved="false"/>
|
||||
<ListPreference
|
||||
android:defaultValue="1"
|
||||
android:entries="@array/theme_entries"
|
||||
@ -25,5 +20,44 @@
|
||||
android:summary="%s"
|
||||
android:title="@string/pref_view_theme_dark"
|
||||
app:iconSpaceReserved="false" />
|
||||
<SwitchPreference
|
||||
android:defaultValue="true"
|
||||
android:key="@string/pref_key_attendance_present"
|
||||
android:title="@string/pref_view_present"
|
||||
app:iconSpaceReserved="false" />
|
||||
</PreferenceCategory>
|
||||
<PreferenceCategory
|
||||
android:title="@string/pref_services_header"
|
||||
app:iconSpaceReserved="false">
|
||||
<SwitchPreference
|
||||
android:defaultValue="true"
|
||||
android:key="@string/pref_key_services_enable"
|
||||
android:title="@string/pref_services_switch"
|
||||
app:iconSpaceReserved="false" />
|
||||
<ListPreference
|
||||
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" />
|
||||
<SwitchPreference
|
||||
android:defaultValue="false"
|
||||
android:dependency="services_enable"
|
||||
android:key="@string/pref_key_services_wifi_only"
|
||||
android:title="@string/pref_services_wifi"
|
||||
app:iconSpaceReserved="false" />
|
||||
</PreferenceCategory>
|
||||
<PreferenceCategory
|
||||
android:title="@string/pref_notify_header"
|
||||
app:iconSpaceReserved="false">
|
||||
<SwitchPreference
|
||||
android:defaultValue="true"
|
||||
android:dependency="services_enable"
|
||||
android:key="@string/pref_key_notifications_enable"
|
||||
android:title="@string/pref_notify_switch"
|
||||
app:iconSpaceReserved="false" />
|
||||
</PreferenceCategory>
|
||||
</PreferenceScreen>
|
||||
|
@ -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
|
||||
|
@ -1,5 +1,5 @@
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.2.71'
|
||||
ext.kotlin_version = '1.3.0'
|
||||
repositories {
|
||||
mavenCentral()
|
||||
google()
|
||||
|
Loading…
x
Reference in New Issue
Block a user