Add force sync feature in settings (#643)

This commit is contained in:
Dominik Korsa 2020-04-05 16:46:49 +02:00 committed by GitHub
parent 3612326628
commit 6a0ce3a58d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 128 additions and 10 deletions

View File

@ -154,6 +154,8 @@ dependencies {
implementation "androidx.work:work-rxjava2:$work_manager" implementation "androidx.work:work-rxjava2:$work_manager"
implementation "androidx.work:work-gcm:$work_manager" implementation "androidx.work:work-gcm:$work_manager"
implementation 'com.github.PaulinaSadowska:RxWorkManagerObservers:1.0.0'
implementation "androidx.room:room-runtime:$room" implementation "androidx.room:room-runtime:$room"
implementation "androidx.room:room-rxjava2:$room" implementation "androidx.room:room-rxjava2:$room"
implementation "androidx.room:room-ktx:$room" implementation "androidx.room:room-ktx:$room"

View File

@ -5,18 +5,24 @@ import android.os.Build.VERSION_CODES.O
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import androidx.work.BackoffPolicy.EXPONENTIAL import androidx.work.BackoffPolicy.EXPONENTIAL
import androidx.work.Constraints import androidx.work.Constraints
import androidx.work.Data
import androidx.work.ExistingPeriodicWorkPolicy.KEEP import androidx.work.ExistingPeriodicWorkPolicy.KEEP
import androidx.work.ExistingPeriodicWorkPolicy.REPLACE import androidx.work.ExistingPeriodicWorkPolicy.REPLACE
import androidx.work.ExistingWorkPolicy
import androidx.work.NetworkType.CONNECTED import androidx.work.NetworkType.CONNECTED
import androidx.work.NetworkType.UNMETERED import androidx.work.NetworkType.UNMETERED
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.PeriodicWorkRequestBuilder import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkInfo
import androidx.work.WorkManager import androidx.work.WorkManager
import com.paulinasadowska.rxworkmanagerobservers.extensions.getWorkInfoByIdObservable
import io.github.wulkanowy.data.db.SharedPrefProvider import io.github.wulkanowy.data.db.SharedPrefProvider
import io.github.wulkanowy.data.db.SharedPrefProvider.Companion.APP_VERSION_CODE_KEY import io.github.wulkanowy.data.db.SharedPrefProvider.Companion.APP_VERSION_CODE_KEY
import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository
import io.github.wulkanowy.services.sync.channels.Channel import io.github.wulkanowy.services.sync.channels.Channel
import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.isHolidays import io.github.wulkanowy.utils.isHolidays
import io.reactivex.Observable
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
@ -42,13 +48,13 @@ class SyncManager @Inject constructor(
} }
if (sharedPrefProvider.getLong(APP_VERSION_CODE_KEY, -1L) != appInfo.versionCode.toLong()) { if (sharedPrefProvider.getLong(APP_VERSION_CODE_KEY, -1L) != appInfo.versionCode.toLong()) {
startSyncWorker(true) startPeriodicSyncWorker(true)
sharedPrefProvider.putLong(APP_VERSION_CODE_KEY, appInfo.versionCode.toLong(), true) sharedPrefProvider.putLong(APP_VERSION_CODE_KEY, appInfo.versionCode.toLong(), true)
} }
Timber.i("SyncManager was initialized") Timber.i("SyncManager was initialized")
} }
fun startSyncWorker(restart: Boolean = false) { fun startPeriodicSyncWorker(restart: Boolean = false) {
if (preferencesRepository.isServiceEnabled && !now().isHolidays) { if (preferencesRepository.isServiceEnabled && !now().isHolidays) {
workManager.enqueueUniquePeriodicWork(SyncWorker::class.java.simpleName, if (restart) REPLACE else KEEP, workManager.enqueueUniquePeriodicWork(SyncWorker::class.java.simpleName, if (restart) REPLACE else KEEP,
PeriodicWorkRequestBuilder<SyncWorker>(preferencesRepository.servicesInterval, MINUTES) PeriodicWorkRequestBuilder<SyncWorker>(preferencesRepository.servicesInterval, MINUTES)
@ -61,6 +67,19 @@ class SyncManager @Inject constructor(
} }
} }
fun startOneTimeSyncWorker(): Observable<WorkInfo> {
val work = OneTimeWorkRequestBuilder<SyncWorker>()
.setInputData(
Data.Builder()
.putBoolean("one_time", true)
.build()
)
.build()
workManager.enqueueUniqueWork("${SyncWorker::class.java.simpleName}_one_time", ExistingWorkPolicy.REPLACE, work)
return workManager.getWorkInfoByIdObservable(work.id)
}
fun stopSyncWorker() { fun stopSyncWorker() {
workManager.cancelUniqueWork(SyncWorker::class.java.simpleName) workManager.cancelUniqueWork(SyncWorker::class.java.simpleName)
} }

View File

@ -1,10 +1,12 @@
package io.github.wulkanowy.services.sync package io.github.wulkanowy.services.sync
import android.content.Context import android.content.Context
import android.os.Build.VERSION_CODES.LOLLIPOP
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationCompat.BigTextStyle import androidx.core.app.NotificationCompat.BigTextStyle
import androidx.core.app.NotificationCompat.PRIORITY_DEFAULT import androidx.core.app.NotificationCompat.PRIORITY_DEFAULT
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import androidx.work.Data
import androidx.work.ListenableWorker import androidx.work.ListenableWorker
import androidx.work.RxWorker import androidx.work.RxWorker
import androidx.work.WorkerParameters import androidx.work.WorkerParameters
@ -17,6 +19,7 @@ import io.github.wulkanowy.data.repositories.student.StudentRepository
import io.github.wulkanowy.sdk.exception.FeatureDisabledException import io.github.wulkanowy.sdk.exception.FeatureDisabledException
import io.github.wulkanowy.services.sync.channels.DebugChannel import io.github.wulkanowy.services.sync.channels.DebugChannel
import io.github.wulkanowy.services.sync.works.Work import io.github.wulkanowy.services.sync.works.Work
import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.getCompatColor import io.github.wulkanowy.utils.getCompatColor
import io.reactivex.Completable import io.reactivex.Completable
import io.reactivex.Single import io.reactivex.Single
@ -30,7 +33,8 @@ class SyncWorker @AssistedInject constructor(
private val semesterRepository: SemesterRepository, private val semesterRepository: SemesterRepository,
private val works: Set<@JvmSuppressWildcards Work>, private val works: Set<@JvmSuppressWildcards Work>,
private val preferencesRepository: PreferencesRepository, private val preferencesRepository: PreferencesRepository,
private val notificationManager: NotificationManagerCompat private val notificationManager: NotificationManagerCompat,
private val appInfo: AppInfo
) : RxWorker(appContext, workerParameters) { ) : RxWorker(appContext, workerParameters) {
override fun createWork(): Single<Result> { override fun createWork(): Single<Result> {
@ -52,8 +56,15 @@ class SyncWorker @AssistedInject constructor(
.toSingleDefault(Result.success()) .toSingleDefault(Result.success())
.onErrorReturn { .onErrorReturn {
Timber.e(it, "There was an error during synchronization") Timber.e(it, "There was an error during synchronization")
if (it is FeatureDisabledException) Result.success() when {
else Result.retry() it is FeatureDisabledException -> Result.success()
inputData.getBoolean("one_time", false) -> {
Result.failure(Data.Builder()
.putString("error", it.toString())
.build())
}
else -> Result.retry()
}
} }
.doOnSuccess { .doOnSuccess {
if (preferencesRepository.isDebugNotificationEnable) notify(it) if (preferencesRepository.isDebugNotificationEnable) notify(it)
@ -64,7 +75,7 @@ class SyncWorker @AssistedInject constructor(
private fun notify(result: Result) { private fun notify(result: Result) {
notificationManager.notify(Random.nextInt(Int.MAX_VALUE), NotificationCompat.Builder(applicationContext, DebugChannel.CHANNEL_ID) notificationManager.notify(Random.nextInt(Int.MAX_VALUE), NotificationCompat.Builder(applicationContext, DebugChannel.CHANNEL_ID)
.setContentTitle("Debug notification") .setContentTitle("Debug notification")
.setSmallIcon(R.drawable.ic_more_settings) .setSmallIcon(R.drawable.ic_stat_push)
.setAutoCancel(true) .setAutoCancel(true)
.setColor(applicationContext.getCompatColor(R.color.colorPrimary)) .setColor(applicationContext.getCompatColor(R.color.colorPrimary))
.setStyle(BigTextStyle().bigText("${SyncWorker::class.java.simpleName} result: $result")) .setStyle(BigTextStyle().bigText("${SyncWorker::class.java.simpleName} result: $result"))

View File

@ -33,7 +33,7 @@ class MainPresenter @Inject constructor(
Timber.i("Main view was initialized with $startMenuIndex menu index and $startMenuMoreIndex more index") Timber.i("Main view was initialized with $startMenuIndex menu index and $startMenuMoreIndex more index")
} }
syncManager.startSyncWorker() syncManager.startPeriodicSyncWorker()
analytics.logEvent("app_open", "destination" to initMenu?.name) analytics.logEvent("app_open", "destination" to initMenu?.name)
} }

View File

@ -3,6 +3,7 @@ package io.github.wulkanowy.ui.modules.settings
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import android.os.Bundle import android.os.Bundle
import androidx.appcompat.app.AlertDialog
import androidx.preference.Preference import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import com.yariksoffice.lingver.Lingver import com.yariksoffice.lingver.Lingver
@ -33,11 +34,24 @@ class SettingsFragment : PreferenceFragmentCompat(),
override val titleStringId get() = R.string.settings_title override val titleStringId get() = R.string.settings_title
override val syncSuccessString get() = getString(R.string.pref_services_message_sync_success)
override val syncFailedString get() = getString(R.string.pref_services_message_sync_failed)
override fun onAttach(context: Context) { override fun onAttach(context: Context) {
AndroidSupportInjection.inject(this) AndroidSupportInjection.inject(this)
super.onAttach(context) super.onAttach(context)
} }
override fun initView() {
findPreference<Preference>(getString(R.string.pref_key_services_force_sync))?.run {
onPreferenceClickListener = Preference.OnPreferenceClickListener {
presenter.onSyncNowClicked()
true
}
}
}
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState) super.onActivityCreated(savedInstanceState)
presenter.onAttachView(this) presenter.onAttachView(this)
@ -61,12 +75,19 @@ class SettingsFragment : PreferenceFragmentCompat(),
} }
override fun setServicesSuspended(serviceEnablesKey: String, isHolidays: Boolean) { override fun setServicesSuspended(serviceEnablesKey: String, isHolidays: Boolean) {
findPreference<Preference>(serviceEnablesKey)?.apply { findPreference<Preference>(serviceEnablesKey)?.run {
summary = if (isHolidays) getString(R.string.pref_services_suspended) else "" summary = if (isHolidays) getString(R.string.pref_services_suspended) else ""
isEnabled = !isHolidays isEnabled = !isHolidays
} }
} }
override fun setSyncInProgress(inProgress: Boolean) {
findPreference<Preference>(getString(R.string.pref_key_services_force_sync))?.run {
isEnabled = !inProgress
summary = if (inProgress) getString(R.string.pref_services_sync_in_progress) else ""
}
}
override fun showError(text: String, error: Throwable) { override fun showError(text: String, error: Throwable) {
(activity as? BaseActivity<*>)?.showError(text, error) (activity as? BaseActivity<*>)?.showError(text, error)
} }
@ -87,6 +108,15 @@ class SettingsFragment : PreferenceFragmentCompat(),
ErrorDialog.newInstance(error).show(childFragmentManager, error.toString()) ErrorDialog.newInstance(error).show(childFragmentManager, error.toString())
} }
override fun showForceSyncDialog() {
AlertDialog.Builder(requireContext())
.setTitle(R.string.pref_services_dialog_force_sync_title)
.setMessage(R.string.pref_services_dialog_force_sync_summary)
.setPositiveButton(android.R.string.ok) { _, _ -> presenter.onForceSyncDialogSubmit() }
.setNegativeButton(android.R.string.cancel) { _, _ -> }
.show()
}
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
preferenceScreen.sharedPreferences.registerOnSharedPreferenceChangeListener(this) preferenceScreen.sharedPreferences.registerOnSharedPreferenceChangeListener(this)

View File

@ -1,5 +1,6 @@
package io.github.wulkanowy.ui.modules.settings package io.github.wulkanowy.ui.modules.settings
import androidx.work.WorkInfo
import com.chuckerteam.chucker.api.ChuckerCollector import com.chuckerteam.chucker.api.ChuckerCollector
import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository
import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.data.repositories.student.StudentRepository
@ -29,6 +30,7 @@ class SettingsPresenter @Inject constructor(
super.onAttachView(view) super.onAttachView(view)
Timber.i("Settings view was initialized") Timber.i("Settings view was initialized")
view.setServicesSuspended(preferencesRepository.serviceEnableKey, now().isHolidays) view.setServicesSuspended(preferencesRepository.serviceEnableKey, now().isHolidays)
view.initView()
} }
fun onSharedPreferenceChanged(key: String) { fun onSharedPreferenceChanged(key: String) {
@ -36,8 +38,8 @@ class SettingsPresenter @Inject constructor(
with(preferencesRepository) { with(preferencesRepository) {
when (key) { when (key) {
serviceEnableKey -> with(syncManager) { if (isServiceEnabled) startSyncWorker() else stopSyncWorker() } serviceEnableKey -> with(syncManager) { if (isServiceEnabled) startPeriodicSyncWorker() else stopSyncWorker() }
servicesIntervalKey, servicesOnlyWifiKey -> syncManager.startSyncWorker(true) servicesIntervalKey, servicesOnlyWifiKey -> syncManager.startPeriodicSyncWorker(true)
isDebugNotificationEnableKey -> chuckerCollector.showNotification = isDebugNotificationEnable isDebugNotificationEnableKey -> chuckerCollector.showNotification = isDebugNotificationEnable
appThemeKey -> view?.recreateView() appThemeKey -> view?.recreateView()
appLanguageKey -> view?.run { appLanguageKey -> view?.run {
@ -49,4 +51,25 @@ class SettingsPresenter @Inject constructor(
} }
analytics.logEvent("setting_changed", "name" to key) analytics.logEvent("setting_changed", "name" to key)
} }
fun onSyncNowClicked() {
view?.showForceSyncDialog()
}
fun onForceSyncDialogSubmit() {
view?.run {
Timber.i("Setting sync now started")
analytics.logEvent("sync_now_started")
disposable.add(syncManager.startOneTimeSyncWorker()
.doOnSubscribe { setSyncInProgress(true) }
.doFinally { setSyncInProgress(false) }
.subscribe({ workInfo ->
if (workInfo.state == WorkInfo.State.SUCCEEDED) showMessage(syncSuccessString)
else if (workInfo.state == WorkInfo.State.FAILED) showError(syncFailedString, Throwable(workInfo.outputData.getString("error")))
}, {
Timber.e("Sync now failed")
})
)
}
}
} }

View File

@ -4,9 +4,19 @@ import io.github.wulkanowy.ui.base.BaseView
interface SettingsView : BaseView { interface SettingsView : BaseView {
val syncSuccessString: String
val syncFailedString: String
fun initView()
fun recreateView() fun recreateView()
fun updateLanguage(langCode: String) fun updateLanguage(langCode: String)
fun setServicesSuspended(serviceEnablesKey: String, isHolidays: Boolean) fun setServicesSuspended(serviceEnablesKey: String, isHolidays: Boolean)
fun setSyncInProgress(inProgress: Boolean)
fun showForceSyncDialog()
} }

View File

@ -375,6 +375,15 @@
<string name="pref_services_suspended">Zawieszona na wakacjach</string> <string name="pref_services_suspended">Zawieszona na wakacjach</string>
<string name="pref_services_interval">Interwał aktualizacji</string> <string name="pref_services_interval">Interwał aktualizacji</string>
<string name="pref_services_wifi">Tylko WiFi</string> <string name="pref_services_wifi">Tylko WiFi</string>
<string name="pref_services_force_sync">Synchronizuj teraz</string>
<string name="pref_services_message_sync_success">Zsynchronizowano!</string>
<string name="pref_services_message_sync_failed">Synchronizacja nie powiodła się</string>
<string name="pref_services_sync_in_progress">Synchronizacja w trakcie</string>
<string name="pref_services_dialog_force_sync_title">Synchronizacja</string>
<string name="pref_services_dialog_force_sync_summary">
Ręczna synchronizacja nie odświeża widoków w aplikacji.
\nAby zobaczyć zsynchronizowane informacje uruchom ponownie aplikację po zsynchronizowaniu.
</string>
<string name="pref_other_header">Inne</string> <string name="pref_other_header">Inne</string>
<string name="pref_other_grade_modifier_plus">Wartość plusa</string> <string name="pref_other_grade_modifier_plus">Wartość plusa</string>

View File

@ -11,6 +11,7 @@
<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>
<string name="pref_key_services_force_sync">services_force_sync</string>
<string name="pref_key_notifications_enable">notifications_enable</string> <string name="pref_key_notifications_enable">notifications_enable</string>
<string name="pref_key_notification_debug">notification_debug</string> <string name="pref_key_notification_debug">notification_debug</string>
<string name="pref_key_grade_modifier_plus">grade_modifier_plus</string> <string name="pref_key_grade_modifier_plus">grade_modifier_plus</string>

View File

@ -360,6 +360,15 @@
<string name="pref_services_suspended">Suspended on holidays</string> <string name="pref_services_suspended">Suspended on holidays</string>
<string name="pref_services_interval">Updates interval</string> <string name="pref_services_interval">Updates interval</string>
<string name="pref_services_wifi">Wi-Fi only</string> <string name="pref_services_wifi">Wi-Fi only</string>
<string name="pref_services_force_sync">Sync now</string>
<string name="pref_services_message_sync_success">Synced!</string>
<string name="pref_services_message_sync_failed">Sync failed</string>
<string name="pref_services_sync_in_progress">Sync in progress</string>
<string name="pref_services_dialog_force_sync_title">Synchronization</string>
<string name="pref_services_dialog_force_sync_summary">
Manual sync doesn\'t refresh app views.
\nTo see the synced data relaunch the app after syncing.
</string>
<string name="pref_other_header">Other</string> <string name="pref_other_header">Other</string>
<string name="pref_other_grade_modifier_plus">Value of the plus</string> <string name="pref_other_grade_modifier_plus">Value of the plus</string>

View File

@ -77,6 +77,10 @@
app:iconSpaceReserved="false" app:iconSpaceReserved="false"
app:key="@string/pref_key_services_wifi_only" app:key="@string/pref_key_services_wifi_only"
app:title="@string/pref_services_wifi" /> app:title="@string/pref_services_wifi" />
<Preference
app:iconSpaceReserved="false"
app:title="@string/pref_services_force_sync"
app:key="@string/pref_key_services_force_sync" />
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory <PreferenceCategory
app:iconSpaceReserved="false" app:iconSpaceReserved="false"