mirror of
https://github.com/rhunk/SnapEnhance.git
synced 2025-05-28 04:20:20 +02:00
config refactor + ui
This commit is contained in:
parent
e2249417eb
commit
79d3bb5ba9
@ -50,16 +50,6 @@
|
||||
android:name=".ui.map.MapActivity"
|
||||
android:exported="true"
|
||||
android:excludeFromRecents="true" />
|
||||
<activity
|
||||
android:name=".ui.config.ConfigActivity"
|
||||
android:theme="@style/AppTheme"
|
||||
android:excludeFromRecents="true"
|
||||
android:exported="true" />
|
||||
<activity
|
||||
android:name=".ui.spoof.DeviceSpooferActivity"
|
||||
android:theme="@style/AppTheme"
|
||||
android:excludeFromRecents="true"
|
||||
android:exported="true" />
|
||||
<activity android:name=".bridge.ForceStartActivity"
|
||||
android:theme="@android:style/Theme.NoDisplay"
|
||||
android:excludeFromRecents="true"
|
||||
|
@ -1,15 +1,12 @@
|
||||
package me.rhunk.snapenhance.manager
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import me.rhunk.snapenhance.Logger
|
||||
import me.rhunk.snapenhance.manager.data.ManagerContext
|
||||
import me.rhunk.snapenhance.manager.util.SaveFolderChecker
|
||||
import me.rhunk.snapenhance.util.ActivityResultCallback
|
||||
|
@ -20,7 +20,6 @@ import androidx.navigation.NavDestination.Companion.hierarchy
|
||||
import androidx.navigation.NavGraph.Companion.findStartDestination
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||
import me.rhunk.snapenhance.manager.data.ManagerContext
|
||||
|
||||
@ -36,16 +35,15 @@ class Navigation(
|
||||
) {
|
||||
val sections = remember { EnumSection.values().toList().map {
|
||||
it to it.section.constructors.first().call()
|
||||
}.onEach { (_, instance) ->
|
||||
}.onEach { (section, instance) ->
|
||||
instance.enumSection = section
|
||||
instance.manager = context
|
||||
instance.navController = navController
|
||||
} }
|
||||
|
||||
NavHost(navController, startDestination = startDestination.route, Modifier.padding(innerPadding)) {
|
||||
sections.forEach { (section, instance) ->
|
||||
composable(section.route) {
|
||||
instance.Content()
|
||||
}
|
||||
sections.forEach { (_, instance) ->
|
||||
instance.build(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,10 +9,12 @@ import androidx.compose.material.icons.filled.Stars
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.compose.composable
|
||||
import me.rhunk.snapenhance.manager.data.ManagerContext
|
||||
import me.rhunk.snapenhance.manager.sections.FeaturesSection
|
||||
import me.rhunk.snapenhance.manager.sections.HomeSection
|
||||
import me.rhunk.snapenhance.manager.sections.NotImplemented
|
||||
import me.rhunk.snapenhance.manager.sections.features.FeaturesSection
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
enum class EnumSection(
|
||||
@ -59,9 +61,16 @@ enum class EnumSection(
|
||||
|
||||
|
||||
open class Section {
|
||||
lateinit var enumSection: EnumSection
|
||||
lateinit var manager: ManagerContext
|
||||
lateinit var navController: NavController
|
||||
|
||||
@Composable
|
||||
open fun Content() { NotImplemented() }
|
||||
|
||||
open fun build(navGraphBuilder: NavGraphBuilder) {
|
||||
navGraphBuilder.composable(enumSection.route) {
|
||||
Content()
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
package me.rhunk.snapenhance.manager.data
|
||||
|
||||
import android.content.Context
|
||||
import me.rhunk.snapenhance.bridge.wrapper.ConfigWrapper
|
||||
import me.rhunk.snapenhance.bridge.wrapper.MappingsWrapper
|
||||
import me.rhunk.snapenhance.bridge.wrapper.TranslationWrapper
|
||||
import me.rhunk.snapenhance.core.config.ModConfig
|
||||
|
@ -1,235 +0,0 @@
|
||||
package me.rhunk.snapenhance.manager.sections
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.layout.wrapContentWidth
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.SnackbarHost
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.OpenInNew
|
||||
import androidx.compose.material.icons.rounded.Save
|
||||
import androidx.compose.material.rememberScaffoldState
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.FilledIconButton
|
||||
import androidx.compose.material3.FloatingActionButton
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import kotlinx.coroutines.launch
|
||||
import me.rhunk.snapenhance.Logger
|
||||
import me.rhunk.snapenhance.config.ConfigProperty
|
||||
import me.rhunk.snapenhance.config.impl.ConfigIntegerValue
|
||||
import me.rhunk.snapenhance.config.impl.ConfigStateListValue
|
||||
import me.rhunk.snapenhance.config.impl.ConfigStateSelection
|
||||
import me.rhunk.snapenhance.config.impl.ConfigStateValue
|
||||
import me.rhunk.snapenhance.config.impl.ConfigStringValue
|
||||
import me.rhunk.snapenhance.manager.Dialogs
|
||||
import me.rhunk.snapenhance.manager.Section
|
||||
|
||||
typealias ClickCallback = (Boolean) -> Unit
|
||||
typealias RegisterClickCallback = (ClickCallback) -> ClickCallback
|
||||
|
||||
class FeaturesSection : Section() {
|
||||
private val dialogs by lazy { Dialogs(manager) }
|
||||
|
||||
@Composable
|
||||
private fun PropertyAction(item: ConfigProperty, registerClickCallback: RegisterClickCallback) {
|
||||
val showDialog = remember { mutableStateOf(false) }
|
||||
val dialogComposable = remember { mutableStateOf<@Composable () -> Unit>({})}
|
||||
|
||||
fun registerDialogOnClickCallback() = registerClickCallback {
|
||||
showDialog.value = true
|
||||
}
|
||||
|
||||
if (showDialog.value) {
|
||||
Dialog(onDismissRequest = { showDialog.value = false }, properties = DialogProperties()) {
|
||||
dialogComposable.value()
|
||||
}
|
||||
}
|
||||
|
||||
when (val container = remember { item.valueContainer }) {
|
||||
is ConfigStateValue -> {
|
||||
val state = remember { mutableStateOf(container.value()) }
|
||||
Switch(
|
||||
checked = state.value,
|
||||
onCheckedChange = registerClickCallback {
|
||||
state.value = state.value.not()
|
||||
container.writeFrom(state.value.toString())
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
is ConfigStateSelection -> {
|
||||
registerDialogOnClickCallback()
|
||||
dialogComposable.value = {
|
||||
dialogs.StateSelectionDialog(item)
|
||||
}
|
||||
Text(
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
modifier = Modifier.widthIn(0.dp, 120.dp),
|
||||
text = if (item.disableValueLocalization) container.value() else {
|
||||
manager.translation.propertyOption(item, container.value())
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
is ConfigStateListValue, is ConfigStringValue, is ConfigIntegerValue -> {
|
||||
dialogComposable.value = {
|
||||
when (container) {
|
||||
is ConfigStateListValue -> {
|
||||
dialogs.StateListDialog(item)
|
||||
}
|
||||
is ConfigStringValue, is ConfigIntegerValue -> {
|
||||
dialogs.KeyboardInputDialog(item) { showDialog.value = false }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
registerDialogOnClickCallback().let { { it.invoke(true) } }.also {
|
||||
if (container is ConfigIntegerValue) {
|
||||
FilledIconButton(onClick = it) {
|
||||
Text(text = container.value().toString(), modifier = Modifier.wrapContentWidth(), overflow = TextOverflow.Ellipsis)
|
||||
}
|
||||
} else {
|
||||
IconButton(onClick = it) {
|
||||
Icon(Icons.Filled.OpenInNew, contentDescription = null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PropertyCard(item: ConfigProperty) {
|
||||
val clickCallback = remember { mutableStateOf<ClickCallback?>(null) }
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable {
|
||||
clickCallback.value?.invoke(true)
|
||||
}
|
||||
.padding(start = 10.dp, end = 10.dp, top = 5.dp, bottom = 5.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(all = 4.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterVertically)
|
||||
.weight(1f, fill = true)
|
||||
.padding(all = 10.dp)
|
||||
) {
|
||||
Text(text = manager.translation.propertyName(item), fontSize = 16.sp, fontWeight = FontWeight.Bold)
|
||||
Text(
|
||||
text = manager.translation.propertyDescription(item),
|
||||
fontSize = 12.sp,
|
||||
lineHeight = 15.sp
|
||||
)
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterVertically)
|
||||
.padding(all = 10.dp)
|
||||
) {
|
||||
PropertyAction(item, registerClickCallback = { callback ->
|
||||
clickCallback.value = callback
|
||||
callback
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PropertyContainer() {
|
||||
val properties = remember {
|
||||
val items by manager.config
|
||||
items.properties.map { it.key to it.value }
|
||||
}
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.fillMaxHeight(),
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
items(properties) { (key, value) ->
|
||||
// Logger.debug("key: $key, value: $value")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
@Preview
|
||||
override fun Content() {
|
||||
val scope = rememberCoroutineScope()
|
||||
val scaffoldState = rememberScaffoldState()
|
||||
Scaffold(
|
||||
snackbarHost = { SnackbarHost(scaffoldState.snackbarHostState) },
|
||||
floatingActionButton = {
|
||||
FloatingActionButton(
|
||||
onClick = {
|
||||
//manager.config.writeConfig()
|
||||
scope.launch {
|
||||
scaffoldState.snackbarHostState.showSnackbar("Saved")
|
||||
}
|
||||
},
|
||||
containerColor = MaterialTheme.colors.primary,
|
||||
contentColor = MaterialTheme.colors.onPrimary,
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.Save,
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
},
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
content = { innerPadding ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(innerPadding)
|
||||
) {
|
||||
Text(
|
||||
text = "Features",
|
||||
modifier = Modifier.padding(all = 10.dp),
|
||||
fontSize = 20.sp
|
||||
)
|
||||
PropertyContainer()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
package me.rhunk.snapenhance.manager.sections.features
|
||||
|
||||
typealias ClickCallback = (Boolean) -> Unit
|
||||
typealias RegisterClickCallback = (ClickCallback) -> ClickCallback
|
@ -1,4 +1,4 @@
|
||||
package me.rhunk.snapenhance.manager
|
||||
package me.rhunk.snapenhance.manager.sections.features
|
||||
|
||||
import androidx.compose.foundation.ScrollState
|
||||
import androidx.compose.foundation.clickable
|
||||
@ -29,16 +29,11 @@ import androidx.compose.ui.text.TextRange
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.unit.dp
|
||||
import me.rhunk.snapenhance.config.ConfigProperty
|
||||
import me.rhunk.snapenhance.config.impl.ConfigIntegerValue
|
||||
import me.rhunk.snapenhance.config.impl.ConfigStateListValue
|
||||
import me.rhunk.snapenhance.config.impl.ConfigStateSelection
|
||||
import me.rhunk.snapenhance.manager.data.ManagerContext
|
||||
import me.rhunk.snapenhance.core.config.DataProcessors
|
||||
import me.rhunk.snapenhance.core.config.PropertyPair
|
||||
|
||||
|
||||
class Dialogs(
|
||||
private val context: ManagerContext
|
||||
) {
|
||||
class Dialogs {
|
||||
@Composable
|
||||
fun DefaultDialogCard(content: @Composable ColumnScope.() -> Unit) {
|
||||
Card(
|
||||
@ -65,17 +60,25 @@ class Dialogs(
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun StateSelectionDialog(config: ConfigProperty) {
|
||||
assert(config.valueContainer is ConfigStateSelection)
|
||||
val keys = (config.valueContainer as ConfigStateSelection).keys()
|
||||
val selectedValue = remember {
|
||||
mutableStateOf(config.valueContainer.value())
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun UniqueSelectionDialog(property: PropertyPair<*>) {
|
||||
val keys = (property.value.defaultValues as List<String>).toMutableList().apply {
|
||||
add(0, "disabled")
|
||||
}
|
||||
|
||||
val selectedValue = remember {
|
||||
mutableStateOf(property.value.getNullable()?.toString() ?: "disabled")
|
||||
}
|
||||
|
||||
DefaultDialogCard {
|
||||
keys.forEach { item ->
|
||||
keys.forEachIndexed { index, item ->
|
||||
fun select() {
|
||||
selectedValue.value = item
|
||||
config.valueContainer.writeFrom(item)
|
||||
property.value.setAny(if (index == 0) {
|
||||
null
|
||||
} else {
|
||||
item
|
||||
})
|
||||
}
|
||||
|
||||
Row(
|
||||
@ -83,9 +86,7 @@ class Dialogs(
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
DefaultEntryText(
|
||||
text = if (config.disableValueLocalization)
|
||||
item
|
||||
else context.translation.propertyOption(config, item),
|
||||
text = item,
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
RadioButton(
|
||||
@ -98,12 +99,12 @@ class Dialogs(
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun KeyboardInputDialog(config: ConfigProperty, dismiss: () -> Unit = {}) {
|
||||
fun KeyboardInputDialog(property: PropertyPair<*>, dismiss: () -> Unit = {}) {
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
|
||||
DefaultDialogCard {
|
||||
val fieldValue = remember {
|
||||
mutableStateOf(config.valueContainer.read().let {
|
||||
mutableStateOf(property.value.get().toString().let {
|
||||
TextFieldValue(
|
||||
text = it,
|
||||
selection = TextRange(it.length)
|
||||
@ -123,8 +124,8 @@ class Dialogs(
|
||||
onValueChange = {
|
||||
fieldValue.value = it
|
||||
},
|
||||
keyboardOptions = when (config.valueContainer) {
|
||||
is ConfigIntegerValue -> {
|
||||
keyboardOptions = when (property.key.dataType.type) {
|
||||
DataProcessors.Type.INTEGER -> {
|
||||
KeyboardOptions(keyboardType = KeyboardType.Number)
|
||||
}
|
||||
else -> {
|
||||
@ -142,7 +143,15 @@ class Dialogs(
|
||||
Text(text = "Cancel")
|
||||
}
|
||||
Button(onClick = {
|
||||
config.valueContainer.writeFrom(fieldValue.value.text)
|
||||
if (property.key.dataType.type == DataProcessors.Type.INTEGER) {
|
||||
runCatching {
|
||||
property.value.setAny(fieldValue.value.text.toInt())
|
||||
}.onFailure {
|
||||
property.value.setAny(0)
|
||||
}
|
||||
} else {
|
||||
property.value.setAny(fieldValue.value.text)
|
||||
}
|
||||
dismiss()
|
||||
}) {
|
||||
Text(text = "Ok")
|
||||
@ -152,18 +161,23 @@ class Dialogs(
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun StateListDialog(config: ConfigProperty) {
|
||||
assert(config.valueContainer is ConfigStateListValue)
|
||||
val stateList = (config.valueContainer as ConfigStateListValue).value()
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun MultipleSelectionDialog(property: PropertyPair<*>) {
|
||||
val defaultItems = property.value.defaultValues as List<String>
|
||||
val toggledStates = property.value.get() as MutableList<String>
|
||||
DefaultDialogCard {
|
||||
stateList.keys.forEach { key ->
|
||||
defaultItems.forEach { key ->
|
||||
val state = remember {
|
||||
mutableStateOf(stateList[key] ?: false)
|
||||
mutableStateOf(toggledStates.contains(key))
|
||||
}
|
||||
|
||||
fun toggle(value: Boolean? = null) {
|
||||
state.value = value ?: !state.value
|
||||
stateList[key] = state.value
|
||||
if (state.value) {
|
||||
toggledStates.add(key)
|
||||
} else {
|
||||
toggledStates.remove(key)
|
||||
}
|
||||
}
|
||||
|
||||
Row(
|
||||
@ -171,9 +185,7 @@ class Dialogs(
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
DefaultEntryText(
|
||||
text = if (config.disableValueLocalization)
|
||||
key
|
||||
else context.translation.propertyOption(config, key),
|
||||
text = key,
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
)
|
@ -0,0 +1,293 @@
|
||||
package me.rhunk.snapenhance.manager.sections.features
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.layout.wrapContentWidth
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.OpenInNew
|
||||
import androidx.compose.material.icons.rounded.Save
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.FilledIconButton
|
||||
import androidx.compose.material3.FloatingActionButton
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.rememberBottomSheetScaffoldState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.navigation
|
||||
import kotlinx.coroutines.launch
|
||||
import me.rhunk.snapenhance.core.config.ConfigContainer
|
||||
import me.rhunk.snapenhance.core.config.DataProcessors
|
||||
import me.rhunk.snapenhance.core.config.PropertyPair
|
||||
import me.rhunk.snapenhance.manager.Section
|
||||
|
||||
class FeaturesSection : Section() {
|
||||
private val dialogs by lazy { Dialogs() }
|
||||
|
||||
companion object {
|
||||
private const val MAIN_ROUTE = "root"
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PropertyAction(property: PropertyPair<*>, registerClickCallback: RegisterClickCallback) {
|
||||
val showDialog = remember { mutableStateOf(false) }
|
||||
val dialogComposable = remember { mutableStateOf<@Composable () -> Unit>({}) }
|
||||
|
||||
fun registerDialogOnClickCallback() = registerClickCallback {
|
||||
showDialog.value = true
|
||||
}
|
||||
|
||||
if (showDialog.value) {
|
||||
Dialog(
|
||||
onDismissRequest = { showDialog.value = false },
|
||||
properties = DialogProperties()
|
||||
) {
|
||||
dialogComposable.value()
|
||||
}
|
||||
}
|
||||
|
||||
val propertyValue = property.value
|
||||
|
||||
when (val dataType = remember { property.key.dataType.type }) {
|
||||
DataProcessors.Type.BOOLEAN -> {
|
||||
val state = remember { mutableStateOf(propertyValue.get() as Boolean) }
|
||||
Switch(
|
||||
checked = state.value,
|
||||
onCheckedChange = registerClickCallback {
|
||||
state.value = state.value.not()
|
||||
propertyValue.setAny(state.value)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
DataProcessors.Type.STRING_UNIQUE_SELECTION -> {
|
||||
registerDialogOnClickCallback()
|
||||
|
||||
dialogComposable.value = {
|
||||
dialogs.UniqueSelectionDialog(property)
|
||||
}
|
||||
|
||||
Text(
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
modifier = Modifier.widthIn(0.dp, 120.dp),
|
||||
text = (propertyValue.getNullable() as? String) ?: "Disabled",
|
||||
)
|
||||
}
|
||||
|
||||
DataProcessors.Type.STRING_MULTIPLE_SELECTION, DataProcessors.Type.STRING, DataProcessors.Type.INTEGER -> {
|
||||
dialogComposable.value = {
|
||||
when (dataType) {
|
||||
DataProcessors.Type.STRING_MULTIPLE_SELECTION -> {
|
||||
dialogs.MultipleSelectionDialog(property)
|
||||
}
|
||||
DataProcessors.Type.STRING, DataProcessors.Type.INTEGER -> {
|
||||
dialogs.KeyboardInputDialog(property) { showDialog.value = false }
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
registerDialogOnClickCallback().let { { it.invoke(true) } }.also {
|
||||
if (dataType == DataProcessors.Type.INTEGER) {
|
||||
FilledIconButton(onClick = it) {
|
||||
Text(
|
||||
text = propertyValue.get().toString(),
|
||||
modifier = Modifier.wrapContentWidth(),
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
} else {
|
||||
IconButton(onClick = it) {
|
||||
Icon(Icons.Filled.OpenInNew, contentDescription = null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PropertyCard(property: PropertyPair<*>) {
|
||||
val clickCallback = remember { mutableStateOf<ClickCallback?>(null) }
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 10.dp, end = 10.dp, top = 5.dp, bottom = 5.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.clickable {
|
||||
clickCallback.value?.invoke(true)
|
||||
}
|
||||
.padding(all = 4.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterVertically)
|
||||
.weight(1f, fill = true)
|
||||
.padding(all = 10.dp)
|
||||
) {
|
||||
Text(
|
||||
text = property.name,
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
Text(
|
||||
text = property.name,
|
||||
fontSize = 12.sp,
|
||||
lineHeight = 15.sp
|
||||
)
|
||||
}
|
||||
|
||||
when (property.key.dataType.type) {
|
||||
DataProcessors.Type.CONTAINER -> {
|
||||
clickCallback.value = {
|
||||
navController.navigate("container/${property.name}")
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterVertically)
|
||||
.padding(all = 10.dp)
|
||||
) {
|
||||
PropertyAction(property, registerClickCallback = { callback ->
|
||||
clickCallback.value = callback
|
||||
callback
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun Container(
|
||||
containerName: String,
|
||||
configContainer: ConfigContainer
|
||||
) {
|
||||
val properties = remember {
|
||||
configContainer.properties.map { PropertyPair(it.key, it.value) }
|
||||
}
|
||||
|
||||
val scope = rememberCoroutineScope()
|
||||
val scaffoldState = rememberBottomSheetScaffoldState()
|
||||
Scaffold(
|
||||
snackbarHost = { SnackbarHost(scaffoldState.snackbarHostState) },
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = {
|
||||
Text(text = containerName, textAlign = TextAlign.Center)
|
||||
},
|
||||
navigationIcon = {
|
||||
if (navController.currentBackStackEntry?.destination?.route != MAIN_ROUTE) {
|
||||
IconButton(onClick = { navController.popBackStack() }) {
|
||||
Icon(Icons.Filled.ArrowBack, contentDescription = null)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
floatingActionButton = {
|
||||
FloatingActionButton(
|
||||
onClick = {
|
||||
manager.config.writeConfig()
|
||||
scope.launch {
|
||||
scaffoldState.snackbarHostState.showSnackbar("Saved")
|
||||
}
|
||||
},
|
||||
modifier = Modifier.padding(25.dp),
|
||||
containerColor = MaterialTheme.colors.primary,
|
||||
contentColor = MaterialTheme.colors.onPrimary,
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.Save,
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
},
|
||||
content = { innerPadding ->
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxHeight().padding(innerPadding),
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
items(properties) {
|
||||
PropertyCard(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
override fun build(navGraphBuilder: NavGraphBuilder) {
|
||||
val allContainers by lazy {
|
||||
val containers = mutableMapOf<String, ConfigContainer>()
|
||||
fun queryContainerRecursive(container: ConfigContainer) {
|
||||
container.properties.forEach {
|
||||
if (it.key.dataType.type == DataProcessors.Type.CONTAINER) {
|
||||
containers[it.key.name] = it.value.get() as ConfigContainer
|
||||
queryContainerRecursive(it.value.get() as ConfigContainer)
|
||||
}
|
||||
}
|
||||
}
|
||||
queryContainerRecursive(manager.config.root)
|
||||
containers
|
||||
}
|
||||
|
||||
navGraphBuilder.navigation(route = "features", startDestination = MAIN_ROUTE) {
|
||||
composable(MAIN_ROUTE) {
|
||||
Container(MAIN_ROUTE, manager.config.root)
|
||||
}
|
||||
|
||||
composable("container/{name}") { backStackEntry ->
|
||||
backStackEntry.arguments?.getString("name")?.let { containerName ->
|
||||
allContainers[containerName]?.let {
|
||||
Container(containerName, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -6,7 +6,6 @@ import android.content.Intent
|
||||
import android.widget.Toast
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import me.rhunk.snapenhance.Logger
|
||||
import me.rhunk.snapenhance.core.config.PropertyValue
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
|
@ -14,7 +14,6 @@ import kotlinx.coroutines.asCoroutineDispatcher
|
||||
import me.rhunk.snapenhance.bridge.BridgeClient
|
||||
import me.rhunk.snapenhance.bridge.wrapper.TranslationWrapper
|
||||
import me.rhunk.snapenhance.core.config.ModConfig
|
||||
import me.rhunk.snapenhance.core.config.impl.RootConfig
|
||||
import me.rhunk.snapenhance.data.MessageSender
|
||||
import me.rhunk.snapenhance.database.DatabaseAccess
|
||||
import me.rhunk.snapenhance.features.Feature
|
||||
|
@ -7,7 +7,6 @@ import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Environment
|
||||
import android.provider.Settings
|
||||
import me.rhunk.snapenhance.bridge.wrapper.ConfigWrapper
|
||||
import me.rhunk.snapenhance.bridge.wrapper.TranslationWrapper
|
||||
import me.rhunk.snapenhance.download.DownloadTaskManager
|
||||
import kotlin.system.exitProcess
|
||||
@ -18,7 +17,6 @@ import kotlin.system.exitProcess
|
||||
object SharedContext {
|
||||
lateinit var downloadTaskManager: DownloadTaskManager
|
||||
lateinit var translation: TranslationWrapper
|
||||
lateinit var config: ConfigWrapper
|
||||
|
||||
private fun askForStoragePermission(context: Context) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
@ -76,10 +74,6 @@ object SharedContext {
|
||||
loadFromContext(context)
|
||||
}
|
||||
}
|
||||
if (!this::config.isInitialized) {
|
||||
config = ConfigWrapper().apply { loadFromContext(context) }
|
||||
}
|
||||
|
||||
//askForPermissions(context)
|
||||
}
|
||||
}
|
@ -1,12 +1,10 @@
|
||||
package me.rhunk.snapenhance.action
|
||||
|
||||
import me.rhunk.snapenhance.ModContext
|
||||
import me.rhunk.snapenhance.config.ConfigProperty
|
||||
import java.io.File
|
||||
|
||||
abstract class AbstractAction(
|
||||
val nameKey: String,
|
||||
val dependsOnProperty: ConfigProperty? = null,
|
||||
val nameKey: String
|
||||
) {
|
||||
lateinit var context: ModContext
|
||||
|
||||
|
@ -1,10 +1,9 @@
|
||||
package me.rhunk.snapenhance.action.impl
|
||||
|
||||
import me.rhunk.snapenhance.action.AbstractAction
|
||||
import me.rhunk.snapenhance.config.ConfigProperty
|
||||
import me.rhunk.snapenhance.features.impl.AutoUpdater
|
||||
|
||||
class CheckForUpdates : AbstractAction("action.check_for_updates", dependsOnProperty = ConfigProperty.AUTO_UPDATER) {
|
||||
class CheckForUpdates : AbstractAction("action.check_for_updates") {
|
||||
override fun run() {
|
||||
context.executeAsync {
|
||||
runCatching {
|
||||
|
@ -3,11 +3,10 @@ package me.rhunk.snapenhance.action.impl
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import me.rhunk.snapenhance.action.AbstractAction
|
||||
import me.rhunk.snapenhance.config.ConfigProperty
|
||||
import me.rhunk.snapenhance.core.BuildConfig
|
||||
import me.rhunk.snapenhance.ui.map.MapActivity
|
||||
|
||||
class OpenMap: AbstractAction("action.open_map", dependsOnProperty = ConfigProperty.LOCATION_SPOOF) {
|
||||
class OpenMap: AbstractAction("action.open_map") {
|
||||
override fun run() {
|
||||
context.runOnUiThread {
|
||||
val mapActivityIntent = Intent()
|
||||
|
@ -10,10 +10,10 @@ import android.os.Handler
|
||||
import android.os.HandlerThread
|
||||
import android.os.IBinder
|
||||
import de.robv.android.xposed.XposedHelpers
|
||||
import me.rhunk.snapenhance.core.BuildConfig
|
||||
import me.rhunk.snapenhance.Logger.xposedLog
|
||||
import me.rhunk.snapenhance.ModContext
|
||||
import me.rhunk.snapenhance.bridge.types.BridgeFileType
|
||||
import me.rhunk.snapenhance.core.BuildConfig
|
||||
import me.rhunk.snapenhance.data.LocalePair
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.concurrent.Executors
|
||||
|
@ -1,73 +0,0 @@
|
||||
package me.rhunk.snapenhance.bridge.wrapper
|
||||
|
||||
import android.content.Context
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.JsonObject
|
||||
import me.rhunk.snapenhance.Logger
|
||||
import me.rhunk.snapenhance.bridge.BridgeClient
|
||||
import me.rhunk.snapenhance.bridge.FileLoaderWrapper
|
||||
import me.rhunk.snapenhance.bridge.types.BridgeFileType
|
||||
import me.rhunk.snapenhance.config.ConfigAccessor
|
||||
import me.rhunk.snapenhance.config.ConfigProperty
|
||||
|
||||
class ConfigWrapper: ConfigAccessor() {
|
||||
companion object {
|
||||
private val gson = GsonBuilder().setPrettyPrinting().create()
|
||||
}
|
||||
|
||||
private val file = FileLoaderWrapper(BridgeFileType.CONFIG, "{}".toByteArray(Charsets.UTF_8))
|
||||
|
||||
fun load() {
|
||||
ConfigProperty.sortedByCategory().forEach { key ->
|
||||
set(key, key.valueContainer)
|
||||
}
|
||||
|
||||
if (!file.isFileExists()) {
|
||||
writeConfig()
|
||||
return
|
||||
}
|
||||
|
||||
runCatching {
|
||||
loadConfig()
|
||||
}.onFailure {
|
||||
Logger.error("Failed to load config", it)
|
||||
writeConfig()
|
||||
}
|
||||
}
|
||||
|
||||
fun save() {
|
||||
writeConfig()
|
||||
}
|
||||
|
||||
private fun loadConfig() {
|
||||
val configContent = file.read()
|
||||
|
||||
val configObject: JsonObject = gson.fromJson(
|
||||
configContent.toString(Charsets.UTF_8),
|
||||
JsonObject::class.java
|
||||
)
|
||||
|
||||
entries().forEach { (key, value) ->
|
||||
value.writeFrom(configObject.get(key.name)?.asString ?: value.read())
|
||||
}
|
||||
}
|
||||
|
||||
fun writeConfig() {
|
||||
val configObject = JsonObject()
|
||||
entries().forEach { (key, value) ->
|
||||
configObject.addProperty(key.name, value.read())
|
||||
}
|
||||
|
||||
file.write(gson.toJson(configObject).toByteArray(Charsets.UTF_8))
|
||||
}
|
||||
|
||||
fun loadFromContext(context: Context) {
|
||||
file.loadFromContext(context)
|
||||
load()
|
||||
}
|
||||
|
||||
fun loadFromBridge(bridgeClient: BridgeClient) {
|
||||
file.loadFromBridge(bridgeClient)
|
||||
load()
|
||||
}
|
||||
}
|
@ -5,7 +5,6 @@ import com.google.gson.JsonObject
|
||||
import com.google.gson.JsonParser
|
||||
import me.rhunk.snapenhance.Logger
|
||||
import me.rhunk.snapenhance.bridge.BridgeClient
|
||||
import me.rhunk.snapenhance.config.ConfigProperty
|
||||
import me.rhunk.snapenhance.data.LocalePair
|
||||
import java.util.Locale
|
||||
|
||||
@ -80,18 +79,6 @@ class TranslationWrapper {
|
||||
return translationMap[key] ?: key.also { Logger.debug("Missing translation for $key") }
|
||||
}
|
||||
|
||||
fun propertyName(property: ConfigProperty): String {
|
||||
return get("property.${property.translationKey}.name")
|
||||
}
|
||||
|
||||
fun propertyDescription(property: ConfigProperty): String {
|
||||
return get("property.${property.translationKey}.description")
|
||||
}
|
||||
|
||||
fun propertyOption(property: ConfigProperty, item: String): String {
|
||||
return get(property.getOptionTranslationKey(item))
|
||||
}
|
||||
|
||||
fun format(key: String, vararg args: Pair<String, String>): String {
|
||||
return args.fold(get(key)) { acc, pair ->
|
||||
acc.replace("{${pair.first}}", pair.second)
|
||||
|
@ -1,62 +0,0 @@
|
||||
package me.rhunk.snapenhance.config
|
||||
|
||||
open class ConfigAccessor(
|
||||
private val configMap: MutableMap<ConfigProperty, ConfigValue<*>> = mutableMapOf()
|
||||
) {
|
||||
fun bool(key: ConfigProperty): Boolean {
|
||||
return get(key).value() as Boolean
|
||||
}
|
||||
|
||||
fun int(key: ConfigProperty): Int {
|
||||
return get(key).value() as Int
|
||||
}
|
||||
|
||||
fun string(key: ConfigProperty): String {
|
||||
return get(key).value() as String
|
||||
}
|
||||
|
||||
fun double(key: ConfigProperty): Double {
|
||||
return get(key).value() as Double
|
||||
}
|
||||
|
||||
fun float(key: ConfigProperty): Float {
|
||||
return get(key).value() as Float
|
||||
}
|
||||
|
||||
fun long(key: ConfigProperty): Long {
|
||||
return get(key).value() as Long
|
||||
}
|
||||
|
||||
fun short(key: ConfigProperty): Short {
|
||||
return get(key).value() as Short
|
||||
}
|
||||
|
||||
fun byte(key: ConfigProperty): Byte {
|
||||
return get(key).value() as Byte
|
||||
}
|
||||
|
||||
fun char(key: ConfigProperty): Char {
|
||||
return get(key).value() as Char
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun options(key: ConfigProperty): Map<String, Boolean> {
|
||||
return get(key).value() as Map<String, Boolean>
|
||||
}
|
||||
|
||||
fun state(key: ConfigProperty): String {
|
||||
return get(key).value() as String
|
||||
}
|
||||
|
||||
fun get(key: ConfigProperty): ConfigValue<*> {
|
||||
return configMap[key]!!
|
||||
}
|
||||
|
||||
fun set(key: ConfigProperty, value: ConfigValue<*>) {
|
||||
configMap[key] = value
|
||||
}
|
||||
|
||||
fun entries(): Set<Map.Entry<ConfigProperty, ConfigValue<*>>> {
|
||||
return configMap.entries
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
package me.rhunk.snapenhance.config
|
||||
|
||||
enum class ConfigCategory(
|
||||
val key: String,
|
||||
val hidden: Boolean = false
|
||||
) {
|
||||
SPYING_PRIVACY("spying_privacy"),
|
||||
MEDIA_MANAGEMENT("media_manager"),
|
||||
UI_TWEAKS("ui_tweaks"),
|
||||
UPDATES("updates"),
|
||||
CAMERA("camera"),
|
||||
EXPERIMENTAL_DEBUGGING("experimental_debugging"),
|
||||
DEVICE_SPOOFER("device_spoofer", hidden = true)
|
||||
}
|
@ -1,417 +0,0 @@
|
||||
package me.rhunk.snapenhance.config
|
||||
|
||||
import me.rhunk.snapenhance.config.impl.ConfigIntegerValue
|
||||
import me.rhunk.snapenhance.config.impl.ConfigStateListValue
|
||||
import me.rhunk.snapenhance.config.impl.ConfigStateSelection
|
||||
import me.rhunk.snapenhance.config.impl.ConfigStateValue
|
||||
import me.rhunk.snapenhance.config.impl.ConfigStringValue
|
||||
import me.rhunk.snapenhance.data.NotificationType
|
||||
import me.rhunk.snapenhance.features.impl.tweaks.CameraTweaks
|
||||
|
||||
enum class ConfigProperty(
|
||||
val translationKey: String,
|
||||
val category: ConfigCategory,
|
||||
val valueContainer: ConfigValue<*>,
|
||||
val valueContainerTranslationKey: String? = null,
|
||||
val shouldAppearInSettings: Boolean = true,
|
||||
val disableValueLocalization: Boolean = false
|
||||
) {
|
||||
|
||||
//SPYING AND PRIVACY
|
||||
MESSAGE_LOGGER("message_logger",
|
||||
ConfigCategory.SPYING_PRIVACY,
|
||||
ConfigStateValue(false)
|
||||
),
|
||||
PREVENT_READ_RECEIPTS(
|
||||
"prevent_read_receipts",
|
||||
ConfigCategory.SPYING_PRIVACY,
|
||||
ConfigStateValue(false)
|
||||
),
|
||||
HIDE_BITMOJI_PRESENCE(
|
||||
"hide_bitmoji_presence",
|
||||
ConfigCategory.SPYING_PRIVACY,
|
||||
ConfigStateValue(false)
|
||||
),
|
||||
BETTER_NOTIFICATIONS(
|
||||
"better_notifications",
|
||||
ConfigCategory.SPYING_PRIVACY,
|
||||
ConfigStateListValue(
|
||||
listOf("snap", "chat", "reply_button", "download_button"),
|
||||
mutableMapOf(
|
||||
"snap" to false,
|
||||
"chat" to false,
|
||||
"reply_button" to false,
|
||||
"download_button" to false
|
||||
)
|
||||
)
|
||||
),
|
||||
NOTIFICATION_BLACKLIST(
|
||||
"notification_blacklist",
|
||||
ConfigCategory.SPYING_PRIVACY,
|
||||
ConfigStateListValue(
|
||||
NotificationType.getIncomingValues().map { it.key },
|
||||
NotificationType.getIncomingValues().associate { it.key to false }.toMutableMap()
|
||||
),
|
||||
valueContainerTranslationKey = "notifications",
|
||||
),
|
||||
DISABLE_METRICS("disable_metrics",
|
||||
ConfigCategory.SPYING_PRIVACY,
|
||||
ConfigStateValue(false)
|
||||
),
|
||||
BLOCK_ADS("block_ads",
|
||||
ConfigCategory.SPYING_PRIVACY,
|
||||
ConfigStateValue(false)
|
||||
),
|
||||
UNLIMITED_SNAP_VIEW_TIME("unlimited_snap_view_time",
|
||||
ConfigCategory.SPYING_PRIVACY,
|
||||
ConfigStateValue(false)
|
||||
),
|
||||
PREVENT_SENDING_MESSAGES(
|
||||
"prevent_sending_messages",
|
||||
ConfigCategory.SPYING_PRIVACY,
|
||||
ConfigStateListValue(
|
||||
NotificationType.getOutgoingValues().map { it.key },
|
||||
NotificationType.getOutgoingValues().associate { it.key to false }.toMutableMap()
|
||||
),
|
||||
valueContainerTranslationKey = "notifications",
|
||||
),
|
||||
ANONYMOUS_STORY_VIEW(
|
||||
"anonymous_story_view",
|
||||
ConfigCategory.SPYING_PRIVACY,
|
||||
ConfigStateValue(false)
|
||||
),
|
||||
HIDE_TYPING_NOTIFICATION(
|
||||
"hide_typing_notification",
|
||||
ConfigCategory.SPYING_PRIVACY,
|
||||
ConfigStateValue(false)
|
||||
),
|
||||
|
||||
//MEDIA MANAGEMENT
|
||||
SAVE_FOLDER(
|
||||
"save_folder",
|
||||
ConfigCategory.MEDIA_MANAGEMENT,
|
||||
ConfigStringValue("", isFolderPath =true),
|
||||
),
|
||||
AUTO_DOWNLOAD_OPTIONS(
|
||||
"auto_download_options",
|
||||
ConfigCategory.MEDIA_MANAGEMENT,
|
||||
ConfigStateListValue(
|
||||
listOf("friend_snaps", "friend_stories", "public_stories", "spotlight"),
|
||||
mutableMapOf(
|
||||
"friend_snaps" to false,
|
||||
"friend_stories" to false,
|
||||
"public_stories" to false,
|
||||
"spotlight" to false
|
||||
)
|
||||
)
|
||||
),
|
||||
DOWNLOAD_OPTIONS(
|
||||
"download_options",
|
||||
ConfigCategory.MEDIA_MANAGEMENT,
|
||||
ConfigStateListValue(
|
||||
listOf(
|
||||
"allow_duplicate",
|
||||
"create_user_folder",
|
||||
"append_hash",
|
||||
"append_date_time",
|
||||
"append_type",
|
||||
"append_username",
|
||||
"merge_overlay"
|
||||
),
|
||||
mutableMapOf(
|
||||
"allow_duplicate" to false,
|
||||
"create_user_folder" to true,
|
||||
"append_hash" to true,
|
||||
"append_date_time" to true,
|
||||
"append_type" to false,
|
||||
"append_username" to false,
|
||||
"merge_overlay" to false,
|
||||
)
|
||||
)
|
||||
),
|
||||
CHAT_DOWNLOAD_CONTEXT_MENU(
|
||||
"chat_download_context_menu",
|
||||
ConfigCategory.MEDIA_MANAGEMENT,
|
||||
ConfigStateValue(false)
|
||||
),
|
||||
GALLERY_MEDIA_SEND_OVERRIDE(
|
||||
"gallery_media_send_override",
|
||||
ConfigCategory.MEDIA_MANAGEMENT,
|
||||
ConfigStateSelection(
|
||||
listOf("OFF", "NOTE", "SNAP", "LIVE_SNAP"),
|
||||
"OFF"
|
||||
)
|
||||
),
|
||||
AUTO_SAVE_MESSAGES("auto_save_messages",
|
||||
ConfigCategory.MEDIA_MANAGEMENT,
|
||||
ConfigStateListValue(
|
||||
listOf("CHAT", "SNAP", "NOTE", "EXTERNAL_MEDIA", "STICKER")
|
||||
)
|
||||
),
|
||||
|
||||
FORCE_MEDIA_SOURCE_QUALITY(
|
||||
"force_media_source_quality",
|
||||
ConfigCategory.MEDIA_MANAGEMENT,
|
||||
ConfigStateValue(false)
|
||||
),
|
||||
DOWNLOAD_LOGGING(
|
||||
"download_logging",
|
||||
ConfigCategory.MEDIA_MANAGEMENT,
|
||||
ConfigStateListValue(
|
||||
listOf("started", "success", "progress", "failure"),
|
||||
mutableMapOf(
|
||||
"started" to false,
|
||||
"success" to true,
|
||||
"progress" to false,
|
||||
"failure" to true
|
||||
)
|
||||
)
|
||||
),
|
||||
|
||||
//UI AND TWEAKS
|
||||
ENABLE_FRIEND_FEED_MENU_BAR(
|
||||
"enable_friend_feed_menu_bar",
|
||||
ConfigCategory.UI_TWEAKS,
|
||||
ConfigStateValue(false)
|
||||
),
|
||||
FRIEND_FEED_MENU_BUTTONS(
|
||||
"friend_feed_menu_buttons",
|
||||
ConfigCategory.UI_TWEAKS,
|
||||
ConfigStateListValue(
|
||||
listOf("auto_download_blacklist", "anti_auto_save", "stealth_mode", "conversation_info"),
|
||||
mutableMapOf(
|
||||
"auto_download_blacklist" to false,
|
||||
"anti_auto_save" to false,
|
||||
"stealth_mode" to true,
|
||||
"conversation_info" to true
|
||||
)
|
||||
)
|
||||
),
|
||||
FRIEND_FEED_MENU_POSITION("friend_feed_menu_buttons_position",
|
||||
ConfigCategory.UI_TWEAKS,
|
||||
ConfigIntegerValue(1)
|
||||
),
|
||||
HIDE_UI_ELEMENTS(
|
||||
"hide_ui_elements",
|
||||
ConfigCategory.UI_TWEAKS,
|
||||
ConfigStateListValue(
|
||||
listOf("remove_voice_record_button", "remove_stickers_button", "remove_cognac_button", "remove_live_location_share_button", "remove_call_buttons", "remove_camera_borders"),
|
||||
mutableMapOf(
|
||||
"remove_voice_record_button" to false,
|
||||
"remove_stickers_button" to false,
|
||||
"remove_cognac_button" to false,
|
||||
"remove_live_location_share_button" to false,
|
||||
"remove_call_buttons" to false,
|
||||
"remove_camera_borders" to false
|
||||
)
|
||||
)
|
||||
),
|
||||
HIDE_STORY_SECTION(
|
||||
"hide_story_section",
|
||||
ConfigCategory.UI_TWEAKS,
|
||||
ConfigStateListValue(
|
||||
listOf("hide_friend_suggestions", "hide_friends", "hide_following", "hide_for_you"),
|
||||
mutableMapOf(
|
||||
"hide_friend_suggestions" to false,
|
||||
"hide_friends" to false,
|
||||
"hide_following" to false,
|
||||
"hide_for_you" to false
|
||||
)
|
||||
)
|
||||
),
|
||||
STORY_VIEWER_OVERRIDE("story_viewer_override",
|
||||
ConfigCategory.UI_TWEAKS,
|
||||
ConfigStateSelection(
|
||||
listOf("OFF", "DISCOVER_PLAYBACK_SEEKBAR", "VERTICAL_STORY_VIEWER"),
|
||||
"OFF"
|
||||
)
|
||||
),
|
||||
STREAK_EXPIRATION_INFO(
|
||||
"streak_expiration_info",
|
||||
ConfigCategory.UI_TWEAKS,
|
||||
ConfigStateValue(false)
|
||||
),
|
||||
DISABLE_SNAP_SPLITTING(
|
||||
"disable_snap_splitting",
|
||||
ConfigCategory.UI_TWEAKS,
|
||||
ConfigStateValue(false)
|
||||
),
|
||||
DISABLE_VIDEO_LENGTH_RESTRICTION(
|
||||
"disable_video_length_restriction",
|
||||
ConfigCategory.UI_TWEAKS,
|
||||
ConfigStateValue(false)
|
||||
),
|
||||
SNAPCHAT_PLUS("snapchat_plus",
|
||||
ConfigCategory.UI_TWEAKS,
|
||||
ConfigStateValue(false)
|
||||
),
|
||||
NEW_MAP_UI("new_map_ui",
|
||||
ConfigCategory.UI_TWEAKS,
|
||||
ConfigStateValue(false)
|
||||
),
|
||||
LOCATION_SPOOF(
|
||||
"location_spoof",
|
||||
ConfigCategory.UI_TWEAKS,
|
||||
ConfigStateValue(false)
|
||||
),
|
||||
LATITUDE(
|
||||
"latitude_value",
|
||||
ConfigCategory.UI_TWEAKS,
|
||||
ConfigStringValue("0.0000"),
|
||||
shouldAppearInSettings = false
|
||||
),
|
||||
LONGITUDE(
|
||||
"longitude_value",
|
||||
ConfigCategory.UI_TWEAKS,
|
||||
ConfigStringValue("0.0000"),
|
||||
shouldAppearInSettings = false
|
||||
),
|
||||
MESSAGE_PREVIEW_LENGTH(
|
||||
"message_preview_length",
|
||||
ConfigCategory.UI_TWEAKS,
|
||||
ConfigIntegerValue(20)
|
||||
),
|
||||
UNLIMITED_CONVERSATION_PINNING(
|
||||
"unlimited_conversation_pinning",
|
||||
ConfigCategory.UI_TWEAKS,
|
||||
ConfigStateValue(false)
|
||||
),
|
||||
DISABLE_SPOTLIGHT(
|
||||
"disable_spotlight",
|
||||
ConfigCategory.UI_TWEAKS,
|
||||
ConfigStateValue(false)
|
||||
),
|
||||
ENABLE_APP_APPEARANCE(
|
||||
"enable_app_appearance",
|
||||
ConfigCategory.UI_TWEAKS,
|
||||
ConfigStateValue(false)
|
||||
),
|
||||
STARTUP_PAGE_OVERRIDE(
|
||||
"startup_page_override",
|
||||
ConfigCategory.UI_TWEAKS,
|
||||
ConfigStateSelection(
|
||||
listOf(
|
||||
"OFF",
|
||||
"ngs_map_icon_container",
|
||||
"ngs_chat_icon_container",
|
||||
"ngs_camera_icon_container",
|
||||
"ngs_community_icon_container",
|
||||
"ngs_spotlight_icon_container",
|
||||
"ngs_search_icon_container"
|
||||
),
|
||||
"OFF"
|
||||
)
|
||||
),
|
||||
DISABLE_GOOGLE_PLAY_DIALOGS(
|
||||
"disable_google_play_dialogs",
|
||||
ConfigCategory.UI_TWEAKS,
|
||||
ConfigStateValue(false)
|
||||
),
|
||||
|
||||
//CAMERA
|
||||
CAMERA_DISABLE(
|
||||
"disable_camera",
|
||||
ConfigCategory.CAMERA,
|
||||
ConfigStateValue(false)
|
||||
),
|
||||
IMMERSIVE_CAMERA_PREVIEW(
|
||||
"immersive_camera_preview",
|
||||
ConfigCategory.CAMERA,
|
||||
ConfigStateValue(false)
|
||||
),
|
||||
OVERRIDE_PREVIEW_RESOLUTION(
|
||||
"preview_resolution",
|
||||
ConfigCategory.CAMERA,
|
||||
ConfigStateSelection(
|
||||
CameraTweaks.resolutions,
|
||||
"OFF"
|
||||
),
|
||||
disableValueLocalization = true
|
||||
),
|
||||
OVERRIDE_PICTURE_RESOLUTION(
|
||||
"picture_resolution",
|
||||
ConfigCategory.CAMERA,
|
||||
ConfigStateSelection(
|
||||
CameraTweaks.resolutions,
|
||||
"OFF"
|
||||
),
|
||||
disableValueLocalization = true
|
||||
),
|
||||
FORCE_HIGHEST_FRAME_RATE(
|
||||
"force_highest_frame_rate",
|
||||
ConfigCategory.CAMERA,
|
||||
ConfigStateValue(false)
|
||||
),
|
||||
FORCE_CAMERA_SOURCE_ENCODING(
|
||||
"force_camera_source_encoding",
|
||||
ConfigCategory.CAMERA,
|
||||
ConfigStateValue(false)
|
||||
),
|
||||
|
||||
// UPDATES
|
||||
AUTO_UPDATER(
|
||||
"auto_updater",
|
||||
ConfigCategory.UPDATES,
|
||||
ConfigStateSelection(
|
||||
listOf("DISABLED", "EVERY_LAUNCH", "DAILY", "WEEKLY"),
|
||||
"DAILY"
|
||||
)
|
||||
),
|
||||
|
||||
// EXPERIMENTAL DEBUGGING
|
||||
APP_PASSCODE(
|
||||
"app_passcode",
|
||||
ConfigCategory.EXPERIMENTAL_DEBUGGING,
|
||||
ConfigStringValue("", isHidden = true)
|
||||
),
|
||||
APP_LOCK_ON_RESUME(
|
||||
"app_lock_on_resume",
|
||||
ConfigCategory.EXPERIMENTAL_DEBUGGING,
|
||||
ConfigStateValue(false)
|
||||
),
|
||||
INFINITE_STORY_BOOST(
|
||||
"infinite_story_boost",
|
||||
ConfigCategory.EXPERIMENTAL_DEBUGGING,
|
||||
ConfigStateValue(false)
|
||||
),
|
||||
MEO_PASSCODE_BYPASS(
|
||||
"meo_passcode_bypass",
|
||||
ConfigCategory.EXPERIMENTAL_DEBUGGING,
|
||||
ConfigStateValue(false)
|
||||
),
|
||||
AMOLED_DARK_MODE(
|
||||
"amoled_dark_mode",
|
||||
ConfigCategory.EXPERIMENTAL_DEBUGGING,
|
||||
ConfigStateValue(false)
|
||||
),
|
||||
UNLIMITED_MULTI_SNAP(
|
||||
"unlimited_multi_snap",
|
||||
ConfigCategory.EXPERIMENTAL_DEBUGGING,
|
||||
ConfigStateValue(false)
|
||||
),
|
||||
|
||||
//DEVICE SPOOFER
|
||||
DEVICE_SPOOF(
|
||||
"device_spoof",
|
||||
ConfigCategory.DEVICE_SPOOFER,
|
||||
ConfigStateValue(false)
|
||||
),
|
||||
FINGERPRINT(
|
||||
"device_fingerprint",
|
||||
ConfigCategory.DEVICE_SPOOFER,
|
||||
ConfigStringValue("")
|
||||
),
|
||||
ANDROID_ID(
|
||||
"android_id",
|
||||
ConfigCategory.DEVICE_SPOOFER,
|
||||
ConfigStringValue("")
|
||||
);
|
||||
|
||||
fun getOptionTranslationKey(key: String) = "option.property.${valueContainerTranslationKey ?: translationKey}.$key"
|
||||
|
||||
companion object {
|
||||
fun sortedByCategory(): List<ConfigProperty> {
|
||||
return values().sortedBy { it.category.ordinal }
|
||||
}
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
package me.rhunk.snapenhance.config
|
||||
|
||||
abstract class ConfigValue<T> {
|
||||
private val propertyChangeListeners = mutableListOf<(T) -> Unit>()
|
||||
|
||||
fun addPropertyChangeListener(listener: (T) -> Unit) = propertyChangeListeners.add(listener)
|
||||
fun removePropertyChangeListener(listener: (T) -> Unit) = propertyChangeListeners.remove(listener)
|
||||
|
||||
abstract fun value(): T
|
||||
abstract fun read(): String
|
||||
protected abstract fun write(value: String)
|
||||
|
||||
protected fun onValueChanged() {
|
||||
propertyChangeListeners.forEach { it(value()) }
|
||||
}
|
||||
|
||||
fun writeFrom(value: String) {
|
||||
val oldValue = read()
|
||||
write(value)
|
||||
if (oldValue != value) onValueChanged()
|
||||
}
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
package me.rhunk.snapenhance.config.impl
|
||||
|
||||
import me.rhunk.snapenhance.config.ConfigValue
|
||||
|
||||
class ConfigIntegerValue(
|
||||
private var value: Int
|
||||
) : ConfigValue<Int>() {
|
||||
override fun value() = value
|
||||
|
||||
override fun read(): String {
|
||||
return value.toString()
|
||||
}
|
||||
|
||||
override fun write(value: String) {
|
||||
this.value = value.toInt()
|
||||
}
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
package me.rhunk.snapenhance.config.impl
|
||||
|
||||
import me.rhunk.snapenhance.config.ConfigValue
|
||||
|
||||
class ConfigStateListValue(
|
||||
private val keys: List<String>,
|
||||
private var states: MutableMap<String, Boolean> = mutableMapOf()
|
||||
) : ConfigValue<Map<String, Boolean>>() {
|
||||
override fun value() = states
|
||||
|
||||
fun setKey(key: String, state: Boolean) {
|
||||
states[key] = state
|
||||
onValueChanged()
|
||||
}
|
||||
|
||||
operator fun get(key: String) = states[key] ?: false
|
||||
|
||||
override fun read(): String {
|
||||
return keys.joinToString("|") { "$it:${states[it]}" }
|
||||
}
|
||||
|
||||
override fun write(value: String) {
|
||||
value.split("|").forEach {
|
||||
val (key, state) = it.split(":")
|
||||
states[key] = state.toBoolean()
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return states.filter { it.value }.keys.joinToString(", ") { it }
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
package me.rhunk.snapenhance.config.impl
|
||||
|
||||
import me.rhunk.snapenhance.config.ConfigValue
|
||||
|
||||
class ConfigStateSelection(
|
||||
private val keys: List<String>,
|
||||
private var state: String = ""
|
||||
) : ConfigValue<String>() {
|
||||
fun keys(): List<String> {
|
||||
return keys
|
||||
}
|
||||
|
||||
override fun value() = state
|
||||
|
||||
override fun read(): String {
|
||||
return state
|
||||
}
|
||||
|
||||
override fun write(value: String) {
|
||||
state = value
|
||||
}
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
package me.rhunk.snapenhance.config.impl
|
||||
|
||||
import me.rhunk.snapenhance.config.ConfigValue
|
||||
|
||||
class ConfigStateValue(
|
||||
private var value: Boolean
|
||||
) : ConfigValue<Boolean>() {
|
||||
override fun value() = value
|
||||
|
||||
override fun read(): String {
|
||||
return value.toString()
|
||||
}
|
||||
|
||||
override fun write(value: String) {
|
||||
this.value = value.toBoolean()
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
package me.rhunk.snapenhance.config.impl
|
||||
|
||||
import me.rhunk.snapenhance.config.ConfigValue
|
||||
|
||||
class ConfigStringValue(
|
||||
private var value: String = "",
|
||||
val isFolderPath: Boolean = false,
|
||||
val isHidden: Boolean = false
|
||||
) : ConfigValue<String>() {
|
||||
override fun value() = value
|
||||
|
||||
fun hiddenValue() = if (isHidden) value.map { '*' }.joinToString("") else value
|
||||
|
||||
override fun read(): String {
|
||||
return value
|
||||
}
|
||||
|
||||
override fun write(value: String) {
|
||||
this.value = value
|
||||
}
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
package me.rhunk.snapenhance.core.config
|
||||
|
||||
import com.google.gson.JsonObject
|
||||
import me.rhunk.snapenhance.Logger
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
typealias ConfigParamsBuilder = ConfigParams.() -> Unit
|
||||
@ -57,7 +56,7 @@ open class ConfigContainer(
|
||||
fun toJson(): JsonObject {
|
||||
val json = JsonObject()
|
||||
properties.forEach { (propertyKey, propertyValue) ->
|
||||
val serializedValue = propertyValue.getNullable()?.let { propertyKey.dataProcessor.serializeAny(it) }
|
||||
val serializedValue = propertyValue.getNullable()?.let { propertyKey.dataType.serializeAny(it) }
|
||||
json.add(propertyKey.name, serializedValue)
|
||||
}
|
||||
return json
|
||||
@ -66,7 +65,7 @@ open class ConfigContainer(
|
||||
fun fromJson(json: JsonObject) {
|
||||
properties.forEach { (key, _) ->
|
||||
val jsonElement = json.get(key.name) ?: return@forEach
|
||||
key.dataProcessor.deserializeAny(jsonElement)?.let {
|
||||
key.dataType.deserializeAny(jsonElement)?.let {
|
||||
properties[key]?.setAny(it)
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,20 @@
|
||||
package me.rhunk.snapenhance.core.config
|
||||
|
||||
import me.rhunk.snapenhance.Logger
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
|
||||
data class PropertyPair<T>(
|
||||
val key: PropertyKey<T>,
|
||||
val value: PropertyValue<*>
|
||||
) {
|
||||
val name get() = key.name
|
||||
}
|
||||
|
||||
class ConfigParams(
|
||||
var shouldTranslate: Boolean = false,
|
||||
var hidden: Boolean = false,
|
||||
var isFolder: Boolean = false
|
||||
var isFolder: Boolean = false,
|
||||
val disabledKey: String? = null
|
||||
)
|
||||
|
||||
class PropertyValue<T>(
|
||||
@ -34,7 +42,7 @@ class PropertyValue<T>(
|
||||
|
||||
class PropertyKey<T>(
|
||||
val name: String,
|
||||
val dataProcessor: DataProcessors.PropertyDataProcessor<T>,
|
||||
val dataType: DataProcessors.PropertyDataProcessor<T>,
|
||||
val params: ConfigParams = ConfigParams(),
|
||||
)
|
||||
|
||||
|
@ -65,7 +65,7 @@ object DataProcessors {
|
||||
type = Type.STRING_MULTIPLE_SELECTION,
|
||||
serialize = { JsonArray().apply { it.forEach { add(it) } } },
|
||||
deserialize = { obj ->
|
||||
obj.asJsonArray.map { it.asString }
|
||||
obj.asJsonArray.map { it.asString }.toMutableList()
|
||||
},
|
||||
)
|
||||
|
||||
|
@ -5,7 +5,7 @@ import me.rhunk.snapenhance.data.NotificationType
|
||||
|
||||
class Global : ConfigContainer() {
|
||||
val snapchatPlus = boolean("snapchat_plus")
|
||||
val autoUpdater = unique("auto_updater", "DAILY","EVERY_LAUNCH", "DAILY", "WEEKLY")
|
||||
val autoUpdater = unique("auto_updater", "EVERY_LAUNCH", "DAILY", "WEEKLY")
|
||||
val disableMetrics = boolean("disable_metrics")
|
||||
val disableVideoLengthRestrictions = boolean("disable_video_length_restrictions")
|
||||
val disableGooglePlayDialogs = boolean("disable_google_play_dialogs")
|
||||
|
@ -1,8 +1,6 @@
|
||||
package me.rhunk.snapenhance.core.config.impl
|
||||
|
||||
import me.rhunk.snapenhance.core.config.ConfigContainer
|
||||
import me.rhunk.snapenhance.data.NotificationType
|
||||
import me.rhunk.snapenhance.features.impl.tweaks.CameraTweaks
|
||||
|
||||
class RootConfig : ConfigContainer() {
|
||||
val downloader = container("downloader", DownloaderConfig())
|
||||
|
@ -18,8 +18,6 @@ import me.rhunk.snapenhance.Constants
|
||||
import me.rhunk.snapenhance.Logger
|
||||
import me.rhunk.snapenhance.SharedContext
|
||||
import me.rhunk.snapenhance.bridge.DownloadCallback
|
||||
import me.rhunk.snapenhance.bridge.wrapper.ConfigWrapper
|
||||
import me.rhunk.snapenhance.config.ConfigProperty
|
||||
import me.rhunk.snapenhance.core.config.ModConfig
|
||||
import me.rhunk.snapenhance.data.FileType
|
||||
import me.rhunk.snapenhance.download.data.DownloadMetadata
|
||||
|
@ -1,7 +1,6 @@
|
||||
package me.rhunk.snapenhance.features.impl
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import me.rhunk.snapenhance.config.ConfigProperty
|
||||
import me.rhunk.snapenhance.features.Feature
|
||||
import me.rhunk.snapenhance.features.FeatureLoadParams
|
||||
import me.rhunk.snapenhance.hook.HookStage
|
||||
@ -95,10 +94,6 @@ class ConfigEnumKeys : Feature("Config enum keys", loadParams = FeatureLoadParam
|
||||
}
|
||||
}
|
||||
|
||||
ConfigProperty.ENABLE_APP_APPEARANCE.valueContainer.addPropertyChangeListener {
|
||||
context.softRestartApp(true)
|
||||
}
|
||||
|
||||
val sharedPreferencesImpl = context.androidContext.classLoader.loadClass("android.app.SharedPreferencesImpl")
|
||||
|
||||
sharedPreferencesImpl.methods.first { it.name == "getBoolean" }.hook(HookStage.BEFORE) { param ->
|
||||
|
@ -1,14 +1,13 @@
|
||||
package me.rhunk.snapenhance.features.impl
|
||||
|
||||
import me.rhunk.snapenhance.config.ConfigProperty
|
||||
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.impl.spying.StealthMode
|
||||
import me.rhunk.snapenhance.hook.HookStage
|
||||
import me.rhunk.snapenhance.hook.Hooker
|
||||
import me.rhunk.snapenhance.hook.hook
|
||||
import me.rhunk.snapenhance.util.getObjectField
|
||||
import me.rhunk.snapenhance.features.impl.spying.StealthMode;
|
||||
|
||||
class Messaging : Feature("Messaging", loadParams = FeatureLoadParams.ACTIVITY_CREATE_SYNC or FeatureLoadParams.INIT_ASYNC or FeatureLoadParams.INIT_SYNC) {
|
||||
lateinit var conversationManager: Any
|
||||
|
@ -11,7 +11,6 @@ import me.rhunk.snapenhance.Constants.ARROYO_URL_KEY_PROTO_PATH
|
||||
import me.rhunk.snapenhance.Logger
|
||||
import me.rhunk.snapenhance.Logger.xposedLog
|
||||
import me.rhunk.snapenhance.bridge.DownloadCallback
|
||||
import me.rhunk.snapenhance.config.ConfigProperty
|
||||
import me.rhunk.snapenhance.data.ContentType
|
||||
import me.rhunk.snapenhance.data.FileType
|
||||
import me.rhunk.snapenhance.data.wrapper.impl.media.MediaInfo
|
||||
|
@ -4,7 +4,6 @@ import android.annotation.SuppressLint
|
||||
import android.content.res.TypedArray
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import me.rhunk.snapenhance.Constants
|
||||
import me.rhunk.snapenhance.config.ConfigProperty
|
||||
import me.rhunk.snapenhance.features.Feature
|
||||
import me.rhunk.snapenhance.features.FeatureLoadParams
|
||||
import me.rhunk.snapenhance.hook.HookStage
|
||||
|
@ -1,7 +1,6 @@
|
||||
package me.rhunk.snapenhance.features.impl.experiments
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.AlertDialog
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.text.Editable
|
||||
@ -9,7 +8,6 @@ import android.text.InputType
|
||||
import android.text.TextWatcher
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.EditText
|
||||
import me.rhunk.snapenhance.config.ConfigProperty
|
||||
import me.rhunk.snapenhance.features.Feature
|
||||
import me.rhunk.snapenhance.features.FeatureLoadParams
|
||||
import me.rhunk.snapenhance.ui.ViewAppearanceHelper
|
||||
|
@ -1,7 +1,6 @@
|
||||
package me.rhunk.snapenhance.features.impl.experiments
|
||||
|
||||
import me.rhunk.snapenhance.Logger
|
||||
import me.rhunk.snapenhance.config.ConfigProperty
|
||||
import me.rhunk.snapenhance.features.Feature
|
||||
import me.rhunk.snapenhance.features.FeatureLoadParams
|
||||
import me.rhunk.snapenhance.hook.HookStage
|
||||
|
@ -1,6 +1,5 @@
|
||||
package me.rhunk.snapenhance.features.impl.experiments
|
||||
|
||||
import me.rhunk.snapenhance.config.ConfigProperty
|
||||
import me.rhunk.snapenhance.features.Feature
|
||||
import me.rhunk.snapenhance.features.FeatureLoadParams
|
||||
import me.rhunk.snapenhance.hook.HookStage
|
||||
|
@ -1,6 +1,5 @@
|
||||
package me.rhunk.snapenhance.features.impl.experiments
|
||||
|
||||
import me.rhunk.snapenhance.config.ConfigProperty
|
||||
import me.rhunk.snapenhance.features.Feature
|
||||
import me.rhunk.snapenhance.features.FeatureLoadParams
|
||||
import me.rhunk.snapenhance.hook.HookStage
|
||||
|
@ -1,6 +1,5 @@
|
||||
package me.rhunk.snapenhance.features.impl.experiments
|
||||
|
||||
import me.rhunk.snapenhance.config.ConfigProperty
|
||||
import me.rhunk.snapenhance.features.Feature
|
||||
import me.rhunk.snapenhance.features.FeatureLoadParams
|
||||
import me.rhunk.snapenhance.hook.HookStage
|
||||
|
@ -1,10 +1,8 @@
|
||||
package me.rhunk.snapenhance.features.impl.privacy
|
||||
|
||||
import de.robv.android.xposed.XposedHelpers
|
||||
import me.rhunk.snapenhance.config.ConfigProperty
|
||||
import me.rhunk.snapenhance.features.Feature
|
||||
import me.rhunk.snapenhance.features.FeatureLoadParams
|
||||
import me.rhunk.snapenhance.hook.HookAdapter
|
||||
import me.rhunk.snapenhance.hook.HookStage
|
||||
import me.rhunk.snapenhance.hook.Hooker
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
package me.rhunk.snapenhance.features.impl.privacy
|
||||
|
||||
import me.rhunk.snapenhance.Logger
|
||||
import me.rhunk.snapenhance.config.ConfigProperty
|
||||
import me.rhunk.snapenhance.data.NotificationType
|
||||
import me.rhunk.snapenhance.data.wrapper.impl.MessageContent
|
||||
import me.rhunk.snapenhance.features.Feature
|
||||
|
@ -1,6 +1,5 @@
|
||||
package me.rhunk.snapenhance.features.impl.spying
|
||||
|
||||
import me.rhunk.snapenhance.config.ConfigProperty
|
||||
import me.rhunk.snapenhance.features.Feature
|
||||
import me.rhunk.snapenhance.features.FeatureLoadParams
|
||||
import me.rhunk.snapenhance.hook.HookStage
|
||||
|
@ -4,7 +4,6 @@ import android.os.DeadObjectException
|
||||
import com.google.gson.JsonObject
|
||||
import com.google.gson.JsonParser
|
||||
import me.rhunk.snapenhance.Logger
|
||||
import me.rhunk.snapenhance.config.ConfigProperty
|
||||
import me.rhunk.snapenhance.data.ContentType
|
||||
import me.rhunk.snapenhance.data.MessageState
|
||||
import me.rhunk.snapenhance.data.wrapper.impl.Message
|
||||
|
@ -1,6 +1,5 @@
|
||||
package me.rhunk.snapenhance.features.impl.spying
|
||||
|
||||
import me.rhunk.snapenhance.config.ConfigProperty
|
||||
import me.rhunk.snapenhance.data.wrapper.impl.SnapUUID
|
||||
import me.rhunk.snapenhance.features.Feature
|
||||
import me.rhunk.snapenhance.features.FeatureLoadParams
|
||||
|
@ -1,7 +1,6 @@
|
||||
package me.rhunk.snapenhance.features.impl.tweaks
|
||||
|
||||
import me.rhunk.snapenhance.Logger
|
||||
import me.rhunk.snapenhance.config.ConfigProperty
|
||||
import me.rhunk.snapenhance.data.MessageState
|
||||
import me.rhunk.snapenhance.data.wrapper.impl.Message
|
||||
import me.rhunk.snapenhance.data.wrapper.impl.SnapUUID
|
||||
|
@ -5,7 +5,6 @@ import android.annotation.SuppressLint
|
||||
import android.content.ContextWrapper
|
||||
import android.content.pm.PackageManager
|
||||
import android.hardware.camera2.CameraManager
|
||||
import me.rhunk.snapenhance.config.ConfigProperty
|
||||
import me.rhunk.snapenhance.data.wrapper.impl.ScSize
|
||||
import me.rhunk.snapenhance.features.Feature
|
||||
import me.rhunk.snapenhance.features.FeatureLoadParams
|
||||
|
@ -1,6 +1,5 @@
|
||||
package me.rhunk.snapenhance.features.impl.tweaks
|
||||
|
||||
import me.rhunk.snapenhance.config.ConfigProperty
|
||||
import me.rhunk.snapenhance.features.Feature
|
||||
import me.rhunk.snapenhance.features.FeatureLoadParams
|
||||
import me.rhunk.snapenhance.hook.HookStage
|
||||
|
@ -1,7 +1,5 @@
|
||||
package me.rhunk.snapenhance.features.impl.tweaks
|
||||
|
||||
import android.app.AlertDialog
|
||||
import me.rhunk.snapenhance.config.ConfigProperty
|
||||
import me.rhunk.snapenhance.data.ContentType
|
||||
import me.rhunk.snapenhance.data.MessageSender
|
||||
import me.rhunk.snapenhance.data.wrapper.impl.MessageContent
|
||||
|
@ -2,7 +2,6 @@ package me.rhunk.snapenhance.features.impl.tweaks
|
||||
|
||||
import android.app.AlertDialog
|
||||
import me.rhunk.snapenhance.Logger
|
||||
import me.rhunk.snapenhance.config.ConfigProperty
|
||||
import me.rhunk.snapenhance.features.Feature
|
||||
import me.rhunk.snapenhance.features.FeatureLoadParams
|
||||
import me.rhunk.snapenhance.hook.HookStage
|
||||
|
@ -1,7 +1,6 @@
|
||||
package me.rhunk.snapenhance.features.impl.tweaks
|
||||
|
||||
import android.content.Intent
|
||||
import me.rhunk.snapenhance.config.ConfigProperty
|
||||
import me.rhunk.snapenhance.features.Feature
|
||||
import me.rhunk.snapenhance.features.FeatureLoadParams
|
||||
import me.rhunk.snapenhance.hook.HookStage
|
||||
|
@ -1,6 +1,5 @@
|
||||
package me.rhunk.snapenhance.features.impl.tweaks
|
||||
|
||||
import me.rhunk.snapenhance.config.ConfigProperty
|
||||
import me.rhunk.snapenhance.features.Feature
|
||||
import me.rhunk.snapenhance.features.FeatureLoadParams
|
||||
import me.rhunk.snapenhance.hook.HookStage
|
||||
|
@ -14,7 +14,6 @@ import de.robv.android.xposed.XposedBridge
|
||||
import de.robv.android.xposed.XposedHelpers
|
||||
import me.rhunk.snapenhance.Constants
|
||||
import me.rhunk.snapenhance.Logger
|
||||
import me.rhunk.snapenhance.config.ConfigProperty
|
||||
import me.rhunk.snapenhance.data.ContentType
|
||||
import me.rhunk.snapenhance.data.MediaReferenceType
|
||||
import me.rhunk.snapenhance.data.wrapper.impl.Message
|
||||
|
@ -1,6 +1,5 @@
|
||||
package me.rhunk.snapenhance.features.impl.tweaks
|
||||
|
||||
import me.rhunk.snapenhance.config.ConfigProperty
|
||||
import me.rhunk.snapenhance.features.Feature
|
||||
import me.rhunk.snapenhance.features.FeatureLoadParams
|
||||
import me.rhunk.snapenhance.hook.HookStage
|
||||
|
@ -1,6 +1,5 @@
|
||||
package me.rhunk.snapenhance.features.impl.tweaks
|
||||
|
||||
import me.rhunk.snapenhance.config.ConfigProperty
|
||||
import me.rhunk.snapenhance.data.ContentType
|
||||
import me.rhunk.snapenhance.data.MessageState
|
||||
import me.rhunk.snapenhance.data.wrapper.impl.Message
|
||||
|
@ -5,7 +5,6 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.LinearLayout
|
||||
import me.rhunk.snapenhance.Constants
|
||||
import me.rhunk.snapenhance.config.ConfigProperty
|
||||
import me.rhunk.snapenhance.features.Feature
|
||||
import me.rhunk.snapenhance.features.FeatureLoadParams
|
||||
import me.rhunk.snapenhance.hook.HookStage
|
||||
|
@ -8,7 +8,6 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import me.rhunk.snapenhance.Constants
|
||||
import me.rhunk.snapenhance.config.ConfigProperty
|
||||
import me.rhunk.snapenhance.features.Feature
|
||||
import me.rhunk.snapenhance.features.FeatureLoadParams
|
||||
import me.rhunk.snapenhance.hook.HookAdapter
|
||||
|
@ -1,6 +1,5 @@
|
||||
package me.rhunk.snapenhance.manager.impl
|
||||
|
||||
import me.rhunk.snapenhance.core.BuildConfig
|
||||
import me.rhunk.snapenhance.ModContext
|
||||
import me.rhunk.snapenhance.action.AbstractAction
|
||||
import me.rhunk.snapenhance.action.impl.CheckForUpdates
|
||||
@ -9,6 +8,7 @@ import me.rhunk.snapenhance.action.impl.ClearMessageLogger
|
||||
import me.rhunk.snapenhance.action.impl.ExportChatMessages
|
||||
import me.rhunk.snapenhance.action.impl.OpenMap
|
||||
import me.rhunk.snapenhance.action.impl.RefreshMappings
|
||||
import me.rhunk.snapenhance.core.BuildConfig
|
||||
import me.rhunk.snapenhance.manager.Manager
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
|
@ -1,58 +0,0 @@
|
||||
package me.rhunk.snapenhance.manager.impl
|
||||
|
||||
import com.google.gson.JsonObject
|
||||
import me.rhunk.snapenhance.Logger
|
||||
import me.rhunk.snapenhance.ModContext
|
||||
import me.rhunk.snapenhance.bridge.types.BridgeFileType
|
||||
import me.rhunk.snapenhance.config.ConfigAccessor
|
||||
import me.rhunk.snapenhance.config.ConfigProperty
|
||||
import me.rhunk.snapenhance.manager.Manager
|
||||
import java.nio.charset.StandardCharsets
|
||||
|
||||
class ConfigManager(
|
||||
private val context: ModContext
|
||||
) : ConfigAccessor(), Manager {
|
||||
|
||||
override fun init() {
|
||||
ConfigProperty.sortedByCategory().forEach { key ->
|
||||
set(key, key.valueContainer)
|
||||
}
|
||||
|
||||
if (!context.bridgeClient.isFileExists(BridgeFileType.CONFIG)) {
|
||||
writeConfig()
|
||||
return
|
||||
}
|
||||
|
||||
runCatching {
|
||||
loadConfig()
|
||||
}.onFailure {
|
||||
Logger.xposedLog("Failed to load config", it)
|
||||
writeConfig()
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadConfig() {
|
||||
val configContent = context.bridgeClient.createAndReadFile(
|
||||
BridgeFileType.CONFIG,
|
||||
"{}".toByteArray(Charsets.UTF_8)
|
||||
)
|
||||
val configObject: JsonObject = context.gson.fromJson(
|
||||
String(configContent, StandardCharsets.UTF_8),
|
||||
JsonObject::class.java
|
||||
)
|
||||
entries().forEach { (key, value) ->
|
||||
value.writeFrom(configObject.get(key.name)?.asString ?: value.read())
|
||||
}
|
||||
}
|
||||
|
||||
fun writeConfig() {
|
||||
val configObject = JsonObject()
|
||||
entries().forEach { (key, value) ->
|
||||
configObject.addProperty(key.name, value.read())
|
||||
}
|
||||
context.bridgeClient.writeFile(
|
||||
BridgeFileType.CONFIG,
|
||||
context.gson.toJson(configObject).toByteArray(Charsets.UTF_8)
|
||||
)
|
||||
}
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
package me.rhunk.snapenhance.manager.impl
|
||||
|
||||
import android.app.AlertDialog
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonParser
|
||||
import me.rhunk.snapenhance.Constants
|
||||
|
@ -1,87 +0,0 @@
|
||||
package me.rhunk.snapenhance.ui
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.AlertDialog
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.widget.EditText
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import me.rhunk.snapenhance.core.R
|
||||
import me.rhunk.snapenhance.SharedContext
|
||||
import me.rhunk.snapenhance.config.ConfigProperty
|
||||
import me.rhunk.snapenhance.util.ActivityResultCallback
|
||||
import kotlin.math.abs
|
||||
import kotlin.random.Random
|
||||
|
||||
class ItemHelper(
|
||||
private val context : Context
|
||||
) {
|
||||
val positiveButtonText by lazy {
|
||||
SharedContext.translation["button.ok"]
|
||||
}
|
||||
|
||||
val cancelButtonText by lazy {
|
||||
SharedContext.translation["button.cancel"]
|
||||
}
|
||||
|
||||
fun longToast(message: String, context: Context) {
|
||||
Toast.makeText(context, message, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
|
||||
fun createTranslatedTextView(property: ConfigProperty, shouldTranslatePropertyValue: Boolean = true): TextView {
|
||||
return object: TextView(context) {
|
||||
override fun setText(text: CharSequence?, type: BufferType?) {
|
||||
val newText = text?.takeIf { it.isNotEmpty() }?.let {
|
||||
if (!shouldTranslatePropertyValue || property.disableValueLocalization) it
|
||||
else SharedContext.translation[property.getOptionTranslationKey(it.toString())]
|
||||
}?.let {
|
||||
if (it.length > 20) {
|
||||
"...${it.substring(it.length - 20)}"
|
||||
} else {
|
||||
it
|
||||
}
|
||||
} ?: ""
|
||||
super.setTextColor(context.getColor(R.color.tertiaryText))
|
||||
super.setText(newText, type)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun askForValue(property: ConfigProperty, requestedInputType: Int, callback: (String) -> Unit) {
|
||||
val editText = EditText(context).apply {
|
||||
inputType = requestedInputType
|
||||
setText(property.valueContainer.value().toString())
|
||||
}
|
||||
AlertDialog.Builder(context)
|
||||
.setTitle(SharedContext.translation["property.${property.translationKey}.name"])
|
||||
.setView(editText)
|
||||
.setPositiveButton(positiveButtonText) { _, _ ->
|
||||
callback(editText.text.toString())
|
||||
}
|
||||
.setNegativeButton(cancelButtonText) { dialog, _ ->
|
||||
dialog.cancel()
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
fun askForFolder(activity: Activity, property: ConfigProperty, callback: (String) -> Unit): Pair<Int, ActivityResultCallback> {
|
||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
|
||||
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||
|
||||
val requestCode = abs(Random.nextInt())
|
||||
activity.startActivityForResult(intent, requestCode)
|
||||
|
||||
return requestCode to let@{_, resultCode, data ->
|
||||
if (resultCode != Activity.RESULT_OK) return@let
|
||||
val uri = data?.data ?: return@let
|
||||
val value = uri.toString()
|
||||
activity.contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||
property.valueContainer.writeFrom(value)
|
||||
callback(value)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -1,279 +0,0 @@
|
||||
package me.rhunk.snapenhance.ui.config
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.AlertDialog
|
||||
import android.content.Intent
|
||||
import android.content.res.ColorStateList
|
||||
import android.os.Bundle
|
||||
import android.text.Html
|
||||
import android.text.InputType
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageButton
|
||||
import android.widget.Switch
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import me.rhunk.snapenhance.core.BuildConfig
|
||||
import me.rhunk.snapenhance.core.R
|
||||
import me.rhunk.snapenhance.SharedContext
|
||||
import me.rhunk.snapenhance.config.ConfigCategory
|
||||
import me.rhunk.snapenhance.config.ConfigProperty
|
||||
import me.rhunk.snapenhance.config.impl.ConfigIntegerValue
|
||||
import me.rhunk.snapenhance.config.impl.ConfigStateListValue
|
||||
import me.rhunk.snapenhance.config.impl.ConfigStateSelection
|
||||
import me.rhunk.snapenhance.config.impl.ConfigStateValue
|
||||
import me.rhunk.snapenhance.config.impl.ConfigStringValue
|
||||
import me.rhunk.snapenhance.ui.ItemHelper
|
||||
import me.rhunk.snapenhance.util.ActivityResultCallback
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
|
||||
class ConfigActivity : Activity() {
|
||||
private val itemHelper = ItemHelper(this)
|
||||
private val activityResultCallbacks = mutableMapOf<Int, ActivityResultCallback>()
|
||||
|
||||
@Deprecated("Deprecated in Java")
|
||||
@Suppress("DEPRECATION")
|
||||
override fun onBackPressed() {
|
||||
super.onBackPressed()
|
||||
finish()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
SharedContext.config.writeConfig()
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
SharedContext.config.writeConfig()
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
activityResultCallbacks[requestCode]?.invoke(requestCode, resultCode, data)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
SharedContext.ensureInitialized(this)
|
||||
setContentView(R.layout.config_activity)
|
||||
|
||||
findViewById<View>(R.id.title_bar).let { titleBar ->
|
||||
titleBar.findViewById<TextView>(R.id.title).text = SharedContext.translation["config_activity.title"]
|
||||
titleBar.findViewById<ImageButton>(R.id.back_button).visibility = View.GONE
|
||||
}
|
||||
|
||||
val propertyListLayout = findViewById<ViewGroup>(R.id.property_list)
|
||||
|
||||
if (intent.getBooleanExtra("lspatched", false) ||
|
||||
applicationInfo.packageName != "me.rhunk.snapenhance" ||
|
||||
BuildConfig.DEBUG) {
|
||||
propertyListLayout.addView(
|
||||
layoutInflater.inflate(
|
||||
R.layout.config_activity_debug_item,
|
||||
propertyListLayout,
|
||||
false
|
||||
).apply {
|
||||
findViewById<TextView>(R.id.debug_item_content).apply {
|
||||
text = Html.fromHtml(
|
||||
"You are using a <u><b>debug/unofficial</b></u> build!\n" +
|
||||
"Please consider downloading stable builds from <a href=\"https://github.com/rhunk/SnapEnhance\">GitHub</a>.",
|
||||
Html.FROM_HTML_MODE_COMPACT
|
||||
)
|
||||
movementMethod = android.text.method.LinkMovementMethod.getInstance()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//check if save folder is set
|
||||
//TODO: first run activity
|
||||
run {
|
||||
val saveFolder = SharedContext.config.string(ConfigProperty.SAVE_FOLDER)
|
||||
val itemHelper = ItemHelper(this)
|
||||
|
||||
if (saveFolder.isEmpty() || !saveFolder.startsWith("content://")) {
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle("Save folder")
|
||||
.setMessage("Please select a folder where you want to save downloaded files.")
|
||||
.setPositiveButton("Select") { _, _ ->
|
||||
val (requestCode, callback) = itemHelper.askForFolder(
|
||||
this,
|
||||
ConfigProperty.SAVE_FOLDER
|
||||
) {}
|
||||
activityResultCallbacks[requestCode] = { a1, a2, a3 ->
|
||||
callback(a1, a2, a3)
|
||||
Toast.makeText(this, "Save Folder set!", Toast.LENGTH_SHORT).show()
|
||||
finish()
|
||||
}
|
||||
}
|
||||
.setNegativeButton("Cancel") { _, _ ->
|
||||
exitProcess(0)
|
||||
}
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
var currentCategory: ConfigCategory? = null
|
||||
|
||||
SharedContext.config.entries().filter { !it.key.category.hidden }.forEach { (property, value) ->
|
||||
val configItem = layoutInflater.inflate(R.layout.config_activity_item, propertyListLayout, false)
|
||||
|
||||
fun addSeparator() {
|
||||
//add separator
|
||||
propertyListLayout.addView(View(this).apply {
|
||||
layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 1)
|
||||
setBackgroundColor(getColor(R.color.tertiaryBackground))
|
||||
})
|
||||
}
|
||||
|
||||
if (property.category != currentCategory) {
|
||||
if(!property.shouldAppearInSettings) return@forEach
|
||||
currentCategory = property.category
|
||||
with(layoutInflater.inflate(R.layout.config_activity_item, propertyListLayout, false)) {
|
||||
findViewById<TextView>(R.id.name).apply {
|
||||
text = SharedContext.translation["category.${property.category.key}"]
|
||||
textSize = 20f
|
||||
typeface = typeface?.let { android.graphics.Typeface.create(it, android.graphics.Typeface.BOLD) }
|
||||
}
|
||||
propertyListLayout.addView(this)
|
||||
}
|
||||
addSeparator()
|
||||
}
|
||||
|
||||
if (!property.shouldAppearInSettings) return@forEach
|
||||
|
||||
val propertyName = SharedContext.translation["property.${property.translationKey}.name"]
|
||||
|
||||
configItem.findViewById<TextView>(R.id.name).text = propertyName
|
||||
configItem.findViewById<TextView>(R.id.description).also {
|
||||
it.text = SharedContext.translation["property.${property.translationKey}.description"]
|
||||
it.visibility = if (it.text.isEmpty()) View.GONE else View.VISIBLE
|
||||
}
|
||||
|
||||
fun addValueView(view: View) {
|
||||
configItem.findViewById<ViewGroup>(R.id.value).addView(view)
|
||||
}
|
||||
|
||||
when (value) {
|
||||
is ConfigStateValue -> {
|
||||
val switch = Switch(this)
|
||||
switch.isChecked = value.value()
|
||||
switch.trackTintList = ColorStateList(
|
||||
arrayOf(
|
||||
intArrayOf(android.R.attr.state_checked),
|
||||
intArrayOf(-android.R.attr.state_checked)
|
||||
),
|
||||
intArrayOf(
|
||||
switch.highlightColor,
|
||||
getColor(R.color.tertiaryBackground)
|
||||
)
|
||||
)
|
||||
switch.setOnCheckedChangeListener { _, isChecked ->
|
||||
value.writeFrom(isChecked.toString())
|
||||
}
|
||||
configItem.setOnClickListener { switch.toggle() }
|
||||
addValueView(switch)
|
||||
}
|
||||
is ConfigStringValue, is ConfigIntegerValue -> {
|
||||
val textView = itemHelper.createTranslatedTextView(property, shouldTranslatePropertyValue = false).also {
|
||||
it.text = value.value().toString()
|
||||
}
|
||||
configItem.setOnClickListener {
|
||||
if (value is ConfigStringValue && value.isFolderPath) {
|
||||
val (requestCode, callback) = itemHelper.askForFolder(this, property) {
|
||||
value.writeFrom(it)
|
||||
textView.text = value.value()
|
||||
}
|
||||
|
||||
activityResultCallbacks[requestCode] = callback
|
||||
return@setOnClickListener
|
||||
}
|
||||
|
||||
if (value is ConfigIntegerValue) {
|
||||
itemHelper.askForValue(property, InputType.TYPE_CLASS_NUMBER) {
|
||||
try {
|
||||
value.writeFrom(it)
|
||||
textView.text = value.value().toString()
|
||||
} catch (e: NumberFormatException) {
|
||||
itemHelper.longToast(SharedContext.translation["config_activity.invalid_number_toast"], this)
|
||||
}
|
||||
}
|
||||
return@setOnClickListener
|
||||
}
|
||||
itemHelper.askForValue(property, InputType.TYPE_CLASS_TEXT) {
|
||||
value.writeFrom(it)
|
||||
textView.text = value.value().toString()
|
||||
}
|
||||
}
|
||||
addValueView(textView)
|
||||
}
|
||||
is ConfigStateListValue -> {
|
||||
val textView = itemHelper.createTranslatedTextView(property, shouldTranslatePropertyValue = false)
|
||||
val values = value.value()
|
||||
|
||||
fun updateText() {
|
||||
textView.text = SharedContext.translation.format("config_activity.selected_text", "count" to values.filter { it.value }.size.toString())
|
||||
}
|
||||
|
||||
updateText()
|
||||
|
||||
configItem.setOnClickListener {
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle(propertyName)
|
||||
.setPositiveButton(itemHelper.positiveButtonText) { _, _ ->
|
||||
updateText()
|
||||
}
|
||||
.setMultiChoiceItems(
|
||||
values.keys.map {
|
||||
if (property.disableValueLocalization) it
|
||||
else SharedContext.translation[property.getOptionTranslationKey(it)]
|
||||
}.toTypedArray(),
|
||||
values.map { it.value }.toBooleanArray()
|
||||
) { _, which, isChecked ->
|
||||
value.setKey(values.keys.elementAt(which), isChecked)
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
addValueView(textView)
|
||||
}
|
||||
is ConfigStateSelection -> {
|
||||
val textView = itemHelper.createTranslatedTextView(property, shouldTranslatePropertyValue = true)
|
||||
textView.text = value.value()
|
||||
|
||||
configItem.setOnClickListener {
|
||||
val builder = AlertDialog.Builder(this)
|
||||
builder.setTitle(propertyName)
|
||||
|
||||
builder.setSingleChoiceItems(
|
||||
value.keys().toTypedArray().map {
|
||||
if (property.disableValueLocalization) it
|
||||
else SharedContext.translation[property.getOptionTranslationKey(it)]
|
||||
}.toTypedArray(),
|
||||
value.keys().indexOf(value.value())
|
||||
) { _, which ->
|
||||
value.writeFrom(value.keys()[which])
|
||||
}
|
||||
|
||||
builder.setPositiveButton(itemHelper.positiveButtonText) { _, _ ->
|
||||
textView.text = value.value()
|
||||
}
|
||||
|
||||
builder.show()
|
||||
}
|
||||
addValueView(textView)
|
||||
}
|
||||
}
|
||||
|
||||
propertyListLayout.addView(configItem)
|
||||
addSeparator()
|
||||
}
|
||||
|
||||
propertyListLayout.addView(layoutInflater.inflate(R.layout.config_activity_debug_item, propertyListLayout, false).apply {
|
||||
findViewById<TextView>(R.id.debug_item_content).apply {
|
||||
text = Html.fromHtml("Made by rhunk on <a href=\"https://github.com/rhunk/SnapEnhance\">GitHub</a>", Html.FROM_HTML_MODE_COMPACT)
|
||||
movementMethod = android.text.method.LinkMovementMethod.getInstance()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
package me.rhunk.snapenhance.ui.download
|
||||
|
||||
import android.app.AlertDialog
|
||||
import android.content.Intent
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ArrayAdapter
|
||||
@ -9,11 +8,9 @@ import android.widget.ImageButton
|
||||
import android.widget.ListView
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import me.rhunk.snapenhance.core.R
|
||||
import me.rhunk.snapenhance.SharedContext
|
||||
import me.rhunk.snapenhance.bridge.types.BridgeFileType
|
||||
import me.rhunk.snapenhance.ui.config.ConfigActivity
|
||||
import me.rhunk.snapenhance.ui.spoof.DeviceSpooferActivity
|
||||
import me.rhunk.snapenhance.core.R
|
||||
import java.io.File
|
||||
|
||||
class ActionListAdapter(
|
||||
@ -68,12 +65,6 @@ class DebugSettingsLayoutInflater(
|
||||
|
||||
debugSettingsLayout.findViewById<ListView>(R.id.setting_page_list).apply {
|
||||
adapter = ActionListAdapter(activity, R.layout.debug_setting_item, mutableListOf<Pair<String, () -> Unit>>().apply {
|
||||
add(SharedContext.translation["config_activity.title"] to {
|
||||
activity.startActivity(Intent(activity, ConfigActivity::class.java))
|
||||
})
|
||||
add(SharedContext.translation["spoof_activity.title"] to {
|
||||
activity.startActivity(Intent(activity, DeviceSpooferActivity::class.java))
|
||||
})
|
||||
add(debugSettingsTranslation["clear_cache_title"] to {
|
||||
context.cacheDir.listFiles()?.forEach {
|
||||
it.deleteRecursively()
|
||||
|
@ -23,8 +23,8 @@ import kotlinx.coroutines.job
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withTimeout
|
||||
import me.rhunk.snapenhance.Logger
|
||||
import me.rhunk.snapenhance.core.R
|
||||
import me.rhunk.snapenhance.SharedContext
|
||||
import me.rhunk.snapenhance.core.R
|
||||
import me.rhunk.snapenhance.data.FileType
|
||||
import me.rhunk.snapenhance.download.data.PendingDownload
|
||||
import me.rhunk.snapenhance.download.enums.DownloadStage
|
||||
|
@ -15,10 +15,10 @@ import android.widget.ImageButton
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import me.rhunk.snapenhance.core.BuildConfig
|
||||
import me.rhunk.snapenhance.core.R
|
||||
import me.rhunk.snapenhance.SharedContext
|
||||
import me.rhunk.snapenhance.bridge.wrapper.TranslationWrapper
|
||||
import me.rhunk.snapenhance.core.BuildConfig
|
||||
import me.rhunk.snapenhance.core.R
|
||||
import me.rhunk.snapenhance.download.data.PendingDownload
|
||||
|
||||
class DownloadManagerActivity : Activity() {
|
||||
|
@ -10,7 +10,6 @@ import android.widget.Button
|
||||
import android.widget.LinearLayout
|
||||
import me.rhunk.snapenhance.Constants
|
||||
import me.rhunk.snapenhance.Constants.VIEW_INJECTED_CODE
|
||||
import me.rhunk.snapenhance.config.ConfigProperty
|
||||
import me.rhunk.snapenhance.features.impl.Messaging
|
||||
import me.rhunk.snapenhance.features.impl.downloader.MediaDownloader
|
||||
import me.rhunk.snapenhance.features.impl.spying.MessageLogger
|
||||
|
@ -21,7 +21,6 @@ import android.widget.LinearLayout
|
||||
import android.widget.Switch
|
||||
import android.widget.Toast
|
||||
import me.rhunk.snapenhance.Logger
|
||||
import me.rhunk.snapenhance.config.ConfigProperty
|
||||
import me.rhunk.snapenhance.data.ContentType
|
||||
import me.rhunk.snapenhance.data.wrapper.impl.FriendActionButton
|
||||
import me.rhunk.snapenhance.database.objects.ConversationMessage
|
||||
|
@ -6,9 +6,7 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.LinearLayout
|
||||
import de.robv.android.xposed.XC_MethodHook.Unhook
|
||||
import me.rhunk.snapenhance.Constants
|
||||
import me.rhunk.snapenhance.config.ConfigProperty
|
||||
import me.rhunk.snapenhance.features.Feature
|
||||
import me.rhunk.snapenhance.features.FeatureLoadParams
|
||||
import me.rhunk.snapenhance.features.impl.Messaging
|
||||
|
@ -10,8 +10,8 @@ import android.widget.ScrollView
|
||||
import me.rhunk.snapenhance.Constants
|
||||
import me.rhunk.snapenhance.Logger
|
||||
import me.rhunk.snapenhance.features.impl.downloader.MediaDownloader
|
||||
import me.rhunk.snapenhance.ui.menu.AbstractMenu
|
||||
import me.rhunk.snapenhance.ui.ViewAppearanceHelper.applyTheme
|
||||
import me.rhunk.snapenhance.ui.menu.AbstractMenu
|
||||
|
||||
@SuppressLint("DiscouragedApi")
|
||||
class OperaContextActionMenu : AbstractMenu() {
|
||||
|
@ -6,9 +6,8 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.ImageView
|
||||
import me.rhunk.snapenhance.core.BuildConfig
|
||||
import me.rhunk.snapenhance.Constants
|
||||
import me.rhunk.snapenhance.ui.config.ConfigActivity
|
||||
import me.rhunk.snapenhance.core.BuildConfig
|
||||
import me.rhunk.snapenhance.ui.menu.AbstractMenu
|
||||
import java.io.File
|
||||
|
||||
|
@ -1,26 +1,13 @@
|
||||
package me.rhunk.snapenhance.ui.menu.impl
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.AlertDialog
|
||||
import android.graphics.Color
|
||||
import android.graphics.Typeface
|
||||
import android.text.InputType
|
||||
import android.view.View
|
||||
import android.widget.Button
|
||||
import android.widget.EditText
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.Switch
|
||||
import android.widget.TextView
|
||||
import me.rhunk.snapenhance.config.ConfigProperty
|
||||
import me.rhunk.snapenhance.config.impl.ConfigIntegerValue
|
||||
import me.rhunk.snapenhance.config.impl.ConfigStateListValue
|
||||
import me.rhunk.snapenhance.config.impl.ConfigStateSelection
|
||||
import me.rhunk.snapenhance.config.impl.ConfigStateValue
|
||||
import me.rhunk.snapenhance.config.impl.ConfigStringValue
|
||||
import me.rhunk.snapenhance.ui.menu.AbstractMenu
|
||||
import me.rhunk.snapenhance.ui.ViewAppearanceHelper
|
||||
import me.rhunk.snapenhance.ui.menu.AbstractMenu
|
||||
|
||||
class SettingsMenu : AbstractMenu() {
|
||||
/*
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
private fun createCategoryTitle(key: String): TextView {
|
||||
val categoryText = TextView(context.androidContext)
|
||||
@ -186,7 +173,7 @@ class SettingsMenu : AbstractMenu() {
|
||||
setPadding(0, 0, 0, thickness)
|
||||
setBackgroundColor(color)
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
//TODO: quick settings
|
||||
@SuppressLint("SetTextI18n")
|
||||
@ -194,9 +181,7 @@ class SettingsMenu : AbstractMenu() {
|
||||
val actions = context.actionManager.getActions().map {
|
||||
Pair(it) {
|
||||
val button = Button(viewModel.context)
|
||||
button.text = (it.dependsOnProperty?.let { property ->
|
||||
"["+context.translation["property.${property.translationKey}.name"] + "] "
|
||||
}?: "") + context.translation[it.nameKey]
|
||||
button.text = context.translation[it.nameKey]
|
||||
|
||||
button.setOnClickListener { _ ->
|
||||
it.run()
|
||||
|
@ -1,111 +0,0 @@
|
||||
package me.rhunk.snapenhance.ui.spoof
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.res.ColorStateList
|
||||
import android.os.Bundle
|
||||
import android.text.InputType
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageButton
|
||||
import android.widget.Switch
|
||||
import android.widget.TextView
|
||||
import me.rhunk.snapenhance.core.R
|
||||
import me.rhunk.snapenhance.SharedContext
|
||||
import me.rhunk.snapenhance.config.ConfigCategory
|
||||
import me.rhunk.snapenhance.config.impl.ConfigIntegerValue
|
||||
import me.rhunk.snapenhance.config.impl.ConfigStateValue
|
||||
import me.rhunk.snapenhance.config.impl.ConfigStringValue
|
||||
import me.rhunk.snapenhance.ui.ItemHelper
|
||||
|
||||
class DeviceSpooferActivity: Activity() {
|
||||
private val itemHelper = ItemHelper(this)
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
SharedContext.ensureInitialized(this)
|
||||
setContentView(R.layout.device_spoofer_activity)
|
||||
|
||||
findViewById<TextView>(R.id.title).text = "Device Spoofer"
|
||||
findViewById<ImageButton>(R.id.back_button).setOnClickListener { finish() }
|
||||
val propertyListLayout = findViewById<ViewGroup>(R.id.spoof_property_list)
|
||||
|
||||
SharedContext.config.entries().filter { it.key.category == ConfigCategory.DEVICE_SPOOFER }.forEach { (property, value) ->
|
||||
val configItem = layoutInflater.inflate(R.layout.config_activity_item, propertyListLayout, false)
|
||||
|
||||
val propertyName = SharedContext.translation["property.${property.translationKey}.name"]
|
||||
|
||||
fun addSeparator() {
|
||||
//add separator
|
||||
propertyListLayout.addView(View(this).apply {
|
||||
layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 1)
|
||||
setBackgroundColor(getColor(R.color.tertiaryBackground))
|
||||
})
|
||||
}
|
||||
|
||||
configItem.findViewById<TextView>(R.id.name).text = propertyName
|
||||
configItem.findViewById<TextView>(R.id.description).also {
|
||||
it.text = SharedContext.translation["property.${property.translationKey}.description"]
|
||||
it.visibility = if (it.text.isEmpty()) View.GONE else View.VISIBLE
|
||||
}
|
||||
|
||||
fun addValueView(view: View) {
|
||||
configItem.findViewById<ViewGroup>(R.id.value).addView(view)
|
||||
}
|
||||
|
||||
when (value) {
|
||||
is ConfigStateValue -> {
|
||||
val switch = Switch(this)
|
||||
switch.isChecked = value.value()
|
||||
switch.trackTintList = ColorStateList(
|
||||
arrayOf(
|
||||
intArrayOf(android.R.attr.state_checked),
|
||||
intArrayOf(-android.R.attr.state_checked)
|
||||
),
|
||||
intArrayOf(
|
||||
switch.highlightColor,
|
||||
getColor(R.color.tertiaryBackground)
|
||||
)
|
||||
)
|
||||
switch.setOnCheckedChangeListener { _, isChecked ->
|
||||
value.writeFrom(isChecked.toString())
|
||||
}
|
||||
configItem.setOnClickListener { switch.toggle() }
|
||||
addValueView(switch)
|
||||
}
|
||||
is ConfigStringValue, is ConfigIntegerValue -> {
|
||||
val textView = itemHelper.createTranslatedTextView(property, shouldTranslatePropertyValue = false).also {
|
||||
it.text = value.value().toString()
|
||||
}
|
||||
configItem.setOnClickListener {
|
||||
if (value is ConfigIntegerValue) {
|
||||
itemHelper.askForValue(property, InputType.TYPE_CLASS_NUMBER) {
|
||||
try {
|
||||
value.writeFrom(it)
|
||||
textView.text = value.value().toString()
|
||||
} catch (e: NumberFormatException) {
|
||||
itemHelper.longToast(SharedContext.translation["config_activity.invalid_number_toast"], this)
|
||||
}
|
||||
}
|
||||
return@setOnClickListener
|
||||
}
|
||||
itemHelper.askForValue(property, InputType.TYPE_CLASS_TEXT) {
|
||||
value.writeFrom(it)
|
||||
textView.text = value.value().toString()
|
||||
}
|
||||
}
|
||||
addValueView(textView)
|
||||
}
|
||||
}
|
||||
|
||||
propertyListLayout.addView(configItem)
|
||||
addSeparator()
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("Deprecated in Java")
|
||||
@Suppress("DEPRECATION")
|
||||
override fun onBackPressed() {
|
||||
super.onBackPressed()
|
||||
finish()
|
||||
}
|
||||
}
|
@ -10,9 +10,9 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.withContext
|
||||
import me.rhunk.snapenhance.core.BuildConfig
|
||||
import me.rhunk.snapenhance.Logger
|
||||
import me.rhunk.snapenhance.ModContext
|
||||
import me.rhunk.snapenhance.core.BuildConfig
|
||||
import me.rhunk.snapenhance.data.ContentType
|
||||
import me.rhunk.snapenhance.data.FileType
|
||||
import me.rhunk.snapenhance.data.MediaReferenceType
|
||||
|
@ -1,10 +1,10 @@
|
||||
[versions]
|
||||
agp = "8.2.0-alpha13"
|
||||
agp = "8.2.0-alpha14"
|
||||
androidx-material = "1.6.0-alpha02"
|
||||
junit = "4.13.2"
|
||||
kotlin = "1.8.22"
|
||||
kotlinx-coroutines-android = "1.7.2"
|
||||
kotlin-reflect = "1.8.21"
|
||||
kotlin-reflect = "1.8.22"
|
||||
material-icons-extended = "1.6.0-alpha03"
|
||||
navigation-compose = "2.6.0"
|
||||
recyclerview = "1.3.1"
|
||||
|
Loading…
x
Reference in New Issue
Block a user