mirror of
https://github.com/FWGS/xash3d-fwgs.git
synced 2026-08-05 03:24:56 +08:00
186 lines
6.1 KiB
Kotlin
186 lines
6.1 KiB
Kotlin
package su.xash.engine
|
|
|
|
import android.content.ActivityNotFoundException
|
|
import android.content.Context
|
|
import android.content.Intent
|
|
import android.os.Bundle
|
|
import android.provider.Settings
|
|
import androidx.appcompat.app.AlertDialog
|
|
import androidx.appcompat.app.AppCompatActivity
|
|
import androidx.core.net.toUri
|
|
import androidx.lifecycle.lifecycleScope
|
|
import androidx.navigation.NavController
|
|
import androidx.navigation.fragment.NavHostFragment
|
|
import androidx.navigation.ui.AppBarConfiguration
|
|
import androidx.navigation.ui.navigateUp
|
|
import androidx.navigation.ui.setupActionBarWithNavController
|
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|
import kotlinx.coroutines.launch
|
|
import su.xash.engine.databinding.ActivityMainBinding
|
|
import su.xash.engine.model.AppUpdater
|
|
import su.xash.engine.util.CrashReports
|
|
import su.xash.engine.util.monospaceTextView
|
|
import su.xash.engine.util.showDownloadProgressDialog
|
|
import java.io.File
|
|
import java.text.SimpleDateFormat
|
|
import java.util.Date
|
|
import java.util.Locale
|
|
|
|
class MainActivity : AppCompatActivity() {
|
|
private lateinit var binding: ActivityMainBinding
|
|
private lateinit var appBarConfiguration: AppBarConfiguration
|
|
private lateinit var navController: NavController
|
|
|
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|
super.onCreate(savedInstanceState)
|
|
|
|
binding = ActivityMainBinding.inflate(layoutInflater)
|
|
setContentView(binding.root)
|
|
|
|
setSupportActionBar(binding.toolbar)
|
|
|
|
val navHostFragment =
|
|
supportFragmentManager.findFragmentById(R.id.fragmentContainerView) as NavHostFragment
|
|
navController = navHostFragment.navController
|
|
appBarConfiguration = AppBarConfiguration(navController.graph)
|
|
setupActionBarWithNavController(navController, appBarConfiguration)
|
|
|
|
CrashReports.prune(this)
|
|
showPendingCrashReport()
|
|
|
|
checkForEngineUpdate()
|
|
}
|
|
|
|
private fun checkForEngineUpdate() {
|
|
val prefs = getSharedPreferences(UPDATE_PREFS, Context.MODE_PRIVATE)
|
|
val now = System.currentTimeMillis()
|
|
if (now - prefs.getLong(KEY_LAST_CHECK, 0L) < CHECK_INTERVAL_MS)
|
|
return
|
|
|
|
val updater = AppUpdater(this)
|
|
lifecycleScope.launch {
|
|
val info = updater.checkForUpdate()
|
|
prefs.edit().putLong(KEY_LAST_CHECK, now).apply()
|
|
if (info == null)
|
|
return@launch
|
|
if (prefs.getInt(KEY_DISMISSED_BUILDNUM, -1) >= info.buildNum)
|
|
return@launch
|
|
val changelog = updater.fetchChangelog(BuildConfig.GIT_HASH, info.tagName)
|
|
showEngineUpdateDialog(updater, info.buildNum, changelog, prefs)
|
|
}
|
|
}
|
|
|
|
private fun showEngineUpdateDialog(
|
|
updater: AppUpdater,
|
|
remoteBuildNum: Int,
|
|
changelog: List<AppUpdater.CommitInfo>?,
|
|
prefs: android.content.SharedPreferences,
|
|
) {
|
|
val builder = MaterialAlertDialogBuilder(this)
|
|
.setTitle(R.string.engine_update_available)
|
|
.setMessage(getString(R.string.engine_update_message, remoteBuildNum))
|
|
.setPositiveButton(R.string.engine_update_download) { _, _ ->
|
|
showEngineDownloadDialog(updater)
|
|
}
|
|
.setNegativeButton(R.string.engine_update_later) { _, _ ->
|
|
prefs.edit().putInt(KEY_DISMISSED_BUILDNUM, remoteBuildNum).apply()
|
|
}
|
|
|
|
if (!changelog.isNullOrEmpty()) {
|
|
val text = buildString {
|
|
append(getString(R.string.engine_update_changelog_header))
|
|
val shown = changelog.take(CHANGELOG_MAX_LINES)
|
|
for (c in shown)
|
|
append("\n• ").append(c.subject)
|
|
val extra = changelog.size - shown.size
|
|
if (extra > 0)
|
|
append("\n").append(getString(R.string.engine_update_changelog_more, extra))
|
|
}
|
|
builder.setView(monospaceTextView(this, text))
|
|
}
|
|
|
|
builder.show()
|
|
}
|
|
|
|
private fun showEngineDownloadDialog(updater: AppUpdater) {
|
|
if (!updater.canInstall()) {
|
|
promptForInstallPermission()
|
|
return
|
|
}
|
|
showDownloadProgressDialog(
|
|
ctx = this,
|
|
titleRes = R.string.engine_update_downloading,
|
|
cancelable = true,
|
|
scope = lifecycleScope,
|
|
download = { onProgress -> updater.downloadAndInstall(onProgress) },
|
|
)
|
|
}
|
|
|
|
private fun promptForInstallPermission() {
|
|
MaterialAlertDialogBuilder(this)
|
|
.setTitle(R.string.engine_update_permission_needed)
|
|
.setMessage(R.string.engine_update_permission_message)
|
|
.setPositiveButton(R.string.engine_update_open_settings) { _, _ ->
|
|
val packageIntent = Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES,
|
|
"package:$packageName".toUri())
|
|
try {
|
|
startActivity(packageIntent)
|
|
} catch (_: ActivityNotFoundException) {
|
|
try {
|
|
startActivity(Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES))
|
|
} catch (_: ActivityNotFoundException) {
|
|
// no settings screen — nothing more we can do
|
|
}
|
|
}
|
|
}
|
|
.setNegativeButton(android.R.string.cancel, null)
|
|
.show()
|
|
}
|
|
|
|
override fun onSupportNavigateUp(): Boolean {
|
|
return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
|
|
}
|
|
|
|
private fun showPendingCrashReport() {
|
|
val pending = CrashReports.pendingStacktrace(this)
|
|
if (!pending.exists() || pending.length() == 0L)
|
|
return
|
|
|
|
val historyDir = CrashReports.historyDir(this).apply { mkdirs() }
|
|
val ts = SimpleDateFormat("yyyyMMdd-HHmmss", Locale.US).format(Date())
|
|
val entryDir = File(historyDir, "crash-$ts").apply { mkdirs() }
|
|
|
|
moveOrCopy(pending, File(entryDir, CrashReports.STACKTRACE_NAME))
|
|
moveOrCopy(CrashReports.pendingSysinfo(this), File(entryDir, CrashReports.SYSINFO_NAME))
|
|
moveOrCopy(CrashReports.pendingIntent(this), File(entryDir, CrashReports.INTENT_NAME))
|
|
|
|
val entry = CrashReports.Entry(entryDir)
|
|
AlertDialog.Builder(this)
|
|
.setTitle(R.string.crash_dialog_title)
|
|
.setView(monospaceTextView(this, entry.summary()))
|
|
.setPositiveButton(R.string.crash_send_to_developers) { _, _ -> CrashReports.sendByEmail(this, entry) }
|
|
.setNeutralButton(R.string.crash_share) { _, _ -> CrashReports.share(this, entry) }
|
|
.setNegativeButton(R.string.crash_dismiss, null)
|
|
.show()
|
|
}
|
|
|
|
private fun moveOrCopy(src: File, dst: File) {
|
|
if (!src.exists())
|
|
return
|
|
|
|
if (src.renameTo(dst))
|
|
return
|
|
|
|
dst.writeText(src.readText())
|
|
src.delete()
|
|
}
|
|
|
|
companion object {
|
|
private const val CHANGELOG_MAX_LINES = 15
|
|
private const val UPDATE_PREFS = "app_updater"
|
|
private const val KEY_LAST_CHECK = "last_check_ms"
|
|
private const val KEY_DISMISSED_BUILDNUM = "dismissed_buildnum"
|
|
private const val CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000L
|
|
}
|
|
}
|