Add in-app updates support (#914)

This commit is contained in:
Mikołaj Pich 2020-10-15 01:00:41 +02:00 committed by GitHub
parent ca67e144e4
commit db9c2640c7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 164 additions and 2 deletions

View File

@ -112,6 +112,7 @@ play {
serviceAccountCredentials = file('key.p12')
defaultToAppBundles = false
track = 'alpha'
updatePriority = 0
}
ext {
@ -183,11 +184,12 @@ dependencies {
implementation "io.github.wulkanowy:AppKillerManager:3.0.0"
implementation 'me.xdrop:fuzzywuzzy:1.3.1'
playImplementation 'com.google.firebase:firebase-analytics:17.5.0'
playImplementation 'com.google.firebase:firebase-analytics:17.6.0'
playImplementation 'com.google.firebase:firebase-inappmessaging-display-ktx:19.1.1'
playImplementation "com.google.firebase:firebase-inappmessaging-ktx:19.1.1"
playImplementation 'com.google.firebase:firebase-messaging:20.3.0'
playImplementation 'com.google.firebase:firebase-crashlytics:17.2.2'
playImplementation 'com.google.android.play:core-ktx:1.8.1'
playImplementation 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava'
releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker"

View File

@ -0,0 +1,17 @@
package io.github.wulkanowy.utils
import android.app.Activity
import android.view.View
import javax.inject.Inject
@Suppress("UNUSED_PARAMETER")
class UpdateHelper @Inject constructor() {
lateinit var messageContainer: View
fun checkAndInstallUpdates(activity: Activity) {}
fun onActivityResult(requestCode: Int, resultCode: Int) {}
fun onResume(activity: Activity) {}
}

View File

@ -14,6 +14,7 @@ import io.github.wulkanowy.ui.modules.login.form.LoginFormFragment
import io.github.wulkanowy.ui.modules.login.recover.LoginRecoverFragment
import io.github.wulkanowy.ui.modules.login.studentselect.LoginStudentSelectFragment
import io.github.wulkanowy.ui.modules.login.symbol.LoginSymbolFragment
import io.github.wulkanowy.utils.UpdateHelper
import io.github.wulkanowy.utils.setOnSelectPageListener
import javax.inject.Inject
@ -25,6 +26,9 @@ class LoginActivity : BaseActivity<LoginPresenter, ActivityLoginBinding>(), Logi
private val loginAdapter = BaseFragmentPagerAdapter(supportFragmentManager)
@Inject
lateinit var updateHelper: UpdateHelper
companion object {
fun getStartIntent(context: Context) = Intent(context, LoginActivity::class.java)
@ -37,8 +41,20 @@ class LoginActivity : BaseActivity<LoginPresenter, ActivityLoginBinding>(), Logi
setContentView(ActivityLoginBinding.inflate(layoutInflater).apply { binding = this }.root)
setSupportActionBar(binding.loginToolbar)
messageContainer = binding.loginContainer
updateHelper.messageContainer = binding.loginContainer
presenter.onAttachView(this)
updateHelper.checkAndInstallUpdates(this)
}
override fun onResume() {
super.onResume()
updateHelper.onResume(this)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
updateHelper.onActivityResult(requestCode, resultCode)
}
override fun initView() {

View File

@ -40,6 +40,7 @@ import io.github.wulkanowy.ui.modules.note.NoteFragment
import io.github.wulkanowy.ui.modules.timetable.TimetableFragment
import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.UpdateHelper
import io.github.wulkanowy.utils.dpToPx
import io.github.wulkanowy.utils.getThemeAttrColor
import io.github.wulkanowy.utils.safelyPopFragments
@ -56,6 +57,9 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
@Inject
lateinit var analytics: FirebaseAnalyticsHelper
@Inject
lateinit var updateHelper: UpdateHelper
@Inject
lateinit var appInfo: AppInfo
@ -100,6 +104,7 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
setContentView(ActivityMainBinding.inflate(layoutInflater).apply { binding = this }.root)
setSupportActionBar(binding.mainToolbar)
messageContainer = binding.mainFragmentContainer
updateHelper.messageContainer = binding.mainFragmentContainer
presenter.onAttachView(this, MainView.Section.values().singleOrNull { it.id == intent.getIntExtra(EXTRA_START_MENU, -1) })
@ -107,6 +112,18 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
initialize(startMenuIndex, savedInstanceState)
pushFragment(moreMenuFragments[startMenuMoreIndex])
}
updateHelper.checkAndInstallUpdates(this)
}
override fun onResume() {
super.onResume()
updateHelper.onResume(this)
}
@SuppressLint("NewApi")
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
updateHelper.onActivityResult(requestCode, resultCode)
if (appInfo.systemVersion >= Build.VERSION_CODES.N_MR1) initShortcuts()
}

View File

@ -472,6 +472,12 @@
<string name="all_copied">Copied</string>
<string name="all_undo">Undo</string>
<!--Update helper-->
<string name="update_download_started">Start downloading update…</string>
<string name="update_download_success">An update has just been downloaded.</string>
<string name="update_download_success_button">Restart</string>
<string name="update_failed">Update failed! Wulkanowy may not function properly. Consider updating</string>
<!--Errors-->
<string name="error_no_internet">No internet connection</string>

View File

@ -0,0 +1,104 @@
package io.github.wulkanowy.utils
import android.app.Activity
import android.app.Activity.RESULT_OK
import android.content.Context
import android.view.View
import android.widget.Toast
import com.google.android.material.snackbar.Snackbar
import com.google.android.play.core.appupdate.AppUpdateInfo
import com.google.android.play.core.appupdate.AppUpdateManagerFactory
import com.google.android.play.core.install.InstallStateUpdatedListener
import com.google.android.play.core.install.model.AppUpdateType.FLEXIBLE
import com.google.android.play.core.install.model.AppUpdateType.IMMEDIATE
import com.google.android.play.core.install.model.InstallStatus.DOWNLOADED
import com.google.android.play.core.install.model.InstallStatus.PENDING
import com.google.android.play.core.install.model.UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS
import com.google.android.play.core.install.model.UpdateAvailability.UPDATE_AVAILABLE
import com.google.android.play.core.ktx.isFlexibleUpdateAllowed
import com.google.android.play.core.ktx.isImmediateUpdateAllowed
import io.github.wulkanowy.R
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class UpdateHelper @Inject constructor(private val context: Context) {
lateinit var messageContainer: View
companion object {
const val IN_APP_UPDATE_REQUEST_CODE = 1721
const val DAYS_FOR_FLEXIBLE_UPDATE = 7
const val HIGH_PRIORITY_UPDATE = 4
}
private val appUpdateManager by lazy { AppUpdateManagerFactory.create(context) }
private val flexibleUpdateListener = InstallStateUpdatedListener { state ->
when (state.installStatus()) {
PENDING -> Toast.makeText(context, R.string.update_download_started, Toast.LENGTH_SHORT).show()
DOWNLOADED -> popupSnackBarForCompleteUpdate()
else -> Timber.d("Update state: ${state.installStatus()}")
}
}
private inline val AppUpdateInfo.isImmediateUpdateAvailable: Boolean
get() = updateAvailability() == UPDATE_AVAILABLE && isImmediateUpdateAllowed &&
updatePriority() >= HIGH_PRIORITY_UPDATE
private inline val AppUpdateInfo.isFlexibleUpdateAvailable: Boolean
get() = updateAvailability() == UPDATE_AVAILABLE && isFlexibleUpdateAllowed &&
clientVersionStalenessDays() ?: 0 >= DAYS_FOR_FLEXIBLE_UPDATE
fun checkAndInstallUpdates(activity: Activity) {
Timber.d("Checking for updates...")
appUpdateManager.appUpdateInfo.addOnSuccessListener { appUpdateInfo ->
when {
appUpdateInfo.isImmediateUpdateAvailable -> startUpdate(activity, appUpdateInfo, IMMEDIATE)
appUpdateInfo.isFlexibleUpdateAvailable -> {
appUpdateManager.registerListener(flexibleUpdateListener)
startUpdate(activity, appUpdateInfo, FLEXIBLE)
}
else -> Timber.d("No update available")
}
}
}
private fun startUpdate(activity: Activity, appUpdateInfo: AppUpdateInfo, updateType: Int) {
appUpdateManager.startUpdateFlowForResult(
appUpdateInfo, updateType, activity, IN_APP_UPDATE_REQUEST_CODE
)
}
fun onActivityResult(requestCode: Int, resultCode: Int) {
if (requestCode == IN_APP_UPDATE_REQUEST_CODE && resultCode != RESULT_OK) {
Timber.e("Update failed! Result code: $resultCode")
Toast.makeText(context, R.string.update_failed, Toast.LENGTH_LONG).show()
}
}
fun onResume(activity: Activity) {
appUpdateManager.appUpdateInfo.addOnSuccessListener { info ->
Timber.d("InAppUpdate.onResume() listener: $info")
when {
DOWNLOADED == info.installStatus() -> popupSnackBarForCompleteUpdate()
DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS == info.updateAvailability() -> {
startUpdate(activity, info, if (info.isImmediateUpdateAvailable) IMMEDIATE else FLEXIBLE)
}
}
}
}
private fun popupSnackBarForCompleteUpdate() {
Snackbar.make(messageContainer, R.string.update_download_success, Snackbar.LENGTH_INDEFINITE).apply {
setAction(R.string.update_download_success_button) {
appUpdateManager.completeUpdate()
appUpdateManager.unregisterListener(flexibleUpdateListener)
}
show()
}
}
}

View File

@ -16,7 +16,7 @@ buildscript {
classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
classpath 'com.google.gms:google-services:4.3.4'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.3.0'
classpath "com.github.triplet.gradle:play-publisher:2.7.5"
classpath "com.github.triplet.gradle:play-publisher:2.8.0"
classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.0"
classpath "gradle.plugin.com.star-zero.gradle:githook:1.2.0"
classpath "com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin:$about_libraries"