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/wrapper/
#branches:
# only:
# - develop
branches:
only:
- develop
- 0.9.2
android:
licenses:

View File

@ -6,6 +6,7 @@ apply plugin: 'io.fabric'
apply plugin: 'com.github.triplet.play'
apply from: 'jacoco.gradle'
apply from: 'sonarqube.gradle'
apply from: 'hooks.gradle'
android {
compileSdkVersion 28
@ -16,8 +17,8 @@ android {
testApplicationId "io.github.tests.wulkanowy"
minSdkVersion 15
targetSdkVersion 28
versionCode 38
versionName "0.9.0"
versionCode 41
versionName "0.9.3"
multiDexEnabled true
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
@ -97,74 +98,79 @@ play {
}
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 "androidx.legacy:legacy-support-v4:1.0.0"
implementation "androidx.core:core: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.constraintlayout:constraintlayout:1.1.3'
implementation "androidx.recyclerview:recyclerview:1.0.0"
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.github.wulkanowy:MaterialChipsInput:b72fd0ee6f'
implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0'
implementation "com.github.wulkanowy:MaterialChipsInput:b72fd0ee6f"
implementation "com.github.PhilJay:MPAndroidChart:v3.1.0"
implementation "androidx.work:work-runtime:2.0.1"
implementation "androidx.work:work-rxjava2:2.0.1"
implementation "androidx.room:room-runtime:2.1.0-rc01"
implementation "androidx.room:room-rxjava2:2.1.0-rc01"
kapt "androidx.room:room-compiler:2.1.0-rc01"
implementation "androidx.room:room-runtime:2.1.0"
implementation "androidx.room:room-rxjava2:2.1.0"
kapt "androidx.room:room-compiler:2.1.0"
implementation "com.google.dagger:dagger-android-support:2.23.1"
kapt "com.google.dagger:dagger-compiler:2.23.1"
kapt "com.google.dagger:dagger-android-processor:2.23.1"
implementation 'com.squareup.inject:assisted-inject-annotations-dagger2:0.4.0'
kapt 'com.squareup.inject:assisted-inject-processor-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"
implementation "eu.davidea:flexible-adapter:5.1.0"
implementation "eu.davidea:flexible-adapter-ui:1.0.0"
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 'io.reactivex.rxjava2:rxandroid:2.1.1'
implementation "com.github.pwittchen:reactivenetwork-rx2:3.0.3"
implementation "io.reactivex.rxjava2:rxandroid:2.1.1"
implementation "io.reactivex.rxjava2:rxjava:2.2.9"
implementation 'com.google.code.gson:gson:2.8.5'
implementation "com.jakewharton.threetenabp:threetenabp:1.2.0"
implementation "com.google.code.gson:gson:2.8.5"
implementation "com.jakewharton.threetenabp:threetenabp:1.2.1"
implementation "com.jakewharton.timber:timber:4.7.1"
implementation "at.favre.lib:slf4j-timber:1.0.1"
implementation "com.squareup.okhttp3:logging-interceptor:3.12.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.crashlytics.sdk.android:crashlytics:2.10.1'
playImplementation "com.google.firebase:firebase-core:16.0.9"
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"
testImplementation "junit:junit:4.12"
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-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:runner:1.2.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation "androidx.test:core:1.2.0"
androidTestImplementation "androidx.test:runner:1.2.0"
androidTestImplementation "androidx.test.ext:junit:1.1.1"
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.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'
}
}

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
-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 timber.log.Timber
fun initCrashlytics(context: Context, appInfo: AppInfo) {
// do nothing
}
class CrashlyticsTree : Timber.Tree() {
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
// do nothing
}
}
fun initCrashlytics(context: Context) {
// do nothing
}

View File

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

View File

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

View File

