feat: config export

- activity launcher helper
This commit is contained in:
rhunk
2023-08-23 00:44:05 +02:00
parent d12a5d689e
commit ae70b29180
13 changed files with 198 additions and 109 deletions

View File

@ -4,6 +4,8 @@ import android.app.Activity
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import coil.ImageLoader import coil.ImageLoader
import coil.decode.VideoFrameDecoder import coil.decode.VideoFrameDecoder
@ -20,6 +22,7 @@ import me.rhunk.snapenhance.ui.manager.data.ModMappingsInfo
import me.rhunk.snapenhance.ui.manager.data.SnapchatAppInfo import me.rhunk.snapenhance.ui.manager.data.SnapchatAppInfo
import me.rhunk.snapenhance.ui.setup.Requirements import me.rhunk.snapenhance.ui.setup.Requirements
import me.rhunk.snapenhance.ui.setup.SetupActivity import me.rhunk.snapenhance.ui.setup.SetupActivity
import me.rhunk.snapenhance.ui.util.ActivityLauncherHelper
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
class RemoteSideContext( class RemoteSideContext(
@ -28,9 +31,14 @@ class RemoteSideContext(
private var _activity: WeakReference<Activity>? = null private var _activity: WeakReference<Activity>? = null
lateinit var bridgeService: BridgeService lateinit var bridgeService: BridgeService
lateinit var activityLauncherHelper: ActivityLauncherHelper
var activity: Activity? var activity: Activity?
get() = _activity?.get() get() = _activity?.get()
set(value) { _activity?.clear(); _activity = WeakReference(value) } set(value) {
_activity?.clear();
_activity = WeakReference(value)
activityLauncherHelper = ActivityLauncherHelper(value as ComponentActivity)
}
val config = ModConfig() val config = ModConfig()
val translation = LocaleWrapper() val translation = LocaleWrapper()
@ -84,6 +92,20 @@ class RemoteSideContext(
} else null } else null
) )
fun longToast(message: Any) {
activity?.runOnUiThread {
Toast.makeText(activity, message.toString(), Toast.LENGTH_LONG).show()
}
Logger.debug(message.toString())
}
fun shortToast(message: Any) {
activity?.runOnUiThread {
Toast.makeText(activity, message.toString(), Toast.LENGTH_SHORT).show()
}
Logger.debug(message.toString())
}
fun checkForRequirements(overrideRequirements: Int? = null): Boolean { fun checkForRequirements(overrideRequirements: Int? = null): Boolean {
var requirements = overrideRequirements ?: 0 var requirements = overrideRequirements ?: 0

View File

@ -1,6 +1,6 @@
package me.rhunk.snapenhance.ui.manager.sections.features package me.rhunk.snapenhance.ui.manager.sections.features
import androidx.activity.ComponentActivity import android.net.Uri
import androidx.compose.animation.AnimatedContentTransitionScope import androidx.compose.animation.AnimatedContentTransitionScope
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
import androidx.compose.foundation.background import androidx.compose.foundation.background
@ -27,12 +27,14 @@ import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.FolderOpen import androidx.compose.material.icons.filled.FolderOpen
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material.icons.filled.OpenInNew import androidx.compose.material.icons.filled.OpenInNew
import androidx.compose.material.icons.filled.Rule
import androidx.compose.material.icons.filled.Search import androidx.compose.material.icons.filled.Search
import androidx.compose.material.icons.rounded.Save import androidx.compose.material.icons.rounded.Save
import androidx.compose.material3.BottomSheetScaffoldState import androidx.compose.material3.BottomSheetScaffoldState
import androidx.compose.material3.Card import androidx.compose.material3.Card
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FilledIconButton import androidx.compose.material3.FilledIconButton
import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.FloatingActionButton
@ -78,7 +80,9 @@ import me.rhunk.snapenhance.core.config.PropertyKey
import me.rhunk.snapenhance.core.config.PropertyPair import me.rhunk.snapenhance.core.config.PropertyPair
import me.rhunk.snapenhance.core.config.PropertyValue import me.rhunk.snapenhance.core.config.PropertyValue
import me.rhunk.snapenhance.ui.manager.Section import me.rhunk.snapenhance.ui.manager.Section
import me.rhunk.snapenhance.ui.util.ChooseFolderHelper import me.rhunk.snapenhance.ui.util.chooseFolder
import me.rhunk.snapenhance.ui.util.openFile
import me.rhunk.snapenhance.ui.util.saveFile
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
class FeaturesSection : Section() { class FeaturesSection : Section() {
@ -90,8 +94,6 @@ class FeaturesSection : Section() {
const val SEARCH_FEATURE_ROUTE = "search_feature/{keyword}" const val SEARCH_FEATURE_ROUTE = "search_feature/{keyword}"
} }
private lateinit var openFolderCallback: (uri: String) -> Unit
private lateinit var openFolderLauncher: () -> Unit
private val featuresRouteName by lazy { context.translation["manager.routes.features"] } private val featuresRouteName by lazy { context.translation["manager.routes.features"] }
@ -122,32 +124,13 @@ class FeaturesSection : Section() {
properties properties
} }
override fun init() {
openFolderLauncher = ChooseFolderHelper.createChooseFolder(context.activity!! as ComponentActivity) {
openFolderCallback(it)
}
}
override fun canGoBack() = sectionTopBarName() != featuresRouteName override fun canGoBack() = sectionTopBarName() != featuresRouteName
override fun sectionTopBarName(): String { override fun sectionTopBarName(): String {
navController.currentBackStackEntry?.arguments?.getString("name")?.let { routeName -> navController.currentBackStackEntry?.arguments?.getString("name")?.let { routeName ->
val currentContainerPair = allContainers[routeName] val currentContainerPair = allContainers[routeName]
val propertyTree = run { return context.translation["${currentContainerPair?.key?.propertyTranslationPath()}.name"]
var key = currentContainerPair?.key
val tree = mutableListOf<String>()
while (key != null) {
tree.add(key.propertyTranslationPath())
key = key.parentKey
}
tree
}
val translatedKey = propertyTree.reversed().joinToString(" > ") {
context.translation["$it.name"]
}
return "$featuresRouteName > $translatedKey"
} }
return featuresRouteName return featuresRouteName
} }
@ -203,10 +186,9 @@ class FeaturesSection : Section() {
if (property.key.params.flags.contains(ConfigFlag.FOLDER)) { if (property.key.params.flags.contains(ConfigFlag.FOLDER)) {
IconButton(onClick = registerClickCallback { IconButton(onClick = registerClickCallback {
openFolderCallback = { uri -> context.activityLauncherHelper.chooseFolder { uri ->
propertyValue.setAny(uri) propertyValue.setAny(uri)
} }
openFolderLauncher()
}.let { { it.invoke(true) } }) { }.let { { it.invoke(true) } }) {
Icon(Icons.Filled.FolderOpen, contentDescription = null) Icon(Icons.Filled.FolderOpen, contentDescription = null)
} }
@ -460,6 +442,63 @@ class FeaturesSection : Section() {
contentDescription = null contentDescription = null
) )
} }
if (showSearchBar) return
var showExportDropdownMenu by remember { mutableStateOf(false) }
val actions = remember {
mapOf(
"Export" to {
context.activityLauncherHelper.saveFile("config.json", "application/json") { uri ->
context.androidContext.contentResolver.openOutputStream(Uri.parse(uri))?.use {
context.config.writeConfig()
context.config.exportToString().byteInputStream().copyTo(it)
context.shortToast("Config exported successfully!")
}
}
},
"Import" to {
context.activityLauncherHelper.openFile("application/json") { uri ->
context.androidContext.contentResolver.openInputStream(Uri.parse(uri))?.use {
runCatching {
context.config.loadFromString(it.readBytes().toString(Charsets.UTF_8))
}.onFailure {
context.longToast("Failed to import config ${it.message}")
return@use
}
context.shortToast("Config successfully loaded!")
}
}
},
"Reset" to {
context.config.reset()
context.shortToast("Config successfully reset!")
}
)
}
IconButton(onClick = { showExportDropdownMenu = !showExportDropdownMenu}) {
Icon(
imageVector = Icons.Filled.MoreVert,
contentDescription = null
)
}
if (showExportDropdownMenu) {
DropdownMenu(expanded = showExportDropdownMenu, onDismissRequest = { showExportDropdownMenu = false }) {
actions.forEach { (name, action) ->
DropdownMenuItem(
text = {
Text(text = name)
},
onClick = {
action()
showExportDropdownMenu = false
}
)
}
}
}
} }
@Composable @Composable

