SEDownloadTab.kt (12016B) - raw


      1 package me.rhunk.snapenhance.manager.ui.tab.impl.download
      2 
      3 import android.os.Bundle
      4 import androidx.activity.ComponentActivity
      5 import androidx.compose.foundation.background
      6 import androidx.compose.foundation.border
      7 import androidx.compose.foundation.clickable
      8 import androidx.compose.foundation.layout.Arrangement
      9 import androidx.compose.foundation.layout.Column
     10 import androidx.compose.foundation.layout.Row
     11 import androidx.compose.foundation.layout.fillMaxWidth
     12 import androidx.compose.foundation.layout.padding
     13 import androidx.compose.foundation.lazy.LazyColumn
     14 import androidx.compose.foundation.lazy.items
     15 import androidx.compose.material.icons.Icons
     16 import androidx.compose.material.icons.filled.Android
     17 import androidx.compose.material3.*
     18 import androidx.compose.runtime.*
     19 import androidx.compose.ui.Alignment
     20 import androidx.compose.ui.Modifier
     21 import androidx.compose.ui.unit.dp
     22 import androidx.compose.ui.unit.sp
     23 import androidx.compose.ui.window.Dialog
     24 import com.google.gson.JsonParser
     25 import kotlinx.coroutines.Dispatchers
     26 import kotlinx.coroutines.launch
     27 import me.rhunk.snapenhance.manager.BuildConfig
     28 import me.rhunk.snapenhance.manager.data.download.SEArtifact
     29 import me.rhunk.snapenhance.manager.data.download.SEVersion
     30 import me.rhunk.snapenhance.manager.ui.components.DowngradeNoticeDialog
     31 import me.rhunk.snapenhance.manager.ui.tab.Tab
     32 import okhttp3.OkHttpClient
     33 import okhttp3.Request
     34 import java.text.SimpleDateFormat
     35 import java.util.Locale
     36 
     37 
     38 class SEDownloadTab : Tab("se_download") {
     39     private fun fetchSEReleases(): List<SEVersion>? {
     40         return runCatching {
     41             val endpoint = Request.Builder().url("https://api.github.com/repos/rhunk/SnapEnhance/releases").build()
     42             val response = OkHttpClient().newCall(endpoint).execute()
     43             if (!response.isSuccessful) return null
     44 
     45             val releases = JsonParser.parseString(response.body.string()).asJsonArray.also {
     46                 if (it.size() == 0) return null
     47             }
     48             val isoDateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.getDefault())
     49 
     50             releases.map { releaseObject ->
     51                 val release = releaseObject.asJsonObject
     52                 val versionName = release.getAsJsonPrimitive("tag_name").asString
     53                 val releaseDate = release.getAsJsonPrimitive("published_at").asString.let { time ->
     54                     isoDateFormat.parse(time)?.let { date ->
     55                         SimpleDateFormat("dd MMM yyyy", Locale.getDefault()).format(date)
     56                     } ?: time
     57                 }
     58                 val downloadAssets = release.getAsJsonArray("assets").associate { asset ->
     59                     val assetObject = asset.asJsonObject
     60                     SEArtifact(
     61                         fileName = assetObject.getAsJsonPrimitive("name").asString,
     62                         size = assetObject.getAsJsonPrimitive("size").asLong,
     63                         downloadUrl = assetObject.getAsJsonPrimitive("browser_download_url").asString
     64                     ).let { it.fileName to it }
     65                 }
     66                 SEVersion(versionName, releaseDate, downloadAssets)
     67             }
     68         }.onFailure {
     69             it.printStackTrace()
     70         }.getOrNull()
     71     }
     72 
     73     override fun init(activity: ComponentActivity) {
     74         super.init(activity)
     75     }
     76 
     77     @Composable
     78     override fun Content() {
     79         val coroutineScope = rememberCoroutineScope()
     80         val snapEnhanceReleases = remember {
     81             mutableStateOf(null as List<SEVersion>?)
     82         }
     83 
     84         var selectedVersion by remember { mutableStateOf(null as SEVersion?) }
     85         var selectedArtifact by remember { mutableStateOf(null as SEArtifact?) }
     86         val snapEnhanceApp = remember {
     87             runCatching { activity.packageManager.getPackageInfo(BuildConfig.APPLICATION_ID, 0) }.getOrNull()
     88         }
     89 
     90         var showDowngradeNotice by remember { mutableStateOf(false) }
     91 
     92         fun triggerPackageInstallation(shouldUninstall: Boolean) {
     93             navigation.navigateTo(InstallPackageTab::class, Bundle().apply {
     94                 putString("downloadPath", selectedArtifact?.downloadUrl)
     95                 putString("appPackage", sharedConfig.snapEnhancePackageName)
     96                 putBoolean("uninstall", shouldUninstall)
     97             }, noHistory = true)
     98         }
     99 
    100         if (showDowngradeNotice) {
    101             Dialog(onDismissRequest = { showDowngradeNotice = false }) {
    102                 DowngradeNoticeDialog(onDismiss = { showDowngradeNotice = false }, onSuccess = {
    103                     triggerPackageInstallation(false)
    104                 })
    105             }
    106         }
    107 
    108         Column(
    109             modifier = Modifier
    110                 .fillMaxWidth()
    111                 .padding(16.dp),
    112             verticalArrangement = Arrangement.spacedBy(16.dp),
    113             horizontalAlignment = Alignment.CenterHorizontally
    114         ) {
    115             Text(text = "Choose SnapEnhance version")
    116 
    117             LazyColumn(
    118                 modifier = Modifier
    119                     .fillMaxWidth()
    120                     .weight(1f),
    121                 verticalArrangement = Arrangement.spacedBy(10.dp)
    122             ) {
    123                 item {
    124                     if (snapEnhanceReleases.value == null) {
    125                         Row(
    126                             horizontalArrangement = Arrangement.Center,
    127                             modifier = Modifier
    128                                 .fillMaxWidth()
    129                                 .padding(16.dp)
    130                         ) {
    131                             CircularProgressIndicator()
    132                         }
    133                     }
    134                 }
    135                 items(snapEnhanceReleases.value ?: listOf()) { version ->
    136                     OutlinedCard(
    137                         shape = MaterialTheme.shapes.small,
    138                         modifier = Modifier
    139                             .clickable {
    140                                 selectedArtifact =
    141                                     if (selectedVersion != version) null else selectedArtifact
    142                                 selectedVersion = if (selectedVersion == version) null else version
    143                             },
    144                     ) {
    145                         Row(
    146                             modifier = Modifier
    147                                 .fillMaxWidth()
    148                                 .padding(16.dp),
    149                             verticalAlignment = Alignment.CenterVertically
    150                         ) {
    151                             Column {
    152                                 Text(text = version.versionName, fontSize = 24.sp)
    153                                 Text(text = "Release ${version.releaseDate}", fontSize = 12.sp)
    154                             }
    155                             Row(
    156                                 modifier = Modifier
    157                                     .weight(1f),
    158                                 horizontalArrangement = Arrangement.End,
    159                                 verticalAlignment = Alignment.CenterVertically
    160                             ) {
    161                                 Text(text = "${version.downloadAssets.size} assets", fontSize = 12.sp)
    162                             }
    163                         }
    164                     }
    165 
    166                     selectedVersion?.takeIf { it == version }?.let { selVersion ->
    167                         Column(
    168                             modifier = Modifier
    169                                 .fillMaxWidth()
    170                                 .padding(10.dp),
    171                             verticalArrangement = Arrangement.spacedBy(16.dp),
    172                         ) {
    173                             selVersion.downloadAssets.values.forEach { artifact ->
    174                                 Row(
    175                                     modifier = Modifier
    176                                         .fillMaxWidth()
    177                                         .border(
    178                                             shape = MaterialTheme.shapes.medium,
    179                                             width = 1.dp,
    180                                             color = MaterialTheme.colorScheme.onSurfaceVariant
    181                                         )
    182                                         .clickable {
    183                                             selectedArtifact =
    184                                                 if (selectedArtifact == artifact) null else artifact
    185                                         }
    186                                         .background(
    187                                             if (selectedArtifact == artifact) MaterialTheme.colorScheme.surfaceVariant else MaterialTheme.colorScheme.surface,
    188                                             shape = MaterialTheme.shapes.medium
    189                                         )
    190                                         .padding(16.dp),
    191                                     verticalAlignment = Alignment.CenterVertically
    192                                 ) {
    193                                     Icon(imageVector = Icons.Default.Android, contentDescription = null, modifier = Modifier.padding(start = 2.dp, end = 2.dp))
    194                                     Column(
    195                                         modifier = Modifier
    196                                             .padding(start = 13.dp)
    197                                     ) {
    198                                         Text(text = artifact.fileName, fontSize = 15.sp)
    199                                         Text(
    200                                             text = "${artifact.size / 1024 / 1024} MB",
    201                                             fontSize = 10.sp
    202                                         )
    203                                     }
    204                                 }
    205                             }
    206                         }
    207                     }
    208                 }
    209             }
    210             Column(
    211                 modifier = Modifier
    212                     .fillMaxWidth(),
    213                 horizontalAlignment = Alignment.CenterHorizontally
    214             ) {
    215                 if (snapEnhanceApp != null) {
    216                     if (sharedConfig.enableRepackage && sharedConfig.snapEnhancePackageName != snapEnhanceApp.packageName) {
    217                         Button(
    218                             onClick = {
    219                                 navigation.navigateTo(RepackageTab::class, Bundle().apply {
    220                                     putString("apkPath", snapEnhanceApp.applicationInfo.sourceDir)
    221                                     putString("oldPackage", snapEnhanceApp.packageName)
    222                                 }, noHistory = true)
    223                             },
    224                             enabled = true,
    225                             modifier = Modifier.fillMaxWidth()
    226                         ) {
    227                             Text(text = "Repackage installed version (>=2.0.0)")
    228                         }
    229                     }
    230                     Button(
    231                         onClick = {
    232                             triggerPackageInstallation(true)
    233                         },
    234                         enabled = selectedVersion != null && selectedArtifact != null,
    235                         modifier = Modifier.fillMaxWidth()
    236                     ) {
    237                         Text(text = "Uninstall & Install")
    238                     }
    239                 }
    240                 Button(
    241                     onClick = {
    242                         if (snapEnhanceApp != null) {
    243                             showDowngradeNotice = true
    244                         } else {
    245                             triggerPackageInstallation(false)
    246                         }
    247                     },
    248                     enabled = selectedVersion != null && selectedArtifact != null,
    249                     modifier = Modifier.fillMaxWidth()
    250                 ) {
    251                     Text(text = if (snapEnhanceApp != null) "Update" else "Install")
    252                 }
    253             }
    254         }
    255 
    256         LaunchedEffect(Unit) {
    257             coroutineScope.launch(Dispatchers.IO) {
    258                 snapEnhanceReleases.value = fetchSEReleases()
    259             }
    260         }
    261     }
    262 }