mirror of
https://github.com/rhunk/SnapEnhance.git
synced 2025-06-13 05:37:48 +02:00
feat: rule system
This commit is contained in:
@ -123,11 +123,12 @@ class BridgeService : Service() {
|
|||||||
).onReceive(intent)
|
).onReceive(intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getRules(objectType: String, uuid: String): MutableList<String> {
|
override fun getRules(uuid: String): List<String> {
|
||||||
remoteSideContext.modDatabase.getRulesFromId(SocialScope.valueOf(objectType), uuid)
|
return remoteSideContext.modDatabase.getRules(uuid).map { it.key }
|
||||||
.let { rules ->
|
}
|
||||||
return rules.map { it.toJson() }.toMutableList()
|
|
||||||
}
|
override fun setRule(uuid: String, rule: String, state: Boolean) {
|
||||||
|
remoteSideContext.modDatabase.setRule(uuid, rule, state)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun sync(callback: SyncCallback) {
|
override fun sync(callback: SyncCallback) {
|
||||||
|
@ -6,8 +6,7 @@ import me.rhunk.snapenhance.RemoteSideContext
|
|||||||
import me.rhunk.snapenhance.core.messaging.FriendStreaks
|
import me.rhunk.snapenhance.core.messaging.FriendStreaks
|
||||||
import me.rhunk.snapenhance.core.messaging.MessagingFriendInfo
|
import me.rhunk.snapenhance.core.messaging.MessagingFriendInfo
|
||||||
import me.rhunk.snapenhance.core.messaging.MessagingGroupInfo
|
import me.rhunk.snapenhance.core.messaging.MessagingGroupInfo
|
||||||
import me.rhunk.snapenhance.core.messaging.MessagingRule
|
import me.rhunk.snapenhance.core.messaging.MessagingRuleType
|
||||||
import me.rhunk.snapenhance.core.messaging.SocialScope
|
|
||||||
import me.rhunk.snapenhance.database.objects.FriendInfo
|
import me.rhunk.snapenhance.database.objects.FriendInfo
|
||||||
import me.rhunk.snapenhance.util.SQLiteDatabaseHelper
|
import me.rhunk.snapenhance.util.SQLiteDatabaseHelper
|
||||||
import me.rhunk.snapenhance.util.ktx.getInteger
|
import me.rhunk.snapenhance.util.ktx.getInteger
|
||||||
@ -53,10 +52,8 @@ class ModDatabase(
|
|||||||
),
|
),
|
||||||
"rules" to listOf(
|
"rules" to listOf(
|
||||||
"id INTEGER PRIMARY KEY AUTOINCREMENT",
|
"id INTEGER PRIMARY KEY AUTOINCREMENT",
|
||||||
"scope VARCHAR",
|
"type VARCHAR",
|
||||||
"targetUuid VARCHAR",
|
"targetUuid VARCHAR"
|
||||||
//"mode VARCHAR",
|
|
||||||
"subject VARCHAR"
|
|
||||||
),
|
),
|
||||||
"streaks" to listOf(
|
"streaks" to listOf(
|
||||||
"userId VARCHAR PRIMARY KEY",
|
"userId VARCHAR PRIMARY KEY",
|
||||||
@ -157,34 +154,29 @@ class ModDatabase(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getRulesFromId(type: SocialScope, targetUuid: String): List<MessagingRule> {
|
fun getRules(targetUuid: String): List<MessagingRuleType> {
|
||||||
return database.rawQuery("SELECT * FROM rules WHERE scope = ? AND targetUuid = ?", arrayOf(type.name, targetUuid)).use { cursor ->
|
return database.rawQuery("SELECT type FROM rules WHERE targetUuid = ?", arrayOf(
|
||||||
val rules = mutableListOf<MessagingRule>()
|
targetUuid
|
||||||
|
)).use { cursor ->
|
||||||
|
val rules = mutableListOf<MessagingRuleType>()
|
||||||
while (cursor.moveToNext()) {
|
while (cursor.moveToNext()) {
|
||||||
rules.add(MessagingRule(
|
rules.add(MessagingRuleType.getByName(cursor.getStringOrNull("type")!!))
|
||||||
id = cursor.getInteger("id"),
|
|
||||||
socialScope = SocialScope.valueOf(cursor.getStringOrNull("scope")!!),
|
|
||||||
targetUuid = cursor.getStringOrNull("targetUuid")!!,
|
|
||||||
subject = cursor.getStringOrNull("subject")!!
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
rules
|
rules
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toggleRuleFor(type: SocialScope, targetUuid: String, subject: String, enabled: Boolean) {
|
fun setRule(targetUuid: String, type: String, enabled: Boolean) {
|
||||||
executeAsync {
|
executeAsync {
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
database.execSQL("INSERT OR REPLACE INTO rules (scope, targetUuid, subject) VALUES (?, ?, ?)", arrayOf(
|
database.execSQL("INSERT OR REPLACE INTO rules (targetUuid, type) VALUES (?, ?)", arrayOf(
|
||||||
type.name,
|
|
||||||
targetUuid,
|
targetUuid,
|
||||||
subject
|
type
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
database.execSQL("DELETE FROM rules WHERE scope = ? AND targetUuid = ? AND subject = ?", arrayOf(
|
database.execSQL("DELETE FROM rules WHERE targetUuid = ? AND type = ?", arrayOf(
|
||||||
type.name,
|
|
||||||
targetUuid,
|
targetUuid,
|
||||||
subject
|
type
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -57,10 +57,7 @@ class Dialogs(
|
|||||||
@Composable
|
@Composable
|
||||||
fun TranslatedText(property: PropertyPair<*>, key: String, modifier: Modifier = Modifier) {
|
fun TranslatedText(property: PropertyPair<*>, key: String, modifier: Modifier = Modifier) {
|
||||||
Text(
|
Text(
|
||||||
text = when (key) {
|
text = property.key.propertyOption(translation, key),
|
||||||
"null" -> translation["manager.features.disabled"]
|
|
||||||
else -> if (property.key.params.shouldTranslate) translation["features.options.${property.key.name}.$key"] else key
|
|
||||||
},
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(10.dp, 10.dp, 10.dp, 10.dp)
|
.padding(10.dp, 10.dp, 10.dp, 10.dp)
|
||||||
.then(modifier)
|
.then(modifier)
|
||||||
|
@ -28,6 +28,7 @@ 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.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
|
||||||
@ -71,6 +72,7 @@ import kotlinx.coroutines.Job
|
|||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import me.rhunk.snapenhance.core.config.ConfigContainer
|
import me.rhunk.snapenhance.core.config.ConfigContainer
|
||||||
|
import me.rhunk.snapenhance.core.config.ConfigFlag
|
||||||
import me.rhunk.snapenhance.core.config.DataProcessors
|
import me.rhunk.snapenhance.core.config.DataProcessors
|
||||||
import me.rhunk.snapenhance.core.config.PropertyKey
|
import me.rhunk.snapenhance.core.config.PropertyKey
|
||||||
import me.rhunk.snapenhance.core.config.PropertyPair
|
import me.rhunk.snapenhance.core.config.PropertyPair
|
||||||
@ -172,8 +174,8 @@ class FeaturesSection : Section() {
|
|||||||
backStackEntry.arguments?.getString("keyword")?.let { keyword ->
|
backStackEntry.arguments?.getString("keyword")?.let { keyword ->
|
||||||
val properties = allProperties.filter {
|
val properties = allProperties.filter {
|
||||||
it.key.name.contains(keyword, ignoreCase = true) ||
|
it.key.name.contains(keyword, ignoreCase = true) ||
|
||||||
context.translation["${it.key.propertyTranslationPath()}.name"].contains(keyword, ignoreCase = true) ||
|
context.translation[it.key.propertyName()].contains(keyword, ignoreCase = true) ||
|
||||||
context.translation["${it.key.propertyTranslationPath()}.description"].contains(keyword, ignoreCase = true)
|
context.translation[it.key.propertyDescription()].contains(keyword, ignoreCase = true)
|
||||||
}.map { PropertyPair(it.key, it.value) }
|
}.map { PropertyPair(it.key, it.value) }
|
||||||
|
|
||||||
PropertiesView(properties)
|
PropertiesView(properties)
|
||||||
@ -199,7 +201,7 @@ class FeaturesSection : Section() {
|
|||||||
|
|
||||||
val propertyValue = property.value
|
val propertyValue = property.value
|
||||||
|
|
||||||
if (property.key.params.isFolder) {
|
if (property.key.params.flags.contains(ConfigFlag.FOLDER)) {
|
||||||
IconButton(onClick = registerClickCallback {
|
IconButton(onClick = registerClickCallback {
|
||||||
openFolderCallback = { uri ->
|
openFolderCallback = { uri ->
|
||||||
propertyValue.setAny(uri)
|
propertyValue.setAny(uri)
|
||||||
@ -234,11 +236,9 @@ class FeaturesSection : Section() {
|
|||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
modifier = Modifier.widthIn(0.dp, 120.dp),
|
modifier = Modifier.widthIn(0.dp, 120.dp),
|
||||||
text = (propertyValue.getNullable() as? String)?.let{
|
text = (propertyValue.getNullable() as? String ?: "null").let {
|
||||||
if (property.key.params.shouldTranslate) {
|
property.key.propertyOption(context.translation, it)
|
||||||
context.translation["features.options.${property.name}.$it"]
|
}
|
||||||
} else it
|
|
||||||
} ?: context.translation["manager.features.disabled"],
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -353,12 +353,12 @@ class FeaturesSection : Section() {
|
|||||||
.padding(all = 10.dp)
|
.padding(all = 10.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = context.translation["${property.key.propertyTranslationPath()}.name"],
|
text = context.translation[property.key.propertyName()],
|
||||||
fontSize = 16.sp,
|
fontSize = 16.sp,
|
||||||
fontWeight = FontWeight.Bold
|
fontWeight = FontWeight.Bold
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = context.translation["${property.key.propertyTranslationPath()}.description"],
|
text = context.translation[property.key.propertyDescription()],
|
||||||
fontSize = 12.sp,
|
fontSize = 12.sp,
|
||||||
lineHeight = 15.sp
|
lineHeight = 15.sp
|
||||||
)
|
)
|
||||||
|
@ -71,23 +71,33 @@ class ScopeContent(
|
|||||||
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
val scopeRules = context.modDatabase.getRulesFromId(scope, id)
|
val rules = context.modDatabase.getRules(id)
|
||||||
|
|
||||||
SectionTitle("Rules")
|
SectionTitle("Rules")
|
||||||
|
|
||||||
ContentCard {
|
ContentCard {
|
||||||
//manager anti features etc
|
//manager anti features etc
|
||||||
MessagingRuleType.values().forEach { feature ->
|
MessagingRuleType.values().forEach { ruleType ->
|
||||||
var featureEnabled by remember {
|
var ruleEnabled by remember {
|
||||||
mutableStateOf(scopeRules.any { it.subject == feature.key })
|
mutableStateOf(rules.any { it.key == ruleType.key })
|
||||||
}
|
}
|
||||||
val featureEnabledText = if (featureEnabled) "Enabled" else "Disabled"
|
|
||||||
|
|
||||||
Row {
|
val ruleState = context.config.root.rules.getRuleState(ruleType)
|
||||||
Text(text = "${feature.key}: $featureEnabledText", maxLines = 1)
|
|
||||||
Switch(checked = featureEnabled, onCheckedChange = {
|
Row(
|
||||||
context.modDatabase.toggleRuleFor(scope, id, feature.key, it)
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
featureEnabled = it
|
modifier = Modifier.padding(all = 4.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = if (ruleType.listMode && ruleState != null) {
|
||||||
|
context.translation["rules.properties.${ruleType.key}.options.${ruleState.key}"]
|
||||||
|
} else context.translation["rules.properties.${ruleType.key}.name"],
|
||||||
|
maxLines = 1,
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
Switch(checked = ruleEnabled, enabled = if (ruleType.listMode) ruleState != null else true, onCheckedChange = {
|
||||||
|
context.modDatabase.setRule(id, ruleType.key, it)
|
||||||
|
ruleEnabled = it
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -195,9 +205,9 @@ class ScopeContent(
|
|||||||
fontSize = 12.sp,
|
fontSize = 12.sp,
|
||||||
fontWeight = FontWeight.Light
|
fontWeight = FontWeight.Light
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
// Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
DeleteScopeEntityButton()
|
//DeleteScopeEntityButton()
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
@ -63,8 +63,12 @@ interface BridgeInterface {
|
|||||||
/**
|
/**
|
||||||
* Get rules for a given user or conversation
|
* Get rules for a given user or conversation
|
||||||
*/
|
*/
|
||||||
|
List<String> getRules(String uuid);
|
||||||
|
|
||||||
List<String> getRules(String objectType, String uuid);
|
/**
|
||||||
|
* Update rule for a giver user or conversation
|
||||||
|
*/
|
||||||
|
void setRule(String uuid, String type, boolean state);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sync groups and friends
|
* Sync groups and friends
|
||||||
|
@ -28,6 +28,42 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"rules": {
|
||||||
|
"modes": {
|
||||||
|
"blacklist": "Blacklist mode",
|
||||||
|
"whitelist": "Whitelist mode"
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"auto_download": {
|
||||||
|
"name": "Auto download",
|
||||||
|
"description": "Auto download snaps when viewed",
|
||||||
|
"options": {
|
||||||
|
"blacklist": "Exclude from Auto Download",
|
||||||
|
"whitelist": "Auto Download"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"stealth": {
|
||||||
|
"name": "Stealth Mode",
|
||||||
|
"description": "Prevents anyone from knowing you've opened their Snaps/Chats and conversations",
|
||||||
|
"options": {
|
||||||
|
"blacklist": "Exclude from stealth mode",
|
||||||
|
"whitelist": "Stealth mode"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"auto_save": {
|
||||||
|
"name": "Auto Save",
|
||||||
|
"description": "Saves chat messages when viewed",
|
||||||
|
"options": {
|
||||||
|
"blacklist": "Exclude from auto save",
|
||||||
|
"whitelist": "Auto save"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"hide_chat_feed": {
|
||||||
|
"name": "Hide from chat feed"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
"action": {
|
"action": {
|
||||||
"clean_cache": "Clean Cache",
|
"clean_cache": "Clean Cache",
|
||||||
"clear_message_logger": "Clear Message Logger",
|
"clear_message_logger": "Clear Message Logger",
|
||||||
@ -239,6 +275,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"rules": {
|
||||||
|
"name": "Rules",
|
||||||
|
"description": "Manage automatic features\nThe social tab lets you assign a rule to an object"
|
||||||
|
},
|
||||||
"camera": {
|
"camera": {
|
||||||
"name": "Camera",
|
"name": "Camera",
|
||||||
"description": "Adjust the right settings for the perfect snap",
|
"description": "Adjust the right settings for the perfect snap",
|
||||||
|
@ -15,10 +15,8 @@ import me.rhunk.snapenhance.ModContext
|
|||||||
import me.rhunk.snapenhance.bridge.types.BridgeFileType
|
import me.rhunk.snapenhance.bridge.types.BridgeFileType
|
||||||
import me.rhunk.snapenhance.bridge.types.FileActionType
|
import me.rhunk.snapenhance.bridge.types.FileActionType
|
||||||
import me.rhunk.snapenhance.core.BuildConfig
|
import me.rhunk.snapenhance.core.BuildConfig
|
||||||
import me.rhunk.snapenhance.core.messaging.MessagingRule
|
import me.rhunk.snapenhance.core.messaging.MessagingRuleType
|
||||||
import me.rhunk.snapenhance.core.messaging.SocialScope
|
|
||||||
import me.rhunk.snapenhance.data.LocalePair
|
import me.rhunk.snapenhance.data.LocalePair
|
||||||
import me.rhunk.snapenhance.util.SerializableDataObject
|
|
||||||
import java.util.concurrent.CompletableFuture
|
import java.util.concurrent.CompletableFuture
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
import kotlin.system.exitProcess
|
import kotlin.system.exitProcess
|
||||||
@ -138,9 +136,10 @@ class BridgeClient(
|
|||||||
|
|
||||||
fun passGroupsAndFriends(groups: List<String>, friends: List<String>) = service.passGroupsAndFriends(groups, friends)
|
fun passGroupsAndFriends(groups: List<String>, friends: List<String>) = service.passGroupsAndFriends(groups, friends)
|
||||||
|
|
||||||
fun getRulesFromId(type: SocialScope, targetUuid: String): List<MessagingRule> {
|
fun getRules(targetUuid: String): List<MessagingRuleType> {
|
||||||
return service.getRules(type.name, targetUuid).map {
|
return service.getRules(targetUuid).map { MessagingRuleType.getByName(it) }
|
||||||
SerializableDataObject.fromJson(it, MessagingRule::class.java)
|
|
||||||
}.toList()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setRule(targetUuid: String, type: MessagingRuleType, state: Boolean)
|
||||||
|
= service.setRule(targetUuid, type.key, state)
|
||||||
}
|
}
|
||||||
|
@ -71,9 +71,8 @@ open class ConfigContainer(
|
|||||||
fun fromJson(json: JsonObject) {
|
fun fromJson(json: JsonObject) {
|
||||||
properties.forEach { (key, _) ->
|
properties.forEach { (key, _) ->
|
||||||
val jsonElement = json.get(key.name) ?: return@forEach
|
val jsonElement = json.get(key.name) ?: return@forEach
|
||||||
key.dataType.deserializeAny(jsonElement)?.let {
|
//TODO: check incoming values
|
||||||
properties[key]?.setAny(it)
|
properties[key]?.setAny(key.dataType.deserializeAny(jsonElement))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package me.rhunk.snapenhance.core.config
|
package me.rhunk.snapenhance.core.config
|
||||||
|
|
||||||
|
import me.rhunk.snapenhance.bridge.wrapper.LocaleWrapper
|
||||||
import kotlin.reflect.KProperty
|
import kotlin.reflect.KProperty
|
||||||
|
|
||||||
|
|
||||||
@ -10,17 +11,43 @@ data class PropertyPair<T>(
|
|||||||
val name get() = key.name
|
val name get() = key.name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum class FeatureNotice(
|
||||||
|
val id: Int,
|
||||||
|
val key: String
|
||||||
|
) {
|
||||||
|
UNSTABLE(0b0001, "unstable"),
|
||||||
|
MAY_BAN(0b0010, "may_ban"),
|
||||||
|
MAY_BREAK_INTERNAL_BEHAVIOR(0b0100, "may_break_internal_behavior"),
|
||||||
|
MAY_CAUSE_CRASHES(0b1000, "may_cause_crashes");
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class ConfigFlag(
|
||||||
|
val id: Int
|
||||||
|
) {
|
||||||
|
NO_TRANSLATE(0b0001),
|
||||||
|
HIDDEN(0b0010),
|
||||||
|
FOLDER(0b0100),
|
||||||
|
NO_DISABLE_KEY(0b1000)
|
||||||
|
}
|
||||||
|
|
||||||
class ConfigParams(
|
class ConfigParams(
|
||||||
|
private var _flags: Int? = null,
|
||||||
private var _notices: Int? = null,
|
private var _notices: Int? = null,
|
||||||
var shouldTranslate: Boolean = true,
|
|
||||||
var isHidden: Boolean = false,
|
var icon: String? = null,
|
||||||
var isFolder: Boolean = false,
|
|
||||||
var disabledKey: String? = null,
|
var disabledKey: String? = null,
|
||||||
var icon: String? = null
|
var customTranslationPath: String? = null,
|
||||||
|
var customOptionTranslationPath: String? = null
|
||||||
) {
|
) {
|
||||||
val notices get() = _notices?.let { FeatureNotice.values().filter { flag -> it and flag.id != 0 } } ?: emptyList()
|
val notices get() = _notices?.let { FeatureNotice.values().filter { flag -> it and flag.id != 0 } } ?: emptyList()
|
||||||
fun addNotices(vararg flags: FeatureNotice) {
|
val flags get() = _flags?.let { ConfigFlag.values().filter { flag -> it and flag.id != 0 } } ?: emptyList()
|
||||||
this._notices = (this._notices ?: 0) or flags.fold(0) { acc, featureNotice -> acc or featureNotice.id }
|
|
||||||
|
fun addNotices(vararg values: FeatureNotice) {
|
||||||
|
this._notices = (this._notices ?: 0) or values.fold(0) { acc, featureNotice -> acc or featureNotice.id }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addFlags(vararg values: ConfigFlag) {
|
||||||
|
this._flags = (this._flags ?: 0) or values.fold(0) { acc, flag -> acc or flag.id }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,12 +82,28 @@ data class PropertyKey<T>(
|
|||||||
) {
|
) {
|
||||||
val parentKey by lazy { _parent() }
|
val parentKey by lazy { _parent() }
|
||||||
|
|
||||||
fun propertyTranslationPath(): String {
|
fun propertyOption(translation: LocaleWrapper, key: String): String {
|
||||||
return if (parentKey != null) {
|
if (key == "null") {
|
||||||
"${parentKey!!.propertyTranslationPath()}.properties.$name"
|
return translation[params.disabledKey ?: "manager.features.disabled"]
|
||||||
} else {
|
|
||||||
"features.properties.$name"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return if (!params.flags.contains(ConfigFlag.NO_TRANSLATE))
|
||||||
|
translation[params.customOptionTranslationPath?.let {
|
||||||
|
"$it.$key"
|
||||||
|
} ?: "features.options.${name}.$key"]
|
||||||
|
else key
|
||||||
|
}
|
||||||
|
|
||||||
|
fun propertyName() = propertyTranslationPath() + ".name"
|
||||||
|
fun propertyDescription() = propertyTranslationPath() + ".description"
|
||||||
|
|
||||||
|
fun propertyTranslationPath(): String {
|
||||||
|
params.customTranslationPath?.let {
|
||||||
|
return it
|
||||||
|
}
|
||||||
|
return parentKey?.let {
|
||||||
|
"${it.propertyTranslationPath()}.properties.$name"
|
||||||
|
} ?: "features.properties.$name"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package me.rhunk.snapenhance.core.config.impl
|
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.ConfigFlag
|
||||||
import me.rhunk.snapenhance.core.config.FeatureNotice
|
import me.rhunk.snapenhance.core.config.FeatureNotice
|
||||||
import me.rhunk.snapenhance.features.impl.tweaks.CameraTweaks
|
import me.rhunk.snapenhance.features.impl.tweaks.CameraTweaks
|
||||||
|
|
||||||
@ -8,9 +9,9 @@ class Camera : ConfigContainer() {
|
|||||||
val disable = boolean("disable_camera")
|
val disable = boolean("disable_camera")
|
||||||
val immersiveCameraPreview = boolean("immersive_camera_preview") { addNotices(FeatureNotice.MAY_CAUSE_CRASHES) }
|
val immersiveCameraPreview = boolean("immersive_camera_preview") { addNotices(FeatureNotice.MAY_CAUSE_CRASHES) }
|
||||||
val overridePreviewResolution = unique("override_preview_resolution", *CameraTweaks.resolutions.toTypedArray())
|
val overridePreviewResolution = unique("override_preview_resolution", *CameraTweaks.resolutions.toTypedArray())
|
||||||
{ shouldTranslate = false }
|
{ addFlags(ConfigFlag.NO_TRANSLATE) }
|
||||||
val overridePictureResolution = unique("override_picture_resolution", *CameraTweaks.resolutions.toTypedArray())
|
val overridePictureResolution = unique("override_picture_resolution", *CameraTweaks.resolutions.toTypedArray())
|
||||||
{ shouldTranslate = false }
|
{ addFlags(ConfigFlag.NO_TRANSLATE) }
|
||||||
val forceHighestFrameRate = boolean("force_highest_frame_rate") { addNotices(FeatureNotice.MAY_BREAK_INTERNAL_BEHAVIOR) }
|
val forceHighestFrameRate = boolean("force_highest_frame_rate") { addNotices(FeatureNotice.MAY_BREAK_INTERNAL_BEHAVIOR) }
|
||||||
val forceCameraSourceEncoding = boolean("force_camera_source_encoding")
|
val forceCameraSourceEncoding = boolean("force_camera_source_encoding")
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
package me.rhunk.snapenhance.core.config.impl
|
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.ConfigFlag
|
||||||
import me.rhunk.snapenhance.core.config.FeatureNotice
|
import me.rhunk.snapenhance.core.config.FeatureNotice
|
||||||
|
|
||||||
class DownloaderConfig : ConfigContainer() {
|
class DownloaderConfig : ConfigContainer() {
|
||||||
val saveFolder = string("save_folder") { isFolder = true }
|
val saveFolder = string("save_folder") { addFlags(ConfigFlag.FOLDER) }
|
||||||
val autoDownloadOptions = multiple("auto_download_options",
|
val autoDownloadOptions = multiple("auto_download_options",
|
||||||
"friend_snaps",
|
"friend_snaps",
|
||||||
"friend_stories",
|
"friend_stories",
|
||||||
|
@ -6,7 +6,7 @@ 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")
|
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) }
|
||||||
|
@ -7,6 +7,7 @@ class RootConfig : ConfigContainer() {
|
|||||||
val userInterface = container("user_interface", UserInterfaceTweaks()) { icon = "RemoveRedEye"}
|
val userInterface = container("user_interface", UserInterfaceTweaks()) { icon = "RemoveRedEye"}
|
||||||
val messaging = container("messaging", MessagingTweaks()) { icon = "Send" }
|
val messaging = container("messaging", MessagingTweaks()) { icon = "Send" }
|
||||||
val global = container("global", Global()) { icon = "MiscellaneousServices" }
|
val global = container("global", Global()) { icon = "MiscellaneousServices" }
|
||||||
|
val rules = container("rules", Rules()) { icon = "Rule" }
|
||||||
val camera = container("camera", Camera()) { icon = "Camera"}
|
val camera = container("camera", Camera()) { icon = "Camera"}
|
||||||
val experimental = container("experimental", Experimental()) { icon = "Science" }
|
val experimental = container("experimental", Experimental()) { icon = "Science" }
|
||||||
val spoof = container("spoof", Spoof()) { icon = "Fingerprint" }
|
val spoof = container("spoof", Spoof()) { icon = "Fingerprint" }
|
||||||
|
@ -0,0 +1,26 @@
|
|||||||
|
package me.rhunk.snapenhance.core.config.impl
|
||||||
|
|
||||||
|
import me.rhunk.snapenhance.core.config.ConfigContainer
|
||||||
|
import me.rhunk.snapenhance.core.config.PropertyValue
|
||||||
|
import me.rhunk.snapenhance.core.messaging.MessagingRuleType
|
||||||
|
import me.rhunk.snapenhance.core.messaging.RuleState
|
||||||
|
|
||||||
|
|
||||||
|
class Rules : ConfigContainer() {
|
||||||
|
private val rules = mutableMapOf<MessagingRuleType, PropertyValue<String>>()
|
||||||
|
|
||||||
|
fun getRuleState(ruleType: MessagingRuleType): RuleState? {
|
||||||
|
return rules[ruleType]?.getNullable()?.let { RuleState.getByName(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
MessagingRuleType.values().filter { it.listMode }.forEach { ruleType ->
|
||||||
|
rules[ruleType] = unique(ruleType.key,"whitelist", "blacklist") {
|
||||||
|
customTranslationPath = "rules.properties.${ruleType.key}"
|
||||||
|
customOptionTranslationPath = "rules.modes"
|
||||||
|
}.apply {
|
||||||
|
set("whitelist")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,15 +2,22 @@ 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.core.messaging.MessagingRuleType
|
||||||
|
|
||||||
class UserInterfaceTweaks : ConfigContainer() {
|
class UserInterfaceTweaks : ConfigContainer() {
|
||||||
val enableAppAppearance = boolean("enable_app_appearance")
|
val enableAppAppearance = boolean("enable_app_appearance")
|
||||||
|
val friendFeedMenuButtons = multiple(
|
||||||
|
"friend_feed_menu_buttons","conversation_info", *MessagingRuleType.values().toList().filter { it.listMode }.map { it.key }.toTypedArray()
|
||||||
|
).apply {
|
||||||
|
set(mutableListOf("conversation_info", MessagingRuleType.STEALTH.key))
|
||||||
|
}
|
||||||
|
val friendFeedMenuPosition = integer("friend_feed_menu_position", defaultValue = 1)
|
||||||
val amoledDarkMode = boolean("amoled_dark_mode") { addNotices(FeatureNotice.UNSTABLE) }
|
val amoledDarkMode = boolean("amoled_dark_mode") { addNotices(FeatureNotice.UNSTABLE) }
|
||||||
val mapFriendNameTags = boolean("map_friend_nametags")
|
val mapFriendNameTags = boolean("map_friend_nametags")
|
||||||
val streakExpirationInfo = boolean("streak_expiration_info")
|
val streakExpirationInfo = boolean("streak_expiration_info")
|
||||||
val hideStorySections = multiple("hide_story_sections", "hide_friend_suggestions", "hide_friends", "hide_following", "hide_for_you")
|
val hideStorySections = multiple("hide_story_sections",
|
||||||
val hideUiComponents = multiple(
|
"hide_friend_suggestions", "hide_friends", "hide_following", "hide_for_you")
|
||||||
"hide_ui_components",
|
val hideUiComponents = multiple("hide_ui_components",
|
||||||
"hide_voice_record_button",
|
"hide_voice_record_button",
|
||||||
"hide_stickers_button",
|
"hide_stickers_button",
|
||||||
"hide_cognac_button",
|
"hide_cognac_button",
|
||||||
@ -27,7 +34,4 @@ class UserInterfaceTweaks : ConfigContainer() {
|
|||||||
"ngs_search_icon_container"
|
"ngs_search_icon_container"
|
||||||
)
|
)
|
||||||
val storyViewerOverride = unique("story_viewer_override", "DISCOVER_PLAYBACK_SEEKBAR", "VERTICAL_STORY_VIEWER") { addNotices(FeatureNotice.UNSTABLE) }
|
val storyViewerOverride = unique("story_viewer_override", "DISCOVER_PLAYBACK_SEEKBAR", "VERTICAL_STORY_VIEWER") { addNotices(FeatureNotice.UNSTABLE) }
|
||||||
val friendFeedMenuButtons = multiple("friend_feed_menu_buttons", "auto_download_blacklist", "anti_auto_save", "stealth_mode", "conversation_info")
|
|
||||||
val friendFeedMenuPosition = integer("friend_feed_menu_position", defaultValue = 1)
|
|
||||||
val enableFriendFeedMenuBar = boolean("enable_friend_feed_menu_bar")
|
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,15 @@ package me.rhunk.snapenhance.core.messaging
|
|||||||
import me.rhunk.snapenhance.util.SerializableDataObject
|
import me.rhunk.snapenhance.util.SerializableDataObject
|
||||||
|
|
||||||
|
|
||||||
enum class Mode {
|
enum class RuleState(
|
||||||
BLACKLIST,
|
val key: String
|
||||||
WHITELIST
|
) {
|
||||||
|
BLACKLIST("blacklist"),
|
||||||
|
WHITELIST("whitelist");
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun getByName(name: String) = values().first { it.key == name }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class SocialScope(
|
enum class SocialScope(
|
||||||
@ -18,11 +24,20 @@ enum class SocialScope(
|
|||||||
|
|
||||||
enum class MessagingRuleType(
|
enum class MessagingRuleType(
|
||||||
val key: String,
|
val key: String,
|
||||||
val socialScope: SocialScope,
|
val listMode: Boolean
|
||||||
) {
|
) {
|
||||||
DOWNLOAD("download", SocialScope.FRIEND),
|
AUTO_DOWNLOAD("auto_download", true),
|
||||||
STEALTH("stealth", SocialScope.GROUP),
|
STEALTH("stealth", true),
|
||||||
AUTO_SAVE("auto_save", SocialScope.GROUP);
|
AUTO_SAVE("auto_save", true),
|
||||||
|
HIDE_CHAT_FEED("hide_chat_feed", false);
|
||||||
|
|
||||||
|
fun translateOptionKey(optionKey: String): String {
|
||||||
|
return "rules.properties.${key}.options.${optionKey}"
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun getByName(name: String) = values().first { it.key == name }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class FriendStreaks(
|
data class FriendStreaks(
|
||||||
@ -50,8 +65,8 @@ data class MessagingFriendInfo(
|
|||||||
|
|
||||||
data class MessagingRule(
|
data class MessagingRule(
|
||||||
val id: Int,
|
val id: Int,
|
||||||
|
val type: MessagingRuleType,
|
||||||
val socialScope: SocialScope,
|
val socialScope: SocialScope,
|
||||||
val targetUuid: String,
|
val targetUuid: String,
|
||||||
//val mode: Mode?,
|
//val mode: Mode?,
|
||||||
val subject: String
|
|
||||||
) : SerializableDataObject()
|
) : SerializableDataObject()
|
@ -86,6 +86,23 @@ class DatabaseAccess(private val context: ModContext) : Manager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val myUserId by lazy {
|
||||||
|
safeDatabaseOperation(openArroyo()) { arroyoDatabase: SQLiteDatabase ->
|
||||||
|
val cursor = arroyoDatabase.rawQuery(buildString {
|
||||||
|
append("SELECT * FROM required_values WHERE key = 'USERID'")
|
||||||
|
}, null)
|
||||||
|
|
||||||
|
if (!cursor.moveToFirst()) {
|
||||||
|
cursor.close()
|
||||||
|
return@safeDatabaseOperation null
|
||||||
|
}
|
||||||
|
|
||||||
|
val userId = cursor.getString(cursor.getColumnIndex("value"))
|
||||||
|
cursor.close()
|
||||||
|
userId
|
||||||
|
}!!
|
||||||
|
}
|
||||||
|
|
||||||
fun getFeedEntryByConversationId(conversationId: String): FriendFeedEntry? {
|
fun getFeedEntryByConversationId(conversationId: String): FriendFeedEntry? {
|
||||||
return safeDatabaseOperation(openMain()) {
|
return safeDatabaseOperation(openMain()) {
|
||||||
readDatabaseObject(
|
readDatabaseObject(
|
||||||
@ -157,7 +174,7 @@ class DatabaseAccess(private val context: ModContext) : Manager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getDMConversationIdFromUserId(userId: String): UserConversationLink? {
|
fun getConversationLinkFromUserId(userId: String): UserConversationLink? {
|
||||||
return safeDatabaseOperation(openArroyo()) {
|
return safeDatabaseOperation(openArroyo()) {
|
||||||
readDatabaseObject(
|
readDatabaseObject(
|
||||||
UserConversationLink(),
|
UserConversationLink(),
|
||||||
@ -169,6 +186,22 @@ class DatabaseAccess(private val context: ModContext) : Manager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getDMOtherParticipant(conversationId: String): String? {
|
||||||
|
return safeDatabaseOperation(openArroyo()) { cursor ->
|
||||||
|
val query = cursor.rawQuery(
|
||||||
|
"SELECT * FROM user_conversation WHERE client_conversation_id = ? AND conversation_type = 0",
|
||||||
|
arrayOf(conversationId)
|
||||||
|
)
|
||||||
|
val participants = mutableListOf<String>()
|
||||||
|
while (query.moveToNext()) {
|
||||||
|
participants.add(query.getString(query.getColumnIndex("user_id")))
|
||||||
|
}
|
||||||
|
query.close()
|
||||||
|
participants.firstOrNull { it != myUserId }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
fun getStoryEntryFromId(storyId: String): StoryEntry? {
|
fun getStoryEntryFromId(storyId: String): StoryEntry? {
|
||||||
return safeDatabaseOperation(openMain()) {
|
return safeDatabaseOperation(openMain()) {
|
||||||
readDatabaseObject(StoryEntry(), it, "Story", "storyId = ?", arrayOf(storyId))
|
readDatabaseObject(StoryEntry(), it, "Story", "storyId = ?", arrayOf(storyId))
|
||||||
@ -194,23 +227,6 @@ class DatabaseAccess(private val context: ModContext) : Manager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getMyUserId(): String? {
|
|
||||||
return safeDatabaseOperation(openArroyo()) { arroyoDatabase: SQLiteDatabase ->
|
|
||||||
val cursor = arroyoDatabase.rawQuery(buildString {
|
|
||||||
append("SELECT * FROM required_values WHERE key = 'USERID'")
|
|
||||||
}, null)
|
|
||||||
|
|
||||||
if (!cursor.moveToFirst()) {
|
|
||||||
cursor.close()
|
|
||||||
return@safeDatabaseOperation null
|
|
||||||
}
|
|
||||||
|
|
||||||
val userId = cursor.getString(cursor.getColumnIndex("value"))
|
|
||||||
cursor.close()
|
|
||||||
userId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getMessagesFromConversationId(
|
fun getMessagesFromConversationId(
|
||||||
conversationId: String,
|
conversationId: String,
|
||||||
limit: Int
|
limit: Int
|
||||||
|
@ -0,0 +1,33 @@
|
|||||||
|
package me.rhunk.snapenhance.features
|
||||||
|
|
||||||
|
import me.rhunk.snapenhance.core.messaging.MessagingRuleType
|
||||||
|
import me.rhunk.snapenhance.core.messaging.RuleState
|
||||||
|
|
||||||
|
abstract class MessagingRuleFeature(name: String, val ruleType: MessagingRuleType, loadParams: Int = 0) : Feature(name, loadParams) {
|
||||||
|
init {
|
||||||
|
if (!ruleType.listMode) throw IllegalArgumentException("Rule type must be a list mode")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getRuleState() = context.config.rules.getRuleState(ruleType)
|
||||||
|
|
||||||
|
fun setState(conversationId: String, state: Boolean) {
|
||||||
|
context.bridgeClient.setRule(
|
||||||
|
context.database.getDMOtherParticipant(conversationId) ?: conversationId,
|
||||||
|
ruleType,
|
||||||
|
state
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getState(conversationId: String) =
|
||||||
|
context.bridgeClient.getRules(
|
||||||
|
context.database.getDMOtherParticipant(conversationId) ?: conversationId
|
||||||
|
).contains(ruleType) && getRuleState() != null
|
||||||
|
|
||||||
|
fun canUseRule(conversationId: String): Boolean {
|
||||||
|
val state = getState(conversationId)
|
||||||
|
if (context.config.rules.getRuleState(ruleType) == RuleState.BLACKLIST) {
|
||||||
|
return !state
|
||||||
|
}
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
}
|
@ -68,7 +68,7 @@ class Messaging : Feature("Messaging", loadParams = FeatureLoadParams.ACTIVITY_C
|
|||||||
|
|
||||||
arrayOf("activate", "deactivate", "processTypingActivity").forEach { hook ->
|
arrayOf("activate", "deactivate", "processTypingActivity").forEach { hook ->
|
||||||
Hooker.hook(context.classCache.presenceSession, hook, HookStage.BEFORE, {
|
Hooker.hook(context.classCache.presenceSession, hook, HookStage.BEFORE, {
|
||||||
hideBitmojiPresence || stealthMode.isStealth(openedConversationUUID.toString())
|
hideBitmojiPresence || stealthMode.canUseRule(openedConversationUUID.toString())
|
||||||
}) {
|
}) {
|
||||||
it.setResult(null)
|
it.setResult(null)
|
||||||
}
|
}
|
||||||
@ -86,7 +86,7 @@ class Messaging : Feature("Messaging", loadParams = FeatureLoadParams.ACTIVITY_C
|
|||||||
}
|
}
|
||||||
|
|
||||||
Hooker.hook(context.classCache.conversationManager, "sendTypingNotification", HookStage.BEFORE, {
|
Hooker.hook(context.classCache.conversationManager, "sendTypingNotification", HookStage.BEFORE, {
|
||||||
hideTypingNotification || stealthMode.isStealth(openedConversationUUID.toString())
|
hideTypingNotification || stealthMode.canUseRule(openedConversationUUID.toString())
|
||||||
}) {
|
}) {
|
||||||
it.setResult(null)
|
it.setResult(null)
|
||||||
}
|
}
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
package me.rhunk.snapenhance.features.impl.downloader
|
|
||||||
|
|
||||||
import me.rhunk.snapenhance.bridge.types.BridgeFileType
|
|
||||||
import me.rhunk.snapenhance.features.BridgeFileFeature
|
|
||||||
import me.rhunk.snapenhance.features.FeatureLoadParams
|
|
||||||
|
|
||||||
class AntiAutoDownload : BridgeFileFeature("AntiAutoDownload", BridgeFileType.ANTI_AUTO_DOWNLOAD, loadParams = FeatureLoadParams.ACTIVITY_CREATE_SYNC) {
|
|
||||||
override fun onActivityCreate() {
|
|
||||||
readFile()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setUserIgnored(userId: String, state: Boolean) {
|
|
||||||
setState(userId.hashCode().toLong().toString(16), state)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isUserIgnored(userId: String): Boolean {
|
|
||||||
return exists(userId.hashCode().toLong().toString(16))
|
|
||||||
}
|
|
||||||
}
|
|
@ -9,6 +9,7 @@ import kotlinx.coroutines.runBlocking
|
|||||||
import me.rhunk.snapenhance.Logger
|
import me.rhunk.snapenhance.Logger
|
||||||
import me.rhunk.snapenhance.Logger.xposedLog
|
import me.rhunk.snapenhance.Logger.xposedLog
|
||||||
import me.rhunk.snapenhance.bridge.DownloadCallback
|
import me.rhunk.snapenhance.bridge.DownloadCallback
|
||||||
|
import me.rhunk.snapenhance.core.messaging.MessagingRuleType
|
||||||
import me.rhunk.snapenhance.data.ContentType
|
import me.rhunk.snapenhance.data.ContentType
|
||||||
import me.rhunk.snapenhance.data.FileType
|
import me.rhunk.snapenhance.data.FileType
|
||||||
import me.rhunk.snapenhance.data.wrapper.impl.media.MediaInfo
|
import me.rhunk.snapenhance.data.wrapper.impl.media.MediaInfo
|
||||||
@ -24,8 +25,8 @@ import me.rhunk.snapenhance.download.data.InputMedia
|
|||||||
import me.rhunk.snapenhance.download.data.MediaFilter
|
import me.rhunk.snapenhance.download.data.MediaFilter
|
||||||
import me.rhunk.snapenhance.download.data.SplitMediaAssetType
|
import me.rhunk.snapenhance.download.data.SplitMediaAssetType
|
||||||
import me.rhunk.snapenhance.download.data.toKeyPair
|
import me.rhunk.snapenhance.download.data.toKeyPair
|
||||||
import me.rhunk.snapenhance.features.Feature
|
|
||||||
import me.rhunk.snapenhance.features.FeatureLoadParams
|
import me.rhunk.snapenhance.features.FeatureLoadParams
|
||||||
|
import me.rhunk.snapenhance.features.MessagingRuleFeature
|
||||||
import me.rhunk.snapenhance.features.impl.Messaging
|
import me.rhunk.snapenhance.features.impl.Messaging
|
||||||
import me.rhunk.snapenhance.features.impl.spying.MessageLogger
|
import me.rhunk.snapenhance.features.impl.spying.MessageLogger
|
||||||
import me.rhunk.snapenhance.hook.HookAdapter
|
import me.rhunk.snapenhance.hook.HookAdapter
|
||||||
@ -47,7 +48,7 @@ import kotlin.io.encoding.Base64
|
|||||||
import kotlin.io.encoding.ExperimentalEncodingApi
|
import kotlin.io.encoding.ExperimentalEncodingApi
|
||||||
|
|
||||||
@OptIn(ExperimentalEncodingApi::class)
|
@OptIn(ExperimentalEncodingApi::class)
|
||||||
class MediaDownloader : Feature("MediaDownloader", loadParams = FeatureLoadParams.ACTIVITY_CREATE_ASYNC) {
|
class MediaDownloader : MessagingRuleFeature("MediaDownloader", MessagingRuleType.AUTO_DOWNLOAD, loadParams = FeatureLoadParams.ACTIVITY_CREATE_ASYNC) {
|
||||||
private var lastSeenMediaInfoMap: MutableMap<SplitMediaAssetType, MediaInfo>? = null
|
private var lastSeenMediaInfoMap: MutableMap<SplitMediaAssetType, MediaInfo>? = null
|
||||||
private var lastSeenMapParams: ParamMap? = null
|
private var lastSeenMapParams: ParamMap? = null
|
||||||
|
|
||||||
@ -230,7 +231,7 @@ class MediaDownloader : Feature("MediaDownloader", loadParams = FeatureLoadParam
|
|||||||
val senderId = conversationMessage.senderId!!
|
val senderId = conversationMessage.senderId!!
|
||||||
val conversationId = conversationMessage.clientConversationId!!
|
val conversationId = conversationMessage.clientConversationId!!
|
||||||
|
|
||||||
if (!forceDownload && context.feature(AntiAutoDownload::class).isUserIgnored(senderId)) {
|
if (!forceDownload && canUseRule(senderId)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -272,7 +273,7 @@ class MediaDownloader : Feature("MediaDownloader", loadParams = FeatureLoadParam
|
|||||||
|
|
||||||
val author = context.database.getFriendInfo(
|
val author = context.database.getFriendInfo(
|
||||||
if (storyUserId == null || storyUserId == "null")
|
if (storyUserId == null || storyUserId == "null")
|
||||||
context.database.getMyUserId()!!
|
context.database.myUserId
|
||||||
else storyUserId
|
else storyUserId
|
||||||
) ?: throw Exception("Friend not found in database")
|
) ?: throw Exception("Friend not found in database")
|
||||||
val authorName = author.usernameForSorting!!
|
val authorName = author.usernameForSorting!!
|
||||||
|
@ -37,8 +37,6 @@ class MessageLogger : Feature("MessageLogger",
|
|||||||
private val fetchedMessages = mutableListOf<Long>()
|
private val fetchedMessages = mutableListOf<Long>()
|
||||||
private val deletedMessageCache = mutableMapOf<Long, JsonObject>()
|
private val deletedMessageCache = mutableMapOf<Long, JsonObject>()
|
||||||
|
|
||||||
private val myUserId by lazy { context.database.getMyUserId() }
|
|
||||||
|
|
||||||
fun isMessageRemoved(conversationId: String, orderKey: Long) = deletedMessageCache.containsKey(computeMessageIdentifier(conversationId, orderKey))
|
fun isMessageRemoved(conversationId: String, orderKey: Long) = deletedMessageCache.containsKey(computeMessageIdentifier(conversationId, orderKey))
|
||||||
|
|
||||||
fun deleteMessage(conversationId: String, clientMessageId: Long) {
|
fun deleteMessage(conversationId: String, clientMessageId: Long) {
|
||||||
@ -83,7 +81,7 @@ class MessageLogger : Feature("MessageLogger",
|
|||||||
if (message.messageState != MessageState.COMMITTED) return
|
if (message.messageState != MessageState.COMMITTED) return
|
||||||
|
|
||||||
//exclude messages sent by me
|
//exclude messages sent by me
|
||||||
if (message.senderId.toString() == myUserId) return
|
if (message.senderId.toString() == context.database.myUserId) return
|
||||||
|
|
||||||
val conversationId = message.messageDescriptor.conversationId.toString()
|
val conversationId = message.messageDescriptor.conversationId.toString()
|
||||||
val serverIdentifier = computeMessageIdentifier(conversationId, message.orderKey)
|
val serverIdentifier = computeMessageIdentifier(conversationId, message.orderKey)
|
||||||
|
@ -12,7 +12,7 @@ class PreventReadReceipts : Feature("PreventReadReceipts", loadParams = FeatureL
|
|||||||
val preventReadReceipts by context.config.messaging.preventReadReceipts
|
val preventReadReceipts by context.config.messaging.preventReadReceipts
|
||||||
val isConversationInStealthMode: (SnapUUID) -> Boolean = hook@{
|
val isConversationInStealthMode: (SnapUUID) -> Boolean = hook@{
|
||||||
if (preventReadReceipts) return@hook true
|
if (preventReadReceipts) return@hook true
|
||||||
context.feature(StealthMode::class).isStealth(it.toString())
|
context.feature(StealthMode::class).canUseRule(it.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
arrayOf("mediaMessagesDisplayed", "displayedMessages").forEach { methodName: String ->
|
arrayOf("mediaMessagesDisplayed", "displayedMessages").forEach { methodName: String ->
|
||||||
|
@ -1,19 +1,6 @@
|
|||||||
package me.rhunk.snapenhance.features.impl.spying
|
package me.rhunk.snapenhance.features.impl.spying
|
||||||
|
|
||||||
import me.rhunk.snapenhance.bridge.types.BridgeFileType
|
import me.rhunk.snapenhance.core.messaging.MessagingRuleType
|
||||||
import me.rhunk.snapenhance.features.BridgeFileFeature
|
import me.rhunk.snapenhance.features.MessagingRuleFeature
|
||||||
import me.rhunk.snapenhance.features.FeatureLoadParams
|
|
||||||
|
|
||||||
class StealthMode : BridgeFileFeature("StealthMode", BridgeFileType.STEALTH, loadParams = FeatureLoadParams.ACTIVITY_CREATE_SYNC) {
|
class StealthMode : MessagingRuleFeature("StealthMode", MessagingRuleType.STEALTH)
|
||||||
override fun onActivityCreate() {
|
|
||||||
readFile()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setStealth(conversationId: String, stealth: Boolean) {
|
|
||||||
setState(conversationId.hashCode().toLong().toString(16), stealth)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isStealth(conversationId: String): Boolean {
|
|
||||||
return exists(conversationId.hashCode().toLong().toString(16))
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,11 +1,12 @@
|
|||||||
package me.rhunk.snapenhance.features.impl.tweaks
|
package me.rhunk.snapenhance.features.impl.tweaks
|
||||||
|
|
||||||
import me.rhunk.snapenhance.Logger
|
import me.rhunk.snapenhance.Logger
|
||||||
|
import me.rhunk.snapenhance.core.messaging.MessagingRuleType
|
||||||
import me.rhunk.snapenhance.data.MessageState
|
import me.rhunk.snapenhance.data.MessageState
|
||||||
import me.rhunk.snapenhance.data.wrapper.impl.Message
|
import me.rhunk.snapenhance.data.wrapper.impl.Message
|
||||||
import me.rhunk.snapenhance.data.wrapper.impl.SnapUUID
|
import me.rhunk.snapenhance.data.wrapper.impl.SnapUUID
|
||||||
import me.rhunk.snapenhance.features.Feature
|
|
||||||
import me.rhunk.snapenhance.features.FeatureLoadParams
|
import me.rhunk.snapenhance.features.FeatureLoadParams
|
||||||
|
import me.rhunk.snapenhance.features.MessagingRuleFeature
|
||||||
import me.rhunk.snapenhance.features.impl.Messaging
|
import me.rhunk.snapenhance.features.impl.Messaging
|
||||||
import me.rhunk.snapenhance.features.impl.spying.MessageLogger
|
import me.rhunk.snapenhance.features.impl.spying.MessageLogger
|
||||||
import me.rhunk.snapenhance.features.impl.spying.StealthMode
|
import me.rhunk.snapenhance.features.impl.spying.StealthMode
|
||||||
@ -15,14 +16,12 @@ import me.rhunk.snapenhance.util.CallbackBuilder
|
|||||||
import me.rhunk.snapenhance.util.ktx.getObjectField
|
import me.rhunk.snapenhance.util.ktx.getObjectField
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
|
|
||||||
class AutoSave : Feature("Auto Save", loadParams = FeatureLoadParams.ACTIVITY_CREATE_ASYNC) {
|
class AutoSave : MessagingRuleFeature("Auto Save", MessagingRuleType.AUTO_SAVE, loadParams = FeatureLoadParams.ACTIVITY_CREATE_ASYNC) {
|
||||||
private val asyncSaveExecutorService = Executors.newSingleThreadExecutor()
|
private val asyncSaveExecutorService = Executors.newSingleThreadExecutor()
|
||||||
|
|
||||||
private val messageLogger by lazy { context.feature(MessageLogger::class) }
|
private val messageLogger by lazy { context.feature(MessageLogger::class) }
|
||||||
private val messaging by lazy { context.feature(Messaging::class) }
|
private val messaging by lazy { context.feature(Messaging::class) }
|
||||||
|
|
||||||
private val myUserId by lazy { context.database.getMyUserId() }
|
|
||||||
|
|
||||||
private val fetchConversationWithMessagesCallbackClass by lazy { context.mappings.getMappedClass("callbacks", "FetchConversationWithMessagesCallback") }
|
private val fetchConversationWithMessagesCallbackClass by lazy { context.mappings.getMappedClass("callbacks", "FetchConversationWithMessagesCallback") }
|
||||||
private val callbackClass by lazy { context.mappings.getMappedClass("callbacks", "Callback") }
|
private val callbackClass by lazy { context.mappings.getMappedClass("callbacks", "Callback") }
|
||||||
|
|
||||||
@ -62,7 +61,7 @@ class AutoSave : Feature("Auto Save", loadParams = FeatureLoadParams.ACTIVITY_CR
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun canSaveMessage(message: Message): Boolean {
|
private fun canSaveMessage(message: Message): Boolean {
|
||||||
if (message.messageMetadata.savedBy.any { uuid -> uuid.toString() == myUserId }) return false
|
if (message.messageMetadata.savedBy.any { uuid -> uuid.toString() == context.database.myUserId }) return false
|
||||||
val contentType = message.messageContent.contentType.toString()
|
val contentType = message.messageContent.contentType.toString()
|
||||||
|
|
||||||
return autoSaveFilter.any { it == contentType }
|
return autoSaveFilter.any { it == contentType }
|
||||||
@ -74,8 +73,8 @@ class AutoSave : Feature("Auto Save", loadParams = FeatureLoadParams.ACTIVITY_CR
|
|||||||
with(context.feature(Messaging::class)) {
|
with(context.feature(Messaging::class)) {
|
||||||
if (openedConversationUUID == null) return@canSave false
|
if (openedConversationUUID == null) return@canSave false
|
||||||
val conversation = openedConversationUUID.toString()
|
val conversation = openedConversationUUID.toString()
|
||||||
if (context.feature(StealthMode::class).isStealth(conversation)) return@canSave false
|
if (context.feature(StealthMode::class).canUseRule(conversation)) return@canSave false
|
||||||
if (context.feature(AntiAutoSave::class).isConversationIgnored(conversation)) return@canSave false
|
if (canUseRule(conversation)) return@canSave false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -154,7 +154,7 @@ class Notifications : Feature("Notifications", loadParams = FeatureLoadParams.IN
|
|||||||
val input = RemoteInput.getResultsFromIntent(intent).getCharSequence("chat_reply_input")
|
val input = RemoteInput.getResultsFromIntent(intent).getCharSequence("chat_reply_input")
|
||||||
.toString()
|
.toString()
|
||||||
|
|
||||||
context.database.getMyUserId()?.let { context.database.getFriendInfo(it) }?.let { myUser ->
|
context.database.myUserId.let { context.database.getFriendInfo(it) }?.let { myUser ->
|
||||||
cachedMessages.computeIfAbsent(conversationId) { mutableListOf() }.add("${myUser.displayName}: $input")
|
cachedMessages.computeIfAbsent(conversationId) { mutableListOf() }.add("${myUser.displayName}: $input")
|
||||||
|
|
||||||
updateNotification(notificationId) { notification ->
|
updateNotification(notificationId) { notification ->
|
||||||
|
@ -7,7 +7,6 @@ import me.rhunk.snapenhance.features.FeatureLoadParams
|
|||||||
import me.rhunk.snapenhance.features.impl.AutoUpdater
|
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.AntiAutoDownload
|
|
||||||
import me.rhunk.snapenhance.features.impl.downloader.MediaDownloader
|
import me.rhunk.snapenhance.features.impl.downloader.MediaDownloader
|
||||||
import me.rhunk.snapenhance.features.impl.experiments.AmoledDarkMode
|
import me.rhunk.snapenhance.features.impl.experiments.AmoledDarkMode
|
||||||
import me.rhunk.snapenhance.features.impl.experiments.AppPasscode
|
import me.rhunk.snapenhance.features.impl.experiments.AppPasscode
|
||||||
@ -76,7 +75,6 @@ class FeatureManager(private val context: ModContext) : Manager {
|
|||||||
register(AutoSave::class)
|
register(AutoSave::class)
|
||||||
register(UITweaks::class)
|
register(UITweaks::class)
|
||||||
register(ConfigurationOverride::class)
|
register(ConfigurationOverride::class)
|
||||||
register(AntiAutoDownload::class)
|
|
||||||
register(GalleryMediaSendOverride::class)
|
register(GalleryMediaSendOverride::class)
|
||||||
register(AntiAutoSave::class)
|
register(AntiAutoSave::class)
|
||||||
register(UnlimitedSnapViewTime::class)
|
register(UnlimitedSnapViewTime::class)
|
||||||
|
@ -4,32 +4,23 @@ import android.annotation.SuppressLint
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.DialogInterface
|
import android.content.DialogInterface
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import android.graphics.Bitmap
|
|
||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
import android.graphics.Canvas
|
|
||||||
import android.graphics.Color
|
|
||||||
import android.graphics.Paint
|
|
||||||
import android.graphics.drawable.BitmapDrawable
|
import android.graphics.drawable.BitmapDrawable
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.view.Gravity
|
|
||||||
import android.view.MotionEvent
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.widget.Button
|
import android.widget.Button
|
||||||
import android.widget.CompoundButton
|
import android.widget.CompoundButton
|
||||||
import android.widget.LinearLayout
|
|
||||||
import android.widget.Switch
|
import android.widget.Switch
|
||||||
import android.widget.Toast
|
|
||||||
import me.rhunk.snapenhance.Logger
|
import me.rhunk.snapenhance.Logger
|
||||||
import me.rhunk.snapenhance.data.ContentType
|
import me.rhunk.snapenhance.data.ContentType
|
||||||
import me.rhunk.snapenhance.data.wrapper.impl.FriendActionButton
|
|
||||||
import me.rhunk.snapenhance.database.objects.ConversationMessage
|
import me.rhunk.snapenhance.database.objects.ConversationMessage
|
||||||
import me.rhunk.snapenhance.database.objects.FriendInfo
|
import me.rhunk.snapenhance.database.objects.FriendInfo
|
||||||
import me.rhunk.snapenhance.database.objects.UserConversationLink
|
import me.rhunk.snapenhance.database.objects.UserConversationLink
|
||||||
|
import me.rhunk.snapenhance.features.MessagingRuleFeature
|
||||||
import me.rhunk.snapenhance.features.impl.Messaging
|
import me.rhunk.snapenhance.features.impl.Messaging
|
||||||
import me.rhunk.snapenhance.features.impl.downloader.AntiAutoDownload
|
import me.rhunk.snapenhance.features.impl.downloader.MediaDownloader
|
||||||
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.ui.ViewAppearanceHelper
|
import me.rhunk.snapenhance.ui.ViewAppearanceHelper
|
||||||
import me.rhunk.snapenhance.ui.menu.AbstractMenu
|
import me.rhunk.snapenhance.ui.menu.AbstractMenu
|
||||||
import me.rhunk.snapenhance.util.snap.BitmojiSelfie
|
import me.rhunk.snapenhance.util.snap.BitmojiSelfie
|
||||||
@ -106,17 +97,18 @@ class FriendFeedInfoMenu : AbstractMenu() {
|
|||||||
context.config.messaging.messagePreviewLength.get()
|
context.config.messaging.messagePreviewLength.get()
|
||||||
)?.reversed()
|
)?.reversed()
|
||||||
|
|
||||||
if (messages.isNullOrEmpty()) {
|
if (messages == null) {
|
||||||
Toast.makeText(androidCtx, "No messages found", Toast.LENGTH_SHORT).show()
|
context.longToast("Can't fetch messages")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val participants: Map<String, FriendInfo> = context.database.getConversationParticipants(conversationId)!!
|
val participants: Map<String, FriendInfo> = context.database.getConversationParticipants(conversationId)!!
|
||||||
.map { context.database.getFriendInfo(it)!! }
|
.map { context.database.getFriendInfo(it)!! }
|
||||||
.associateBy { it.userId!! }
|
.associateBy { it.userId!! }
|
||||||
|
|
||||||
val messageBuilder = StringBuilder()
|
val messageBuilder = StringBuilder()
|
||||||
|
|
||||||
messages.forEach{ message: ConversationMessage ->
|
messages.forEach { message ->
|
||||||
val sender: FriendInfo? = participants[message.senderId]
|
val sender: FriendInfo? = participants[message.senderId]
|
||||||
|
|
||||||
var messageString: String = message.getMessageAsString() ?: ContentType.fromId(message.contentType).name
|
var messageString: String = message.getMessageAsString() ?: ContentType.fromId(message.contentType).name
|
||||||
@ -164,7 +156,6 @@ class FriendFeedInfoMenu : AbstractMenu() {
|
|||||||
.append("\n")
|
.append("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//alert dialog
|
//alert dialog
|
||||||
val builder = ViewAppearanceHelper.newAlertDialogBuilder(context.mainActivity)
|
val builder = ViewAppearanceHelper.newAlertDialogBuilder(context.mainActivity)
|
||||||
builder.setTitle(context.translation["conversation_preview.title"])
|
builder.setTitle(context.translation["conversation_preview.title"])
|
||||||
@ -182,22 +173,6 @@ class FriendFeedInfoMenu : AbstractMenu() {
|
|||||||
builder.show()
|
builder.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createEmojiDrawable(text: String, width: Int, height: Int, textSize: Float, disabled: Boolean = false): Drawable {
|
|
||||||
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
|
||||||
val canvas = Canvas(bitmap)
|
|
||||||
val paint = Paint()
|
|
||||||
paint.textSize = textSize
|
|
||||||
paint.color = Color.BLACK
|
|
||||||
paint.textAlign = Paint.Align.CENTER
|
|
||||||
canvas.drawText(text, width / 2f, height.toFloat() - paint.descent(), paint)
|
|
||||||
if (disabled) {
|
|
||||||
paint.color = Color.RED
|
|
||||||
paint.strokeWidth = 5f
|
|
||||||
canvas.drawLine(0f, 0f, width.toFloat(), height.toFloat(), paint)
|
|
||||||
}
|
|
||||||
return BitmapDrawable(context.resources, bitmap)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getCurrentConversationInfo(): Pair<String, String?> {
|
private fun getCurrentConversationInfo(): Pair<String, String?> {
|
||||||
val messaging = context.feature(Messaging::class)
|
val messaging = context.feature(Messaging::class)
|
||||||
val focusedConversationTargetUser: String? = messaging.lastFetchConversationUserUUID?.toString()
|
val focusedConversationTargetUser: String? = messaging.lastFetchConversationUserUUID?.toString()
|
||||||
@ -213,7 +188,7 @@ class FriendFeedInfoMenu : AbstractMenu() {
|
|||||||
|
|
||||||
//old conversation fetch
|
//old conversation fetch
|
||||||
val conversationId = if (messaging.lastFetchConversationUUID == null && focusedConversationTargetUser != null) {
|
val conversationId = if (messaging.lastFetchConversationUUID == null && focusedConversationTargetUser != null) {
|
||||||
val conversation: UserConversationLink = context.database.getDMConversationIdFromUserId(focusedConversationTargetUser) ?: throw IllegalStateException("No conversation found")
|
val conversation: UserConversationLink = context.database.getConversationLinkFromUserId(focusedConversationTargetUser) ?: throw IllegalStateException("No conversation found")
|
||||||
conversation.clientConversationId!!.trim().lowercase()
|
conversation.clientConversationId!!.trim().lowercase()
|
||||||
} else {
|
} else {
|
||||||
messaging.lastFetchConversationUUID.toString()
|
messaging.lastFetchConversationUUID.toString()
|
||||||
@ -244,148 +219,38 @@ class FriendFeedInfoMenu : AbstractMenu() {
|
|||||||
|
|
||||||
val (conversationId, targetUser) = getCurrentConversationInfo()
|
val (conversationId, targetUser) = getCurrentConversationInfo()
|
||||||
|
|
||||||
if (!context.config.userInterface.enableFriendFeedMenuBar.get()) {
|
val previewButton = Button(viewModel.context).apply {
|
||||||
//preview button
|
text = modContext.translation["friend_menu_option.preview"]
|
||||||
val previewButton = Button(viewModel.context).apply {
|
ViewAppearanceHelper.applyTheme(this, viewModel.width)
|
||||||
text = modContext.translation["friend_menu_option.preview"]
|
setOnClickListener {
|
||||||
ViewAppearanceHelper.applyTheme(this, viewModel.width)
|
showPreview(
|
||||||
setOnClickListener {
|
targetUser,
|
||||||
showPreview(
|
|
||||||
targetUser,
|
|
||||||
conversationId,
|
|
||||||
context
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//stealth switch
|
|
||||||
val stealthSwitch = Switch(viewModel.context).apply {
|
|
||||||
text = modContext.translation["friend_menu_option.stealth_mode"]
|
|
||||||
isChecked = modContext.feature(StealthMode::class).isStealth(conversationId)
|
|
||||||
ViewAppearanceHelper.applyTheme(this)
|
|
||||||
setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
|
|
||||||
modContext.feature(StealthMode::class).setStealth(
|
|
||||||
conversationId,
|
|
||||||
isChecked
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (friendFeedMenuOptions.contains("anti_auto_save")) {
|
|
||||||
createToggleFeature(viewConsumer,
|
|
||||||
"friend_menu_option.anti_auto_save",
|
|
||||||
{ context.feature(AntiAutoSave::class).isConversationIgnored(conversationId) },
|
|
||||||
{ context.feature(AntiAutoSave::class).setConversationIgnored(conversationId, it) }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
run {
|
|
||||||
val userId = context.database.getFeedEntryByConversationId(conversationId)?.friendUserId ?: return@run
|
|
||||||
if (friendFeedMenuOptions.contains("auto_download_blacklist")) {
|
|
||||||
createToggleFeature(viewConsumer,
|
|
||||||
"friend_menu_option.auto_download_blacklist",
|
|
||||||
{ context.feature(AntiAutoDownload::class).isUserIgnored(userId) },
|
|
||||||
{ context.feature(AntiAutoDownload::class).setUserIgnored(userId, it) }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (friendFeedMenuOptions.contains("stealth_mode")) {
|
|
||||||
viewConsumer(stealthSwitch)
|
|
||||||
}
|
|
||||||
if (friendFeedMenuOptions.contains("conversation_info")) {
|
|
||||||
viewConsumer(previewButton)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val menuButtonBar = LinearLayout(viewModel.context).apply {
|
|
||||||
layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
|
|
||||||
orientation = LinearLayout.HORIZONTAL
|
|
||||||
gravity = Gravity.CENTER
|
|
||||||
}
|
|
||||||
|
|
||||||
fun createActionButton(icon: String, isDisabled: Boolean? = null, onClick: (Boolean) -> Unit) {
|
|
||||||
//FIXME: hardcoded values
|
|
||||||
menuButtonBar.addView(LinearLayout(viewModel.context).apply {
|
|
||||||
layoutParams = LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, 1f)
|
|
||||||
gravity = Gravity.CENTER
|
|
||||||
isClickable = false
|
|
||||||
|
|
||||||
var isLineThrough = isDisabled ?: false
|
|
||||||
FriendActionButton.new(viewModel.context).apply {
|
|
||||||
fun setLineThrough(value: Boolean) {
|
|
||||||
setIconDrawable(createEmojiDrawable(icon, 60, 60, 50f, if (isDisabled == null) false else value))
|
|
||||||
}
|
|
||||||
setLineThrough(isLineThrough)
|
|
||||||
(instanceNonNull() as View).apply {
|
|
||||||
layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT).apply {
|
|
||||||
setMargins(0, 40, 0, 40)
|
|
||||||
}
|
|
||||||
setOnTouchListener { _, event ->
|
|
||||||
if (event.action == MotionEvent.ACTION_UP) {
|
|
||||||
isLineThrough = !isLineThrough
|
|
||||||
onClick(isLineThrough)
|
|
||||||
setLineThrough(isLineThrough)
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}.also { addView(it.instanceNonNull() as View) }
|
|
||||||
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (friendFeedMenuOptions.contains("auto_download_blacklist")) {
|
|
||||||
run {
|
|
||||||
val userId =
|
|
||||||
context.database.getFeedEntryByConversationId(conversationId)?.friendUserId
|
|
||||||
?: return@run
|
|
||||||
createActionButton(
|
|
||||||
"\u2B07\uFE0F",
|
|
||||||
isDisabled = !context.feature(AntiAutoDownload::class).isUserIgnored(userId)
|
|
||||||
) {
|
|
||||||
context.feature(AntiAutoDownload::class).setUserIgnored(userId, !it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (friendFeedMenuOptions.contains("anti_auto_save")) {
|
|
||||||
//diskette
|
|
||||||
createActionButton("\uD83D\uDCAC",
|
|
||||||
isDisabled = !context.feature(AntiAutoSave::class)
|
|
||||||
.isConversationIgnored(conversationId)
|
|
||||||
) {
|
|
||||||
context.feature(AntiAutoSave::class).setConversationIgnored(conversationId, !it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (friendFeedMenuOptions.contains("stealth_mode")) {
|
|
||||||
//eyes
|
|
||||||
createActionButton(
|
|
||||||
"\uD83D\uDC7B",
|
|
||||||
isDisabled = !context.feature(StealthMode::class).isStealth(conversationId)
|
|
||||||
) { isChecked ->
|
|
||||||
context.feature(StealthMode::class).setStealth(
|
|
||||||
conversationId,
|
conversationId,
|
||||||
!isChecked
|
context
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (friendFeedMenuOptions.contains("conversation_info")) {
|
if (friendFeedMenuOptions.contains("conversation_info")) {
|
||||||
//user
|
viewConsumer(previewButton)
|
||||||
createActionButton("\uD83D\uDC64") {
|
|
||||||
showPreview(
|
|
||||||
targetUser,
|
|
||||||
conversationId,
|
|
||||||
viewModel.context
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
viewConsumer(menuButtonBar)
|
val rules: Array<MessagingRuleFeature> = arrayOf(
|
||||||
|
StealthMode::class,
|
||||||
|
AutoSave::class,
|
||||||
|
MediaDownloader::class
|
||||||
|
).map { modContext.feature(it) }.toTypedArray()
|
||||||
|
|
||||||
|
rules.forEach { ruleFeature ->
|
||||||
|
if (!friendFeedMenuOptions.contains(ruleFeature.ruleType.key)) return@forEach
|
||||||
|
Logger.debug("${ruleFeature.ruleType.key} ${ruleFeature.getRuleState()}")
|
||||||
|
|
||||||
|
val ruleState = ruleFeature.getRuleState() ?: return@forEach
|
||||||
|
createToggleFeature(viewConsumer,
|
||||||
|
ruleFeature.ruleType.translateOptionKey(ruleState.key),
|
||||||
|
{ ruleFeature.getState(conversationId) },
|
||||||
|
{ ruleFeature.setState(conversationId, it) }
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Reference in New Issue
Block a user