diff --git a/app/build.gradle b/app/build.gradle index 580d069a7..2b4c6818c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -154,6 +154,8 @@ dependencies { implementation "androidx.work:work-rxjava2:$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-rxjava2:$room" implementation "androidx.room:room-ktx:$room" diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/SyncManager.kt b/app/src/main/java/io/github/wulkanowy/services/sync/SyncManager.kt index 3d0cbcf63..dda2abe0c 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/SyncManager.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/SyncManager.kt @@ -5,18 +5,24 @@ import android.os.Build.VERSION_CODES.O import androidx.core.app.NotificationManagerCompat import androidx.work.BackoffPolicy.EXPONENTIAL import androidx.work.Constraints +import androidx.work.Data import androidx.work.ExistingPeriodicWorkPolicy.KEEP import androidx.work.ExistingPeriodicWorkPolicy.REPLACE +import androidx.work.ExistingWorkPolicy import androidx.work.NetworkType.CONNECTED import androidx.work.NetworkType.UNMETERED +import androidx.work.OneTimeWorkRequestBuilder import androidx.work.PeriodicWorkRequestBuilder +import androidx.work.WorkInfo import androidx.work.WorkManager +import com.paulinasadowska.rxworkmanagerobservers.extensions.getWorkInfoByIdObservable import io.github.wulkanowy.data.db.SharedPrefProvider import io.github.wulkanowy.data.db.SharedPrefProvider.Companion.APP_VERSION_CODE_KEY import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository import io.github.wulkanowy.services.sync.channels.Channel import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.isHolidays +import io.reactivex.Observable import org.threeten.bp.LocalDate.now import timber.log.Timber 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()) { - startSyncWorker(true) + startPeriodicSyncWorker(true) sharedPrefProvider.putLong(APP_VERSION_CODE_KEY, appInfo.versionCode.toLong(), true) } Timber.i("SyncManager was initialized") } - fun startSyncWorker(restart: Boolean = false) { + fun startPeriodicSyncWorker(restart: Boolean = false) { if (preferencesRepository.isServiceEnabled && !now().isHolidays) { workManager.enqueueUniquePeriodicWork(SyncWorker::class.java.simpleName, if (restart) REPLACE else KEEP, PeriodicWorkRequestBuilder(preferencesRepository.servicesInterval, MINUTES) @@ -61,6 +67,19 @@ class SyncManager @Inject constructor( } } + fun startOneTimeSyncWorker(): Observable { + val work = OneTimeWorkRequestBuilder() + .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() { workManager.cancelUniqueWork(SyncWorker::class.java.simpleName) } diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/SyncWorker.kt b/app/src/main/java/io/github/wulkanowy/services/sync/SyncWorker.kt index 68702d9a9..8a38fa557 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/SyncWorker.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/SyncWorker.kt @@ -1,10 +1,12 @@ package io.github.wulkanowy.services.sync import android.content.Context +import android.os.Build.VERSION_CODES.LOLLIPOP import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat.BigTextStyle import androidx.core.app.NotificationCompat.PRIORITY_DEFAULT import androidx.core.app.NotificationManagerCompat +import androidx.work.Data import androidx.work.ListenableWorker import androidx.work.RxWorker 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.services.sync.channels.DebugChannel import io.github.wulkanowy.services.sync.works.Work +import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.getCompatColor import io.reactivex.Completable import io.reactivex.Single @@ -30,7 +33,8 @@ class SyncWorker @AssistedInject constructor( private val semesterRepository: SemesterRepository, private val works: Set<@JvmSuppressWildcards Work>, private val preferencesRepository: PreferencesRepository, - private val notificationManager: NotificationManagerCompat + private val notificationManager: NotificationManagerCompat, + private val appInfo: AppInfo ) : RxWorker(appContext, workerParameters) { override fun createWork(): Single { @@ -52,8 +56,15 @@ class SyncWorker @AssistedInject constructor( .toSingleDefault(Result.success()) .onErrorReturn { Timber.e(it, "There was an error during synchronization") - if (it is FeatureDisabledException) Result.success() - else Result.retry() + when { + it is FeatureDisabledException -> Result.success() + inputData.getBoolean("one_time", false) -> { + Result.failure(Data.Builder() + .putString("error", it.toString()) + .build()) + } + else -> Result.retry() + } } .doOnSuccess { if (preferencesRepository.isDebugNotificationEnable) notify(it) @@ -64,7 +75,7 @@ class SyncWorker @AssistedInject constructor( private fun notify(result: Result) { notificationManager.notify(Random.nextInt(Int.MAX_VALUE), NotificationCompat.Builder(applicationContext, DebugChannel.CHANNEL_ID) .setContentTitle("Debug notification") - .setSmallIcon(R.drawable.ic_more_settings) + .setSmallIcon(R.drawable.ic_stat_push) .setAutoCancel(true) .setColor(applicationContext.getCompatColor(R.color.colorPrimary)) .setStyle(BigTextStyle().bigText("${SyncWorker::class.java.simpleName} result: $result")) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt index e6c299171..f5a490044 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt @@ -33,7 +33,7 @@ class MainPresenter @Inject constructor( 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) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsFragment.kt index 03a89d274..5a8593a06 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsFragment.kt @@ -3,6 +3,7 @@ package io.github.wulkanowy.ui.modules.settings import android.content.Context import android.content.SharedPreferences import android.os.Bundle +import androidx.appcompat.app.AlertDialog import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat import com.yariksoffice.lingver.Lingver @@ -33,11 +34,24 @@ class SettingsFragment : PreferenceFragmentCompat(), 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) { AndroidSupportInjection.inject(this) super.onAttach(context) } + override fun initView() { + findPreference(getString(R.string.pref_key_services_force_sync))?.run { + onPreferenceClickListener = Preference.OnPreferenceClickListener { + presenter.onSyncNowClicked() + true + } + } + } + override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) presenter.onAttachView(this) @@ -61,12 +75,19 @@ class SettingsFragment : PreferenceFragmentCompat(), } override fun setServicesSuspended(serviceEnablesKey: String, isHolidays: Boolean) { - findPreference(serviceEnablesKey)?.apply { + findPreference(serviceEnablesKey)?.run { summary = if (isHolidays) getString(R.string.pref_services_suspended) else "" isEnabled = !isHolidays } } + override fun setSyncInProgress(inProgress: Boolean) { + findPreference(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) { (activity as? BaseActivity<*>)?.showError(text, error) } @@ -87,6 +108,15 @@ class SettingsFragment : PreferenceFragmentCompat(), 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() { super.onResume() preferenceScreen.sharedPreferences.registerOnSharedPreferenceChangeListener(this) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsPresenter.kt index 3f2bf714c..ce5100ea8 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsPresenter.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.ui.modules.settings +import androidx.work.WorkInfo import com.chuckerteam.chucker.api.ChuckerCollector import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository import io.github.wulkanowy.data.repositories.student.StudentRepository @@ -29,6 +30,7 @@ class SettingsPresenter @Inject constructor( super.onAttachView(view) Timber.i("Settings view was initialized") view.setServicesSuspended(preferencesRepository.serviceEnableKey, now().isHolidays) + view.initView() } fun onSharedPreferenceChanged(key: String) { @@ -36,8 +38,8 @@ class SettingsPresenter @Inject constructor( with(preferencesRepository) { when (key) { - serviceEnableKey -> with(syncManager) { if (isServiceEnabled) startSyncWorker() else stopSyncWorker() } - servicesIntervalKey, servicesOnlyWifiKey -> syncManager.startSyncWorker(true) + serviceEnableKey -> with(syncManager) { if (isServiceEnabled) startPeriodicSyncWorker() else stopSyncWorker() } + servicesIntervalKey, servicesOnlyWifiKey -> syncManager.startPeriodicSyncWorker(true) isDebugNotificationEnableKey -> chuckerCollector.showNotification = isDebugNotificationEnable appThemeKey -> view?.recreateView() appLanguageKey -> view?.run { @@ -49,4 +51,25 @@ class SettingsPresenter @Inject constructor( } 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") + }) + ) + } + } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsView.kt index e50eb47b3..4a1b0c766 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsView.kt @@ -4,9 +4,19 @@ import io.github.wulkanowy.ui.base.BaseView interface SettingsView : BaseView { + val syncSuccessString: String + + val syncFailedString: String + + fun initView() + fun recreateView() fun updateLanguage(langCode: String) fun setServicesSuspended(serviceEnablesKey: String, isHolidays: Boolean) + + fun setSyncInProgress(inProgress: Boolean) + + fun showForceSyncDialog() } diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 4d2c7c458..cbdf94e39 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -375,6 +375,15 @@ Zawieszona na wakacjach Interwał aktualizacji Tylko WiFi + Synchronizuj teraz + Zsynchronizowano! + Synchronizacja nie powiodła się + Synchronizacja w trakcie + Synchronizacja + + Ręczna synchronizacja nie odświeża widoków w aplikacji. + \nAby zobaczyć zsynchronizowane informacje uruchom ponownie aplikację po zsynchronizowaniu. + Inne Wartość plusa diff --git a/app/src/main/res/values/preferences_keys.xml b/app/src/main/res/values/preferences_keys.xml index 49601b550..559e2159c 100644 --- a/app/src/main/res/values/preferences_keys.xml +++ b/app/src/main/res/values/preferences_keys.xml @@ -11,6 +11,7 @@ services_enable services_interval services_disable_wifi_only + services_force_sync notifications_enable notification_debug grade_modifier_plus diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f3aa468af..a3384c74d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -360,6 +360,15 @@ Suspended on holidays Updates interval Wi-Fi only + Sync now + Synced! + Sync failed + Sync in progress + Synchronization + + Manual sync doesn\'t refresh app views. + \nTo see the synced data relaunch the app after syncing. + Other Value of the plus diff --git a/app/src/main/res/xml/scheme_preferences.xml b/app/src/main/res/xml/scheme_preferences.xml index e92aca63f..bb9eb5fcd 100644 --- a/app/src/main/res/xml/scheme_preferences.xml +++ b/app/src/main/res/xml/scheme_preferences.xml @@ -77,6 +77,10 @@ app:iconSpaceReserved="false" app:key="@string/pref_key_services_wifi_only" app:title="@string/pref_services_wifi" /> +