commit ae70b2918012a9d332d5cfd071d04441514b8820
parent d12a5d689e903efb9621a7b8203163a914c26ce1
Author: rhunk <101876869+rhunk@users.noreply.github.com>
Date:   Wed, 23 Aug 2023 00:44:05 +0200

feat: config export
- activity launcher helper

Diffstat:
Mapp/src/main/kotlin/me/rhunk/snapenhance/RemoteSideContext.kt | 24+++++++++++++++++++++++-
Mapp/src/main/kotlin/me/rhunk/snapenhance/ui/manager/sections/features/FeaturesSection.kt | 93++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------
Mapp/src/main/kotlin/me/rhunk/snapenhance/ui/setup/screens/impl/SaveFolderScreen.kt | 11++++-------
Aapp/src/main/kotlin/me/rhunk/snapenhance/ui/util/ActivityLauncherHelper.kt | 82+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dapp/src/main/kotlin/me/rhunk/snapenhance/ui/util/ChooseFolderHelper.kt | 27---------------------------
Mcore/src/main/assets/lang/en_US.json | 4----
Mcore/src/main/kotlin/me/rhunk/snapenhance/bridge/types/BridgeFileType.kt | 7++-----
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/config/ModConfig.kt | 23++++++++++++++++++++---
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/config/impl/MessagingTweaks.kt | 6++----
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/messaging/MessagingCoreObjects.kt | 10----------
Mcore/src/main/kotlin/me/rhunk/snapenhance/features/impl/spying/PreventReadReceipts.kt | 2--
Dcore/src/main/kotlin/me/rhunk/snapenhance/features/impl/tweaks/AntiAutoSave.kt | 20--------------------
Mcore/src/main/kotlin/me/rhunk/snapenhance/manager/impl/FeatureManager.kt | 2--
13 files changed, 199 insertions(+), 112 deletions(-)

diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/RemoteSideContext.kt b/app/src/main/kotlin/me/rhunk/snapenhance/RemoteSideContext.kt @@ -4,6 +4,8 @@ import android.app.Activity import android.content.Context import android.content.Intent import android.net.Uri +import android.widget.Toast +import androidx.activity.ComponentActivity import androidx.documentfile.provider.DocumentFile import coil.ImageLoader 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.setup.Requirements import me.rhunk.snapenhance.ui.setup.SetupActivity +import me.rhunk.snapenhance.ui.util.ActivityLauncherHelper import java.lang.ref.WeakReference class RemoteSideContext( @@ -28,9 +31,14 @@ class RemoteSideContext( private var _activity: WeakReference<Activity>? = null lateinit var bridgeService: BridgeService + lateinit var activityLauncherHelper: ActivityLauncherHelper var activity: Activity? 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 translation = LocaleWrapper() @@ -84,6 +92,20 @@ class RemoteSideContext( } 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 { var requirements = overrideRequirements ?: 0 diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/sections/features/FeaturesSection.kt b/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/sections/features/FeaturesSection.kt @@ -1,6 +1,6 @@ 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.core.tween 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.filled.Close 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.Rule import androidx.compose.material.icons.filled.Search import androidx.compose.material.icons.rounded.Save import androidx.compose.material3.BottomSheetScaffoldState import androidx.compose.material3.Card +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.FilledIconButton 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.PropertyValue 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) class FeaturesSection : Section() { @@ -90,8 +94,6 @@ class FeaturesSection : Section() { 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"] } @@ -122,32 +124,13 @@ class FeaturesSection : Section() { properties } - override fun init() { - openFolderLauncher = ChooseFolderHelper.createChooseFolder(context.activity!! as ComponentActivity) { - openFolderCallback(it) - } - } override fun canGoBack() = sectionTopBarName() != featuresRouteName override fun sectionTopBarName(): String { navController.currentBackStackEntry?.arguments?.getString("name")?.let { routeName -> val currentContainerPair = allContainers[routeName] - val propertyTree = run { - 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 context.translation["${currentContainerPair?.key?.propertyTranslationPath()}.name"] } return featuresRouteName } @@ -203,10 +186,9 @@ class FeaturesSection : Section() { if (property.key.params.flags.contains(ConfigFlag.FOLDER)) { IconButton(onClick = registerClickCallback { - openFolderCallback = { uri -> + context.activityLauncherHelper.chooseFolder { uri -> propertyValue.setAny(uri) } - openFolderLauncher() }.let { { it.invoke(true) } }) { Icon(Icons.Filled.FolderOpen, contentDescription = null) } @@ -460,6 +442,63 @@ class FeaturesSection : Section() { 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 diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/ui/setup/screens/impl/SaveFolderScreen.kt b/app/src/main/kotlin/me/rhunk/snapenhance/ui/setup/screens/impl/SaveFolderScreen.kt @@ -1,6 +1,5 @@ package me.rhunk.snapenhance.ui.setup.screens.impl -import androidx.activity.ComponentActivity import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.material3.Button @@ -10,12 +9,11 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import me.rhunk.snapenhance.Logger 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.chooseFolder class SaveFolderScreen : SetupScreen() { private lateinit var saveFolder: ObservableMutableState<String> - private lateinit var openFolderLauncher: () -> Unit override fun init() { saveFolder = ObservableMutableState( @@ -29,9 +27,6 @@ class SaveFolderScreen : SetupScreen() { } } ) - openFolderLauncher = ChooseFolderHelper.createChooseFolder(context.activity as ComponentActivity) { uri -> - saveFolder.value = uri - } } @Composable @@ -39,7 +34,9 @@ class SaveFolderScreen : SetupScreen() { DialogText(text = context.translation["setup.dialogs.save_folder"]) Spacer(modifier = Modifier.height(16.dp)) Button(onClick = { - openFolderLauncher() + context.activityLauncherHelper.chooseFolder { + saveFolder.value = it + } }) { Text(text = context.translation["setup.dialogs.select_save_folder_button"]) } diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/ui/util/ActivityLauncherHelper.kt b/app/src/main/kotlin/me/rhunk/snapenhance/ui/util/ActivityLauncherHelper.kt @@ -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) + } +}+ \ No newline at end of file diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/ui/util/ChooseFolderHelper.kt b/app/src/main/kotlin/me/rhunk/snapenhance/ui/util/ChooseFolderHelper.kt @@ -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) - ) - } - } -}- \ No newline at end of file diff --git a/core/src/main/assets/lang/en_US.json b/core/src/main/assets/lang/en_US.json @@ -191,10 +191,6 @@ "name": "Anonymous Story Viewing", "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": { "name": "Hide Bitmoji Presence", "description": "Hides your Bitmoji presence from the chat" diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/bridge/types/BridgeFileType.kt b/core/src/main/kotlin/me/rhunk/snapenhance/bridge/types/BridgeFileType.kt @@ -8,11 +8,8 @@ enum class BridgeFileType(val value: Int, val fileName: String, val displayName: CONFIG(0, "config.json", "Config"), MAPPINGS(1, "mappings.json", "Mappings"), MESSAGE_LOGGER_DATABASE(2, "message_logger.db", "Message Logger",true), - STEALTH(3, "stealth.txt", "Stealth Conversations"), - ANTI_AUTO_DOWNLOAD(4, "anti_auto_download.txt", "Anti Auto Download"), - 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"); + AUTO_UPDATER_TIMESTAMP(3, "auto_updater_timestamp.txt", "Auto Updater Timestamp"), + PINNED_CONVERSATIONS(4, "pinned_conversations.txt", "Pinned Conversations"); fun resolve(context: Context): File = if (isDatabase) { context.getDatabasePath(fileName) diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/config/ModConfig.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/config/ModConfig.kt @@ -19,10 +19,11 @@ class ModConfig { private val file = FileLoaderWrapper(BridgeFileType.CONFIG, "{}".toByteArray(Charsets.UTF_8)) var wasPresent by Delegates.notNull<Boolean>() - val root = RootConfig() + lateinit var root: RootConfig operator fun getValue(thisRef: Any?, property: Any?) = root private fun load() { + root = RootConfig() wasPresent = file.isFileExists() if (!file.isFileExists()) { writeConfig() @@ -44,10 +45,26 @@ class ModConfig { root.fromJson(configObject) } - fun writeConfig() { + fun exportToString(): String { val configObject = root.toJson() 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) { diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/config/impl/MessagingTweaks.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/config/impl/MessagingTweaks.kt @@ -6,12 +6,9 @@ import me.rhunk.snapenhance.data.NotificationType class MessagingTweaks : ConfigContainer() { val anonymousStoryViewing = boolean("anonymous_story_viewing") - val preventReadReceipts = boolean("prevent_read_receipts") val hideBitmojiPresence = boolean("hide_bitmoji_presence") val hideTypingNotifications = boolean("hide_typing_notifications") 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", "CHAT", "SNAP", @@ -19,7 +16,8 @@ class MessagingTweaks : ConfigContainer() { "EXTERNAL_MEDIA", "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 messagePreviewLength = integer("message_preview_length", defaultValue = 20) } \ No newline at end of file diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/messaging/MessagingCoreObjects.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/messaging/MessagingCoreObjects.kt @@ -61,12 +61,3 @@ data class MessagingFriendInfo( val bitmojiId: String?, val selfieId: String? ) : SerializableDataObject() - - -data class MessagingRule( - val id: Int, - val type: MessagingRuleType, - val socialScope: SocialScope, - val targetUuid: String, - //val mode: Mode?, -) : SerializableDataObject()- \ No newline at end of file diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/features/impl/spying/PreventReadReceipts.kt b/core/src/main/kotlin/me/rhunk/snapenhance/features/impl/spying/PreventReadReceipts.kt @@ -9,9 +9,7 @@ import me.rhunk.snapenhance.hook.Hooker class PreventReadReceipts : Feature("PreventReadReceipts", loadParams = FeatureLoadParams.ACTIVITY_CREATE_SYNC) { override fun onActivityCreate() { - val preventReadReceipts by context.config.messaging.preventReadReceipts val isConversationInStealthMode: (SnapUUID) -> Boolean = hook@{ - if (preventReadReceipts) return@hook true context.feature(StealthMode::class).canUseRule(it.toString()) } diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/features/impl/tweaks/AntiAutoSave.kt b/core/src/main/kotlin/me/rhunk/snapenhance/features/impl/tweaks/AntiAutoSave.kt @@ -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)) - } -}- \ No newline at end of file diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/manager/impl/FeatureManager.kt b/core/src/main/kotlin/me/rhunk/snapenhance/manager/impl/FeatureManager.kt @@ -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.PreventReadReceipts 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.CameraTweaks import me.rhunk.snapenhance.features.impl.tweaks.DisableVideoLengthRestriction @@ -76,7 +75,6 @@ class FeatureManager(private val context: ModContext) : Manager { register(UITweaks::class) register(ConfigurationOverride::class) register(GalleryMediaSendOverride::class) - register(AntiAutoSave::class) register(UnlimitedSnapViewTime::class) register(DisableVideoLengthRestriction::class) register(MediaQualityLevelOverride::class)