forked from geode-sdk/android-launcher
replace updater with packageinstaller
This commit is contained in:
5 files changed
+243
-46
No files matched your search
@@ -20,7 +20,8 @@
|
|||||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||||
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
|
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
|
||||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
|
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||||
|
<uses-permission android:name="android.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION" />
|
||||||
<uses-permission android:name="android.permission.VIBRATE" />
|
<uses-permission android:name="android.permission.VIBRATE" />
|
||||||
<uses-permission android:name="android.permission.LIGHTS" />
|
<uses-permission android:name="android.permission.LIGHTS" />
|
||||||
|
|
||||||
@@ -38,14 +39,18 @@
|
|||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:appCategory="game"
|
android:appCategory="game"
|
||||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
|
android:enableOnBackInvokedCallback="true"
|
||||||
android:fullBackupContent="@xml/backup_rules"
|
android:fullBackupContent="@xml/backup_rules"
|
||||||
|
android:hasFragileUserData="true"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:hasFragileUserData="true"
|
|
||||||
android:requestLegacyExternalStorage="true"
|
android:requestLegacyExternalStorage="true"
|
||||||
android:enableOnBackInvokedCallback="true"
|
|
||||||
android:theme="@style/Theme.GeodeLauncher"
|
android:theme="@style/Theme.GeodeLauncher"
|
||||||
tools:targetApi="35">
|
tools:targetApi="35">
|
||||||
|
<receiver
|
||||||
|
android:name=".InstallReceiver"
|
||||||
|
android:enabled="true"
|
||||||
|
android:exported="false" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".LegacyMainActivity"
|
android:name=".LegacyMainActivity"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
@@ -74,15 +79,14 @@
|
|||||||
android:name=".preferences.SettingsActivity"
|
android:name=".preferences.SettingsActivity"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:label="@string/title_activity_settings"
|
android:label="@string/title_activity_settings"
|
||||||
android:theme="@style/Theme.GeodeLauncher">
|
android:theme="@style/Theme.GeodeLauncher" />
|
||||||
</activity>
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".GeometryDashActivity"
|
android:name=".GeometryDashActivity"
|
||||||
|
android:configChanges="orientation|screenSize|smallestScreenSize"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:hardwareAccelerated="true"
|
android:hardwareAccelerated="true"
|
||||||
android:screenOrientation="landscape"
|
android:screenOrientation="landscape"
|
||||||
android:theme="@style/Theme.NoActionBar.Fullscreen"
|
android:theme="@style/Theme.NoActionBar.Fullscreen"
|
||||||
android:configChanges="orientation|screenSize|smallestScreenSize"
|
|
||||||
android:windowSoftInputMode="stateAlwaysHidden">
|
android:windowSoftInputMode="stateAlwaysHidden">
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.app.lib_name"
|
android:name="android.app.lib_name"
|
||||||
@@ -94,45 +98,47 @@
|
|||||||
android:theme="@style/Theme.GeodeLauncher">
|
android:theme="@style/Theme.GeodeLauncher">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity-alias
|
<activity-alias
|
||||||
android:name=".MainActivityGeode"
|
android:name=".MainActivityTrans"
|
||||||
android:enabled="false"
|
android:enabled="false"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
|
android:icon="@mipmap/ic_launcher_trans"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:icon="@mipmap/ic_launcher_geode"
|
|
||||||
android:targetActivity=".MainActivity">
|
android:targetActivity=".MainActivity">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity-alias>
|
</activity-alias>
|
||||||
|
|
||||||
<activity-alias
|
<activity-alias
|
||||||
android:name=".MainActivityPride"
|
android:name=".MainActivityPride"
|
||||||
android:enabled="false"
|
android:enabled="false"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:label="@string/app_name"
|
|
||||||
android:icon="@mipmap/ic_launcher_pride"
|
android:icon="@mipmap/ic_launcher_pride"
|
||||||
|
android:label="@string/app_name"
|
||||||
android:targetActivity=".MainActivity">
|
android:targetActivity=".MainActivity">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity-alias>
|
</activity-alias>
|
||||||
|
|
||||||
<activity-alias
|
<activity-alias
|
||||||
android:name=".MainActivityTrans"
|
android:name=".MainActivityGeode"
|
||||||
android:enabled="false"
|
android:enabled="false"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
|
android:icon="@mipmap/ic_launcher_geode"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:icon="@mipmap/ic_launcher_trans"
|
|
||||||
android:targetActivity=".MainActivity">
|
android:targetActivity=".MainActivity">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity-alias>
|
</activity-alias>
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package com.geode.launcher
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.pm.PackageInstaller
|
||||||
|
import android.widget.Toast
|
||||||
|
import com.geode.launcher.main.clearDownloadedApks
|
||||||
|
|
||||||
|
class InstallReceiver : BroadcastReceiver() {
|
||||||
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
when (val status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, -1)) {
|
||||||
|
PackageInstaller.STATUS_PENDING_USER_ACTION -> {
|
||||||
|
val activityIntent = intent.getParcelableExtra<Intent>(Intent.EXTRA_INTENT)
|
||||||
|
|
||||||
|
if (activityIntent != null) {
|
||||||
|
context.startActivity(activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PackageInstaller.STATUS_SUCCESS -> {
|
||||||
|
clearDownloadedApks(context)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
val msg = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE)
|
||||||
|
println("failed to install update: $status, $msg")
|
||||||
|
|
||||||
|
val message = context.getString(R.string.launcher_self_update_failed, msg)
|
||||||
|
Toast.makeText(context, message, Toast.LENGTH_SHORT)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
package com.geode.launcher.main
|
package com.geode.launcher.main
|
||||||
|
|
||||||
import android.content.ActivityNotFoundException
|
import android.app.PendingIntent
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.content.pm.PackageInstaller
|
||||||
import android.provider.DocumentsContract
|
import android.os.Build
|
||||||
import android.text.format.Formatter
|
import android.text.format.Formatter
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
@@ -16,9 +16,13 @@ import androidx.compose.foundation.layout.offset
|
|||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.ButtonDefaults
|
import androidx.compose.material3.ButtonDefaults
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.CardDefaults
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.material3.ElevatedCard
|
import androidx.compose.material3.ElevatedCard
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.HorizontalDivider
|
import androidx.compose.material3.HorizontalDivider
|
||||||
@@ -33,10 +37,12 @@ import androidx.compose.material3.TextButton
|
|||||||
import androidx.compose.material3.rememberModalBottomSheetState
|
import androidx.compose.material3.rememberModalBottomSheetState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
@@ -48,8 +54,10 @@ import androidx.compose.ui.text.TextLinkStyles
|
|||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.text.style.TextDecoration
|
import androidx.compose.ui.text.style.TextDecoration
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.window.Dialog
|
||||||
|
import androidx.compose.ui.window.DialogProperties
|
||||||
|
import com.geode.launcher.InstallReceiver
|
||||||
import com.geode.launcher.R
|
import com.geode.launcher.R
|
||||||
import com.geode.launcher.UserDirectoryProvider
|
|
||||||
import com.geode.launcher.ui.theme.Typography
|
import com.geode.launcher.ui.theme.Typography
|
||||||
import com.geode.launcher.updater.ReleaseManager
|
import com.geode.launcher.updater.ReleaseManager
|
||||||
import com.geode.launcher.updater.ReleaseViewModel
|
import com.geode.launcher.updater.ReleaseViewModel
|
||||||
@@ -58,13 +66,17 @@ import com.geode.launcher.utils.PreferenceUtils
|
|||||||
import com.mikepenz.markdown.compose.LocalBulletListHandler
|
import com.mikepenz.markdown.compose.LocalBulletListHandler
|
||||||
import com.mikepenz.markdown.m3.Markdown
|
import com.mikepenz.markdown.m3.Markdown
|
||||||
import com.mikepenz.markdown.m3.markdownTypography
|
import com.mikepenz.markdown.m3.markdownTypography
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import org.intellij.markdown.flavours.gfm.GFMFlavourDescriptor
|
import org.intellij.markdown.flavours.gfm.GFMFlavourDescriptor
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.net.ConnectException
|
import java.net.ConnectException
|
||||||
import java.net.UnknownHostException
|
import java.net.UnknownHostException
|
||||||
import java.text.DateFormat
|
import java.text.DateFormat
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import kotlin.time.ExperimentalTime
|
|
||||||
import kotlin.time.toJavaInstant
|
import kotlin.time.toJavaInstant
|
||||||
|
|
||||||
fun clearDownloadedApks(context: Context) {
|
fun clearDownloadedApks(context: Context) {
|
||||||
@@ -84,64 +96,187 @@ fun clearDownloadedApks(context: Context) {
|
|||||||
it.delete()
|
it.delete()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
context.filesDir.listFiles { _, name -> name.lowercase().endsWith(".apk") }?.forEach {
|
||||||
|
it.delete()
|
||||||
|
}
|
||||||
|
|
||||||
preferenceUtils.setBoolean(PreferenceUtils.Key.CLEANUP_APKS, false)
|
preferenceUtils.setBoolean(PreferenceUtils.Key.CLEANUP_APKS, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun generateInstallIntent(uri: Uri): Intent {
|
// lazy singleton idc
|
||||||
// maybe one day i'll rewrite this to use packageinstaller. not today
|
object LauncherUpdater {
|
||||||
return Intent(Intent.ACTION_INSTALL_PACKAGE).apply {
|
enum class LauncherUpdateState {
|
||||||
setDataAndType(uri, "application/vnd.android.package-archive")
|
Began,
|
||||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
Active,
|
||||||
|
Inactive,
|
||||||
|
Finished;
|
||||||
|
}
|
||||||
|
|
||||||
|
private val _uiState = MutableStateFlow<LauncherUpdateState>(LauncherUpdateState.Inactive)
|
||||||
|
val uiState = _uiState.asStateFlow()
|
||||||
|
|
||||||
|
suspend fun resetState() {
|
||||||
|
_uiState.emit(LauncherUpdateState.Inactive)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun installPackage(context: Context, file: File) {
|
||||||
|
if (!file.exists()) {
|
||||||
|
_uiState.emit(LauncherUpdateState.Finished)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_uiState.emit(LauncherUpdateState.Began)
|
||||||
|
|
||||||
|
val installer = context.packageManager.packageInstaller
|
||||||
|
|
||||||
|
installer.registerSessionCallback(object : PackageInstaller.SessionCallback() {
|
||||||
|
override fun onActiveChanged(sessionId: Int, active: Boolean) {
|
||||||
|
if (active) {
|
||||||
|
_uiState.tryEmit(LauncherUpdateState.Active)
|
||||||
|
} else {
|
||||||
|
_uiState.tryEmit(LauncherUpdateState.Began)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBadgingChanged(sessionId: Int) {}
|
||||||
|
|
||||||
|
override fun onCreated(sessionId: Int) {
|
||||||
|
_uiState.tryEmit(LauncherUpdateState.Began)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFinished(sessionId: Int, success: Boolean) {
|
||||||
|
_uiState.tryEmit(LauncherUpdateState.Finished)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onProgressChanged(sessionId: Int, progress: Float) {
|
||||||
|
_uiState.tryEmit(LauncherUpdateState.Active)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
file.inputStream().use { apkStream ->
|
||||||
|
val length = file.length()
|
||||||
|
|
||||||
|
val params = PackageInstaller.SessionParams(
|
||||||
|
PackageInstaller.SessionParams.MODE_FULL_INSTALL,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
params.setRequireUserAction(PackageInstaller.SessionParams.USER_ACTION_NOT_REQUIRED)
|
||||||
|
}
|
||||||
|
|
||||||
|
val sessionId = installer.createSession(params)
|
||||||
|
val session = installer.openSession(sessionId)
|
||||||
|
|
||||||
|
session.openWrite("package", 0, length).use { sessionStream ->
|
||||||
|
apkStream.copyTo(sessionStream)
|
||||||
|
session.fsync(sessionStream)
|
||||||
|
}
|
||||||
|
|
||||||
|
val intent = Intent(context, InstallReceiver::class.java)
|
||||||
|
val pi = PendingIntent.getBroadcast(
|
||||||
|
context,
|
||||||
|
0,
|
||||||
|
intent,
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
|
||||||
|
)
|
||||||
|
|
||||||
|
session.commit(pi.intentSender)
|
||||||
|
session.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun installLauncherUpdate(context: Context) {
|
|
||||||
|
suspend fun installLauncherUpdate(context: Context) {
|
||||||
val nextUpdate = ReleaseManager.get(context).availableLauncherUpdate.value
|
val nextUpdate = ReleaseManager.get(context).availableLauncherUpdate.value
|
||||||
val launcherDownload = nextUpdate?.getDownload()
|
val launcherDownload = nextUpdate?.getDownload()
|
||||||
|
|
||||||
if (launcherDownload != null) {
|
if (launcherDownload != null) {
|
||||||
val outputFile = launcherDownload.filename
|
val outputFile = launcherDownload.filename
|
||||||
val baseDirectory = LaunchUtils.getBaseDirectory(context)
|
val baseDirectory = context.filesDir
|
||||||
|
|
||||||
val outputPathFile = File(baseDirectory, outputFile)
|
val outputPathFile = File(baseDirectory, outputFile)
|
||||||
if (!outputPathFile.exists()) {
|
if (!outputPathFile.exists()) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val outputPath = "${UserDirectoryProvider.ROOT}${outputFile}"
|
|
||||||
|
|
||||||
val uri = DocumentsContract.buildDocumentUri("${context.packageName}.user", outputPath)
|
|
||||||
|
|
||||||
PreferenceUtils.get(context).setBoolean(PreferenceUtils.Key.CLEANUP_APKS, true)
|
PreferenceUtils.get(context).setBoolean(PreferenceUtils.Key.CLEANUP_APKS, true)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val intent = generateInstallIntent(uri)
|
LauncherUpdater.installPackage(context, outputPathFile)
|
||||||
context.startActivity(intent)
|
} catch (e: Exception) {
|
||||||
} catch (_: ActivityNotFoundException) {
|
e.printStackTrace()
|
||||||
// if it fails to install, just open it in the browser
|
val msg = context.getString(R.string.launcher_self_update_failed, e.message)
|
||||||
try {
|
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show()
|
||||||
val downloadUrl = launcherDownload.url
|
|
||||||
val downloadIntent = Intent(Intent.ACTION_VIEW).apply {
|
|
||||||
data = Uri.parse(downloadUrl)
|
|
||||||
}
|
|
||||||
|
|
||||||
context.startActivity(downloadIntent)
|
LauncherUpdater.resetState()
|
||||||
} catch (_: ActivityNotFoundException) {
|
|
||||||
Toast.makeText(context, context.getString(R.string.no_activity_found), Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(context, context.getString(R.string.release_fetch_no_releases), Toast.LENGTH_SHORT).show()
|
Toast.makeText(context, context.getString(R.string.release_fetch_no_releases), Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalTime::class)
|
@Composable
|
||||||
|
fun LauncherUpdateDialog(onDismiss: () -> Unit) {
|
||||||
|
val updateState by LauncherUpdater.uiState.collectAsState()
|
||||||
|
|
||||||
|
val cancellable = updateState != LauncherUpdater.LauncherUpdateState.Active
|
||||||
|
|
||||||
|
LaunchedEffect(updateState) {
|
||||||
|
if (updateState == LauncherUpdater.LauncherUpdateState.Finished) {
|
||||||
|
onDismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cancellable) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Dialog(
|
||||||
|
onDismissRequest = {
|
||||||
|
if (cancellable) onDismiss()
|
||||||
|
},
|
||||||
|
properties = DialogProperties(usePlatformDefaultWidth = false)
|
||||||
|
) {
|
||||||
|
Card(
|
||||||
|
shape = RoundedCornerShape(16.dp),
|
||||||
|
colors = CardDefaults.cardColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.surfaceContainerHigh
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(18.dp),
|
||||||
|
modifier = Modifier.padding(24.dp)
|
||||||
|
) {
|
||||||
|
CircularProgressIndicator()
|
||||||
|
|
||||||
|
Text(
|
||||||
|
stringResource(R.string.launcher_self_update_progress),
|
||||||
|
style = MaterialTheme.typography.bodyLarge
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun LauncherUpdateInformation(onDismiss: () -> Unit) {
|
fun LauncherUpdateInformation(onDismiss: () -> Unit) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
val nextRelease = remember { ReleaseManager.get(context).availableLauncherUpdate.value }
|
val nextRelease = remember { ReleaseManager.get(context).availableLauncherUpdate.value }
|
||||||
val sheetState = rememberModalBottomSheetState()
|
val sheetState = rememberModalBottomSheetState()
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
var beginUpdate by remember { mutableStateOf(false) }
|
||||||
|
if (beginUpdate) {
|
||||||
|
LauncherUpdateDialog(onDismiss = {
|
||||||
|
beginUpdate = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if (nextRelease != null) {
|
if (nextRelease != null) {
|
||||||
ModalBottomSheet(onDismissRequest = { onDismiss() }, sheetState = sheetState) {
|
ModalBottomSheet(onDismissRequest = { onDismiss() }, sheetState = sheetState) {
|
||||||
@@ -165,7 +300,12 @@ fun LauncherUpdateInformation(onDismiss: () -> Unit) {
|
|||||||
|
|
||||||
Row(modifier = Modifier.padding(vertical = 12.dp)) {
|
Row(modifier = Modifier.padding(vertical = 12.dp)) {
|
||||||
Button(
|
Button(
|
||||||
onClick = { installLauncherUpdate(context) },
|
onClick = {
|
||||||
|
coroutineScope.launch {
|
||||||
|
installLauncherUpdate(context)
|
||||||
|
beginUpdate = true
|
||||||
|
}
|
||||||
|
},
|
||||||
) {
|
) {
|
||||||
Icon(painterResource(R.drawable.icon_download), contentDescription = null)
|
Icon(painterResource(R.drawable.icon_download), contentDescription = null)
|
||||||
Spacer(Modifier.size(ButtonDefaults.IconSpacing))
|
Spacer(Modifier.size(ButtonDefaults.IconSpacing))
|
||||||
@@ -218,6 +358,7 @@ fun LauncherUpdateInformation(onDismiss: () -> Unit) {
|
|||||||
@Composable
|
@Composable
|
||||||
fun LauncherUpdateIndicator(modifier: Modifier = Modifier) {
|
fun LauncherUpdateIndicator(modifier: Modifier = Modifier) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
|
||||||
var showInfoDialog by remember { mutableStateOf(false) }
|
var showInfoDialog by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
@@ -227,6 +368,13 @@ fun LauncherUpdateIndicator(modifier: Modifier = Modifier) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var beginUpdate by remember { mutableStateOf(false) }
|
||||||
|
if (beginUpdate) {
|
||||||
|
LauncherUpdateDialog {
|
||||||
|
beginUpdate = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ElevatedCard(modifier) {
|
ElevatedCard(modifier) {
|
||||||
Column(
|
Column(
|
||||||
// buttons add enough padding already, lower to compensate
|
// buttons add enough padding already, lower to compensate
|
||||||
@@ -251,7 +399,12 @@ fun LauncherUpdateIndicator(modifier: Modifier = Modifier) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TextButton(
|
TextButton(
|
||||||
onClick = { installLauncherUpdate(context) },
|
onClick = {
|
||||||
|
coroutineScope.launch {
|
||||||
|
installLauncherUpdate(context)
|
||||||
|
beginUpdate = true
|
||||||
|
}
|
||||||
|
},
|
||||||
) {
|
) {
|
||||||
Text(stringResource(R.string.launcher_install))
|
Text(stringResource(R.string.launcher_install))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ class ReleaseManager private constructor(
|
|||||||
private suspend fun downloadLauncherUpdate(release: Downloadable) {
|
private suspend fun downloadLauncherUpdate(release: Downloadable) {
|
||||||
val download = release.getDownload() ?: return
|
val download = release.getDownload() ?: return
|
||||||
|
|
||||||
val outputDirectory = LaunchUtils.getBaseDirectory(applicationContext)
|
val outputDirectory = applicationContext.filesDir
|
||||||
val outputFile = File(outputDirectory, download.filename)
|
val outputFile = File(outputDirectory, download.filename)
|
||||||
|
|
||||||
if (outputFile.exists()) {
|
if (outputFile.exists()) {
|
||||||
|
|||||||
@@ -64,6 +64,11 @@
|
|||||||
<string name="launcher_notification_compatibility_description">An update is required to receive Geode updates.</string>
|
<string name="launcher_notification_compatibility_description">An update is required to receive Geode updates.</string>
|
||||||
<string name="launcher_notification_update_cta">Tap for more information.</string>
|
<string name="launcher_notification_update_cta">Tap for more information.</string>
|
||||||
|
|
||||||
|
<string name="launcher_self_update_failed_title">Update failed</string>
|
||||||
|
|
||||||
|
<string name="launcher_self_update_failed">Failed to install launcher update: %1$s.</string>
|
||||||
|
<string name="launcher_self_update_progress">Updating Geode Launcher…</string>
|
||||||
|
|
||||||
<string name="launcher_starting_game">Starting Geometry Dash…</string>
|
<string name="launcher_starting_game">Starting Geometry Dash…</string>
|
||||||
<string name="launcher_downloading_update">Downloading update…</string>
|
<string name="launcher_downloading_update">Downloading update…</string>
|
||||||
<string name="launcher_downloading_update_details">(%1$s/%2$s)</string>
|
<string name="launcher_downloading_update_details">(%1$s/%2$s)</string>
|
||||||
|
|||||||
Reference in new issue
Block a user