View File

@ -1,6 +1,5 @@
package me.rhunk.snapenhance.ui.setup.screens.impl package me.rhunk.snapenhance.ui.setup.screens.impl
import androidx.activity.ComponentActivity
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.material3.Button import androidx.compose.material3.Button
@ -10,12 +9,11 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import me.rhunk.snapenhance.Logger import me.rhunk.snapenhance.Logger
import me.rhunk.snapenhance.ui.setup.screens.SetupScreen import me.rhunk.snapenhance.ui.setup.screens.SetupScreen
import me.rhunk.snapenhance.ui.util.ChooseFolderHelper
import me.rhunk.snapenhance.ui.util.ObservableMutableState import me.rhunk.snapenhance.ui.util.ObservableMutableState
import me.rhunk.snapenhance.ui.util.chooseFolder
class SaveFolderScreen : SetupScreen() { class SaveFolderScreen : SetupScreen() {
private lateinit var saveFolder: ObservableMutableState<String> private lateinit var saveFolder: ObservableMutableState<String>
private lateinit var openFolderLauncher: () -> Unit
override fun init() { override fun init() {
saveFolder = ObservableMutableState( saveFolder = ObservableMutableState(
@ -29,9 +27,6 @@ class SaveFolderScreen : SetupScreen() {
} }
} }
) )
openFolderLauncher = ChooseFolderHelper.createChooseFolder(context.activity as ComponentActivity) { uri ->
saveFolder.value = uri
}
} }
@Composable @Composable
@ -39,7 +34,9 @@ class SaveFolderScreen : SetupScreen() {
DialogText(text = context.translation["setup.dialogs.save_folder"]) DialogText(text = context.translation["setup.dialogs.save_folder"])
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
Button(onClick = { Button(onClick = {
openFolderLauncher() context.activityLauncherHelper.chooseFolder {
saveFolder.value = it
}
}) { }) {
Text(text = context.translation["setup.dialogs.select_save_folder_button"]) Text(text = context.translation["setup.dialogs.select_save_folder_button"])
} }

View File

@ -0,0 +1,81 @@
package me.rhunk.snapenhance.ui.util
import android.content.Intent
import androidx.activity.ComponentActivity
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import me.rhunk.snapenhance.Logger
class ActivityLauncherHelper(
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.error("Failed to process activity result", it)
}
}
callback = null
}
fun launch(intent: Intent, callback: (Intent) -> Unit) {
if (this.callback != null) {
throw IllegalStateException("Already launching an activity")
}
this.callback = callback
activityResultLauncher.launch(intent)
}
}
fun ActivityLauncherHelper.chooseFolder(callback: (uri: String) -> Unit) {
launch(
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
val value = uri.toString()
this.activity.contentResolver.takePersistableUriPermission(
uri,
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)
callback(value)
}
}
fun ActivityLauncherHelper.saveFile(name: String, type: String = "*/*", callback: (uri: String) -> Unit) {
launch(
Intent(Intent.ACTION_CREATE_DOCUMENT)
.addCategory(Intent.CATEGORY_OPENABLE)
.setType(type)
.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
val value = uri.toString()
this.activity.contentResolver.takePersistableUriPermission(
uri,
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)
callback(value)
}
}
fun ActivityLauncherHelper.openFile(type: String = "*/*", callback: (uri: String) -> Unit) {
launch(
Intent(Intent.ACTION_OPEN_DOCUMENT)
.addCategory(Intent.CATEGORY_OPENABLE)
.setType(type)
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
) {
val uri = it.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)
}
}

