package com.ea.ironmonkey import android.content.Intent import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material3.Button import androidx.compose.material3.LinearProgressIndicator import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.darkColorScheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import kotlin.concurrent.thread /** * First-launch unpacking of the game data bundled in the APK. * * Shown only when [GameDataInstaller.isInstalled] is false, so it appears once * after install and never again. It exists because copying ~595 MB takes long * enough that a tester staring at a frozen launcher would reasonably assume the * game had hung. */ class GameDataUnpackActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { enableEdgeToEdge() super.onCreate(savedInstanceState) if (GameDataInstaller.isInstalled(this)) { startGame() return } var fraction by mutableFloatStateOf(0f) var copiedMb by mutableStateOf(0L) var totalMb by mutableStateOf(0L) var error by mutableStateOf(null) setContent { MaterialTheme(colorScheme = darkColorScheme()) { UnpackScreen( fraction = fraction, copiedMb = copiedMb, totalMb = totalMb, error = error, onRetry = { recreate() }, ) } } thread(name = "game-data-unpack", isDaemon = true) { val result = GameDataInstaller.install(this) { copied, total -> // Updating Compose state is safe from any thread; the recomposition // is scheduled onto the main thread by the snapshot system. fraction = if (total > 0) copied.toFloat() / total else 0f copiedMb = copied / 1_048_576 totalMb = total / 1_048_576 } runOnUiThread { when (result) { is GameDataInstaller.Result.Ready -> startGame() is GameDataInstaller.Result.Failed -> error = result.message } } } } private fun startGame() { startActivity(Intent(this, GameActivityMain::class.java)) finish() } } @Composable private fun UnpackScreen( fraction: Float, copiedMb: Long, totalMb: Long, error: String?, onRetry: () -> Unit, ) { Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> Column( modifier = Modifier .fillMaxSize() .padding(innerPadding) .padding(24.dp), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally, ) { if (error != null) { Text( text = "Не удалось подготовить игровые данные", style = MaterialTheme.typography.headlineSmall, textAlign = TextAlign.Center, ) Text( text = error, style = MaterialTheme.typography.bodyMedium, textAlign = TextAlign.Center, modifier = Modifier.padding(top = 12.dp), ) Button(onClick = onRetry, modifier = Modifier.padding(top = 24.dp)) { Text("Повторить") } } else { Text( text = "Подготовка игровых данных", style = MaterialTheme.typography.headlineSmall, textAlign = TextAlign.Center, ) Text( text = "Выполняется один раз после установки.", style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant, textAlign = TextAlign.Center, modifier = Modifier.padding(top = 8.dp), ) LinearProgressIndicator( progress = { fraction }, modifier = Modifier .fillMaxWidth() .padding(top = 24.dp), ) Text( text = if (totalMb > 0) "$copiedMb из $totalMb МБ" else "…", style = MaterialTheme.typography.bodySmall, modifier = Modifier.padding(top = 12.dp), ) } } } }