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 }