Merge branch 'release/0.9.2'

This commit is contained in:
Rafał Borcz 2019-06-08 11:38:42 +02:00
commit 5c185c5eca
25 changed files with 229 additions and 109 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

@ -17,8 +17,8 @@ android {
testApplicationId "io.github.tests.wulkanowy" testApplicationId "io.github.tests.wulkanowy"
minSdkVersion 15 minSdkVersion 15
targetSdkVersion 28 targetSdkVersion 28
versionCode 39 versionCode 40
versionName "0.9.1" versionName "0.9.2"
multiDexEnabled true multiDexEnabled true
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true
@ -98,18 +98,23 @@ play {
} }
dependencies { dependencies {
implementation 'io.github.wulkanowy:api:0.9.1' implementation "io.github.wulkanowy:api:0.9.2"
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 "com.google.android.material:material:1.1.0-alpha05" implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"
implementation 'com.github.wulkanowy:MaterialChipsInput:b72fd0ee6f' implementation "androidx.constraintlayout:constraintlayout:1.1.3"
implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0' 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 "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"
@ -121,51 +126,51 @@ dependencies {
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-rc01"
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'
} }
} }

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

@ -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

@ -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,25 @@ 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 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()

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,7 +63,21 @@ 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) }
@ -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

@ -1,14 +1,9 @@
Wersja 0.9.0 Wersja 0.9.2
Dodaliśmy:
- zarządzanie dostępem mobilnym do dziennika
- wyświetlanie sumy punktów
- 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 - ustawienia synchronizacji w tle
- błędne wyświetlanie średniej - ręczne odświeżanie ocen jeśli średnia była pobierana z dziennika
- synchronizację w tle na danych komórkowych przy domyślnych ustawieniach - wygląd ładowania w widoku Dostęp Mobilny
- rzadkie błędy podczas ładowania szczęśliwego numerka - sporadyczne awarie aplikacji na starszych urządzeniach
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

@ -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

@ -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

@ -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)
} }