1
0

Compare commits

...

25 Commits
0.9.0 ... 0.9.3

Author SHA1 Message Date
08ecbb5341 Version 0.9.3 2019-06-15 01:13:36 +02:00
e38e458386 Fix crash on login when error message is null (#420) 2019-06-15 00:51:54 +02:00
14674b7778 Bump room-testing from 2.1.0-rc01 to 2.1.0 (#417) 2019-06-13 22:33:12 +00:00
c6f0588165 Bump room-compiler from 2.1.0-rc01 to 2.1.0 (#415) 2019-06-13 22:12:23 +00:00
7591af0de1 Bump room-rxjava2 from 2.1.0-rc01 to 2.1.0 (#411) 2019-06-13 21:52:32 +00:00
cbfed09b52 Bump room-runtime from 2.1.0-rc01 to 2.1.0 (#419) 2019-06-13 21:29:47 +00:00
4a026e4a70 Merge tag '0.9.2' into develop
0.9.2 0.9.2
2019-06-08 11:38:42 +02:00
5c185c5eca Merge branch 'release/0.9.2' 2019-06-08 11:38:42 +02:00
0bccbc6011 Version 0.9.2 2019-06-08 11:38:06 +02:00
ed6a0f8cd0 Add StackOverflowError to RxJava's error handler (#408) 2019-06-07 21:38:53 +02:00
58d5e4da0e Fix showing empty view in mobile device view (#407) 2019-06-07 19:03:26 +02:00
ba6fb1a4b9 Fix update of grades in GradeDetailsFragment (#406) 2019-06-07 14:46:11 +02:00
b6b862d4c3 Organize AndroidX libraries (#404) 2019-06-07 14:12:52 +02:00
9b044a19fe Fix typo in service interval (#405) 2019-06-06 22:47:45 +02:00
7485cb2a39 Fix change of worker specs after app update (#402) 2019-06-06 22:32:43 +02:00
75122d0dcd Fix crash TabLayout on Android 4.x in new version of material components (#403) 2019-06-06 19:16:00 +02:00
39af56484b Bump threetenabp from 1.2.0 to 1.2.1 (#401) 2019-06-04 11:23:50 +00:00
05f7e1d115 Merge tag '0.9.1' into develop
Version 0.9.1
2019-06-04 02:44:04 +02:00
9cd5377438 Merge branch 'release/0.9.1' 2019-06-04 02:43:54 +02:00
7a61b233f0 Version 0.9.1 2019-06-04 02:43:50 +02:00
83dbd9874e Add option to force calc average by app (#400) 2019-06-04 02:27:15 +02:00
1d9a49d552 Add githook plugin (#398) 2019-06-04 01:53:20 +02:00
6175081b88 Fix tab layout crash on prelolipop (#399) 2019-06-03 22:47:35 +02:00
116e551186 Merge tag '0.9.0' into develop
0.9.0 0.9.0
2019-06-03 18:15:42 +02:00
713500d892 Merge branch 'release/0.9.0' 2019-06-03 18:15:41 +02:00
38 changed files with 290 additions and 116 deletions

View File

@ -11,9 +11,10 @@ cache:
- $HOME/.gradle/caches/ - $HOME/.gradle/caches/
- $HOME/.gradle/wrapper/ - $HOME/.gradle/wrapper/
#branches: branches:
# only: only:
# - develop - develop
- 0.9.2
android: android:
licenses: licenses:

View File

@ -6,6 +6,7 @@ apply plugin: 'io.fabric'
apply plugin: 'com.github.triplet.play' apply plugin: 'com.github.triplet.play'
apply from: 'jacoco.gradle' apply from: 'jacoco.gradle'
apply from: 'sonarqube.gradle' apply from: 'sonarqube.gradle'
apply from: 'hooks.gradle'
android { android {
compileSdkVersion 28 compileSdkVersion 28
@ -16,8 +17,8 @@ android {
testApplicationId "io.github.tests.wulkanowy" testApplicationId "io.github.tests.wulkanowy"
minSdkVersion 15 minSdkVersion 15
targetSdkVersion 28 targetSdkVersion 28
versionCode 38 versionCode 41
versionName "0.9.0" versionName "0.9.3"
multiDexEnabled true multiDexEnabled true
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true
@ -97,74 +98,79 @@ play {
} }
dependencies { dependencies {
implementation 'io.github.wulkanowy:api:0.9.0' implementation "io.github.wulkanowy:api:0.9.3"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation "androidx.legacy:legacy-support-v4:1.0.0" implementation "androidx.core:core:1.0.2"
implementation "androidx.appcompat:appcompat:1.0.2" implementation "androidx.appcompat:appcompat:1.0.2"
implementation 'androidx.multidex:multidex:2.0.1' implementation "androidx.fragment:fragment:1.0.0"
implementation "androidx.annotation:annotation:1.1.0"
implementation "androidx.multidex:multidex:2.0.1"
implementation "androidx.cardview:cardview:1.0.0" implementation "androidx.recyclerview:recyclerview:1.0.0"
implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation "androidx.viewpager:viewpager:1.0.0"
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"
implementation "androidx.constraintlayout:constraintlayout:1.1.3"
implementation "androidx.coordinatorlayout:coordinatorlayout:1.0.0"
implementation "com.google.android.material:material:1.1.0-alpha07" implementation "com.google.android.material:material:1.1.0-alpha07"
implementation 'com.github.wulkanowy:MaterialChipsInput:b72fd0ee6f' implementation "com.github.wulkanowy:MaterialChipsInput:b72fd0ee6f"
implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0' implementation "com.github.PhilJay:MPAndroidChart:v3.1.0"
implementation "androidx.work:work-runtime:2.0.1" implementation "androidx.work:work-runtime:2.0.1"
implementation "androidx.work:work-rxjava2:2.0.1" implementation "androidx.work:work-rxjava2:2.0.1"
implementation "androidx.room:room-runtime:2.1.0-rc01" implementation "androidx.room:room-runtime:2.1.0"
implementation "androidx.room:room-rxjava2:2.1.0-rc01" implementation "androidx.room:room-rxjava2:2.1.0"
kapt "androidx.room:room-compiler:2.1.0-rc01" kapt "androidx.room:room-compiler:2.1.0"
implementation "com.google.dagger:dagger-android-support:2.23.1" implementation "com.google.dagger:dagger-android-support:2.23.1"
kapt "com.google.dagger:dagger-compiler:2.23.1" kapt "com.google.dagger:dagger-compiler:2.23.1"
kapt "com.google.dagger:dagger-android-processor:2.23.1" kapt "com.google.dagger:dagger-android-processor:2.23.1"
implementation 'com.squareup.inject:assisted-inject-annotations-dagger2:0.4.0' implementation "com.squareup.inject:assisted-inject-annotations-dagger2:0.4.0"
kapt 'com.squareup.inject:assisted-inject-processor-dagger2:0.4.0' kapt "com.squareup.inject:assisted-inject-processor-dagger2:0.4.0"
implementation "eu.davidea:flexible-adapter:5.1.0" implementation "eu.davidea:flexible-adapter:5.1.0"
implementation "eu.davidea:flexible-adapter-ui:1.0.0" implementation "eu.davidea:flexible-adapter-ui:1.0.0"
implementation "com.aurelhubert:ahbottomnavigation:2.3.4" implementation "com.aurelhubert:ahbottomnavigation:2.3.4"
implementation 'com.ncapdevi:frag-nav:3.2.0' implementation "com.ncapdevi:frag-nav:3.2.0"
implementation 'com.github.pwittchen:reactivenetwork-rx2:3.0.3' implementation "com.github.pwittchen:reactivenetwork-rx2:3.0.3"
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' implementation "io.reactivex.rxjava2:rxandroid:2.1.1"
implementation "io.reactivex.rxjava2:rxjava:2.2.9" implementation "io.reactivex.rxjava2:rxjava:2.2.9"
implementation 'com.google.code.gson:gson:2.8.5' implementation "com.google.code.gson:gson:2.8.5"
implementation "com.jakewharton.threetenabp:threetenabp:1.2.0" implementation "com.jakewharton.threetenabp:threetenabp:1.2.1"
implementation "com.jakewharton.timber:timber:4.7.1" implementation "com.jakewharton.timber:timber:4.7.1"
implementation "at.favre.lib:slf4j-timber:1.0.1" implementation "at.favre.lib:slf4j-timber:1.0.1"
implementation "com.squareup.okhttp3:logging-interceptor:3.12.3" implementation "com.squareup.okhttp3:logging-interceptor:3.12.3"
implementation "com.mikepenz:aboutlibraries:6.2.3" implementation "com.mikepenz:aboutlibraries:6.2.3"
implementation 'com.takisoft.preferencex:preferencex:1.0.0' implementation "com.takisoft.preferencex:preferencex:1.0.0"
playImplementation 'com.google.firebase:firebase-core:16.0.9' playImplementation "com.google.firebase:firebase-core:16.0.9"
playImplementation 'com.crashlytics.sdk.android:crashlytics:2.10.1' playImplementation "com.crashlytics.sdk.android:crashlytics:2.10.1"
releaseImplementation 'fr.o80.chucker:library-no-op:2.0.4' releaseImplementation "fr.o80.chucker:library-no-op:2.0.4"
debugImplementation 'fr.o80.chucker:library:2.0.4' debugImplementation "fr.o80.chucker:library:2.0.4"
debugImplementation "com.amitshekhar.android:debug-db:1.0.6" debugImplementation "com.amitshekhar.android:debug-db:1.0.6"
testImplementation "junit:junit:4.12" testImplementation "junit:junit:4.12"
testImplementation "io.mockk:mockk:1.9.2" testImplementation "io.mockk:mockk:1.9.2"
testImplementation 'org.threeten:threetenbp:1.4.0' testImplementation "org.threeten:threetenbp:1.4.0"
testImplementation "org.mockito:mockito-core:2.28.2" testImplementation "org.mockito:mockito-core:2.28.2"
testImplementation("org.mockito:mockito-inline:2.28.2") { testImplementation("org.mockito:mockito-inline:2.28.2") {
exclude group: 'org.mockito', module: 'mockito-core' exclude group: "org.mockito", module: "mockito-core"
} }
androidTestImplementation 'androidx.test:core:1.2.0' androidTestImplementation "androidx.test:core:1.2.0"
androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation "androidx.test:runner:1.2.0"
androidTestImplementation 'androidx.test.ext:junit:1.1.1' androidTestImplementation "androidx.test.ext:junit:1.1.1"
androidTestImplementation "io.mockk:mockk-android:1.9.2" androidTestImplementation "io.mockk:mockk-android:1.9.2"
androidTestImplementation "androidx.room:room-testing:2.1.0-rc01" androidTestImplementation "androidx.room:room-testing:2.1.0"
androidTestImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version" androidTestImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
androidTestImplementation "org.mockito:mockito-core:2.28.2" androidTestImplementation "org.mockito:mockito-core:2.28.2"
androidTestImplementation('org.mockito:mockito-android:2.28.2') { androidTestImplementation("org.mockito:mockito-android:2.28.2") {
exclude group: 'org.mockito', module: 'mockito-core' exclude group: 'org.mockito', module: 'mockito-core'
} }
} }

10
app/hooks.gradle Normal file
View File

@ -0,0 +1,10 @@
apply plugin: "com.star-zero.gradle.githook"
githook {
failOnMissingHooksDir = false
hooks {
"pre-push" {
shell = "./app/play-publish-lint.sh"
}
}
}

7
app/play-publish-lint.sh Executable file
View File

@ -0,0 +1,7 @@
#!/bin/bash -
content=$(cat < "app/src/main/play/release-notes/pl-PL/default.txt") || exit
if [[ "${#content}" -gt 500 ]]; then
echo >&2 "Release notes content has reached the limit of 500 characters"
exit 1
fi

View File

@ -37,3 +37,6 @@
#Config for API #Config for API
-keep class io.github.wulkanowy.api.** {*;} -keep class io.github.wulkanowy.api.** {*;}
#Config for Material Components
-keep class com.google.android.material.tabs.** {*;}

View File

@ -5,13 +5,13 @@ package io.github.wulkanowy.utils
import android.content.Context import android.content.Context
import timber.log.Timber import timber.log.Timber
fun initCrashlytics(context: Context, appInfo: AppInfo) {
// do nothing
}
class CrashlyticsTree : Timber.Tree() { class CrashlyticsTree : Timber.Tree() {
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) { override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
// do nothing // do nothing
} }
} }
fun initCrashlytics(context: Context) {
// do nothing
}

View File

@ -80,7 +80,6 @@
android:name="android.appwidget.provider" android:name="android.appwidget.provider"
android:resource="@xml/provider_widget_timetable" /> android:resource="@xml/provider_widget_timetable" />
</receiver> </receiver>
<receiver <receiver
android:name=".ui.modules.luckynumberwidget.LuckyNumberWidgetProvider" android:name=".ui.modules.luckynumberwidget.LuckyNumberWidgetProvider"
android:label="@string/lucky_number_title"> android:label="@string/lucky_number_title">

View File

@ -1,6 +1,8 @@
package io.github.wulkanowy package io.github.wulkanowy
import android.content.Context import android.content.Context
import android.util.Log.INFO
import android.util.Log.VERBOSE
import androidx.multidex.MultiDex import androidx.multidex.MultiDex
import androidx.work.Configuration import androidx.work.Configuration
import androidx.work.WorkManager import androidx.work.WorkManager
@ -9,10 +11,10 @@ import dagger.android.AndroidInjector
import dagger.android.support.DaggerApplication import dagger.android.support.DaggerApplication
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.utils.Log import eu.davidea.flexibleadapter.utils.Log
import io.github.wulkanowy.BuildConfig.DEBUG
import io.github.wulkanowy.di.DaggerAppComponent import io.github.wulkanowy.di.DaggerAppComponent
import io.github.wulkanowy.services.sync.SyncWorkerFactory import io.github.wulkanowy.services.sync.SyncWorkerFactory
import io.github.wulkanowy.utils.ActivityLifecycleLogger import io.github.wulkanowy.utils.ActivityLifecycleLogger
import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.CrashlyticsTree import io.github.wulkanowy.utils.CrashlyticsTree
import io.github.wulkanowy.utils.DebugLogTree import io.github.wulkanowy.utils.DebugLogTree
import io.github.wulkanowy.utils.initCrashlytics import io.github.wulkanowy.utils.initCrashlytics
@ -27,6 +29,9 @@ class WulkanowyApp : DaggerApplication() {
@Inject @Inject
lateinit var workerFactory: SyncWorkerFactory lateinit var workerFactory: SyncWorkerFactory
@Inject
lateinit var appInfo: AppInfo
override fun attachBaseContext(base: Context?) { override fun attachBaseContext(base: Context?) {
super.attachBaseContext(base) super.attachBaseContext(base)
MultiDex.install(this) MultiDex.install(this)
@ -35,15 +40,23 @@ class WulkanowyApp : DaggerApplication() {
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
AndroidThreeTen.init(this) AndroidThreeTen.init(this)
WorkManager.initialize(this, Configuration.Builder().setWorkerFactory(workerFactory).build())
RxJavaPlugins.setErrorHandler(::onError) RxJavaPlugins.setErrorHandler(::onError)
initCrashlytics(applicationContext) initWorkManager()
initLogging() initLogging()
initCrashlytics(this, appInfo)
}
private fun initWorkManager() {
WorkManager.initialize(this,
Configuration.Builder()
.setWorkerFactory(workerFactory)
.setMinimumLoggingLevel(if (appInfo.isDebug) VERBOSE else INFO)
.build())
} }
private fun initLogging() { private fun initLogging() {
if (DEBUG) { if (appInfo.isDebug) {
Timber.plant(DebugLogTree()) Timber.plant(DebugLogTree())
FlexibleAdapter.enableLogs(Log.Level.DEBUG) FlexibleAdapter.enableLogs(Log.Level.DEBUG)
} else { } else {
@ -53,8 +66,10 @@ class WulkanowyApp : DaggerApplication() {
} }
private fun onError(error: Throwable) { private fun onError(error: Throwable) {
if (error is UndeliverableException && error.cause is IOException || error.cause is InterruptedException) { //RxJava's too deep stack traces may cause SOE on older android devices
Timber.e(error.cause, "An undeliverable error occurred") val cause = error.cause
if (error is UndeliverableException && cause is IOException || cause is InterruptedException || cause is StackOverflowError) {
Timber.e(cause, "An undeliverable error occurred")
} else throw error } else throw error
} }

View File

@ -35,10 +35,18 @@ class MobileDeviceRepository @Inject constructor(
} }
fun unregisterDevice(semester: Semester, device: MobileDevice): Single<Boolean> { fun unregisterDevice(semester: Semester, device: MobileDevice): Single<Boolean> {
return remote.unregisterDevice(semester, device) return ReactiveNetwork.checkInternetConnectivity(settings)
.flatMap {
if (it) remote.unregisterDevice(semester, device)
else Single.error(UnknownHostException())
}
} }
fun getToken(semester: Semester): Single<MobileDeviceToken> { fun getToken(semester: Semester): Single<MobileDeviceToken> {
return remote.getToken(semester) return ReactiveNetwork.checkInternetConnectivity(settings)
.flatMap {
if (it) remote.getToken(semester)
else Single.error(UnknownHostException())
}
} }
} }

View File

@ -20,6 +20,9 @@ class PreferencesRepository @Inject constructor(
val gradeAverageMode: String val gradeAverageMode: String
get() = sharedPref.getString(context.getString(R.string.pref_key_grade_average_mode), "only_one_semester") ?: "only_one_semester" get() = sharedPref.getString(context.getString(R.string.pref_key_grade_average_mode), "only_one_semester") ?: "only_one_semester"
val gradeAverageForceCalc: Boolean
get() = sharedPref.getBoolean(context.getString(R.string.pref_key_grade_average_force_calc), false)
val isGradeExpandable: Boolean val isGradeExpandable: Boolean
get() = !sharedPref.getBoolean(context.getString(R.string.pref_key_expand_grade), false) get() = !sharedPref.getBoolean(context.getString(R.string.pref_key_expand_grade), false)

View File

@ -6,10 +6,9 @@ import dagger.Module
import dagger.Provides import dagger.Provides
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.BuildConfig.DEBUG
import io.github.wulkanowy.WulkanowyApp import io.github.wulkanowy.WulkanowyApp
import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.SchedulersProvider import io.github.wulkanowy.utils.SchedulersProvider
import javax.inject.Named
import javax.inject.Singleton import javax.inject.Singleton
@Module @Module
@ -31,7 +30,6 @@ internal class AppModule {
fun provideAppWidgetManager(context: Context): AppWidgetManager = AppWidgetManager.getInstance(context) fun provideAppWidgetManager(context: Context): AppWidgetManager = AppWidgetManager.getInstance(context)
@Singleton @Singleton
@Named("isDebug")
@Provides @Provides
fun provideIsDebug() = DEBUG fun provideAppInfo() = AppInfo()
} }

View File

@ -10,30 +10,46 @@ import androidx.work.NetworkType.CONNECTED
import androidx.work.NetworkType.UNMETERED import androidx.work.NetworkType.UNMETERED
import androidx.work.PeriodicWorkRequest import androidx.work.PeriodicWorkRequest
import androidx.work.WorkManager import androidx.work.WorkManager
import io.github.wulkanowy.data.db.SharedPrefHelper
import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository
import io.github.wulkanowy.services.sync.channels.DebugChannel import io.github.wulkanowy.services.sync.channels.DebugChannel
import io.github.wulkanowy.services.sync.channels.NewEntriesChannel import io.github.wulkanowy.services.sync.channels.NewEntriesChannel
import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.isHolidays import io.github.wulkanowy.utils.isHolidays
import org.threeten.bp.LocalDate.now import org.threeten.bp.LocalDate.now
import timber.log.Timber import timber.log.Timber
import java.util.concurrent.TimeUnit.MINUTES import java.util.concurrent.TimeUnit.MINUTES
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Named
import javax.inject.Singleton import javax.inject.Singleton
@Singleton @Singleton
class SyncManager @Inject constructor( class SyncManager @Inject constructor(
private val workManager: WorkManager, private val workManager: WorkManager,
private val preferencesRepository: PreferencesRepository, private val preferencesRepository: PreferencesRepository,
sharedPrefHelper: SharedPrefHelper,
newEntriesChannel: NewEntriesChannel, newEntriesChannel: NewEntriesChannel,
debugChannel: DebugChannel, debugChannel: DebugChannel,
@Named("isDebug") isDebug: Boolean appInfo: AppInfo
) { ) {
companion object {
private const val APP_VERSION_CODE_KEY = "app_version_code"
}
init { init {
if (SDK_INT >= O) newEntriesChannel.create()
if (SDK_INT >= O && isDebug) debugChannel.create()
if (now().isHolidays) stopSyncWorker() if (now().isHolidays) stopSyncWorker()
if (SDK_INT > O) {
newEntriesChannel.create()
if (appInfo.isDebug) debugChannel.create()
}
if (sharedPrefHelper.getLong(APP_VERSION_CODE_KEY, -1L) != appInfo.versionCode.toLong()) {
startSyncWorker(true)
sharedPrefHelper.putLong(APP_VERSION_CODE_KEY, appInfo.versionCode.toLong(), true)
}
Timber.i("SyncManager was initialized") Timber.i("SyncManager was initialized")
} }

View File

@ -8,10 +8,10 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import com.mikepenz.aboutlibraries.LibsBuilder import com.mikepenz.aboutlibraries.LibsBuilder
import com.mikepenz.aboutlibraries.LibsFragmentCompat import com.mikepenz.aboutlibraries.LibsFragmentCompat
import io.github.wulkanowy.BuildConfig
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.openInternetBrowser import io.github.wulkanowy.utils.openInternetBrowser
import io.github.wulkanowy.utils.withOnExtraListener import io.github.wulkanowy.utils.withOnExtraListener
import javax.inject.Inject import javax.inject.Inject
@ -24,6 +24,9 @@ class AboutFragment : BaseFragment(), AboutView, MainView.TitledView {
@Inject @Inject
lateinit var fragmentCompat: LibsFragmentCompat lateinit var fragmentCompat: LibsFragmentCompat
@Inject
lateinit var appInfo: AppInfo
companion object { companion object {
fun newInstance() = AboutFragment() fun newInstance() = AboutFragment()
} }
@ -71,9 +74,9 @@ class AboutFragment : BaseFragment(), AboutView, MainView.TitledView {
putExtra(Intent.EXTRA_EMAIL, Array(1) { "wulkanowyinc@gmail.com" }) putExtra(Intent.EXTRA_EMAIL, Array(1) { "wulkanowyinc@gmail.com" })
putExtra(Intent.EXTRA_SUBJECT, "Zgłoszenie błędu") putExtra(Intent.EXTRA_SUBJECT, "Zgłoszenie błędu")
putExtra(Intent.EXTRA_TEXT, "Tu umieść treść zgłoszenia\n\n" + "-".repeat(40) + "\n" + """ putExtra(Intent.EXTRA_TEXT, "Tu umieść treść zgłoszenia\n\n" + "-".repeat(40) + "\n" + """
Build: ${BuildConfig.VERSION_CODE} Build: ${appInfo.versionCode}
SDK: ${android.os.Build.VERSION.SDK_INT} SDK: ${appInfo.systemVersion}
Device: ${android.os.Build.MANUFACTURER} ${android.os.Build.MODEL} Device: ${appInfo.systemManufacturer} ${appInfo.systemModel}
""".trimIndent()) """.trimIndent())
} }

View File

@ -67,6 +67,6 @@ class GradeAverageProvider @Inject constructor(
if (it.any { summary -> summary.average != .0 }) { if (it.any { summary -> summary.average != .0 }) {
Maybe.just(it.map { summary -> summary.subject to summary.average }.toMap()) Maybe.just(it.map { summary -> summary.subject to summary.average }.toMap())
} else Maybe.empty() } else Maybe.empty()
} }.filter { !preferencesRepository.gradeAverageForceCalc }
} }
} }

View File

@ -113,7 +113,7 @@ class GradeDetailsPresenter @Inject constructor(
.flatMap { (student, semesters) -> .flatMap { (student, semesters) ->
averageProvider.getGradeAverage(student, semesters, semesterId, forceRefresh) averageProvider.getGradeAverage(student, semesters, semesterId, forceRefresh)
.flatMap { averages -> .flatMap { averages ->
gradeRepository.getGrades(student, semesters.first { semester -> semester.semesterId == semesterId }) gradeRepository.getGrades(student, semesters.first { it.semesterId == semesterId }, forceRefresh)
.map { it.sortedByDescending { grade -> grade.date } } .map { it.sortedByDescending { grade -> grade.date } }
.map { it.groupBy { grade -> grade.subject }.toSortedMap() } .map { it.groupBy { grade -> grade.subject }.toSortedMap() }
.map { createGradeItems(it, averages) } .map { createGradeItems(it, averages) }

View File

@ -10,11 +10,11 @@ import android.view.ViewGroup
import android.view.inputmethod.EditorInfo.IME_ACTION_DONE import android.view.inputmethod.EditorInfo.IME_ACTION_DONE
import android.view.inputmethod.EditorInfo.IME_NULL import android.view.inputmethod.EditorInfo.IME_NULL
import android.widget.ArrayAdapter import android.widget.ArrayAdapter
import io.github.wulkanowy.BuildConfig.VERSION_NAME
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.login.LoginActivity import io.github.wulkanowy.ui.modules.login.LoginActivity
import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.hideSoftInput import io.github.wulkanowy.utils.hideSoftInput
import io.github.wulkanowy.utils.openInternetBrowser import io.github.wulkanowy.utils.openInternetBrowser
import io.github.wulkanowy.utils.setOnItemSelectedListener import io.github.wulkanowy.utils.setOnItemSelectedListener
@ -28,6 +28,9 @@ class LoginFormFragment : BaseFragment(), LoginFormView {
@Inject @Inject
lateinit var presenter: LoginFormPresenter lateinit var presenter: LoginFormPresenter
@Inject
lateinit var appInfo: AppInfo
companion object { companion object {
fun newInstance() = LoginFormFragment() fun newInstance() = LoginFormFragment()
} }
@ -128,7 +131,7 @@ class LoginFormFragment : BaseFragment(), LoginFormView {
override fun showVersion() { override fun showVersion() {
loginFormVersion.apply { loginFormVersion.apply {
visibility = VISIBLE visibility = VISIBLE
text = "${getString(R.string.app_name)} $VERSION_NAME" text = "${getString(R.string.app_name)} ${appInfo.versionName}"
} }
} }

View File

@ -3,25 +3,26 @@ package io.github.wulkanowy.ui.modules.login.form
import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.data.repositories.student.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.modules.login.LoginErrorHandler import io.github.wulkanowy.ui.modules.login.LoginErrorHandler
import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.SchedulersProvider import io.github.wulkanowy.utils.SchedulersProvider
import io.github.wulkanowy.utils.ifNullOrBlank
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Named
class LoginFormPresenter @Inject constructor( class LoginFormPresenter @Inject constructor(
schedulers: SchedulersProvider, schedulers: SchedulersProvider,
studentRepository: StudentRepository, studentRepository: StudentRepository,
private val loginErrorHandler: LoginErrorHandler, private val loginErrorHandler: LoginErrorHandler,
private val analytics: FirebaseAnalyticsHelper, private val analytics: FirebaseAnalyticsHelper,
@param:Named("isDebug") private val isDebug: Boolean private val appInfo: AppInfo
) : BasePresenter<LoginFormView>(loginErrorHandler, studentRepository, schedulers) { ) : BasePresenter<LoginFormView>(loginErrorHandler, studentRepository, schedulers) {
override fun onAttachView(view: LoginFormView) { override fun onAttachView(view: LoginFormView) {
super.onAttachView(view) super.onAttachView(view)
view.run { view.run {
initView() initView()
if (isDebug) showVersion() else showPrivacyPolicy() if (appInfo.isDebug) showVersion() else showPrivacyPolicy()
loginErrorHandler.onBadCredentials = { loginErrorHandler.onBadCredentials = {
setErrorPassIncorrect() setErrorPassIncorrect()
@ -81,7 +82,7 @@ class LoginFormPresenter @Inject constructor(
view?.notifyParentAccountLogged(it) view?.notifyParentAccountLogged(it)
}, { }, {
Timber.i("Login result: An exception occurred") Timber.i("Login result: An exception occurred")
analytics.logEvent("registration_form", "success" to false, "students" to -1, "endpoint" to endpoint, "error" to it.localizedMessage.ifEmpty { "No message" }) analytics.logEvent("registration_form", "success" to false, "students" to -1, "endpoint" to endpoint, "error" to it.message.ifNullOrBlank { "No message" })
loginErrorHandler.dispatch(it) loginErrorHandler.dispatch(it)
})) }))
} }

View File

@ -7,6 +7,7 @@ import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.modules.login.LoginErrorHandler import io.github.wulkanowy.ui.modules.login.LoginErrorHandler
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.SchedulersProvider import io.github.wulkanowy.utils.SchedulersProvider
import io.github.wulkanowy.utils.ifNullOrBlank
import timber.log.Timber import timber.log.Timber
import java.io.Serializable import java.io.Serializable
import javax.inject.Inject import javax.inject.Inject
@ -81,7 +82,7 @@ class LoginStudentSelectPresenter @Inject constructor(
Timber.i("Registration result: Success") Timber.i("Registration result: Success")
view?.openMainView() view?.openMainView()
}, { error -> }, { error ->
students.forEach { analytics.logEvent("registration_student_select", "success" to false, "endpoint" to it.endpoint, "symbol" to it.symbol, "error" to error.localizedMessage.ifEmpty { "No message" }) } students.forEach { analytics.logEvent("registration_student_select", "success" to false, "endpoint" to it.endpoint, "symbol" to it.symbol, "error" to error.message.ifNullOrBlank { "No message" }) }
Timber.i("Registration result: An exception occurred ") Timber.i("Registration result: An exception occurred ")
loginErrorHandler.dispatch(error) loginErrorHandler.dispatch(error)
view?.apply { view?.apply {

View File

@ -5,6 +5,7 @@ import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.modules.login.LoginErrorHandler import io.github.wulkanowy.ui.modules.login.LoginErrorHandler
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.SchedulersProvider import io.github.wulkanowy.utils.SchedulersProvider
import io.github.wulkanowy.utils.ifNullOrBlank
import io.reactivex.Single import io.reactivex.Single
import timber.log.Timber import timber.log.Timber
import java.io.Serializable import java.io.Serializable
@ -70,7 +71,7 @@ class LoginSymbolPresenter @Inject constructor(
} }
}, { }, {
Timber.i("Login with symbol result: An exception occurred") Timber.i("Login with symbol result: An exception occurred")
analytics.logEvent("registration_symbol", "success" to false, "students" to -1, "endpoint" to loginData?.third, "symbol" to symbol, "error" to it.localizedMessage.ifEmpty { "No message" }) analytics.logEvent("registration_symbol", "success" to false, "students" to -1, "endpoint" to loginData?.third, "symbol" to symbol, "error" to it.message.ifNullOrBlank { "No message" })
loginErrorHandler.dispatch(it) loginErrorHandler.dispatch(it)
})) }))
} }

View File

@ -6,5 +6,5 @@ import io.github.wulkanowy.data.db.entities.MobileDevice
class MobileDeviceAdapter<T : IFlexible<*>> : FlexibleAdapter<T>(null, null, true) { class MobileDeviceAdapter<T : IFlexible<*>> : FlexibleAdapter<T>(null, null, true) {
var onDeviceUnregisterListener: (MobileDevice, position: Int) -> Unit = { _, _ -> } var onDeviceUnregisterListener: (device: MobileDevice, position: Int) -> Unit = { _, _ -> }
} }

View File

@ -8,10 +8,10 @@ import android.view.View.VISIBLE
import android.view.ViewGroup import android.view.ViewGroup
import eu.davidea.flexibleadapter.common.FlexibleItemDecoration import eu.davidea.flexibleadapter.common.FlexibleItemDecoration
import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
import eu.davidea.flexibleadapter.helpers.EmptyViewHelper
import eu.davidea.flexibleadapter.helpers.UndoHelper import eu.davidea.flexibleadapter.helpers.UndoHelper
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.MobileDevice
import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.ui.modules.main.MainView
@ -48,7 +48,7 @@ class MobileDeviceFragment : BaseFragment(), MobileDeviceView, MainView.TitledVi
} }
override fun initView() { override fun initView() {
mobileDevicesRecycler.run { with(mobileDevicesRecycler) {
layoutManager = SmoothScrollLinearLayoutManager(context) layoutManager = SmoothScrollLinearLayoutManager(context)
adapter = devicesAdapter adapter = devicesAdapter
addItemDecoration(FlexibleItemDecoration(context) addItemDecoration(FlexibleItemDecoration(context)
@ -56,37 +56,43 @@ class MobileDeviceFragment : BaseFragment(), MobileDeviceView, MainView.TitledVi
.withDrawDividerOnLastItem(false) .withDrawDividerOnLastItem(false)
) )
} }
EmptyViewHelper.create(devicesAdapter, mobileDevicesEmpty) with(devicesAdapter) {
isPermanentDelete = false
onDeviceUnregisterListener = presenter::onUnregisterDevice
}
mobileDevicesSwipe.setOnRefreshListener { presenter.onSwipeRefresh() } mobileDevicesSwipe.setOnRefreshListener { presenter.onSwipeRefresh() }
mobileDeviceAddButton.setOnClickListener { presenter.onRegisterDevice() } mobileDeviceAddButton.setOnClickListener { presenter.onRegisterDevice() }
devicesAdapter.run {
isPermanentDelete = false
onDeviceUnregisterListener = { device, position ->
val onActionListener = object : UndoHelper.OnActionListener {
override fun onActionConfirmed(action: Int, event: Int) {
presenter.onUnregister(device)
}
override fun onActionCanceled(action: Int, positions: MutableList<Int>?) {
devicesAdapter.restoreDeletedItems()
}
}
UndoHelper(devicesAdapter, onActionListener)
.withConsecutive(false)
.withAction(UndoHelper.Action.REMOVE)
.start(listOf(position), mobileDevicesRecycler, R.string.mobile_device_removed, R.string.all_undo, 3000)
}
}
} }
override fun updateData(data: List<MobileDeviceItem>) { override fun updateData(data: List<MobileDeviceItem>) {
devicesAdapter.updateDataSet(data) devicesAdapter.updateDataSet(data)
} }
override fun restoreDeleteItem() {
devicesAdapter.restoreDeletedItems()
}
override fun clearData() { override fun clearData() {
devicesAdapter.clear() devicesAdapter.clear()
} }
override fun showUndo(position: Int, device: MobileDevice) {
val onActionListener = object : UndoHelper.OnActionListener {
override fun onActionConfirmed(action: Int, event: Int) {
presenter.onUnregisterConfirmed(device)
}
override fun onActionCanceled(action: Int, positions: MutableList<Int>?) {
presenter.onUnregisterCancelled()
}
}
UndoHelper(devicesAdapter, onActionListener)
.withConsecutive(false)
.withAction(UndoHelper.Action.REMOVE)
.start(listOf(position), mobileDevicesRecycler, R.string.mobile_device_removed, R.string.all_undo, 3000)
}
override fun hideRefresh() { override fun hideRefresh() {
mobileDevicesSwipe.isRefreshing = false mobileDevicesSwipe.isRefreshing = false
} }
@ -95,6 +101,10 @@ class MobileDeviceFragment : BaseFragment(), MobileDeviceView, MainView.TitledVi
mobileDevicesProgress.visibility = if (show) VISIBLE else GONE mobileDevicesProgress.visibility = if (show) VISIBLE else GONE
} }
override fun showEmpty(show: Boolean) {
mobileDevicesEmpty.visibility = if (show) VISIBLE else GONE
}
override fun enableSwipe(enable: Boolean) { override fun enableSwipe(enable: Boolean) {
mobileDevicesSwipe.isEnabled = enable mobileDevicesSwipe.isEnabled = enable
} }

View File

@ -50,6 +50,7 @@ class MobileDevicePresenter @Inject constructor(
view?.run { view?.run {
updateData(it) updateData(it)
showContent(it.isNotEmpty()) showContent(it.isNotEmpty())
showEmpty(it.isEmpty())
} }
analytics.logEvent("load_devices", "items" to it.size, "force_refresh" to forceRefresh) analytics.logEvent("load_devices", "items" to it.size, "force_refresh" to forceRefresh)
}) { }) {
@ -62,13 +63,27 @@ class MobileDevicePresenter @Inject constructor(
view?.showTokenDialog() view?.showTokenDialog()
} }
fun onUnregister(device: MobileDevice) { fun onUnregisterDevice(device: MobileDevice, position: Int) {
view?.run {
showUndo(position, device)
showEmpty(isViewEmpty)
}
}
fun onUnregisterCancelled() {
view?.run {
restoreDeleteItem()
showEmpty(isViewEmpty)
}
}
fun onUnregisterConfirmed(device: MobileDevice) {
Timber.i("Unregister device started") Timber.i("Unregister device started")
disposable.add(studentRepository.getCurrentStudent() disposable.add(studentRepository.getCurrentStudent()
.flatMap { semesterRepository.getCurrentSemester(it) } .flatMap { semesterRepository.getCurrentSemester(it) }
.flatMap { semester -> .flatMap { semester ->
mobileDeviceRepository.unregisterDevice(semester, device) mobileDeviceRepository.unregisterDevice(semester, device)
.flatMap { mobileDeviceRepository.getDevices(semester, it) } .flatMap { mobileDeviceRepository.getDevices(semester, it) }
} }
.map { items -> items.map { MobileDeviceItem(it) } } .map { items -> items.map { MobileDeviceItem(it) } }
.subscribeOn(schedulers.backgroundThread) .subscribeOn(schedulers.backgroundThread)
@ -84,6 +99,7 @@ class MobileDevicePresenter @Inject constructor(
view?.run { view?.run {
updateData(it) updateData(it)
showContent(it.isNotEmpty()) showContent(it.isNotEmpty())
showEmpty(it.isEmpty())
} }
}) { }) {
Timber.i("Unregister device result: An exception occurred") Timber.i("Unregister device result: An exception occurred")

View File

@ -1,5 +1,6 @@
package io.github.wulkanowy.ui.modules.mobiledevice package io.github.wulkanowy.ui.modules.mobiledevice
import io.github.wulkanowy.data.db.entities.MobileDevice
import io.github.wulkanowy.ui.base.BaseView import io.github.wulkanowy.ui.base.BaseView
interface MobileDeviceView : BaseView { interface MobileDeviceView : BaseView {
@ -10,6 +11,8 @@ interface MobileDeviceView : BaseView {
fun updateData(data: List<MobileDeviceItem>) fun updateData(data: List<MobileDeviceItem>)
fun restoreDeleteItem()
fun hideRefresh() fun hideRefresh()
fun clearData() fun clearData()
@ -20,5 +23,9 @@ interface MobileDeviceView : BaseView {
fun showContent(show: Boolean) fun showContent(show: Boolean)
fun showEmpty(show: Boolean)
fun showUndo(position: Int, device: MobileDevice)
fun showTokenDialog() fun showTokenDialog()
} }

View File

@ -5,10 +5,10 @@ import android.content.SharedPreferences
import android.os.Bundle import android.os.Bundle
import com.takisoft.preferencex.PreferenceFragmentCompat import com.takisoft.preferencex.PreferenceFragmentCompat
import dagger.android.support.AndroidSupportInjection import dagger.android.support.AndroidSupportInjection
import io.github.wulkanowy.BuildConfig.DEBUG
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.ui.base.BaseActivity import io.github.wulkanowy.ui.base.BaseActivity
import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.AppInfo
import javax.inject.Inject import javax.inject.Inject
class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedPreferenceChangeListener, class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedPreferenceChangeListener,
@ -17,6 +17,9 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP
@Inject @Inject
lateinit var presenter: SettingsPresenter lateinit var presenter: SettingsPresenter
@Inject
lateinit var appInfo: AppInfo
companion object { companion object {
fun newInstance() = SettingsFragment() fun newInstance() = SettingsFragment()
} }
@ -36,7 +39,7 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP
override fun onCreatePreferencesFix(savedInstanceState: Bundle?, rootKey: String?) { override fun onCreatePreferencesFix(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.scheme_preferences) addPreferencesFromResource(R.xml.scheme_preferences)
findPreference(getString(R.string.pref_key_notification_debug)).isVisible = DEBUG findPreference(getString(R.string.pref_key_notification_debug)).isVisible = appInfo.isDebug
} }
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) { override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {

View File

@ -33,7 +33,7 @@ class SettingsPresenter @Inject constructor(
Timber.i("Change settings $key") Timber.i("Change settings $key")
preferencesRepository.apply { preferencesRepository.apply {
when (key) { when (key) {
serviceEnableKey -> syncManager.run { if (isServiceEnabled) startSyncWorker() else stopSyncWorker() } serviceEnableKey -> with(syncManager) { if (isServiceEnabled) startSyncWorker() else stopSyncWorker() }
servicesIntervalKey, servicesOnlyWifiKey -> syncManager.startSyncWorker(true) servicesIntervalKey, servicesOnlyWifiKey -> syncManager.startSyncWorker(true)
isDebugNotificationEnableKey -> chuckCollector.showNotification(isDebugNotificationEnable) isDebugNotificationEnableKey -> chuckCollector.showNotification(isDebugNotificationEnable)
appThemeKey -> view?.recreateView() appThemeKey -> view?.recreateView()

View File

@ -0,0 +1,28 @@
package io.github.wulkanowy.utils
import android.os.Build.MANUFACTURER
import android.os.Build.MODEL
import android.os.Build.VERSION.SDK_INT
import io.github.wulkanowy.BuildConfig.CRASHLYTICS_ENABLED
import io.github.wulkanowy.BuildConfig.DEBUG
import io.github.wulkanowy.BuildConfig.VERSION_CODE
import io.github.wulkanowy.BuildConfig.VERSION_NAME
import javax.inject.Singleton
@Singleton
open class AppInfo {
open val isCrashlyticsEnabled get() = CRASHLYTICS_ENABLED
open val isDebug get() = DEBUG
open val versionCode get() = VERSION_CODE
open val versionName get() = VERSION_NAME
open val systemVersion get() = SDK_INT
open val systemManufacturer: String get() = MANUFACTURER
open val systemModel: String get() = MODEL
}

View File

@ -0,0 +1,3 @@
package io.github.wulkanowy.utils
inline fun String?.ifNullOrBlank(defaultValue: () -> String) = if (this.isNullOrBlank()) defaultValue() else this

View File

@ -1,14 +1,8 @@
Wersja 0.9.0 Wersja 0.9.3
Dodaliśmy:
- zarządzanie dostępem mobilnym do dziennika
- wyświetlanie sumy punktów (jeśli dziennik obsługuje)
- wyświetlanie informacji o wygasłej sesji, gdy zostanie zmienione hasło
Naprawiliśmy: Naprawiliśmy:
- wykrywanie uczniów przy logowaniu, jeśli symbol zawiera cyfry - problemy z logowaniem jeśli symbol zawierał cyfry
- błędne wyświetlanie średniej - bardzo rzadkie problemy z ładowaniem ocen
- synchronizację w tle na danych komórkowych przy domyślnych ustawieniach - jeszcze rzadsze problemy ze stabilnością podczas logowania
- rzadkie błędy podczas ładowania szczęśliwego numerka
Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases

View File

@ -20,7 +20,7 @@
android:gravity="center" android:gravity="center"
android:orientation="vertical" android:orientation="vertical"
android:padding="10dp" android:padding="10dp"
android:alpha="0.0"> android:visibility="gone">
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"

View File

@ -253,6 +253,7 @@
<string name="pref_view_header">Wygląd</string> <string name="pref_view_header">Wygląd</string>
<string name="pref_view_list">Domyślny widok</string> <string name="pref_view_list">Domyślny widok</string>
<string name="pref_view_grade_average_mode">Obliczanie średniej końcoworocznej</string> <string name="pref_view_grade_average_mode">Obliczanie średniej końcoworocznej</string>
<string name="pref_view_grade_average_force_calc">Wymuś obliczanie średniej przez aplikację</string>
<string name="pref_view_present">Pokazuj obecność we frekwencji</string> <string name="pref_view_present">Pokazuj obecność we frekwencji</string>
<string name="pref_view_app_theme">Motyw aplikacji</string> <string name="pref_view_app_theme">Motyw aplikacji</string>
<string name="pref_view_expand_grade">Rozwiń oceny</string> <string name="pref_view_expand_grade">Rozwiń oceny</string>

View File

@ -7,7 +7,7 @@
<item>2 godziny</item> <item>2 godziny</item>
<item>6 godzin</item> <item>6 godzin</item>
<item>12 godzin</item> <item>12 godzin</item>
<item>24 godzin</item> <item>24 godziny</item>
</string-array> </string-array>
<string-array name="app_theme_entries"> <string-array name="app_theme_entries">

View File

@ -6,6 +6,7 @@
<string name="pref_key_grade_color_scheme">grade_color_scheme</string> <string name="pref_key_grade_color_scheme">grade_color_scheme</string>
<string name="pref_key_expand_grade">expand_grade</string> <string name="pref_key_expand_grade">expand_grade</string>
<string name="pref_key_grade_average_mode">grade_average_mode</string> <string name="pref_key_grade_average_mode">grade_average_mode</string>
<string name="pref_key_grade_average_force_calc">grade_average_always_calc</string>
<string name="pref_key_services_enable">services_enable</string> <string name="pref_key_services_enable">services_enable</string>
<string name="pref_key_services_interval">services_interval</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_services_wifi_only">services_disable_wifi_only</string>

View File

@ -237,6 +237,7 @@
<string name="pref_view_header">Appearance</string> <string name="pref_view_header">Appearance</string>
<string name="pref_view_list">Default view</string> <string name="pref_view_list">Default view</string>
<string name="pref_view_grade_average_mode">Calculation of the end-of-year average</string> <string name="pref_view_grade_average_mode">Calculation of the end-of-year average</string>
<string name="pref_view_grade_average_force_calc">Force average calculation by app</string>
<string name="pref_view_present">Show presence in attendance</string> <string name="pref_view_present">Show presence in attendance</string>
<string name="pref_view_app_theme">Application theme</string> <string name="pref_view_app_theme">Application theme</string>
<string name="pref_view_expand_grade">Expand grades</string> <string name="pref_view_expand_grade">Expand grades</string>

View File

@ -105,6 +105,11 @@
android:summary="%s" android:summary="%s"
android:title="@string/pref_view_grade_average_mode" android:title="@string/pref_view_grade_average_mode"
app:iconSpaceReserved="false" /> app:iconSpaceReserved="false" />
<SwitchPreference
android:defaultValue="false"
android:key="@string/pref_key_grade_average_force_calc"
android:title="@string/pref_view_grade_average_force_calc"
app:iconSpaceReserved="false" />
<SwitchPreference <SwitchPreference
android:defaultValue="true" android:defaultValue="true"
android:key="@string/pref_key_fill_message_content" android:key="@string/pref_key_fill_message_content"

View File

@ -4,9 +4,21 @@ import android.content.Context
import com.crashlytics.android.Crashlytics import com.crashlytics.android.Crashlytics
import com.crashlytics.android.core.CrashlyticsCore import com.crashlytics.android.core.CrashlyticsCore
import io.fabric.sdk.android.Fabric import io.fabric.sdk.android.Fabric
import io.github.wulkanowy.BuildConfig
import timber.log.Timber import timber.log.Timber
fun initCrashlytics(context: Context, appInfo: AppInfo) {
Fabric.with(Fabric.Builder(context)
.kits(
Crashlytics.Builder()
.core(CrashlyticsCore.Builder()
.disabled(!appInfo.isCrashlyticsEnabled)
.build())
.build()
)
.debuggable(appInfo.isDebug)
.build())
}
class CrashlyticsTree : Timber.Tree() { class CrashlyticsTree : Timber.Tree() {
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) { override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
@ -17,9 +29,3 @@ class CrashlyticsTree : Timber.Tree() {
else Crashlytics.logException(t) else Crashlytics.logException(t)
} }
} }
fun initCrashlytics(context: Context) {
Fabric.with(Fabric.Builder(context).kits(
Crashlytics.Builder().core(CrashlyticsCore.Builder().disabled(!BuildConfig.CRASHLYTICS_ENABLED).build()).build()
).debuggable(BuildConfig.DEBUG).build())
}

View File

@ -59,6 +59,7 @@ class GradeAverageProviderTest {
doReturn(.33).`when`(preferencesRepository).gradeMinusModifier doReturn(.33).`when`(preferencesRepository).gradeMinusModifier
doReturn(.33).`when`(preferencesRepository).gradePlusModifier doReturn(.33).`when`(preferencesRepository).gradePlusModifier
doReturn(false).`when`(preferencesRepository).gradeAverageForceCalc
doReturn(Single.just(firstGrades)).`when`(gradeRepository).getGrades(student, semesters[1], true) doReturn(Single.just(firstGrades)).`when`(gradeRepository).getGrades(student, semesters[1], true)
doReturn(Single.just(secondGrade)).`when`(gradeRepository).getGrades(student, semesters[2], true) doReturn(Single.just(secondGrade)).`when`(gradeRepository).getGrades(student, semesters[2], true)
@ -128,6 +129,24 @@ class GradeAverageProviderTest {
assertEquals(3.26, averages["Fizyka"]) assertEquals(3.26, averages["Fizyka"])
} }
@Test
fun onlyOneSemester_averageFromSummary_forceCalc() {
doReturn(true).`when`(preferencesRepository).gradeAverageForceCalc
doReturn("all_year").`when`(preferencesRepository).gradeAverageMode
doReturn(Single.just(firstGrades)).`when`(gradeRepository).getGrades(student, semesters[1], false)
doReturn(Single.just(listOf(
getSummary(22, "Matematyka", 3.1),
getSummary(22, "Fizyka", 3.26)
))).`when`(gradeSummaryRepository).getGradesSummary(semesters[2], true)
val averages = gradeAverageProvider.getGradeAverage(student, semesters, semesters[2].semesterId, true)
.blockingGet()
assertEquals(2, averages.size)
assertEquals(3.0, averages["Matematyka"])
assertEquals(3.25, averages["Fizyka"])
}
private fun getGrade(semesterId: Int, subject: String, value: Int): Grade { private fun getGrade(semesterId: Int, subject: String, value: Int): Grade {
return Grade( return Grade(
studentId = 101, studentId = 101,

View File

@ -4,6 +4,7 @@ import io.github.wulkanowy.TestSchedulersProvider
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.data.repositories.student.StudentRepository
import io.github.wulkanowy.ui.modules.login.LoginErrorHandler import io.github.wulkanowy.ui.modules.login.LoginErrorHandler
import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.reactivex.Single import io.reactivex.Single
import org.junit.Before import org.junit.Before
@ -33,13 +34,16 @@ class LoginFormPresenterTest {
@Mock @Mock
lateinit var analytics: FirebaseAnalyticsHelper lateinit var analytics: FirebaseAnalyticsHelper
@Mock
lateinit var appInfo: AppInfo
private lateinit var presenter: LoginFormPresenter private lateinit var presenter: LoginFormPresenter
@Before @Before
fun initPresenter() { fun initPresenter() {
MockitoAnnotations.initMocks(this) MockitoAnnotations.initMocks(this)
clearInvocations(repository, loginFormView) clearInvocations(repository, loginFormView)
presenter = LoginFormPresenter(TestSchedulersProvider(), repository, errorHandler, analytics, false) presenter = LoginFormPresenter(TestSchedulersProvider(), repository, errorHandler, analytics, appInfo)
presenter.onAttachView(loginFormView) presenter.onAttachView(loginFormView)
} }

View File

@ -14,6 +14,7 @@ buildscript {
classpath "io.fabric.tools:gradle:1.29.0" classpath "io.fabric.tools:gradle:1.29.0"
classpath "com.github.triplet.gradle:play-publisher:2.2.1" classpath "com.github.triplet.gradle:play-publisher:2.2.1"
classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.7.1" classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.7.1"
classpath "gradle.plugin.com.star-zero.gradle:githook:1.1.0"
} }
} }