mirror of
https://github.com/rhunk/SnapEnhance.git
synced 2025-05-28 20:40:13 +02:00
feat: permission screen
- single context coroutine scope - refactor activity launcher helper - move updater to home section
This commit is contained in:
parent
a3edd40cfb
commit
6b9938b8b2
@ -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"
|
||||
)
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
)
|
||||
|
@ -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/", ""))
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -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" })
|
||||
}
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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 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)
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
fun launch(intent: Intent, callback: (Intent) -> Unit) {
|
||||
private var activityResultLauncher: ActivityResultLauncher<Intent> =
|
||||
activity.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||
runCatching {
|
||||
callback?.let { it(result.resultCode, result.data) }
|
||||
}.onFailure {
|
||||
Logger.directError("Failed to process activity result", it)
|
||||
}
|
||||
callback = null
|
||||
}
|
||||
|
||||
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)
|
||||
|
@ -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) {
|
||||
|
@ -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) }
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user