mirror of
https://github.com/rhunk/SnapEnhance.git
synced 2025-05-29 21:10:20 +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.decode.VideoFrameDecoder
|
||||||
import coil.disk.DiskCache
|
import coil.disk.DiskCache
|
||||||
import coil.memory.MemoryCache
|
import coil.memory.MemoryCache
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import me.rhunk.snapenhance.bridge.BridgeService
|
import me.rhunk.snapenhance.bridge.BridgeService
|
||||||
import me.rhunk.snapenhance.core.BuildConfig
|
import me.rhunk.snapenhance.core.BuildConfig
|
||||||
@ -71,6 +72,7 @@ class RemoteSideContext(
|
|||||||
}
|
}
|
||||||
.components { add(VideoFrameDecoder.Factory()) }.build()
|
.components { add(VideoFrameDecoder.Factory()) }.build()
|
||||||
}
|
}
|
||||||
|
val coroutineScope = CoroutineScope(Dispatchers.IO)
|
||||||
|
|
||||||
fun reload() {
|
fun reload() {
|
||||||
runCatching {
|
runCatching {
|
||||||
@ -103,7 +105,7 @@ class RemoteSideContext(
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
modInfo = ModInfo(
|
modInfo = ModInfo(
|
||||||
loaderPackageName = MainActivity::class.java.`package`?.name ?: "unknown",
|
loaderPackageName = MainActivity::class.java.`package`?.name,
|
||||||
buildPackageName = BuildConfig.APPLICATION_ID,
|
buildPackageName = BuildConfig.APPLICATION_ID,
|
||||||
buildVersion = BuildConfig.VERSION_NAME,
|
buildVersion = BuildConfig.VERSION_NAME,
|
||||||
buildVersionCode = BuildConfig.VERSION_CODE.toLong(),
|
buildVersionCode = BuildConfig.VERSION_CODE.toLong(),
|
||||||
@ -119,7 +121,6 @@ class RemoteSideContext(
|
|||||||
),
|
),
|
||||||
platformInfo = PlatformInfo(
|
platformInfo = PlatformInfo(
|
||||||
device = Build.DEVICE,
|
device = Build.DEVICE,
|
||||||
buildFingerprint = Build.FINGERPRINT,
|
|
||||||
androidVersion = Build.VERSION.RELEASE,
|
androidVersion = Build.VERSION.RELEASE,
|
||||||
systemAbi = Build.SUPPORTED_ABIS.firstOrNull() ?: "unknown"
|
systemAbi = Build.SUPPORTED_ABIS.firstOrNull() ?: "unknown"
|
||||||
)
|
)
|
||||||
|
@ -316,7 +316,7 @@ class DownloadProcessor (
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun onReceive(intent: Intent) {
|
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 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)
|
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 android.content.Intent
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.graphics.drawable.toBitmap
|
import androidx.core.graphics.drawable.toBitmap
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import me.rhunk.snapenhance.R
|
import me.rhunk.snapenhance.R
|
||||||
import me.rhunk.snapenhance.RemoteSideContext
|
import me.rhunk.snapenhance.RemoteSideContext
|
||||||
@ -26,8 +24,6 @@ class StreaksReminder(
|
|||||||
private const val NOTIFICATION_CHANNEL_ID = "streaks"
|
private const val NOTIFICATION_CHANNEL_ID = "streaks"
|
||||||
}
|
}
|
||||||
|
|
||||||
private val coroutineScope = CoroutineScope(Dispatchers.IO)
|
|
||||||
|
|
||||||
private fun getNotificationManager(context: Context) = context.getSystemService(NotificationManager::class.java).apply {
|
private fun getNotificationManager(context: Context) = context.getSystemService(NotificationManager::class.java).apply {
|
||||||
createNotificationChannel(
|
createNotificationChannel(
|
||||||
NotificationChannel(
|
NotificationChannel(
|
||||||
@ -65,7 +61,7 @@ class StreaksReminder(
|
|||||||
}
|
}
|
||||||
|
|
||||||
notifyFriendList.forEach { (streaks, friend) ->
|
notifyFriendList.forEach { (streaks, friend) ->
|
||||||
coroutineScope.launch {
|
remoteSideContext.coroutineScope.launch {
|
||||||
val bitmojiUrl = BitmojiSelfie.getBitmojiSelfie(friend.selfieId, friend.bitmojiId, BitmojiSelfie.BitmojiSelfieType.THREE_D)
|
val bitmojiUrl = BitmojiSelfie.getBitmojiSelfie(friend.selfieId, friend.bitmojiId, BitmojiSelfie.BitmojiSelfieType.THREE_D)
|
||||||
val bitmojiImage = remoteSideContext.imageLoader.execute(
|
val bitmojiImage = remoteSideContext.imageLoader.execute(
|
||||||
ImageRequestHelper.newBitmojiImageRequest(ctx, bitmojiUrl)
|
ImageRequestHelper.newBitmojiImageRequest(ctx, bitmojiUrl)
|
||||||
|
@ -10,7 +10,7 @@ data class SnapchatAppInfo(
|
|||||||
)
|
)
|
||||||
|
|
||||||
data class ModInfo(
|
data class ModInfo(
|
||||||
val loaderPackageName: String,
|
val loaderPackageName: String?,
|
||||||
val buildPackageName: String,
|
val buildPackageName: String,
|
||||||
val buildVersion: String,
|
val buildVersion: String,
|
||||||
val buildVersionCode: Long,
|
val buildVersionCode: Long,
|
||||||
@ -22,7 +22,6 @@ data class ModInfo(
|
|||||||
|
|
||||||
data class PlatformInfo(
|
data class PlatformInfo(
|
||||||
val device: String,
|
val device: String,
|
||||||
val buildFingerprint: String,
|
|
||||||
val androidVersion: String,
|
val androidVersion: String,
|
||||||
val systemAbi: 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.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import coil.compose.rememberAsyncImagePainter
|
import coil.compose.rememberAsyncImagePainter
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.future.asCompletableFuture
|
import kotlinx.coroutines.future.asCompletableFuture
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@ -64,11 +63,10 @@ import me.rhunk.snapenhance.ui.util.ImageRequestHelper
|
|||||||
class DownloadsSection : Section() {
|
class DownloadsSection : Section() {
|
||||||
private val loadedDownloads = mutableStateOf(mapOf<Int, DownloadObject>())
|
private val loadedDownloads = mutableStateOf(mapOf<Int, DownloadObject>())
|
||||||
private var currentFilter = mutableStateOf(MediaDownloadSource.NONE)
|
private var currentFilter = mutableStateOf(MediaDownloadSource.NONE)
|
||||||
private val coroutineScope = CoroutineScope(Dispatchers.IO)
|
|
||||||
|
|
||||||
override fun onResumed() {
|
override fun onResumed() {
|
||||||
super.onResumed()
|
super.onResumed()
|
||||||
coroutineScope.launch {
|
context.coroutineScope.launch {
|
||||||
loadByFilter(currentFilter.value)
|
loadByFilter(currentFilter.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -129,7 +127,7 @@ class DownloadsSection : Section() {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onClick = {
|
onClick = {
|
||||||
coroutineScope.launch {
|
context.coroutineScope.launch {
|
||||||
loadByFilter(filter)
|
loadByFilter(filter)
|
||||||
showMenu = false
|
showMenu = false
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package me.rhunk.snapenhance.ui.manager.sections.home
|
package me.rhunk.snapenhance.ui.manager.sections.home
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.ScrollState
|
import androidx.compose.foundation.ScrollState
|
||||||
@ -48,9 +49,11 @@ import androidx.compose.ui.unit.sp
|
|||||||
import androidx.navigation.NavGraphBuilder
|
import androidx.navigation.NavGraphBuilder
|
||||||
import androidx.navigation.compose.composable
|
import androidx.navigation.compose.composable
|
||||||
import androidx.navigation.navigation
|
import androidx.navigation.navigation
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import me.rhunk.snapenhance.R
|
import me.rhunk.snapenhance.R
|
||||||
import me.rhunk.snapenhance.ui.manager.Section
|
import me.rhunk.snapenhance.ui.manager.Section
|
||||||
import me.rhunk.snapenhance.ui.manager.data.InstallationSummary
|
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.setup.Requirements
|
||||||
import me.rhunk.snapenhance.ui.util.ActivityLauncherHelper
|
import me.rhunk.snapenhance.ui.util.ActivityLauncherHelper
|
||||||
import me.rhunk.snapenhance.ui.util.saveFile
|
import me.rhunk.snapenhance.ui.util.saveFile
|
||||||
@ -64,9 +67,10 @@ class HomeSection : Section() {
|
|||||||
const val LOGS_SECTION_ROUTE = "home_logs"
|
const val LOGS_SECTION_ROUTE = "home_logs"
|
||||||
}
|
}
|
||||||
|
|
||||||
private val installationSummary = mutableStateOf(null as InstallationSummary?)
|
private var installationSummary: InstallationSummary? = null
|
||||||
private val userLocale = mutableStateOf(null as String?)
|
private var userLocale: String? = null
|
||||||
private val homeSubSection by lazy { HomeSubSection(context) }
|
private val homeSubSection by lazy { HomeSubSection(context) }
|
||||||
|
private var latestUpdate: Updater.LatestRelease? = null
|
||||||
private lateinit var activityLauncherHelper: ActivityLauncherHelper
|
private lateinit var activityLauncherHelper: ActivityLauncherHelper
|
||||||
|
|
||||||
override fun init() {
|
override fun init() {
|
||||||
@ -100,42 +104,16 @@ class HomeSection : Section() {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun SummaryCards(installationSummary: InstallationSummary) {
|
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 {
|
val summaryInfo = remember {
|
||||||
mapOf(
|
mapOf(
|
||||||
"Build Issuer" to (installationSummary.modInfo?.buildIssuer ?: "Unknown"),
|
"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,
|
"Device" to installationSummary.platformInfo.device,
|
||||||
"Android version" to installationSummary.platformInfo.androidVersion,
|
"Android Version" to installationSummary.platformInfo.androidVersion,
|
||||||
"System ABI" to installationSummary.platformInfo.systemAbi,
|
"System ABI" to installationSummary.platformInfo.systemAbi
|
||||||
"Build fingerprint" to installationSummary.platformInfo.buildFingerprint
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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()) {
|
if (!context.mappings.isMappingsLoaded()) {
|
||||||
context.mappings.init(context.androidContext)
|
context.mappings.init(context.androidContext)
|
||||||
}
|
}
|
||||||
installationSummary.value = context.installationSummary
|
context.coroutineScope.launch {
|
||||||
userLocale.value = context.translation.loadedLocale.getDisplayName(Locale.getDefault())
|
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 {
|
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(
|
||||||
text = "An xposed module that enhances the Snapchat experience",
|
text = "An xposed module that enhances the Snapchat experience",
|
||||||
modifier = Modifier.padding(16.dp)
|
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 LANGUAGE = 0b00010
|
||||||
const val MAPPINGS = 0b00100
|
const val MAPPINGS = 0b00100
|
||||||
const val SAVE_FOLDER = 0b01000
|
const val SAVE_FOLDER = 0b01000
|
||||||
|
const val GRANT_PERMISSIONS = 0b10000
|
||||||
|
|
||||||
fun getName(requirement: Int): String {
|
fun getName(requirement: Int): String {
|
||||||
return when (requirement) {
|
return when (requirement) {
|
||||||
@ -12,6 +13,7 @@ object Requirements {
|
|||||||
LANGUAGE -> "LANGUAGE"
|
LANGUAGE -> "LANGUAGE"
|
||||||
MAPPINGS -> "MAPPINGS"
|
MAPPINGS -> "MAPPINGS"
|
||||||
SAVE_FOLDER -> "SAVE_FOLDER"
|
SAVE_FOLDER -> "SAVE_FOLDER"
|
||||||
|
GRANT_PERMISSIONS -> "GRANT_PERMISSIONS"
|
||||||
else -> "UNKNOWN"
|
else -> "UNKNOWN"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,6 +36,7 @@ import me.rhunk.snapenhance.SharedContextHolder
|
|||||||
import me.rhunk.snapenhance.ui.AppMaterialTheme
|
import me.rhunk.snapenhance.ui.AppMaterialTheme
|
||||||
import me.rhunk.snapenhance.ui.setup.screens.SetupScreen
|
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.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.PickLanguageScreen
|
||||||
import me.rhunk.snapenhance.ui.setup.screens.impl.SaveFolderScreen
|
import me.rhunk.snapenhance.ui.setup.screens.impl.SaveFolderScreen
|
||||||
|
|
||||||
@ -65,6 +66,9 @@ class SetupActivity : ComponentActivity() {
|
|||||||
if (isFirstRun || hasRequirement(Requirements.LANGUAGE)) {
|
if (isFirstRun || hasRequirement(Requirements.LANGUAGE)) {
|
||||||
add(PickLanguageScreen().apply { route = "language" })
|
add(PickLanguageScreen().apply { route = "language" })
|
||||||
}
|
}
|
||||||
|
if (isFirstRun || hasRequirement(Requirements.GRANT_PERMISSIONS)) {
|
||||||
|
add(PermissionsScreen().apply { route = "permissions" })
|
||||||
|
}
|
||||||
if (isFirstRun || hasRequirement(Requirements.SAVE_FOLDER)) {
|
if (isFirstRun || hasRequirement(Requirements.SAVE_FOLDER)) {
|
||||||
add(SaveFolderScreen().apply { route = "saveFolder" })
|
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 androidx.activity.result.contract.ActivityResultContracts
|
||||||
import me.rhunk.snapenhance.Logger
|
import me.rhunk.snapenhance.Logger
|
||||||
|
|
||||||
|
typealias ActivityLauncherCallback = (resultCode: Int, intent: Intent?) -> Unit
|
||||||
|
|
||||||
class ActivityLauncherHelper(
|
class ActivityLauncherHelper(
|
||||||
val activity: ComponentActivity
|
val activity: ComponentActivity,
|
||||||
) {
|
) {
|
||||||
private var callback: ((Intent) -> Unit)? = null
|
private var callback: ActivityLauncherCallback? = null
|
||||||
private var activityResultLauncher: ActivityResultLauncher<Intent> =
|
private var permissionResultLauncher: ActivityResultLauncher<String> =
|
||||||
activity.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
activity.registerForActivityResult(ActivityResultContracts.RequestPermission()) { result ->
|
||||||
if (result.resultCode == ComponentActivity.RESULT_OK) {
|
runCatching {
|
||||||
runCatching {
|
callback?.let { it(if (result) ComponentActivity.RESULT_OK else ComponentActivity.RESULT_CANCELED, null) }
|
||||||
callback?.let { it(result.data!!) }
|
}.onFailure {
|
||||||
}.onFailure {
|
Logger.directError("Failed to process activity result", it)
|
||||||
Logger.directError("Failed to process activity result", it)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
callback = null
|
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) {
|
if (this.callback != null) {
|
||||||
throw IllegalStateException("Already launching an activity")
|
throw IllegalStateException("Already launching an activity")
|
||||||
}
|
}
|
||||||
this.callback = callback
|
this.callback = callback
|
||||||
activityResultLauncher.launch(intent)
|
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) {
|
fun ActivityLauncherHelper.chooseFolder(callback: (uri: String) -> Unit) {
|
||||||
@ -36,8 +54,11 @@ fun ActivityLauncherHelper.chooseFolder(callback: (uri: String) -> Unit) {
|
|||||||
Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
|
Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
|
||||||
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||||
) {
|
) { resultCode, intent ->
|
||||||
val uri = it.data ?: return@launch
|
if (resultCode != ComponentActivity.RESULT_OK) {
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
val uri = intent?.data ?: return@launch
|
||||||
val value = uri.toString()
|
val value = uri.toString()
|
||||||
this.activity.contentResolver.takePersistableUriPermission(
|
this.activity.contentResolver.takePersistableUriPermission(
|
||||||
uri,
|
uri,
|
||||||
@ -55,8 +76,11 @@ fun ActivityLauncherHelper.saveFile(name: String, type: String = "*/*", callback
|
|||||||
.putExtra(Intent.EXTRA_TITLE, name)
|
.putExtra(Intent.EXTRA_TITLE, name)
|
||||||
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||||
) {
|
) { resultCode, intent ->
|
||||||
val uri = it.data ?: return@launch
|
if (resultCode != ComponentActivity.RESULT_OK) {
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
val uri = intent?.data ?: return@launch
|
||||||
val value = uri.toString()
|
val value = uri.toString()
|
||||||
this.activity.contentResolver.takePersistableUriPermission(
|
this.activity.contentResolver.takePersistableUriPermission(
|
||||||
uri,
|
uri,
|
||||||
@ -72,8 +96,11 @@ fun ActivityLauncherHelper.openFile(type: String = "*/*", callback: (uri: String
|
|||||||
.setType(type)
|
.setType(type)
|
||||||
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||||
) {
|
) { resultCode, intent ->
|
||||||
val uri = it.data ?: return@launch
|
if (resultCode != ComponentActivity.RESULT_OK) {
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
val uri = intent?.data ?: return@launch
|
||||||
val value = uri.toString()
|
val value = uri.toString()
|
||||||
this.activity.contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
this.activity.contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||||
callback(value)
|
callback(value)
|
||||||
|
@ -101,7 +101,6 @@ class AlertDialogs(
|
|||||||
Text(
|
Text(
|
||||||
text = title,
|
text = title,
|
||||||
fontSize = 20.sp,
|
fontSize = 20.sp,
|
||||||
fontWeight = FontWeight.Bold,
|
|
||||||
modifier = Modifier.padding(start = 5.dp, bottom = 10.dp)
|
modifier = Modifier.padding(start = 5.dp, bottom = 10.dp)
|
||||||
)
|
)
|
||||||
if (message != null) {
|
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.ConfigContainer
|
||||||
import me.rhunk.snapenhance.core.config.FeatureNotice
|
import me.rhunk.snapenhance.core.config.FeatureNotice
|
||||||
import me.rhunk.snapenhance.data.NotificationType
|
|
||||||
|
|
||||||
class Global : ConfigContainer() {
|
class Global : ConfigContainer() {
|
||||||
val snapchatPlus = boolean("snapchat_plus") { addNotices(FeatureNotice.MAY_BAN) }
|
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 disableMetrics = boolean("disable_metrics")
|
||||||
val blockAds = boolean("block_ads")
|
val blockAds = boolean("block_ads")
|
||||||
val disableVideoLengthRestrictions = boolean("disable_video_length_restrictions") { addNotices(FeatureNotice.MAY_BAN) }
|
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.ModContext
|
||||||
import me.rhunk.snapenhance.features.Feature
|
import me.rhunk.snapenhance.features.Feature
|
||||||
import me.rhunk.snapenhance.features.FeatureLoadParams
|
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.ConfigurationOverride
|
||||||
import me.rhunk.snapenhance.features.impl.Messaging
|
import me.rhunk.snapenhance.features.impl.Messaging
|
||||||
import me.rhunk.snapenhance.features.impl.downloader.MediaDownloader
|
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.CameraTweaks
|
||||||
import me.rhunk.snapenhance.features.impl.tweaks.DisableReplayInFF
|
import me.rhunk.snapenhance.features.impl.tweaks.DisableReplayInFF
|
||||||
import me.rhunk.snapenhance.features.impl.tweaks.DisableVideoLengthRestriction
|
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.GooglePlayServicesDialogs
|
||||||
import me.rhunk.snapenhance.features.impl.tweaks.LocationSpoofer
|
import me.rhunk.snapenhance.features.impl.tweaks.LocationSpoofer
|
||||||
import me.rhunk.snapenhance.features.impl.tweaks.MediaQualityLevelOverride
|
import me.rhunk.snapenhance.features.impl.tweaks.MediaQualityLevelOverride
|
||||||
import me.rhunk.snapenhance.features.impl.tweaks.Notifications
|
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.SnapchatPlus
|
||||||
import me.rhunk.snapenhance.features.impl.tweaks.UnlimitedSnapViewTime
|
import me.rhunk.snapenhance.features.impl.tweaks.UnlimitedSnapViewTime
|
||||||
import me.rhunk.snapenhance.features.impl.ui.PinConversations
|
import me.rhunk.snapenhance.features.impl.ui.PinConversations
|
||||||
@ -84,7 +83,6 @@ class FeatureManager(private val context: ModContext) : Manager {
|
|||||||
register(MeoPasscodeBypass::class)
|
register(MeoPasscodeBypass::class)
|
||||||
register(AppPasscode::class)
|
register(AppPasscode::class)
|
||||||
register(LocationSpoofer::class)
|
register(LocationSpoofer::class)
|
||||||
register(AutoUpdater::class)
|
|
||||||
register(CameraTweaks::class)
|
register(CameraTweaks::class)
|
||||||
register(InfiniteStoryBoost::class)
|
register(InfiniteStoryBoost::class)
|
||||||
register(AmoledDarkMode::class)
|
register(AmoledDarkMode::class)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user