commit 6b9938b8b2702a10164ae9e05a67308aaa9409eb parent a3edd40cfbb188ee21ddf2233092aab2ce304aee Author: rhunk <101876869+rhunk@users.noreply.github.com> Date: Fri, 1 Sep 2023 18:15:40 +0200 feat: permission screen - single context coroutine scope - refactor activity launcher helper - move updater to home section Diffstat:
15 files changed, 299 insertions(+), 187 deletions(-)
diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/RemoteSideContext.kt b/app/src/main/kotlin/me/rhunk/snapenhance/RemoteSideContext.kt @@ -14,6 +14,7 @@ import coil.ImageLoader import coil.decode.VideoFrameDecoder import coil.disk.DiskCache import coil.memory.MemoryCache +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import me.rhunk.snapenhance.bridge.BridgeService import me.rhunk.snapenhance.core.BuildConfig @@ -71,6 +72,7 @@ class RemoteSideContext( } .components { add(VideoFrameDecoder.Factory()) }.build() } + val coroutineScope = CoroutineScope(Dispatchers.IO) fun reload() { runCatching { @@ -103,7 +105,7 @@ class RemoteSideContext( ) }, modInfo = ModInfo( - loaderPackageName = MainActivity::class.java.`package`?.name ?: "unknown", + loaderPackageName = MainActivity::class.java.`package`?.name, buildPackageName = BuildConfig.APPLICATION_ID, buildVersion = BuildConfig.VERSION_NAME, buildVersionCode = BuildConfig.VERSION_CODE.toLong(), @@ -119,7 +121,6 @@ class RemoteSideContext( ), platformInfo = PlatformInfo( device = Build.DEVICE, - buildFingerprint = Build.FINGERPRINT, androidVersion = Build.VERSION.RELEASE, systemAbi = Build.SUPPORTED_ABIS.firstOrNull() ?: "unknown" ) diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/download/DownloadProcessor.kt b/app/src/main/kotlin/me/rhunk/snapenhance/download/DownloadProcessor.kt @@ -316,7 +316,7 @@ class DownloadProcessor ( } fun onReceive(intent: Intent) { - CoroutineScope(Dispatchers.IO).launch { + remoteSideContext.coroutineScope.launch { val downloadMetadata = gson.fromJson(intent.getStringExtra(DownloadManagerClient.DOWNLOAD_METADATA_EXTRA)!!, DownloadMetadata::class.java) val downloadRequest = gson.fromJson(intent.getStringExtra(DownloadManagerClient.DOWNLOAD_REQUEST_EXTRA)!!, DownloadRequest::class.java) diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/messaging/StreaksReminder.kt b/app/src/main/kotlin/me/rhunk/snapenhance/messaging/StreaksReminder.kt @@ -9,8 +9,6 @@ import android.content.Context import android.content.Intent import androidx.core.app.NotificationCompat import androidx.core.graphics.drawable.toBitmap -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import me.rhunk.snapenhance.R import me.rhunk.snapenhance.RemoteSideContext @@ -26,8 +24,6 @@ class StreaksReminder( private const val NOTIFICATION_CHANNEL_ID = "streaks" } - private val coroutineScope = CoroutineScope(Dispatchers.IO) - private fun getNotificationManager(context: Context) = context.getSystemService(NotificationManager::class.java).apply { createNotificationChannel( NotificationChannel( @@ -65,7 +61,7 @@ class StreaksReminder( } notifyFriendList.forEach { (streaks, friend) -> - coroutineScope.launch { + remoteSideContext.coroutineScope.launch { val bitmojiUrl = BitmojiSelfie.getBitmojiSelfie(friend.selfieId, friend.bitmojiId, BitmojiSelfie.BitmojiSelfieType.THREE_D) val bitmojiImage = remoteSideContext.imageLoader.execute( ImageRequestHelper.newBitmojiImageRequest(ctx, bitmojiUrl) diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/data/InstallationSummary.kt b/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/data/InstallationSummary.kt @@ -10,7 +10,7 @@ data class SnapchatAppInfo( ) data class ModInfo( - val loaderPackageName: String, + val loaderPackageName: String?, val buildPackageName: String, val buildVersion: String, val buildVersionCode: Long, @@ -22,7 +22,6 @@ data class ModInfo( data class PlatformInfo( val device: String, - val buildFingerprint: String, val androidVersion: String, val systemAbi: String, ) diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/data/Updater.kt b/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/data/Updater.kt @@ -0,0 +1,31 @@ +package me.rhunk.snapenhance.ui.manager.data + +import com.google.gson.JsonParser +import me.rhunk.snapenhance.core.BuildConfig +import okhttp3.OkHttpClient +import okhttp3.Request + + +object Updater { + data class LatestRelease( + val versionName: String, + val releaseUrl: String + ) + + fun checkForLatestRelease(): LatestRelease? { + val endpoint = Request.Builder().url("https://api.github.com/repos/rhunk/SnapEnhance/releases").build() + val response = OkHttpClient().newCall(endpoint).execute() + + if (!response.isSuccessful) throw Throwable("Failed to fetch releases: ${response.code}") + + val releases = JsonParser.parseString(response.body.string()).asJsonArray.also { + if (it.size() == 0) throw Throwable("No releases found") + } + + val latestRelease = releases.get(0).asJsonObject + val latestVersion = latestRelease.getAsJsonPrimitive("tag_name").asString + if (latestVersion.removePrefix("v") == BuildConfig.VERSION_NAME) return null + + return LatestRelease(latestVersion, endpoint.url.toString().replace("api.", "").replace("repos/", "")) + } +}+ \ No newline at end of file diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/sections/downloads/DownloadsSection.kt b/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/sections/downloads/DownloadsSection.kt @@ -50,7 +50,6 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import coil.compose.rememberAsyncImagePainter -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.future.asCompletableFuture import kotlinx.coroutines.launch @@ -64,11 +63,10 @@ import me.rhunk.snapenhance.ui.util.ImageRequestHelper class DownloadsSection : Section() { private val loadedDownloads = mutableStateOf(mapOf<Int, DownloadObject>()) private var currentFilter = mutableStateOf(MediaDownloadSource.NONE) - private val coroutineScope = CoroutineScope(Dispatchers.IO) override fun onResumed() { super.onResumed() - coroutineScope.launch { + context.coroutineScope.launch { loadByFilter(currentFilter.value) } } @@ -129,7 +127,7 @@ class DownloadsSection : Section() { } }, onClick = { - coroutineScope.launch { + context.coroutineScope.launch { loadByFilter(filter) showMenu = false } diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/sections/home/HomeSection.kt b/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/sections/home/HomeSection.kt @@ -1,5 +1,6 @@ package me.rhunk.snapenhance.ui.manager.sections.home +import android.content.Intent import android.net.Uri import androidx.compose.foundation.Image import androidx.compose.foundation.ScrollState @@ -48,9 +49,11 @@ import androidx.compose.ui.unit.sp import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.composable import androidx.navigation.navigation +import kotlinx.coroutines.launch import me.rhunk.snapenhance.R import me.rhunk.snapenhance.ui.manager.Section import me.rhunk.snapenhance.ui.manager.data.InstallationSummary +import me.rhunk.snapenhance.ui.manager.data.Updater import me.rhunk.snapenhance.ui.setup.Requirements import me.rhunk.snapenhance.ui.util.ActivityLauncherHelper import me.rhunk.snapenhance.ui.util.saveFile @@ -64,9 +67,10 @@ class HomeSection : Section() { const val LOGS_SECTION_ROUTE = "home_logs" } - private val installationSummary = mutableStateOf(null as InstallationSummary?) - private val userLocale = mutableStateOf(null as String?) + private var installationSummary: InstallationSummary? = null + private var userLocale: String? = null private val homeSubSection by lazy { HomeSubSection(context) } + private var latestUpdate: Updater.LatestRelease? = null private lateinit var activityLauncherHelper: ActivityLauncherHelper override fun init() { @@ -100,42 +104,16 @@ class HomeSection : Section() { @Composable private fun SummaryCards(installationSummary: InstallationSummary) { - OutlinedCard( - modifier = Modifier - .padding(all = cardMargin) - .fillMaxWidth() - ) { - SummaryCardRow( - icon = Icons.Filled.Map, - title = if (installationSummary.modInfo == null || installationSummary.modInfo.mappingsOutdated == true) { - "Mappings ${if (installationSummary.modInfo == null) "not generated" else "outdated"}" - } else { - "Mappings version ${installationSummary.modInfo.mappingVersion}" - } - ) { - Button(onClick = { - context.checkForRequirements(Requirements.MAPPINGS) - }, modifier = Modifier.height(40.dp)) { - Icon(Icons.Filled.Refresh, contentDescription = null) - } - } - - SummaryCardRow(icon = Icons.Filled.Language, title = userLocale.value ?: "Unknown") { - Button(onClick = { - context.checkForRequirements(Requirements.LANGUAGE) - }, modifier = Modifier.height(40.dp)) { - Icon(Icons.Filled.OpenInNew, contentDescription = null) - } - } - } - val summaryInfo = remember { mapOf( "Build Issuer" to (installationSummary.modInfo?.buildIssuer ?: "Unknown"), + "Build Type" to (if (installationSummary.modInfo?.isDebugBuild == true) "debug" else "release"), + "Build Version" to (installationSummary.modInfo?.buildVersion ?: "Unknown"), + "Build Package" to (installationSummary.modInfo?.buildPackageName ?: "Unknown"), + "Activity Package" to (installationSummary.modInfo?.loaderPackageName ?: "Unknown"), "Device" to installationSummary.platformInfo.device, - "Android version" to installationSummary.platformInfo.androidVersion, - "System ABI" to installationSummary.platformInfo.systemAbi, - "Build fingerprint" to installationSummary.platformInfo.buildFingerprint + "Android Version" to installationSummary.platformInfo.androidVersion, + "System ABI" to installationSummary.platformInfo.systemAbi ) } @@ -172,7 +150,35 @@ class HomeSection : Section() { } } } + } + + OutlinedCard( + modifier = Modifier + .padding(all = cardMargin) + .fillMaxWidth() + ) { + SummaryCardRow( + icon = Icons.Filled.Map, + title = if (installationSummary.modInfo == null || installationSummary.modInfo.mappingsOutdated == true) { + "Mappings ${if (installationSummary.modInfo == null) "not generated" else "outdated"}" + } else { + "Mappings version ${installationSummary.modInfo.mappingVersion}" + } + ) { + Button(onClick = { + context.checkForRequirements(Requirements.MAPPINGS) + }, modifier = Modifier.height(40.dp)) { + Icon(Icons.Filled.Refresh, contentDescription = null) + } + } + SummaryCardRow(icon = Icons.Filled.Language, title = userLocale ?: "Unknown") { + Button(onClick = { + context.checkForRequirements(Requirements.LANGUAGE) + }, modifier = Modifier.height(40.dp)) { + Icon(Icons.Filled.OpenInNew, contentDescription = null) + } + } } } @@ -180,8 +186,19 @@ class HomeSection : Section() { if (!context.mappings.isMappingsLoaded()) { context.mappings.init(context.androidContext) } - installationSummary.value = context.installationSummary - userLocale.value = context.translation.loadedLocale.getDisplayName(Locale.getDefault()) + context.coroutineScope.launch { + userLocale = context.translation.loadedLocale.getDisplayName(Locale.getDefault()) + runCatching { + installationSummary = context.installationSummary + }.onFailure { + context.longToast("SnapEnhance failed to load installation summary: ${it.message}") + } + runCatching { + latestUpdate = Updater.checkForLatestRelease() + }.onFailure { + context.longToast("SnapEnhance failed to check for updates: ${it.message}") + } + } } override fun sectionTopBarName(): String { @@ -304,13 +321,53 @@ class HomeSection : Section() { ) } + if (latestUpdate != null) { + OutlinedCard( + modifier = Modifier + .padding(all = cardMargin) + .fillMaxWidth(), + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.surfaceVariant, + contentColor = MaterialTheme.colorScheme.onSurfaceVariant + ) + ){ + Row( + modifier = Modifier + .fillMaxWidth() + .padding(all = 15.dp), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Column { + Text( + text = "SnapEnhance Update", + fontSize = 14.sp, + fontWeight = FontWeight.Bold, + ) + Text( + fontSize = 12.sp, + text = "Version ${latestUpdate?.versionName} is available!", + lineHeight = 20.sp + ) + } + Button(onClick = { + context.activity?.startActivity( + Intent(Intent.ACTION_VIEW).apply { + data = Uri.parse(latestUpdate?.releaseUrl) + } + ) + }, modifier = Modifier.height(40.dp)) { + Text(text = "Download") + } + } + } + } Text( text = "An xposed module that enhances the Snapchat experience", modifier = Modifier.padding(16.dp) ) - SummaryCards(installationSummary = installationSummary.value ?: return) + SummaryCards(installationSummary = installationSummary ?: return) } } } \ No newline at end of file diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/ui/setup/Requirements.kt b/app/src/main/kotlin/me/rhunk/snapenhance/ui/setup/Requirements.kt @@ -5,6 +5,7 @@ object Requirements { const val LANGUAGE = 0b00010 const val MAPPINGS = 0b00100 const val SAVE_FOLDER = 0b01000 + const val GRANT_PERMISSIONS = 0b10000 fun getName(requirement: Int): String { return when (requirement) { @@ -12,6 +13,7 @@ object Requirements { LANGUAGE -> "LANGUAGE" MAPPINGS -> "MAPPINGS" SAVE_FOLDER -> "SAVE_FOLDER" + GRANT_PERMISSIONS -> "GRANT_PERMISSIONS" else -> "UNKNOWN" } } diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/ui/setup/SetupActivity.kt b/app/src/main/kotlin/me/rhunk/snapenhance/ui/setup/SetupActivity.kt @@ -36,6 +36,7 @@ import me.rhunk.snapenhance.SharedContextHolder import me.rhunk.snapenhance.ui.AppMaterialTheme import me.rhunk.snapenhance.ui.setup.screens.SetupScreen import me.rhunk.snapenhance.ui.setup.screens.impl.MappingsScreen +import me.rhunk.snapenhance.ui.setup.screens.impl.PermissionsScreen import me.rhunk.snapenhance.ui.setup.screens.impl.PickLanguageScreen import me.rhunk.snapenhance.ui.setup.screens.impl.SaveFolderScreen @@ -65,6 +66,9 @@ class SetupActivity : ComponentActivity() { if (isFirstRun || hasRequirement(Requirements.LANGUAGE)) { add(PickLanguageScreen().apply { route = "language" }) } + if (isFirstRun || hasRequirement(Requirements.GRANT_PERMISSIONS)) { + add(PermissionsScreen().apply { route = "permissions" }) + } if (isFirstRun || hasRequirement(Requirements.SAVE_FOLDER)) { add(SaveFolderScreen().apply { route = "saveFolder" }) } diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/ui/setup/screens/impl/PermissionsScreen.kt b/app/src/main/kotlin/me/rhunk/snapenhance/ui/setup/screens/impl/PermissionsScreen.kt @@ -0,0 +1,115 @@ +package me.rhunk.snapenhance.ui.setup.screens.impl + +import android.Manifest +import android.annotation.SuppressLint +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.net.Uri +import android.os.Build +import android.os.PowerManager +import android.provider.Settings +import androidx.activity.ComponentActivity +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Button +import androidx.compose.material3.OutlinedCard +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import kotlinx.coroutines.launch +import me.rhunk.snapenhance.ui.setup.screens.SetupScreen +import me.rhunk.snapenhance.ui.util.ActivityLauncherHelper + +class PermissionsScreen : SetupScreen() { + private lateinit var activityLauncherHelper: ActivityLauncherHelper + + override fun init() { + activityLauncherHelper = ActivityLauncherHelper(context.activity!!) + } + + @SuppressLint("BatteryLife") + @Composable + override fun Content() { + var notificationPermissionGranted by remember { mutableStateOf(true) } + var isBatteryOptimisationIgnored by remember { mutableStateOf(false) } + val coroutineScope = rememberCoroutineScope() + + LaunchedEffect(Unit) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + notificationPermissionGranted = context.androidContext.checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED + } + val powerManager = context.androidContext.getSystemService(Context.POWER_SERVICE) as PowerManager + isBatteryOptimisationIgnored = powerManager.isIgnoringBatteryOptimizations(context.androidContext.packageName) + } + + if (isBatteryOptimisationIgnored && notificationPermissionGranted) { + allowNext(true) + } else { + allowNext(false) + } + + DialogText(text = "To continue you need to fit the following requirements:") + + OutlinedCard( + modifier = Modifier + .padding(16.dp) + .fillMaxWidth(), + ) { + Column( + modifier = Modifier + .padding(5.dp) + ) { + Row( + horizontalArrangement = Arrangement.Absolute.SpaceAround + ) { + DialogText(text = "Notification access", modifier = Modifier.weight(1f)) + if (notificationPermissionGranted) { + DialogText(text = "Granted") + } else { + Button(onClick = { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + activityLauncherHelper.requestPermission(Manifest.permission.POST_NOTIFICATIONS) { resultCode, _ -> + coroutineScope.launch { + notificationPermissionGranted = resultCode == ComponentActivity.RESULT_OK + } + } + } + }) { + Text(text = "Request") + } + } + } + Row { + DialogText(text = "Battery optimisation", modifier = Modifier.weight(1f)) + if (isBatteryOptimisationIgnored) { + DialogText(text = "Ignored") + } else { + Button(onClick = { + activityLauncherHelper.launch(Intent().apply { + action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS + data = Uri.parse("package:${context.androidContext.packageName}") + }) { resultCode, _ -> + coroutineScope.launch { + isBatteryOptimisationIgnored = resultCode == 0 + } + } + }) { + Text(text = "Request") + } + } + } + } + } + } +}+ \ No newline at end of file diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/ui/util/ActivityLauncherHelper.kt b/app/src/main/kotlin/me/rhunk/snapenhance/ui/util/ActivityLauncherHelper.kt @@ -6,29 +6,47 @@ import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts import me.rhunk.snapenhance.Logger +typealias ActivityLauncherCallback = (resultCode: Int, intent: Intent?) -> Unit + class ActivityLauncherHelper( - val activity: ComponentActivity + val activity: ComponentActivity, ) { - private var callback: ((Intent) -> Unit)? = null + private var callback: ActivityLauncherCallback? = null + private var permissionResultLauncher: ActivityResultLauncher<String> = + activity.registerForActivityResult(ActivityResultContracts.RequestPermission()) { result -> + runCatching { + callback?.let { it(if (result) ComponentActivity.RESULT_OK else ComponentActivity.RESULT_CANCELED, null) } + }.onFailure { + Logger.directError("Failed to process activity result", it) + } + callback = null + } + private var activityResultLauncher: ActivityResultLauncher<Intent> = activity.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> - if (result.resultCode == ComponentActivity.RESULT_OK) { - runCatching { - callback?.let { it(result.data!!) } - }.onFailure { - Logger.directError("Failed to process activity result", it) - } + runCatching { + callback?.let { it(result.resultCode, result.data) } + }.onFailure { + Logger.directError("Failed to process activity result", it) } callback = null } - fun launch(intent: Intent, callback: (Intent) -> Unit) { + fun launch(intent: Intent, callback: ActivityLauncherCallback) { if (this.callback != null) { throw IllegalStateException("Already launching an activity") } this.callback = callback activityResultLauncher.launch(intent) } + + fun requestPermission(permission: String, callback: ActivityLauncherCallback) { + if (this.callback != null) { + throw IllegalStateException("Already launching an activity") + } + this.callback = callback + permissionResultLauncher.launch(permission) + } } fun ActivityLauncherHelper.chooseFolder(callback: (uri: String) -> Unit) { @@ -36,8 +54,11 @@ fun ActivityLauncherHelper.chooseFolder(callback: (uri: String) -> Unit) { Intent(Intent.ACTION_OPEN_DOCUMENT_TREE) .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) .addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION) - ) { - val uri = it.data ?: return@launch + ) { resultCode, intent -> + if (resultCode != ComponentActivity.RESULT_OK) { + return@launch + } + val uri = intent?.data ?: return@launch val value = uri.toString() this.activity.contentResolver.takePersistableUriPermission( uri, @@ -55,8 +76,11 @@ fun ActivityLauncherHelper.saveFile(name: String, type: String = "*/*", callback .putExtra(Intent.EXTRA_TITLE, name) .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) .addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION) - ) { - val uri = it.data ?: return@launch + ) { resultCode, intent -> + if (resultCode != ComponentActivity.RESULT_OK) { + return@launch + } + val uri = intent?.data ?: return@launch val value = uri.toString() this.activity.contentResolver.takePersistableUriPermission( uri, @@ -72,8 +96,11 @@ fun ActivityLauncherHelper.openFile(type: String = "*/*", callback: (uri: String .setType(type) .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) .addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION) - ) { - val uri = it.data ?: return@launch + ) { resultCode, intent -> + if (resultCode != ComponentActivity.RESULT_OK) { + return@launch + } + val uri = intent?.data ?: return@launch val value = uri.toString() this.activity.contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION) callback(value) diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/ui/util/AlertDialogs.kt b/app/src/main/kotlin/me/rhunk/snapenhance/ui/util/AlertDialogs.kt @@ -101,7 +101,6 @@ class AlertDialogs( Text( text = title, fontSize = 20.sp, - fontWeight = FontWeight.Bold, modifier = Modifier.padding(start = 5.dp, bottom = 10.dp) ) if (message != null) { diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/config/impl/Global.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/config/impl/Global.kt @@ -2,11 +2,9 @@ package me.rhunk.snapenhance.core.config.impl import me.rhunk.snapenhance.core.config.ConfigContainer import me.rhunk.snapenhance.core.config.FeatureNotice -import me.rhunk.snapenhance.data.NotificationType class Global : ConfigContainer() { val snapchatPlus = boolean("snapchat_plus") { addNotices(FeatureNotice.MAY_BAN) } - val autoUpdater = unique("auto_updater", "EVERY_LAUNCH", "DAILY", "WEEKLY").apply { set("DAILY") } val disableMetrics = boolean("disable_metrics") val blockAds = boolean("block_ads") val disableVideoLengthRestrictions = boolean("disable_video_length_restrictions") { addNotices(FeatureNotice.MAY_BAN) } diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/features/impl/AutoUpdater.kt b/core/src/main/kotlin/me/rhunk/snapenhance/features/impl/AutoUpdater.kt @@ -1,114 +0,0 @@ -package me.rhunk.snapenhance.features.impl - -import android.annotation.SuppressLint -import android.app.DownloadManager -import android.content.BroadcastReceiver -import android.content.Context -import android.content.Intent -import android.content.IntentFilter -import android.net.Uri -import android.os.Build -import android.os.Environment -import com.google.gson.JsonParser -import me.rhunk.snapenhance.core.BuildConfig -import me.rhunk.snapenhance.features.Feature -import me.rhunk.snapenhance.features.FeatureLoadParams -import me.rhunk.snapenhance.ui.ViewAppearanceHelper -import okhttp3.OkHttpClient -import okhttp3.Request - -class AutoUpdater : Feature("AutoUpdater", loadParams = FeatureLoadParams.ACTIVITY_CREATE_ASYNC) { - override fun asyncOnActivityCreate() { - val autoUpdaterTime = context.config.global.autoUpdater.getNullable() ?: return - val currentTimeMillis = System.currentTimeMillis() - val checkForUpdatesTimestamp = context.bridgeClient.getAutoUpdaterTime() - - val delayTimestamp = when (autoUpdaterTime) { - "EVERY_LAUNCH" -> currentTimeMillis - checkForUpdatesTimestamp - "DAILY" -> 86400000L - "WEEKLY" -> 604800000L - else -> return - } - - if (checkForUpdatesTimestamp + delayTimestamp > currentTimeMillis) return - - runCatching { - checkForUpdates() - }.onFailure { - context.log.error("Failed to check for updates: ${it.message}", it) - }.onSuccess { - context.bridgeClient.setAutoUpdaterTime(currentTimeMillis) - } - } - - @SuppressLint("UnspecifiedRegisterReceiverFlag") - fun checkForUpdates(): String? { - val endpoint = Request.Builder().url("https://api.github.com/repos/rhunk/SnapEnhance/releases").build() - val response = OkHttpClient().newCall(endpoint).execute() - - if (!response.isSuccessful) throw Throwable("Failed to fetch releases: ${response.code}") - - val releases = JsonParser.parseString(response.body.string()).asJsonArray.also { - if (it.size() == 0) throw Throwable("No releases found") - } - - val latestRelease = releases.get(0).asJsonObject - val latestVersion = latestRelease.getAsJsonPrimitive("tag_name").asString - if (latestVersion.removePrefix("v") == BuildConfig.VERSION_NAME) return null - - val architectureName = Build.SUPPORTED_ABIS.let { - if (it.contains("arm64-v8a")) return@let "armv8" - if (it.contains("armeabi-v7a") || it.contains("armeabi")) return@let "armv7" - throw Throwable("Failed getting architecture") - } - - val releaseContentBody = latestRelease.getAsJsonPrimitive("body").asString - val downloadEndpoint = "https://github.com/rhunk/SnapEnhance/releases/download/${latestVersion}/app-${latestVersion.removePrefix("v")}-${architectureName}-release-signed.apk" - - context.runOnUiThread { - ViewAppearanceHelper.newAlertDialogBuilder(context.mainActivity) - .setTitle(context.translation["auto_updater.dialog_title"]) - .setMessage( - context.translation.format("auto_updater.dialog_message", - "version" to latestVersion, - "body" to releaseContentBody) - ) - .setNegativeButton(context.translation["auto_updater.dialog_negative_button"]) { dialog, _ -> - dialog.dismiss() - } - .setPositiveButton(context.translation["auto_updater.dialog_positive_button"]) { dialog, _ -> - dialog.dismiss() - context.longToast(context.translation["auto_updater.downloading_toast"]) - - val request = DownloadManager.Request(Uri.parse(downloadEndpoint)) - .setTitle(context.translation["auto_updater.download_manager_notification_title"]) - .setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "latest-snapenhance.apk") - .setMimeType("application/vnd.android.package-archive") - .setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE) - - val downloadManager = context.androidContext.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager - val downloadId = downloadManager.enqueue(request) - - val onCompleteReceiver = object: BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - val id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1) - if (id != downloadId) return - context.unregisterReceiver(this) - context.startActivity( - Intent(Intent.ACTION_VIEW).apply { - setDataAndType(downloadManager.getUriForDownloadedFile(downloadId), "application/vnd.android.package-archive") - flags = Intent.FLAG_ACTIVITY_NEW_TASK - } - ) - } - } - - context.mainActivity?.registerReceiver(onCompleteReceiver, IntentFilter( - DownloadManager.ACTION_DOWNLOAD_COMPLETE - )) - }.show() - } - - return latestVersion - } -}- \ No newline at end of file diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/manager/impl/FeatureManager.kt b/core/src/main/kotlin/me/rhunk/snapenhance/manager/impl/FeatureManager.kt @@ -4,7 +4,6 @@ import me.rhunk.snapenhance.Logger import me.rhunk.snapenhance.ModContext import me.rhunk.snapenhance.features.Feature import me.rhunk.snapenhance.features.FeatureLoadParams -import me.rhunk.snapenhance.features.impl.AutoUpdater import me.rhunk.snapenhance.features.impl.ConfigurationOverride import me.rhunk.snapenhance.features.impl.Messaging import me.rhunk.snapenhance.features.impl.downloader.MediaDownloader @@ -27,11 +26,11 @@ import me.rhunk.snapenhance.features.impl.tweaks.AutoSave import me.rhunk.snapenhance.features.impl.tweaks.CameraTweaks import me.rhunk.snapenhance.features.impl.tweaks.DisableReplayInFF import me.rhunk.snapenhance.features.impl.tweaks.DisableVideoLengthRestriction -import me.rhunk.snapenhance.features.impl.tweaks.SendOverride import me.rhunk.snapenhance.features.impl.tweaks.GooglePlayServicesDialogs import me.rhunk.snapenhance.features.impl.tweaks.LocationSpoofer import me.rhunk.snapenhance.features.impl.tweaks.MediaQualityLevelOverride import me.rhunk.snapenhance.features.impl.tweaks.Notifications +import me.rhunk.snapenhance.features.impl.tweaks.SendOverride import me.rhunk.snapenhance.features.impl.tweaks.SnapchatPlus import me.rhunk.snapenhance.features.impl.tweaks.UnlimitedSnapViewTime import me.rhunk.snapenhance.features.impl.ui.PinConversations @@ -84,7 +83,6 @@ class FeatureManager(private val context: ModContext) : Manager { register(MeoPasscodeBypass::class) register(AppPasscode::class) register(LocationSpoofer::class) - register(AutoUpdater::class) register(CameraTweaks::class) register(InfiniteStoryBoost::class) register(AmoledDarkMode::class)