@ -35,10 +35,18 @@ class MobileDeviceRepository @Inject constructor(
}
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> {
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
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
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 eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.BuildConfig.DEBUG
import io.github.wulkanowy.WulkanowyApp
import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.SchedulersProvider
import javax.inject.Named
import javax.inject.Singleton
@Module
@ -31,7 +30,6 @@ internal class AppModule {
fun provideAppWidgetManager(context: Context): AppWidgetManager = AppWidgetManager.getInstance(context)
@Singleton
@Named("isDebug")
@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.PeriodicWorkRequest
import androidx.work.WorkManager
import io.github.wulkanowy.data.db.SharedPrefHelper
import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository
import io.github.wulkanowy.services.sync.channels.DebugChannel
import io.github.wulkanowy.services.sync.channels.NewEntriesChannel
import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.isHolidays
import org.threeten.bp.LocalDate.now
import timber.log.Timber
import java.util.concurrent.TimeUnit.MINUTES
import javax.inject.Inject
import javax.inject.Named
import javax.inject.Singleton
@Singleton
class SyncManager @Inject constructor(
private val workManager: WorkManager,
private val preferencesRepository: PreferencesRepository,
sharedPrefHelper: SharedPrefHelper,
newEntriesChannel: NewEntriesChannel,
debugChannel: DebugChannel,
@Named("isDebug") isDebug: Boolean
appInfo: AppInfo
) {
companion object {
private const val APP_VERSION_CODE_KEY = "app_version_code"
}
init {
if (SDK_INT >= O) newEntriesChannel.create()
if (SDK_INT >= O && isDebug) debugChannel.create()
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")
}

View File

@ -8,10 +8,10 @@ import android.view.View
import android.view.ViewGroup
import com.mikepenz.aboutlibraries.LibsBuilder
import com.mikepenz.aboutlibraries.LibsFragmentCompat
import io.github.wulkanowy.BuildConfig
import io.github.wulkanowy.R
import io.github.wulkanowy.ui.base.BaseFragment
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.withOnExtraListener
import javax.inject.Inject
@ -24,6 +24,9 @@ class AboutFragment : BaseFragment(), AboutView, MainView.TitledView {
@Inject
lateinit var fragmentCompat: LibsFragmentCompat
@Inject
lateinit var appInfo: AppInfo
companion object {
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_SUBJECT, "Zgłoszenie błędu")
putExtra(Intent.EXTRA_TEXT, "Tu umieść treść zgłoszenia\n\n" + "-".repeat(40) + "\n" + """
Build: ${BuildConfig.VERSION_CODE}
SDK: ${android.os.Build.VERSION.SDK_INT}
Device: ${android.os.Build.MANUFACTURER} ${android.os.Build.MODEL}
Build: ${appInfo.versionCode}
SDK: ${appInfo.systemVersion}
Device: ${appInfo.systemManufacturer} ${appInfo.systemModel}
""".trimIndent())
}

View File

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

View File

@ -113,7 +113,7 @@ class GradeDetailsPresenter @Inject constructor(
.flatMap { (student, semesters) ->
averageProvider.getGradeAverage(student, semesters, semesterId, forceRefresh)
.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.groupBy { grade -> grade.subject }.toSortedMap() }
.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_NULL
import android.widget.ArrayAdapter
import io.github.wulkanowy.BuildConfig.VERSION_NAME
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.ui.base.BaseFragment
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.openInternetBrowser
import io.github.wulkanowy.utils.setOnItemSelectedListener
@ -28,6 +28,9 @@ class LoginFormFragment : BaseFragment(), LoginFormView {
@Inject
lateinit var presenter: LoginFormPresenter
@Inject
lateinit var appInfo: AppInfo
companion object {
fun newInstance() = LoginFormFragment()
}
@ -128,7 +131,7 @@ class LoginFormFragment : BaseFragment(), LoginFormView {
override fun showVersion() {
loginFormVersion.apply {
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.ui.base.BasePresenter
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.SchedulersProvider
import io.github.wulkanowy.utils.ifNullOrBlank
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Named
class LoginFormPresenter @Inject constructor(
schedulers: SchedulersProvider,
studentRepository: StudentRepository,
private val loginErrorHandler: LoginErrorHandler,
private val analytics: FirebaseAnalyticsHelper,
@param:Named("isDebug") private val isDebug: Boolean
private val appInfo: AppInfo
) : BasePresenter<LoginFormView>(loginErrorHandler, studentRepository, schedulers) {
override fun onAttachView(view: LoginFormView) {
super.onAttachView(view)
view.run {
initView()
if (isDebug) showVersion() else showPrivacyPolicy()
if (appInfo.isDebug) showVersion() else showPrivacyPolicy()
loginErrorHandler.onBadCredentials = {
setErrorPassIncorrect()
@ -81,7 +82,7 @@ class LoginFormPresenter @Inject constructor(
view?.notifyParentAccountLogged(it)
}, {
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)
}))
}

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.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.SchedulersProvider
import io.github.wulkanowy.utils.ifNullOrBlank
import timber.log.Timber
import java.io.Serializable
import javax.inject.Inject
@ -81,7 +82,7 @@ class LoginStudentSelectPresenter @Inject constructor(
Timber.i("Registration result: Success")
view?.openMainView()
}, { 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 ")
loginErrorHandler.dispatch(error)
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.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.SchedulersProvider
import io.github.wulkanowy.utils.ifNullOrBlank
import io.reactivex.Single
import timber.log.Timber
import java.io.Serializable
@ -70,7 +71,7 @@ class LoginSymbolPresenter @Inject constructor(
}
}, {
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)
}))
}

View File

@ -6,5 +6,5 @@ import io.github.wulkanowy.data.db.entities.MobileDevice
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 eu.davidea.flexibleadapter.common.FlexibleItemDecoration
import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
import eu.davidea.flexibleadapter.helpers.EmptyViewHelper
import eu.davidea.flexibleadapter.helpers.UndoHelper
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
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.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.main.MainView
@ -48,7 +48,7 @@ class MobileDeviceFragment : BaseFragment(), MobileDeviceView, MainView.TitledVi
}
override fun initView() {
mobileDevicesRecycler.run {
with(mobileDevicesRecycler) {
layoutManager = SmoothScrollLinearLayoutManager(context)
adapter = devicesAdapter
addItemDecoration(FlexibleItemDecoration(context)
@ -56,37 +56,43 @@ class MobileDeviceFragment : BaseFragment(), MobileDeviceView, MainView.TitledVi
.withDrawDividerOnLastItem(false)
)
}
EmptyViewHelper.create(devicesAdapter, mobileDevicesEmpty)
with(devicesAdapter) {
isPermanentDelete = false
onDeviceUnregisterListener = presenter::onUnregisterDevice
}
mobileDevicesSwipe.setOnRefreshListener { presenter.onSwipeRefresh() }
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>) {
devicesAdapter.updateDataSet(data)
}
override fun restoreDeleteItem() {
devicesAdapter.restoreDeletedItems()
}
override fun clearData() {
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() {
mobileDevicesSwipe.isRefreshing = false
}
@ -95,6 +101,10 @@ class MobileDeviceFragment : BaseFragment(), MobileDeviceView, MainView.TitledVi
mobileDevicesProgress.visibility = if (show) VISIBLE else GONE
}
override fun showEmpty(show: Boolean) {
mobileDevicesEmpty.visibility = if (show) VISIBLE else GONE
}
override fun enableSwipe(enable: Boolean) {
mobileDevicesSwipe.isEnabled = enable
}

View File

@ -50,6 +50,7 @@ class MobileDevicePresenter @Inject constructor(
view?.run {
updateData(it)
showContent(it.isNotEmpty())
showEmpty(it.isEmpty())
}
analytics.logEvent("load_devices", "items" to it.size, "force_refresh" to forceRefresh)
}) {
@ -62,13 +63,27 @@ class MobileDevicePresenter @Inject constructor(
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")
disposable.add(studentRepository.getCurrentStudent()
.flatMap { semesterRepository.getCurrentSemester(it) }
.flatMap { semester ->
mobileDeviceRepository.unregisterDevice(semester, device)
.flatMap { mobileDeviceRepository.getDevices(semester, it) }
.flatMap { mobileDeviceRepository.getDevices(semester, it) }
}
.map { items -> items.map { MobileDeviceItem(it) } }
.subscribeOn(schedulers.backgroundThread)
@ -84,6 +99,7 @@ class MobileDevicePresenter @Inject constructor(
view?.run {
updateData(it)
showContent(it.isNotEmpty())
showEmpty(it.isEmpty())
}
}) {
Timber.i("Unregister device result: An exception occurred")

View File

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

View File

@ -5,10 +5,10 @@ import android.content.SharedPreferences
import android.os.Bundle
import com.takisoft.preferencex.PreferenceFragmentCompat
import dagger.android.support.AndroidSupportInjection
import io.github.wulkanowy.BuildConfig.DEBUG
import io.github.wulkanowy.R
import io.github.wulkanowy.ui.base.BaseActivity
import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.AppInfo
import javax.inject.Inject
class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedPreferenceChangeListener,
@ -17,6 +17,9 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP
@Inject
lateinit var presenter: SettingsPresenter
@Inject
lateinit var appInfo: AppInfo
companion object {
fun newInstance() = SettingsFragment()
}
@ -36,7 +39,7 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP
override fun onCreatePreferencesFix(savedInstanceState: Bundle?, rootKey: String?) {
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) {

View File

@ -33,7 +33,7 @@ class SettingsPresenter @Inject constructor(
Timber.i("Change settings $key")
preferencesRepository.apply {
when (key) {
serviceEnableKey -> syncManager.run { if (isServiceEnabled) startSyncWorker() else stopSyncWorker() }
serviceEnableKey -> with(syncManager) { if (isServiceEnabled) startSyncWorker() else stopSyncWorker() }
servicesIntervalKey, servicesOnlyWifiKey -> syncManager.startSyncWorker(true)
isDebugNotificationEnableKey -> chuckCollector.showNotification(isDebugNotificationEnable)
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
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
Wersja 0.9.3
Naprawiliśmy:
- wykrywanie uczniów przy logowaniu, jeśli symbol zawiera cyfry
- błędne wyświetlanie średniej
- synchronizację w tle na danych komórkowych przy domyślnych ustawieniach
- rzadkie błędy podczas ładowania szczęśliwego numerka
- problemy z logowaniem jeśli symbol zawierał cyfry
- bardzo rzadkie problemy z ładowaniem ocen
- jeszcze rzadsze problemy ze stabilnością podczas logowania
Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases

View File

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

View File

@ -253,6 +253,7 @@
<string name="pref_view_header">Wygląd</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_force_calc">Wymuś obliczanie średniej przez aplikację</string>
<string name="pref_view_present">Pokazuj obecność we frekwencji</string>
<string name="pref_view_app_theme">Motyw aplikacji</string>
<string name="pref_view_expand_grade">Rozwiń oceny</string>

View File

@ -7,7 +7,7 @@
<item>2 godziny</item>
<item>6 godzin</item>
<item>12 godzin</item>
<item>24 godzin</item>
<item>24 godziny</item>
</string-array>
<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_expand_grade">expand_grade</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_interval">services_interval</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_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_force_calc">Force average calculation by app</string>
<string name="pref_view_present">Show presence in attendance</string>
<string name="pref_view_app_theme">Application theme</string>
<string name="pref_view_expand_grade">Expand grades</string>

View File

@ -105,6 +105,11 @@
android:summary="%s"
android:title="@string/pref_view_grade_average_mode"
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
android:defaultValue="true"
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.core.CrashlyticsCore
import io.fabric.sdk.android.Fabric
import io.github.wulkanowy.BuildConfig
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() {
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
@ -17,9 +29,3 @@ class CrashlyticsTree : Timber.Tree() {
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).gradePlusModifier
doReturn(false).`when`(preferencesRepository).gradeAverageForceCalc
doReturn(Single.just(firstGrades)).`when`(gradeRepository).getGrades(student, semesters[1], true)
doReturn(Single.just(secondGrade)).`when`(gradeRepository).getGrades(student, semesters[2], true)
@ -128,6 +129,24 @@ class GradeAverageProviderTest {
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 {
return Grade(
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.repositories.student.StudentRepository
import io.github.wulkanowy.ui.modules.login.LoginErrorHandler
import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.reactivex.Single
import org.junit.Before
@ -33,13 +34,16 @@ class LoginFormPresenterTest {
@Mock
lateinit var analytics: FirebaseAnalyticsHelper
@Mock
lateinit var appInfo: AppInfo
private lateinit var presenter: LoginFormPresenter
@Before
fun initPresenter() {
MockitoAnnotations.initMocks(this)
clearInvocations(repository, loginFormView)
presenter = LoginFormPresenter(TestSchedulersProvider(), repository, errorHandler, analytics, false)
presenter = LoginFormPresenter(TestSchedulersProvider(), repository, errorHandler, analytics, appInfo)
presenter.onAttachView(loginFormView)
}

View File

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