mirror of
https://github.com/wulkanowy/wulkanowy.git
synced 2025-01-31 19:02:45 +01:00
Add in-app updates support (#914)
This commit is contained in:
parent
ca67e144e4
commit
db9c2640c7
@ -112,6 +112,7 @@ play {
|
|||||||
serviceAccountCredentials = file('key.p12')
|
serviceAccountCredentials = file('key.p12')
|
||||||
defaultToAppBundles = false
|
defaultToAppBundles = false
|
||||||
track = 'alpha'
|
track = 'alpha'
|
||||||
|
updatePriority = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
ext {
|
ext {
|
||||||
@ -183,11 +184,12 @@ dependencies {
|
|||||||
implementation "io.github.wulkanowy:AppKillerManager:3.0.0"
|
implementation "io.github.wulkanowy:AppKillerManager:3.0.0"
|
||||||
implementation 'me.xdrop:fuzzywuzzy:1.3.1'
|
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-display-ktx:19.1.1'
|
||||||
playImplementation "com.google.firebase:firebase-inappmessaging-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-messaging:20.3.0'
|
||||||
playImplementation 'com.google.firebase:firebase-crashlytics:17.2.2'
|
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'
|
playImplementation 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava'
|
||||||
|
|
||||||
releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker"
|
releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker"
|
||||||
|
@ -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) {}
|
||||||
|
}
|
@ -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.recover.LoginRecoverFragment
|
||||||
import io.github.wulkanowy.ui.modules.login.studentselect.LoginStudentSelectFragment
|
import io.github.wulkanowy.ui.modules.login.studentselect.LoginStudentSelectFragment
|
||||||
import io.github.wulkanowy.ui.modules.login.symbol.LoginSymbolFragment
|
import io.github.wulkanowy.ui.modules.login.symbol.LoginSymbolFragment
|
||||||
|
import io.github.wulkanowy.utils.UpdateHelper
|
||||||
import io.github.wulkanowy.utils.setOnSelectPageListener
|
import io.github.wulkanowy.utils.setOnSelectPageListener
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@ -25,6 +26,9 @@ class LoginActivity : BaseActivity<LoginPresenter, ActivityLoginBinding>(), Logi
|
|||||||
|
|
||||||
private val loginAdapter = BaseFragmentPagerAdapter(supportFragmentManager)
|
private val loginAdapter = BaseFragmentPagerAdapter(supportFragmentManager)
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var updateHelper: UpdateHelper
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
fun getStartIntent(context: Context) = Intent(context, LoginActivity::class.java)
|
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)
|
setContentView(ActivityLoginBinding.inflate(layoutInflater).apply { binding = this }.root)
|
||||||
setSupportActionBar(binding.loginToolbar)
|
setSupportActionBar(binding.loginToolbar)
|
||||||
messageContainer = binding.loginContainer
|
messageContainer = binding.loginContainer
|
||||||
|
updateHelper.messageContainer = binding.loginContainer
|
||||||
|
|
||||||
presenter.onAttachView(this)
|
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() {
|
override fun initView() {
|
||||||
|
@ -40,6 +40,7 @@ import io.github.wulkanowy.ui.modules.note.NoteFragment
|
|||||||
import io.github.wulkanowy.ui.modules.timetable.TimetableFragment
|
import io.github.wulkanowy.ui.modules.timetable.TimetableFragment
|
||||||
import io.github.wulkanowy.utils.AppInfo
|
import io.github.wulkanowy.utils.AppInfo
|
||||||
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
|
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
|
||||||
|
import io.github.wulkanowy.utils.UpdateHelper
|
||||||
import io.github.wulkanowy.utils.dpToPx
|
import io.github.wulkanowy.utils.dpToPx
|
||||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||||
import io.github.wulkanowy.utils.safelyPopFragments
|
import io.github.wulkanowy.utils.safelyPopFragments
|
||||||
@ -56,6 +57,9 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
|
|||||||
@Inject
|
@Inject
|
||||||
lateinit var analytics: FirebaseAnalyticsHelper
|
lateinit var analytics: FirebaseAnalyticsHelper
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var updateHelper: UpdateHelper
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var appInfo: AppInfo
|
lateinit var appInfo: AppInfo
|
||||||
|
|
||||||
@ -100,6 +104,7 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
|
|||||||
setContentView(ActivityMainBinding.inflate(layoutInflater).apply { binding = this }.root)
|
setContentView(ActivityMainBinding.inflate(layoutInflater).apply { binding = this }.root)
|
||||||
setSupportActionBar(binding.mainToolbar)
|
setSupportActionBar(binding.mainToolbar)
|
||||||
messageContainer = binding.mainFragmentContainer
|
messageContainer = binding.mainFragmentContainer
|
||||||
|
updateHelper.messageContainer = binding.mainFragmentContainer
|
||||||
|
|
||||||
presenter.onAttachView(this, MainView.Section.values().singleOrNull { it.id == intent.getIntExtra(EXTRA_START_MENU, -1) })
|
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)
|
initialize(startMenuIndex, savedInstanceState)
|
||||||
pushFragment(moreMenuFragments[startMenuMoreIndex])
|
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()
|
if (appInfo.systemVersion >= Build.VERSION_CODES.N_MR1) initShortcuts()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -472,6 +472,12 @@
|
|||||||
<string name="all_copied">Copied</string>
|
<string name="all_copied">Copied</string>
|
||||||
<string name="all_undo">Undo</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-->
|
<!--Errors-->
|
||||||
<string name="error_no_internet">No internet connection</string>
|
<string name="error_no_internet">No internet connection</string>
|
||||||
|
104
app/src/play/java/io/github/wulkanowy/utils/UpdateHelper.kt
Normal file
104
app/src/play/java/io/github/wulkanowy/utils/UpdateHelper.kt
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -16,7 +16,7 @@ buildscript {
|
|||||||
classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
|
classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
|
||||||
classpath 'com.google.gms:google-services:4.3.4'
|
classpath 'com.google.gms:google-services:4.3.4'
|
||||||
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.3.0'
|
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 "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.0"
|
||||||
classpath "gradle.plugin.com.star-zero.gradle:githook:1.2.0"
|
classpath "gradle.plugin.com.star-zero.gradle:githook:1.2.0"
|
||||||
classpath "com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin:$about_libraries"
|
classpath "com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin:$about_libraries"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user