View File

@ -1,26 +0,0 @@
package me.rhunk.snapenhance.ui.util
import android.app.Activity
import android.content.Intent
import androidx.activity.ComponentActivity
import androidx.activity.result.contract.ActivityResultContracts
object ChooseFolderHelper {
fun createChooseFolder(activity: ComponentActivity, callback: (uri: String) -> Unit): () -> Unit {
val activityResultLauncher = activity.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) result@{
if (it.resultCode != Activity.RESULT_OK) return@result
val uri = it.data?.data ?: return@result
val value = uri.toString()
activity.contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
callback(value)
}
return {
activityResultLauncher.launch(
Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
)
}
}
}

View File

@ -191,10 +191,6 @@
"name": "Anonymous Story Viewing", "name": "Anonymous Story Viewing",
"description": "Prevents anyone from knowing you've seen their story" "description": "Prevents anyone from knowing you've seen their story"
}, },
"prevent_read_receipts": {
"name": "Prevent Read Receipts",
"description": "Prevent anyone from knowing you've opened their Snaps/Chats"
},
"hide_bitmoji_presence": { "hide_bitmoji_presence": {
"name": "Hide Bitmoji Presence", "name": "Hide Bitmoji Presence",
"description": "Hides your Bitmoji presence from the chat" "description": "Hides your Bitmoji presence from the chat"

View File

@ -8,11 +8,8 @@ enum class BridgeFileType(val value: Int, val fileName: String, val displayName:
CONFIG(0, "config.json", "Config"), CONFIG(0, "config.json", "Config"),
MAPPINGS(1, "mappings.json", "Mappings"), MAPPINGS(1, "mappings.json", "Mappings"),
MESSAGE_LOGGER_DATABASE(2, "message_logger.db", "Message Logger",true), MESSAGE_LOGGER_DATABASE(2, "message_logger.db", "Message Logger",true),
STEALTH(3, "stealth.txt", "Stealth Conversations"), AUTO_UPDATER_TIMESTAMP(3, "auto_updater_timestamp.txt", "Auto Updater Timestamp"),
ANTI_AUTO_DOWNLOAD(4, "anti_auto_download.txt", "Anti Auto Download"), PINNED_CONVERSATIONS(4, "pinned_conversations.txt", "Pinned Conversations");
ANTI_AUTO_SAVE(5, "anti_auto_save.txt", "Anti Auto Save"),
AUTO_UPDATER_TIMESTAMP(6, "auto_updater_timestamp.txt", "Auto Updater Timestamp"),
PINNED_CONVERSATIONS(7, "pinned_conversations.txt", "Pinned Conversations");
fun resolve(context: Context): File = if (isDatabase) { fun resolve(context: Context): File = if (isDatabase) {
context.getDatabasePath(fileName) context.getDatabasePath(fileName)

View File

@ -19,10 +19,11 @@ class ModConfig {
private val file = FileLoaderWrapper(BridgeFileType.CONFIG, "{}".toByteArray(Charsets.UTF_8)) private val file = FileLoaderWrapper(BridgeFileType.CONFIG, "{}".toByteArray(Charsets.UTF_8))
var wasPresent by Delegates.notNull<Boolean>() var wasPresent by Delegates.notNull<Boolean>()
val root = RootConfig() lateinit var root: RootConfig
operator fun getValue(thisRef: Any?, property: Any?) = root operator fun getValue(thisRef: Any?, property: Any?) = root
private fun load() { private fun load() {
root = RootConfig()
wasPresent = file.isFileExists() wasPresent = file.isFileExists()
if (!file.isFileExists()) { if (!file.isFileExists()) {
writeConfig() writeConfig()
@ -44,10 +45,26 @@ class ModConfig {
root.fromJson(configObject) root.fromJson(configObject)
} }
fun writeConfig() { fun exportToString(): String {
val configObject = root.toJson() val configObject = root.toJson()
configObject.addProperty("_locale", locale) configObject.addProperty("_locale", locale)
file.write(configObject.toString().toByteArray(Charsets.UTF_8)) return configObject.toString()
}
fun reset() {
root = RootConfig()
writeConfig()
}
fun writeConfig() {
file.write(exportToString().toByteArray(Charsets.UTF_8))
}
fun loadFromString(string: String) {
val configObject = gson.fromJson(string, JsonObject::class.java)
locale = configObject.get("_locale")?.asString ?: LocaleWrapper.DEFAULT_LOCALE
root.fromJson(configObject)
writeConfig()
} }
fun loadFromContext(context: Context) { fun loadFromContext(context: Context) {

View File

@ -6,12 +6,9 @@ import me.rhunk.snapenhance.data.NotificationType
class MessagingTweaks : ConfigContainer() { class MessagingTweaks : ConfigContainer() {
val anonymousStoryViewing = boolean("anonymous_story_viewing") val anonymousStoryViewing = boolean("anonymous_story_viewing")
val preventReadReceipts = boolean("prevent_read_receipts")
val hideBitmojiPresence = boolean("hide_bitmoji_presence") val hideBitmojiPresence = boolean("hide_bitmoji_presence")
val hideTypingNotifications = boolean("hide_typing_notifications") val hideTypingNotifications = boolean("hide_typing_notifications")
val unlimitedSnapViewTime = boolean("unlimited_snap_view_time") val unlimitedSnapViewTime = boolean("unlimited_snap_view_time")
val preventMessageSending = multiple("prevent_message_sending", *NotificationType.getOutgoingValues().map { it.key }.toTypedArray())
val messageLogger = boolean("message_logger") { addNotices(FeatureNotice.MAY_CAUSE_CRASHES) }
val autoSaveMessagesInConversations = multiple("auto_save_messages_in_conversations", val autoSaveMessagesInConversations = multiple("auto_save_messages_in_conversations",
"CHAT", "CHAT",
"SNAP", "SNAP",
@ -19,7 +16,8 @@ class MessagingTweaks : ConfigContainer() {
"EXTERNAL_MEDIA", "EXTERNAL_MEDIA",
"STICKER" "STICKER"
) )
val preventMessageSending = multiple("prevent_message_sending", *NotificationType.getOutgoingValues().map { it.key }.toTypedArray())
val messageLogger = boolean("message_logger") { addNotices(FeatureNotice.MAY_CAUSE_CRASHES) }
val galleryMediaSendOverride = boolean("gallery_media_send_override") val galleryMediaSendOverride = boolean("gallery_media_send_override")
val messagePreviewLength = integer("message_preview_length", defaultValue = 20) val messagePreviewLength = integer("message_preview_length", defaultValue = 20)
} }

View File

@ -61,12 +61,3 @@ data class MessagingFriendInfo(
val bitmojiId: String?, val bitmojiId: String?,
val selfieId: String? val selfieId: String?
) : SerializableDataObject() ) : SerializableDataObject()
data class MessagingRule(
val id: Int,
val type: MessagingRuleType,
val socialScope: SocialScope,
val targetUuid: String,
//val mode: Mode?,
) : SerializableDataObject()

View File

@ -9,9 +9,7 @@ import me.rhunk.snapenhance.hook.Hooker
class PreventReadReceipts : Feature("PreventReadReceipts", loadParams = FeatureLoadParams.ACTIVITY_CREATE_SYNC) { class PreventReadReceipts : Feature("PreventReadReceipts", loadParams = FeatureLoadParams.ACTIVITY_CREATE_SYNC) {
override fun onActivityCreate() { override fun onActivityCreate() {
val preventReadReceipts by context.config.messaging.preventReadReceipts
val isConversationInStealthMode: (SnapUUID) -> Boolean = hook@{ val isConversationInStealthMode: (SnapUUID) -> Boolean = hook@{
if (preventReadReceipts) return@hook true
context.feature(StealthMode::class).canUseRule(it.toString()) context.feature(StealthMode::class).canUseRule(it.toString())
} }

View File

@ -1,19 +0,0 @@
package me.rhunk.snapenhance.features.impl.tweaks
import me.rhunk.snapenhance.bridge.types.BridgeFileType
import me.rhunk.snapenhance.features.BridgeFileFeature
import me.rhunk.snapenhance.features.FeatureLoadParams
class AntiAutoSave : BridgeFileFeature("AntiAutoSave", BridgeFileType.ANTI_AUTO_SAVE, loadParams = FeatureLoadParams.ACTIVITY_CREATE_SYNC) {
override fun onActivityCreate() {
readFile()
}
fun setConversationIgnored(userId: String, state: Boolean) {
setState(userId.hashCode().toLong().toString(16), state)
}
fun isConversationIgnored(userId: String): Boolean {
return exists(userId.hashCode().toLong().toString(16))
}
}

View File

@ -21,7 +21,6 @@ import me.rhunk.snapenhance.features.impl.spying.AnonymousStoryViewing
import me.rhunk.snapenhance.features.impl.spying.MessageLogger import me.rhunk.snapenhance.features.impl.spying.MessageLogger
import me.rhunk.snapenhance.features.impl.spying.PreventReadReceipts import me.rhunk.snapenhance.features.impl.spying.PreventReadReceipts
import me.rhunk.snapenhance.features.impl.spying.StealthMode import me.rhunk.snapenhance.features.impl.spying.StealthMode
import me.rhunk.snapenhance.features.impl.tweaks.AntiAutoSave
import me.rhunk.snapenhance.features.impl.tweaks.AutoSave 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.DisableVideoLengthRestriction import me.rhunk.snapenhance.features.impl.tweaks.DisableVideoLengthRestriction
@ -76,7 +75,6 @@ class FeatureManager(private val context: ModContext) : Manager {
register(UITweaks::class) register(UITweaks::class)
register(ConfigurationOverride::class) register(ConfigurationOverride::class)
register(GalleryMediaSendOverride::class) register(GalleryMediaSendOverride::class)
register(AntiAutoSave::class)
register(UnlimitedSnapViewTime::class) register(UnlimitedSnapViewTime::class)
register(DisableVideoLengthRestriction::class) register(DisableVideoLengthRestriction::class)
register(MediaQualityLevelOverride::class) register(MediaQualityLevelOverride::class)