App udpate activity #880
This commit is contained in:
@@ -245,6 +245,9 @@
|
||||
<activity
|
||||
android:name="org.koitharu.kotatsu.alternatives.ui.AlternativesActivity"
|
||||
android:label="@string/alternatives" />
|
||||
<activity
|
||||
android:name="org.koitharu.kotatsu.settings.about.AppUpdateActivity"
|
||||
android:label="@string/app_update_available" />
|
||||
|
||||
<service
|
||||
android:name="androidx.work.impl.foreground.SystemForegroundService"
|
||||
@@ -357,13 +360,6 @@
|
||||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/widget_recent" />
|
||||
</receiver>
|
||||
<receiver
|
||||
android:name="org.koitharu.kotatsu.settings.about.UpdateDownloadReceiver"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.DOWNLOAD_COMPLETE" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<receiver
|
||||
android:name="org.koitharu.kotatsu.core.ErrorReporterReceiver"
|
||||
android:exported="false">
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.koitharu.kotatsu.main.ui
|
||||
|
||||
import android.Manifest
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager.PERMISSION_GRANTED
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
@@ -66,7 +67,7 @@ import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionFragment
|
||||
import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionListener
|
||||
import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionViewModel
|
||||
import org.koitharu.kotatsu.settings.SettingsActivity
|
||||
import org.koitharu.kotatsu.settings.about.AppUpdateDialog
|
||||
import org.koitharu.kotatsu.settings.about.AppUpdateActivity
|
||||
import javax.inject.Inject
|
||||
import com.google.android.material.R as materialR
|
||||
|
||||
@@ -84,7 +85,6 @@ class MainActivity : BaseActivity<ActivityMainBinding>(), AppBarOwner, BottomNav
|
||||
private val viewModel by viewModels<MainViewModel>()
|
||||
private val searchSuggestionViewModel by viewModels<SearchSuggestionViewModel>()
|
||||
private val closeSearchCallback = CloseSearchCallback()
|
||||
private val appUpdateDialog = AppUpdateDialog(this)
|
||||
private lateinit var navigationDelegate: MainNavigationDelegate
|
||||
private lateinit var appUpdateBadge: OptionsMenuBadgeHelper
|
||||
|
||||
@@ -190,9 +190,8 @@ class MainActivity : BaseActivity<ActivityMainBinding>(), AppBarOwner, BottomNav
|
||||
}
|
||||
|
||||
R.id.action_app_update -> {
|
||||
viewModel.appUpdate.value?.also {
|
||||
appUpdateDialog.show(it)
|
||||
} != null
|
||||
startActivity(Intent(this, AppUpdateActivity::class.java))
|
||||
true
|
||||
}
|
||||
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
|
||||
@@ -29,7 +29,6 @@ import org.koitharu.kotatsu.databinding.ActivitySettingsBinding
|
||||
import org.koitharu.kotatsu.main.ui.owners.AppBarOwner
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.settings.about.AboutSettingsFragment
|
||||
import org.koitharu.kotatsu.settings.about.AppUpdateDialog
|
||||
import org.koitharu.kotatsu.settings.sources.SourceSettingsFragment
|
||||
import org.koitharu.kotatsu.settings.sources.SourcesSettingsFragment
|
||||
import org.koitharu.kotatsu.settings.sources.manage.SourcesManageFragment
|
||||
@@ -43,8 +42,6 @@ class SettingsActivity :
|
||||
AppBarOwner,
|
||||
FragmentManager.OnBackStackChangedListener {
|
||||
|
||||
val appUpdateDialog = AppUpdateDialog(this)
|
||||
|
||||
override val appBar: AppBarLayout
|
||||
get() = viewBinding.appbar
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@ import org.koitharu.kotatsu.core.ui.BasePreferenceFragment
|
||||
import org.koitharu.kotatsu.core.util.ShareHelper
|
||||
import org.koitharu.kotatsu.core.util.ext.observe
|
||||
import org.koitharu.kotatsu.core.util.ext.observeEvent
|
||||
import org.koitharu.kotatsu.settings.SettingsActivity
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
@@ -77,7 +76,7 @@ class AboutSettingsFragment : BasePreferenceFragment(R.string.about) {
|
||||
Snackbar.make(listView, R.string.no_update_available, Snackbar.LENGTH_SHORT).show()
|
||||
return
|
||||
}
|
||||
(activity as SettingsActivity).appUpdateDialog.show(version)
|
||||
startActivity(Intent(requireContext(), AppUpdateActivity::class.java))
|
||||
}
|
||||
|
||||
private fun openLink(url: String, title: CharSequence?) {
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
package org.koitharu.kotatsu.settings.about
|
||||
|
||||
import android.Manifest
|
||||
import android.app.DownloadManager
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.activity.viewModels
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.core.net.toUri
|
||||
import androidx.core.text.buildSpannedString
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.noties.markwon.Markwon
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.github.AppVersion
|
||||
import org.koitharu.kotatsu.core.ui.BaseActivity
|
||||
import org.koitharu.kotatsu.core.util.FileSize
|
||||
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
|
||||
import org.koitharu.kotatsu.core.util.ext.observe
|
||||
import org.koitharu.kotatsu.core.util.ext.observeEvent
|
||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
||||
import org.koitharu.kotatsu.core.util.ext.setTextAndVisible
|
||||
import org.koitharu.kotatsu.core.util.ext.showOrHide
|
||||
import org.koitharu.kotatsu.core.util.ext.textAndVisible
|
||||
import org.koitharu.kotatsu.databinding.ActivityAppUpdateBinding
|
||||
|
||||
@AndroidEntryPoint
|
||||
class AppUpdateActivity : BaseActivity<ActivityAppUpdateBinding>(), View.OnClickListener {
|
||||
|
||||
private val viewModel: AppUpdateViewModel by viewModels()
|
||||
private lateinit var downloadReceiver: UpdateDownloadReceiver
|
||||
|
||||
private val permissionRequest = registerForActivityResult(
|
||||
ActivityResultContracts.RequestPermission(),
|
||||
) {
|
||||
if (it) {
|
||||
viewModel.startDownload()
|
||||
} else {
|
||||
openInBrowser()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(ActivityAppUpdateBinding.inflate(layoutInflater))
|
||||
downloadReceiver = UpdateDownloadReceiver(viewModel)
|
||||
viewModel.nextVersion.observe(this, ::onNextVersionChanged)
|
||||
viewBinding.buttonCancel.setOnClickListener(this)
|
||||
viewBinding.buttonUpdate.setOnClickListener(this)
|
||||
|
||||
ContextCompat.registerReceiver(
|
||||
this,
|
||||
downloadReceiver,
|
||||
IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE),
|
||||
ContextCompat.RECEIVER_EXPORTED,
|
||||
)
|
||||
combine(viewModel.isLoading, viewModel.downloadProgress, ::Pair)
|
||||
.observe(this, ::onProgressChanged)
|
||||
viewModel.downloadState.observe(this, ::onDownloadStateChanged)
|
||||
viewModel.onError.observeEvent(this, ::onError)
|
||||
viewModel.onDownloadDone.observeEvent(this) { intent ->
|
||||
try {
|
||||
startActivity(intent)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
e.printStackTraceDebug()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
unregisterReceiver(downloadReceiver)
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override fun onClick(v: View) {
|
||||
when (v.id) {
|
||||
R.id.button_cancel -> finishAfterTransition()
|
||||
R.id.button_update -> doUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onWindowInsetsChanged(insets: Insets) {
|
||||
val basePadding = resources.getDimensionPixelOffset(R.dimen.screen_padding)
|
||||
viewBinding.root.setPadding(
|
||||
basePadding + insets.left,
|
||||
basePadding + insets.top,
|
||||
basePadding + insets.right,
|
||||
basePadding + insets.bottom,
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun onNextVersionChanged(version: AppVersion?) {
|
||||
viewBinding.buttonUpdate.isEnabled = version != null && !viewModel.isLoading.value
|
||||
if (version == null) {
|
||||
viewBinding.textViewContent.setText(R.string.loading_)
|
||||
return
|
||||
}
|
||||
val message = withContext(Dispatchers.Default) {
|
||||
buildSpannedString {
|
||||
append(getString(R.string.new_version_s, version.name))
|
||||
appendLine()
|
||||
append(getString(R.string.size_s, FileSize.BYTES.format(this@AppUpdateActivity, version.apkSize)))
|
||||
appendLine()
|
||||
appendLine()
|
||||
append(Markwon.create(this@AppUpdateActivity).toMarkdown(version.description))
|
||||
}
|
||||
}
|
||||
viewBinding.textViewContent.setText(message, TextView.BufferType.SPANNABLE)
|
||||
}
|
||||
|
||||
private fun doUpdate() {
|
||||
viewModel.installIntent.value?.let { intent ->
|
||||
try {
|
||||
startActivity(intent)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
onError(e)
|
||||
}
|
||||
return
|
||||
}
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||
permissionRequest.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
} else {
|
||||
viewModel.startDownload()
|
||||
}
|
||||
}
|
||||
|
||||
private fun openInBrowser() {
|
||||
val latestVersion = viewModel.nextVersion.value ?: return
|
||||
val intent = Intent(Intent.ACTION_VIEW, latestVersion.url.toUri())
|
||||
startActivity(Intent.createChooser(intent, getString(R.string.open_in_browser)))
|
||||
}
|
||||
|
||||
private fun onProgressChanged(value: Pair<Boolean, Float>) {
|
||||
val (isLoading, downloadProgress) = value
|
||||
val indicator = viewBinding.progressBar
|
||||
indicator.showOrHide(isLoading)
|
||||
indicator.isIndeterminate = downloadProgress <= 0f
|
||||
if (downloadProgress > 0f) {
|
||||
indicator.setProgressCompat((indicator.max * downloadProgress).toInt(), true)
|
||||
}
|
||||
viewBinding.buttonUpdate.isEnabled = !isLoading && viewModel.nextVersion.value != null
|
||||
}
|
||||
|
||||
private fun onDownloadStateChanged(state: Int) {
|
||||
val message = when (state) {
|
||||
DownloadManager.STATUS_FAILED -> R.string.error_occurred
|
||||
DownloadManager.STATUS_PAUSED -> R.string.downloads_paused
|
||||
else -> 0
|
||||
}
|
||||
viewBinding.textViewError.setTextAndVisible(message)
|
||||
}
|
||||
|
||||
private fun onError(e: Throwable) {
|
||||
viewBinding.textViewError.textAndVisible = e.getDisplayMessage(resources)
|
||||
}
|
||||
|
||||
private class UpdateDownloadReceiver(
|
||||
private val viewModel: AppUpdateViewModel,
|
||||
) : BroadcastReceiver() {
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
when (intent.action) {
|
||||
DownloadManager.ACTION_DOWNLOAD_COMPLETE -> {
|
||||
viewModel.onDownloadComplete(intent)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
package org.koitharu.kotatsu.settings.about
|
||||
|
||||
import android.Manifest
|
||||
import android.app.DownloadManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Environment
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.net.toUri
|
||||
import androidx.core.text.buildSpannedString
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import io.noties.markwon.Markwon
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.github.AppVersion
|
||||
import org.koitharu.kotatsu.core.util.FileSize
|
||||
import org.koitharu.kotatsu.core.util.ext.DIALOG_THEME_CENTERED
|
||||
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
|
||||
|
||||
class AppUpdateDialog(private val activity: AppCompatActivity) {
|
||||
|
||||
private lateinit var latestVersion: AppVersion
|
||||
|
||||
private val permissionRequest = activity.registerForActivityResult(
|
||||
ActivityResultContracts.RequestPermission(),
|
||||
) {
|
||||
if (it) {
|
||||
downloadUpdateImpl()
|
||||
} else {
|
||||
openInBrowser()
|
||||
}
|
||||
}
|
||||
|
||||
fun show(version: AppVersion) {
|
||||
latestVersion = version
|
||||
val message = buildSpannedString {
|
||||
append(activity.getString(R.string.new_version_s, version.name))
|
||||
appendLine()
|
||||
append(activity.getString(R.string.size_s, FileSize.BYTES.format(activity, version.apkSize)))
|
||||
appendLine()
|
||||
appendLine()
|
||||
append(Markwon.create(activity).toMarkdown(version.description))
|
||||
}
|
||||
MaterialAlertDialogBuilder(activity, DIALOG_THEME_CENTERED)
|
||||
.setTitle(R.string.app_update_available)
|
||||
.setMessage(message)
|
||||
.setIcon(R.drawable.ic_app_update)
|
||||
.setNeutralButton(R.string.open_in_browser) { _, _ ->
|
||||
val intent = Intent(Intent.ACTION_VIEW, version.url.toUri())
|
||||
activity.startActivity(Intent.createChooser(intent, activity.getString(R.string.open_in_browser)))
|
||||
}.setPositiveButton(R.string.update) { _, _ ->
|
||||
downloadUpdate()
|
||||
}.setNegativeButton(android.R.string.cancel, null)
|
||||
.setCancelable(false)
|
||||
.create()
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun downloadUpdate() {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||
permissionRequest.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
} else {
|
||||
downloadUpdateImpl()
|
||||
}
|
||||
}
|
||||
|
||||
private fun downloadUpdateImpl() = runCatching {
|
||||
val version = latestVersion
|
||||
val url = version.apkUrl.toUri()
|
||||
val dm = activity.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
||||
val request = DownloadManager.Request(url)
|
||||
.setTitle("${activity.getString(R.string.app_name)} v${version.name}")
|
||||
.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, url.lastPathSegment)
|
||||
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
|
||||
.setMimeType("application/vnd.android.package-archive")
|
||||
dm.enqueue(request)
|
||||
}.onSuccess {
|
||||
Toast.makeText(activity, R.string.download_started, Toast.LENGTH_SHORT).show()
|
||||
}.onFailure { e ->
|
||||
Toast.makeText(activity, e.getDisplayMessage(activity.resources), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
private fun openInBrowser() {
|
||||
val intent = Intent(Intent.ACTION_VIEW, latestVersion.url.toUri())
|
||||
activity.startActivity(Intent.createChooser(intent, activity.getString(R.string.open_in_browser)))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package org.koitharu.kotatsu.settings.about
|
||||
|
||||
import android.app.DownloadManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Environment
|
||||
import androidx.core.net.toUri
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.isActive
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.github.AppUpdateRepository
|
||||
import org.koitharu.kotatsu.core.ui.BaseViewModel
|
||||
import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
|
||||
import org.koitharu.kotatsu.core.util.ext.call
|
||||
import org.koitharu.kotatsu.core.util.ext.requireValue
|
||||
import javax.inject.Inject
|
||||
import kotlin.coroutines.coroutineContext
|
||||
|
||||
@HiltViewModel
|
||||
class AppUpdateViewModel @Inject constructor(
|
||||
private val repository: AppUpdateRepository,
|
||||
@ApplicationContext context: Context,
|
||||
) : BaseViewModel() {
|
||||
|
||||
val nextVersion = repository.observeAvailableUpdate()
|
||||
val downloadProgress = MutableStateFlow(-1f)
|
||||
val downloadState = MutableStateFlow(DownloadManager.STATUS_PENDING)
|
||||
val installIntent = MutableStateFlow<Intent?>(null)
|
||||
val onDownloadDone = MutableEventFlow<Intent>()
|
||||
|
||||
private val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
||||
private val appName = context.getString(R.string.app_name)
|
||||
|
||||
init {
|
||||
if (nextVersion.value == null) {
|
||||
launchLoadingJob(Dispatchers.Default) {
|
||||
repository.fetchUpdate()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun startDownload() {
|
||||
launchLoadingJob(Dispatchers.Default) {
|
||||
val version = nextVersion.requireValue()
|
||||
val url = version.apkUrl.toUri()
|
||||
val request = DownloadManager.Request(url)
|
||||
.setTitle("$appName v${version.name}")
|
||||
.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, url.lastPathSegment)
|
||||
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
|
||||
.setMimeType("application/vnd.android.package-archive")
|
||||
val downloadId = downloadManager.enqueue(request)
|
||||
observeDownload(downloadId)
|
||||
}
|
||||
}
|
||||
|
||||
fun onDownloadComplete(intent: Intent) {
|
||||
launchLoadingJob(Dispatchers.Default) {
|
||||
val downloadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, 0L)
|
||||
if (downloadId == 0L) {
|
||||
return@launchLoadingJob
|
||||
}
|
||||
@Suppress("DEPRECATION")
|
||||
val installerIntent = Intent(Intent.ACTION_INSTALL_PACKAGE)
|
||||
installerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
installerIntent.setDataAndType(
|
||||
downloadManager.getUriForDownloadedFile(downloadId),
|
||||
downloadManager.getMimeTypeForDownloadedFile(downloadId),
|
||||
)
|
||||
installerIntent.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true)
|
||||
installIntent.value = installerIntent
|
||||
onDownloadDone.call(installerIntent)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun observeDownload(id: Long) {
|
||||
val query = DownloadManager.Query()
|
||||
query.setFilterById(id)
|
||||
while (coroutineContext.isActive) {
|
||||
downloadManager.query(query).use { cursor ->
|
||||
if (cursor.moveToFirst()) {
|
||||
val bytesDownloaded = cursor.getLong(
|
||||
cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR),
|
||||
)
|
||||
val bytesTotal = cursor.getLong(
|
||||
cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_TOTAL_SIZE_BYTES),
|
||||
)
|
||||
downloadProgress.value = bytesDownloaded.toFloat() / bytesTotal
|
||||
val state = cursor.getInt(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS))
|
||||
downloadState.value = state
|
||||
if (state == DownloadManager.STATUS_SUCCESSFUL || state == DownloadManager.STATUS_FAILED) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
delay(100)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package org.koitharu.kotatsu.settings.about
|
||||
|
||||
import android.app.DownloadManager
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
||||
|
||||
|
||||
class UpdateDownloadReceiver : BroadcastReceiver() {
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
when (intent.action) {
|
||||
DownloadManager.ACTION_DOWNLOAD_COMPLETE -> {
|
||||
val downloadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, 0L)
|
||||
if (downloadId == 0L) {
|
||||
return
|
||||
}
|
||||
val dm = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
val installIntent = Intent(Intent.ACTION_INSTALL_PACKAGE)
|
||||
installIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
installIntent.setDataAndType(
|
||||
dm.getUriForDownloadedFile(downloadId),
|
||||
dm.getMimeTypeForDownloadedFile(downloadId),
|
||||
)
|
||||
installIntent.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true)
|
||||
try {
|
||||
context.startActivity(installIntent)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
e.printStackTraceDebug()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
95
app/src/main/res/layout/activity_app_update.xml
Normal file
95
app/src/main/res/layout/activity_app_update.xml
Normal file
@@ -0,0 +1,95 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="@dimen/screen_padding">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView_title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:drawablePadding="16dp"
|
||||
android:gravity="center_horizontal"
|
||||
android:text="@string/app_update_available"
|
||||
android:textAppearance="?textAppearanceHeadline5"
|
||||
app:drawableTint="?colorPrimary"
|
||||
app:drawableTopCompat="@drawable/ic_app_update"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.google.android.material.progressindicator.LinearProgressIndicator
|
||||
android:id="@+id/progressBar"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/screen_padding"
|
||||
android:max="100"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/textView_title"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView_error"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:textColor="?colorError"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/progressBar"
|
||||
tools:text="@string/error_corrupted_file"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<ScrollView
|
||||
android:id="@+id/scrollView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginVertical="@dimen/screen_padding"
|
||||
app:layout_constraintBottom_toTopOf="@id/barrier"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/textView_error">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?textAppearanceBodyMedium"
|
||||
tools:text="@tools:sample/lorem/random" />
|
||||
|
||||
</ScrollView>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/button_cancel"
|
||||
style="@style/Widget.Material3.Button.OutlinedButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@android:string/cancel"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/button_update"
|
||||
style="@style/Widget.Material3.Button.TonalButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:enabled="false"
|
||||
android:text="@string/update"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
android:id="@+id/barrier"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:barrierDirection="top"
|
||||
app:constraint_referenced_ids="button_cancel,button_update" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
Reference in New Issue
Block a user