feat: property translation

- add top bar
This commit is contained in:
rhunk 2023-08-04 16:49:11 +02:00
parent 9aef7a1b86
commit c88abd70d4
9 changed files with 404 additions and 304 deletions

View File

@ -39,15 +39,14 @@ class MainActivity : ComponentActivity() {
setContent { setContent {
val navController = rememberNavController() val navController = rememberNavController()
val navigation = remember { Navigation() } val navigation = remember { Navigation(sections, navController) }
AppMaterialTheme { AppMaterialTheme {
Scaffold( Scaffold(
containerColor = MaterialTheme.colorScheme.background, containerColor = MaterialTheme.colorScheme.background,
bottomBar = { navigation.NavBar(navController = navController) } topBar = { navigation.TopBar() },
bottomBar = { navigation.NavBar() }
) { innerPadding -> ) { innerPadding ->
navigation.NavigationHost( navigation.NavigationHost(
sections = sections,
navController = navController,
innerPadding = innerPadding, innerPadding = innerPadding,
startDestination = startDestination startDestination = startDestination
) )

View File

@ -6,15 +6,20 @@ import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredWidth import androidx.compose.foundation.layout.requiredWidth
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.NavigationBar import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation.NavController import androidx.navigation.NavDestination
import androidx.navigation.NavDestination.Companion.hierarchy import androidx.navigation.NavDestination.Companion.hierarchy
import androidx.navigation.NavGraph.Companion.findStartDestination import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
@ -22,30 +27,55 @@ import androidx.navigation.compose.NavHost
import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.currentBackStackEntryAsState
class Navigation{ class Navigation(
private val sections: Map<EnumSection, Section>,
private val navHostController: NavHostController
){
@Composable @Composable
fun NavigationHost( fun NavigationHost(
sections: Map<EnumSection, Section>,
startDestination: EnumSection, startDestination: EnumSection,
navController: NavHostController,
innerPadding: PaddingValues innerPadding: PaddingValues
) { ) {
NavHost(navController, startDestination = startDestination.route, Modifier.padding(innerPadding)) { NavHost(navHostController, startDestination = startDestination.route, Modifier.padding(innerPadding)) {
sections.forEach { (_, instance) -> sections.forEach { (_, instance) ->
instance.navController = navController instance.navController = navHostController
instance.build(this) instance.build(this)
} }
} }
} }
private fun getCurrentSection(navDestination: NavDestination) = sections.firstNotNullOf { (section, instance) ->
if (navDestination.hierarchy.any { it.route == section.route }) {
instance
} else {
null
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun NavBar( fun TopBar() {
navController: NavController val navBackStackEntry by navHostController.currentBackStackEntryAsState()
) { val currentDestination = navBackStackEntry?.destination ?: return
val currentSection = getCurrentSection(currentDestination)
TopAppBar(title = {
Text(text = currentSection.sectionTopBarName())
}, navigationIcon = {
if (currentSection.canGoBack()) {
IconButton(onClick = { navHostController.popBackStack() }) {
Icon(Icons.Filled.ArrowBack, contentDescription = null)
}
}
})
}
@Composable
fun NavBar() {
NavigationBar { NavigationBar {
val navBackStackEntry by navController.currentBackStackEntryAsState() val navBackStackEntry by navHostController.currentBackStackEntryAsState()
val currentDestination = navBackStackEntry?.destination val currentDestination = navBackStackEntry?.destination
EnumSection.values().toList().forEach { section -> sections.keys.forEach { section ->
fun selected() = currentDestination?.hierarchy?.any { it.route == section.route } == true fun selected() = currentDestination?.hierarchy?.any { it.route == section.route } == true
NavigationBarItem( NavigationBarItem(
@ -73,8 +103,8 @@ class Navigation{
}, },
selected = selected(), selected = selected(),
onClick = { onClick = {
navController.navigate(section.route) { navHostController.navigate(section.route) {
popUpTo(navController.graph.findStartDestination().id) { popUpTo(navHostController.graph.findStartDestination().id) {
saveState = true saveState = true
} }
launchSingleTop = true launchSingleTop = true

View File

@ -70,6 +70,9 @@ open class Section {
open fun init() {} open fun init() {}
open fun onResumed() {} open fun onResumed() {}
open fun sectionTopBarName(): String = context.translation["manager.routes.${enumSection.route}"]
open fun canGoBack(): Boolean = false
@Composable @Composable
open fun Content() { NotImplemented() } open fun Content() { NotImplemented() }

View File

@ -23,8 +23,6 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import me.rhunk.snapenhance.Logger
import me.rhunk.snapenhance.ui.manager.Section import me.rhunk.snapenhance.ui.manager.Section
import me.rhunk.snapenhance.ui.manager.data.InstallationSummary import me.rhunk.snapenhance.ui.manager.data.InstallationSummary
import me.rhunk.snapenhance.ui.setup.Requirements import me.rhunk.snapenhance.ui.setup.Requirements
@ -91,13 +89,14 @@ class HomeSection : Section() {
} }
override fun onResumed() { override fun onResumed() {
Logger.debug("HomeSection resumed")
if (!context.mappings.isMappingsLoaded()) { if (!context.mappings.isMappingsLoaded()) {
context.mappings.init() context.mappings.init()
} }
installationSummary.value = context.getInstallationSummary() installationSummary.value = context.getInstallationSummary()
} }
override fun sectionTopBarName() = "SnapEnhance"
@Composable @Composable
@Preview @Preview
override fun Content() { override fun Content() {
@ -106,12 +105,6 @@ class HomeSection : Section() {
.fillMaxSize() .fillMaxSize()
.verticalScroll(ScrollState(0)) .verticalScroll(ScrollState(0))
) { ) {
Text(
"SnapEnhance",
fontSize = 32.sp,
modifier = Modifier.padding(32.dp)
)
Text( Text(
text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec euismod, nisl eget ultricies ultrices, nunc nisl aliquam nunc, quis aliquam nisl nunc eu nisl. Donec euismod, nisl eget ultricies ultrices, nunc nisl aliquam nunc, quis aliquam nisl nunc eu nisl.", text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec euismod, nisl eget ultricies ultrices, nunc nisl aliquam nunc, quis aliquam nisl nunc eu nisl. Donec euismod, nisl eget ultricies ultrices, nunc nisl aliquam nunc, quis aliquam nisl nunc eu nisl.",
modifier = Modifier.padding(16.dp) modifier = Modifier.padding(16.dp)

View File

@ -6,6 +6,7 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
@ -20,7 +21,6 @@ import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.MaterialTheme import androidx.compose.material.MaterialTheme
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.FolderOpen import androidx.compose.material.icons.filled.FolderOpen
import androidx.compose.material.icons.filled.OpenInNew import androidx.compose.material.icons.filled.OpenInNew
import androidx.compose.material.icons.rounded.Save import androidx.compose.material.icons.rounded.Save
@ -34,7 +34,6 @@ import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.Switch import androidx.compose.material3.Switch
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.rememberBottomSheetScaffoldState import androidx.compose.material3.rememberBottomSheetScaffoldState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@ -43,7 +42,6 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight 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.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
@ -63,18 +61,75 @@ class FeaturesSection : Section() {
private val dialogs by lazy { Dialogs() } private val dialogs by lazy { Dialogs() }
companion object { companion object {
private const val MAIN_ROUTE = "root" const val MAIN_ROUTE = "feature_root"
const val FEATURE_CONTAINER_ROOT = "feature_container/{name}"
} }
private lateinit var openFolderCallback: (uri: String) -> Unit private lateinit var openFolderCallback: (uri: String) -> Unit
private lateinit var openFolderLauncher: () -> Unit private lateinit var openFolderLauncher: () -> Unit
private val featuresRouteName by lazy { context.translation["manager.routes.features"] }
private val allContainers by lazy {
val containers = mutableMapOf<String, PropertyPair<*>>()
fun queryContainerRecursive(container: ConfigContainer) {
container.properties.forEach {
if (it.key.dataType.type == DataProcessors.Type.CONTAINER) {
containers[it.key.name] = PropertyPair(it.key, it.value)
queryContainerRecursive(it.value.get() as ConfigContainer)
}
}
}
queryContainerRecursive(context.config.root)
containers
}
override fun init() { override fun init() {
openFolderLauncher = ChooseFolderHelper.createChooseFolder(context.activity!! as ComponentActivity) { openFolderLauncher = ChooseFolderHelper.createChooseFolder(context.activity!! as ComponentActivity) {
openFolderCallback(it) openFolderCallback(it)
} }
} }
override fun canGoBack() = sectionTopBarName() != featuresRouteName
override fun sectionTopBarName(): String {
navController.currentBackStackEntry?.arguments?.getString("name")?.let { routeName ->
val currentContainerPair = allContainers[routeName]
val propertyTree = run {
var key = currentContainerPair?.key
val tree = mutableListOf<String>()
while (key != null) {
tree.add(key.propertyTranslationPath())
key = key.parentKey
}
tree
}
val translatedKey = propertyTree.reversed().joinToString(" > ") {
context.translation["$it.name"]
}
return "$featuresRouteName > $translatedKey"
}
return featuresRouteName
}
override fun build(navGraphBuilder: NavGraphBuilder) {
navGraphBuilder.navigation(route = "features", startDestination = MAIN_ROUTE) {
composable(MAIN_ROUTE) {
Container(context.config.root)
}
composable(FEATURE_CONTAINER_ROOT) { backStackEntry ->
backStackEntry.arguments?.getString("name")?.let { containerName ->
allContainers[containerName]?.let {
Container(it.value.get() as ConfigContainer)
}
}
}
}
}
@Composable @Composable
private fun PropertyAction(property: PropertyPair<*>, registerClickCallback: RegisterClickCallback) { private fun PropertyAction(property: PropertyPair<*>, registerClickCallback: RegisterClickCallback) {
val showDialog = remember { mutableStateOf(false) } val showDialog = remember { mutableStateOf(false) }
@ -130,7 +185,7 @@ class FeaturesSection : Section() {
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
maxLines = 1, maxLines = 1,
modifier = Modifier.widthIn(0.dp, 120.dp), modifier = Modifier.widthIn(0.dp, 120.dp),
text = (propertyValue.getNullable() as? String) ?: "Disabled", text = (propertyValue.getNullable() as? String) ?: context.translation["manager.features.disabled"],
) )
} }
@ -168,7 +223,7 @@ class FeaturesSection : Section() {
val container = propertyValue.get() as ConfigContainer val container = propertyValue.get() as ConfigContainer
registerClickCallback { registerClickCallback {
navController.navigate("container/${property.name}") navController.navigate(FEATURE_CONTAINER_ROOT.replace("{name}", property.name))
} }
if (container.globalState == null) return if (container.globalState == null) return
@ -183,7 +238,10 @@ class FeaturesSection : Section() {
Box(modifier = Modifier Box(modifier = Modifier
.height(50.dp) .height(50.dp)
.width(1.dp) .width(1.dp)
.background(color = MaterialTheme.colors.onBackground.copy(alpha = 0.12f), shape = RoundedCornerShape(5.dp))) .background(
color = MaterialTheme.colors.onBackground.copy(alpha = 0.12f),
shape = RoundedCornerShape(5.dp)
))
} }
Switch( Switch(
@ -222,12 +280,12 @@ class FeaturesSection : Section() {
.padding(all = 10.dp) .padding(all = 10.dp)
) { ) {
Text( Text(
text = property.name, text = context.translation["${property.key.propertyTranslationPath()}.name"],
fontSize = 16.sp, fontSize = 16.sp,
fontWeight = FontWeight.Bold fontWeight = FontWeight.Bold
) )
Text( Text(
text = property.name, text = context.translation["${property.key.propertyTranslationPath()}.description"],
fontSize = 12.sp, fontSize = 12.sp,
lineHeight = 15.sp lineHeight = 15.sp
) )
@ -252,7 +310,6 @@ class FeaturesSection : Section() {
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
private fun Container( private fun Container(
containerName: String,
configContainer: ConfigContainer configContainer: ConfigContainer
) { ) {
val properties = remember { val properties = remember {
@ -264,20 +321,6 @@ class FeaturesSection : Section() {
Scaffold( Scaffold(
snackbarHost = { SnackbarHost(scaffoldState.snackbarHostState) }, snackbarHost = { SnackbarHost(scaffoldState.snackbarHostState) },
modifier = Modifier.fillMaxSize(), 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 = {
FloatingActionButton( FloatingActionButton(
onClick = { onClick = {
@ -302,6 +345,8 @@ class FeaturesSection : Section() {
modifier = Modifier modifier = Modifier
.fillMaxHeight() .fillMaxHeight()
.padding(innerPadding), .padding(innerPadding),
//save button space
contentPadding = PaddingValues(top = 10.dp, bottom = 110.dp),
verticalArrangement = Arrangement.Center verticalArrangement = Arrangement.Center
) { ) {
items(properties) { items(properties) {
@ -310,36 +355,6 @@ class FeaturesSection : Section() {
} }
} }
) )
} }
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(context.config.root)
containers
}
navGraphBuilder.navigation(route = "features", startDestination = MAIN_ROUTE) {
composable(MAIN_ROUTE) {
Container(MAIN_ROUTE, context.config.root)
}
composable("container/{name}") { backStackEntry ->
backStackEntry.arguments?.getString("name")?.let { containerName ->
allContainers[containerName]?.let {
Container(containerName, it)
}
}
}
}
}
} }

View File

@ -16,13 +16,16 @@
} }
}, },
"category": { "manager": {
"spying_privacy": "Spying & Privacy", "routes": {
"media_manager": "Media Manager", "downloads": "Downloads",
"ui_tweaks": "UI & Tweaks", "features": "Features",
"camera": "Camera", "friends": "Friends",
"updates": "Updates", "debug": "Debug"
"experimental_debugging": "Experimental" },
"features": {
"disabled": "Disabled"
}
}, },
"action": { "action": {
@ -34,228 +37,270 @@
"export_chat_messages": "Export chat messages" "export_chat_messages": "Export chat messages"
}, },
"property": { "features": {
"message_logger": { "spoof": {
"name": "Message Logger", "name": "Spoof",
"description": "Prevents messages from being deleted" "description": "Spoof your information",
"properties": {
"location": {
"name": "Location",
"description": "Spoof your location"
},
"device": {
"name": "Device",
"description": "Spoof your device"
}
}
}, },
"prevent_read_receipts": { "downloader": {
"name": "Prevent Read Receipts", "name": "Downloader",
"description": "Prevent anyone from knowing you've opened their Snaps" "description": "Download Snaps and Stories",
"properties": {
"save_folder": {
"name": "Save Folder",
"description": "The directory where all media is saved"
},
"auto_download_options": {
"name": "Auto Download Options",
"description": "Select which medias to auto download"
}
}
}, },
"hide_bitmoji_presence": { "user_interface": {
"name": "Hide Bitmoji Presence", "name": "User Interface",
"description": "Hides your Bitmoji presence from the chat" "description": "Change the look and feel of Snapchat",
"properties": {
"enable_app_appearance": {
"name": "Enable App Appearance Settings",
"description": "Enables the hidden app appearance settings"
},
"amoled_dark_mode": {
"name": "AMOLED Dark Mode",
"description": "Enables AMOLED dark mode\nMake sure Snapchat's dark mode is enabled"
},
"map_friend_nametags": {
"name": "Enhanced Friend Map Nametags",
"description": "Enhances the nametags of friends on the map"
},
"streak_expiration_info": {
"name": "Show Streak Expiration Info",
"description": "Shows Streak expiration info next to streaks"
},
"hide_story_section": {
"name": "Hide Story Section",
"description": "Hide certain UI Elements shown in the story section"
},
"hide_ui_components": {
"name": "Hide UI Components",
"description": "Select which UI components to hide"
},
"disable_spotlight": {
"name": "Disable Spotlight",
"description": "Disables the Spotlight page"
},
"startup_tab": {
"name": "Startup Tab",
"description": "Change the tab that opens on startup"
},
"story_viewer_override": {
"name": "Story Viewer Override",
"description": "Turns on certain features which Snapchat hid"
},
"friend_feed_menu_buttons": {
"name": "Friend Feed Menu Buttons",
"description": "Select which buttons to show in the Friend Feed Menu Bar"
},
"friend_feed_menu_position": {
"name": "Friend Feed Position Index",
"description": "The position of the Friend Feed Menu component"
},
"enable_friend_feed_menu_bar": {
"name": "Friend Feed Menu Bar",
"description": "Enables the new Friend Feed Menu Bar"
}
}
}, },
"better_notifications": { "messaging": {
"name": "Better Notifications", "name": "Messaging",
"description": "Shows more information in notifications" "description": "Change how you interact with friends"
}, },
"notification_blacklist": { "global": {
"name": "Notification Blacklist", "name": "Global",
"description": "Hides selected notification type" "description": "Tweak Snapchat globally"
}, },
"disable_metrics": { "camera": {
"name": "Disable Metrics", "name": "Camera",
"description": "Disables metrics sent to Snapchat" "description": "Adjust the right settings for the perfect snap"
}, },
"block_ads": { "experimental": {
"name": "Block Ads", "name": "Experimental",
"description": "Blocks ads from being displayed" "description": "Experimental features"
},
"unlimited_snap_view_time": {
"name": "Unlimited Snap View Time",
"description": "Removes the time limit for viewing Snaps"
},
"prevent_sending_messages": {
"name": "Prevent Sending Messages",
"description": "Prevents sending certain types of messages"
},
"anonymous_story_view": {
"name": "Anonymous Story View",
"description": "Prevents anyone from knowing you've seen their story"
},
"hide_typing_notification": {
"name": "Hide Typing Notification",
"description": "Prevents typing notifications being sent"
}, },
"save_folder": { "properties": {
"name": "Save Folder", "message_logger": {
"description": "The directory where all media is saved" "name": "Message Logger",
}, "description": "Prevents messages from being deleted"
"auto_download_options": { },
"name": "Auto Download Options", "prevent_read_receipts": {
"description": "Select which medias to auto download" "name": "Prevent Read Receipts",
}, "description": "Prevent anyone from knowing you've opened their Snaps"
"download_options": { },
"name": "Download Options", "hide_bitmoji_presence": {
"description": "Specify the file path format" "name": "Hide Bitmoji Presence",
}, "description": "Hides your Bitmoji presence from the chat"
"chat_download_context_menu": { },
"name": "Chat Download Context Menu", "better_notifications": {
"description": "Enable the chat download context menu" "name": "Better Notifications",
}, "description": "Shows more information in notifications"
"gallery_media_send_override": { },
"name": "Gallery Media Send Override", "notification_blacklist": {
"description": "Overrides media sent from the gallery" "name": "Notification Blacklist",
}, "description": "Hides selected notification type"
"auto_save_messages": { },
"name": "Auto Save Messages", "disable_metrics": {
"description": "Select which type of messages to auto save" "name": "Disable Metrics",
}, "description": "Disables metrics sent to Snapchat"
"force_media_source_quality": { },
"name": "Force Media Source Quality", "block_ads": {
"description": "Overrides the media source quality" "name": "Block Ads",
}, "description": "Blocks ads from being displayed"
"download_logging": { },
"name": "Download Logging", "unlimited_snap_view_time": {
"description": "Show a toast when media is downloading" "name": "Unlimited Snap View Time",
}, "description": "Removes the time limit for viewing Snaps"
},
"prevent_sending_messages": {
"name": "Prevent Sending Messages",
"description": "Prevents sending certain types of messages"
},
"anonymous_story_view": {
"name": "Anonymous Story View",
"description": "Prevents anyone from knowing you've seen their story"
},
"hide_typing_notification": {
"name": "Hide Typing Notification",
"description": "Prevents typing notifications being sent"
},
"enable_friend_feed_menu_bar": { "download_options": {
"name": "Friend Feed Menu Bar", "name": "Download Options",
"description": "Enables the new Friend Feed Menu Bar" "description": "Specify the file path format"
}, },
"friend_feed_menu_buttons": { "chat_download_context_menu": {
"name": "Friend Feed Menu Buttons", "name": "Chat Download Context Menu",
"description": "Select which buttons to show in the Friend Feed Menu Bar" "description": "Enable the chat download context menu"
}, },
"friend_feed_menu_buttons_position": { "gallery_media_send_override": {
"name": "Friend Feed Buttons Position Index", "name": "Gallery Media Send Override",
"description": "The position of the Friend Feed Menu Buttons" "description": "Overrides media sent from the gallery"
}, },
"hide_ui_elements": { "auto_save_messages": {
"name": "Hide UI Elements", "name": "Auto Save Messages",
"description": "Select which UI elements to hide" "description": "Select which type of messages to auto save"
}, },
"hide_story_section": { "force_media_source_quality": {
"name": "Hide Story Section", "name": "Force Media Source Quality",
"description": "Hide certain UI Elements shown in the story section" "description": "Overrides the media source quality"
}, },
"story_viewer_override": { "download_logging": {
"name": "Story Viewer Override", "name": "Download Logging",
"description": "Turns on certain features which Snapchat hid" "description": "Show a toast when media is downloading"
}, },
"streak_expiration_info": {
"name": "Show Streak Expiration Info",
"description": "Shows Streak expiration info next to streaks"
},
"disable_snap_splitting": {
"name": "Disable Snap Splitting",
"description": "Prevents Snaps from being split into multiple parts"
},
"disable_video_length_restriction": {
"name": "Disable Video Length Restriction",
"description": "Disables video length restrictions"
},
"snapchat_plus": {
"name": "Snapchat Plus",
"description": "Enables Snapchat Plus features"
},
"new_map_ui": {
"name": "New Map UI",
"description": "Enables the new map UI"
},
"location_spoof": {
"name": "Snapmap Location Spoofer",
"description": "Spoofs your location on the Snapmap"
},
"message_preview_length": {
"name": "Message Preview Length",
"description": "Specify the amount of messages to be previewed"
},
"unlimited_conversation_pinning": {
"name": "Unlimited Conversation Pinning",
"description": "Enables the ability to pin unlimited conversations"
},
"disable_spotlight": {
"name": "Disable Spotlight",
"description": "Disables the Spotlight page"
},
"enable_app_appearance": {
"name": "Enable App Appearance Settings",
"description": "Enables the hidden app appearance settings"
},
"startup_page_override": {
"name": "Override Startup Page",
"description": "Overrides the startup page"
},
"disable_google_play_dialogs": {
"name": "Disable Google Play Services Dialogs",
"description": "Prevent Google Play Services availability dialogs from being shown"
},
"auto_updater": { "disable_snap_splitting": {
"name": "Auto Updater", "name": "Disable Snap Splitting",
"description": "The interval of checking for updates" "description": "Prevents Snaps from being split into multiple parts"
}, },
"disable_video_length_restriction": {
"name": "Disable Video Length Restriction",
"description": "Disables video length restrictions"
},
"snapchat_plus": {
"name": "Snapchat Plus",
"description": "Enables Snapchat Plus features"
},
"location_spoof": {
"name": "Snapmap Location Spoofer",
"description": "Spoofs your location on the Snapmap"
},
"message_preview_length": {
"name": "Message Preview Length",
"description": "Specify the amount of messages to be previewed"
},
"unlimited_conversation_pinning": {
"name": "Unlimited Conversation Pinning",
"description": "Enables the ability to pin unlimited conversations"
},
"disable_google_play_dialogs": {
"name": "Disable Google Play Services Dialogs",
"description": "Prevent Google Play Services availability dialogs from being shown"
},
"disable_camera": { "auto_updater": {
"name": "Disable Camera", "name": "Auto Updater",
"description": "Prevents Snapchat from being able to use the camera" "description": "The interval of checking for updates"
}, },
"immersive_camera_preview": {
"name": "Immersive Camera Preview",
"description": "Stops Snapchat from cropping the camera preview"
},
"preview_resolution": {
"name": "Preview Resolution",
"description": "Overrides the camera preview resolution"
},
"picture_resolution": {
"name": "Picture Resolution",
"description": "Overrides the picture resolution"
},
"force_highest_frame_rate": {
"name": "Force Highest Frame Rate",
"description": "Forces the highest possible frame rate"
},
"force_camera_source_encoding": {
"name": "Force Camera Source Encoding",
"description": "Forces the camera source encoding"
},
"app_passcode": { "disable_camera": {
"name": "Set App Passcode", "name": "Disable Camera",
"description": "Sets a passcode to lock the app" "description": "Prevents Snapchat from being able to use the camera"
}, },
"app_lock_on_resume": { "immersive_camera_preview": {
"name": "App Lock On Resume", "name": "Immersive Camera Preview",
"description": "Locks the app when it's reopened" "description": "Stops Snapchat from cropping the camera preview"
}, },
"infinite_story_boost": { "preview_resolution": {
"name": "Infinite Story Boost", "name": "Preview Resolution",
"description": "Infinitely boosts your story" "description": "Overrides the camera preview resolution"
}, },
"meo_passcode_bypass": { "picture_resolution": {
"name": "My Eyes Only Passcode Bypass", "name": "Picture Resolution",
"description": "Bypass the My Eyes Only passcode\nThis will only work if the passcode has been entered correctly before" "description": "Overrides the picture resolution"
}, },
"amoled_dark_mode": { "force_highest_frame_rate": {
"name": "AMOLED Dark Mode", "name": "Force Highest Frame Rate",
"description": "Enables AMOLED dark mode\nMake sure Snapchat's dark mode is enabled" "description": "Forces the highest possible frame rate"
}, },
"unlimited_multi_snap": { "force_camera_source_encoding": {
"name": "Unlimited Multi Snap", "name": "Force Camera Source Encoding",
"description": "Allows you to take an unlimited amount of multi snaps" "description": "Forces the camera source encoding"
}, },
"device_spoof": {
"name": "Spoof Device Values",
"description": "Spoofs the devices values"
},
"device_fingerprint": {
"name": "Device Fingerprint",
"description": "Spoofs the device fingerprint"
},
"android_id": {
"name": "Android ID",
"description": "Spoofs the devices Android ID"
}
},
"option": { "app_passcode": {
"property": { "name": "Set App Passcode",
"description": "Sets a passcode to lock the app"
},
"app_lock_on_resume": {
"name": "App Lock On Resume",
"description": "Locks the app when it's reopened"
},
"infinite_story_boost": {
"name": "Infinite Story Boost",
"description": "Infinitely boosts your story"
},
"meo_passcode_bypass": {
"name": "My Eyes Only Passcode Bypass",
"description": "Bypass the My Eyes Only passcode\nThis will only work if the passcode has been entered correctly before"
},
"unlimited_multi_snap": {
"name": "Unlimited Multi Snap",
"description": "Allows you to take an unlimited amount of multi snaps"
},
"device_spoof": {
"name": "Spoof Device Values",
"description": "Spoofs the devices values"
},
"device_fingerprint": {
"name": "Device Fingerprint",
"description": "Spoofs the device fingerprint"
},
"android_id": {
"name": "Android ID",
"description": "Spoofs the devices Android ID"
}
},
"option": {
"better_notifications": { "better_notifications": {
"chat": "Show chat messages", "chat": "Show chat messages",
"snap": "Show medias", "snap": "Show medias",

View File

@ -8,16 +8,19 @@ typealias ConfigParamsBuilder = ConfigParams.() -> Unit
open class ConfigContainer( open class ConfigContainer(
var globalState: Boolean? = null var globalState: Boolean? = null
) { ) {
var parentContainerKey: PropertyKey<*>? = null
val properties = mutableMapOf<PropertyKey<*>, PropertyValue<*>>() val properties = mutableMapOf<PropertyKey<*>, PropertyValue<*>>()
private inline fun <T> registerProperty( private inline fun <T> registerProperty(
key: String, key: String,
type: DataProcessors.PropertyDataProcessor<*>, type: DataProcessors.PropertyDataProcessor<*>,
defaultValue: PropertyValue<T>, defaultValue: PropertyValue<T>,
params: ConfigParams.() -> Unit = {} params: ConfigParams.() -> Unit = {},
propertyKeyCallback: (PropertyKey<*>) -> Unit = {}
): PropertyValue<T> { ): PropertyValue<T> {
val propertyKey = PropertyKey(key, type, ConfigParams().also { it.params() }) val propertyKey = PropertyKey({ parentContainerKey }, key, type, ConfigParams().also { it.params() })
properties[propertyKey] = defaultValue properties[propertyKey] = defaultValue
propertyKeyCallback(propertyKey)
return defaultValue return defaultValue
} }
@ -51,7 +54,9 @@ open class ConfigContainer(
protected fun <T : ConfigContainer> container( protected fun <T : ConfigContainer> container(
key: String, key: String,
container: T container: T
) = registerProperty(key, DataProcessors.container(container), PropertyValue(container)).get() ) = registerProperty(key, DataProcessors.container(container), PropertyValue(container)) {
container.parentContainerKey = it
}.get()
fun toJson(): JsonObject { fun toJson(): JsonObject {
val json = JsonObject() val json = JsonObject()

View File

@ -12,7 +12,7 @@ data class PropertyPair<T>(
class ConfigParams( class ConfigParams(
var shouldTranslate: Boolean = false, var shouldTranslate: Boolean = false,
var hidden: Boolean = false, var isHidden: Boolean = false,
var isFolder: Boolean = false, var isFolder: Boolean = false,
val disabledKey: String? = null val disabledKey: String? = null
) )
@ -40,9 +40,20 @@ class PropertyValue<T>(
operator fun setValue(t: Any?, property: KProperty<*>, t1: T?) = set(t1) operator fun setValue(t: Any?, property: KProperty<*>, t1: T?) = set(t1)
} }
class PropertyKey<T>( data class PropertyKey<T>(
private val _parent: () -> PropertyKey<*>?,
val name: String, val name: String,
val dataType: DataProcessors.PropertyDataProcessor<T>, val dataType: DataProcessors.PropertyDataProcessor<T>,
val params: ConfigParams = ConfigParams(), val params: ConfigParams = ConfigParams(),
) ) {
val parentKey by lazy { _parent() }
fun propertyTranslationPath(): String {
return if (parentKey != null) {
"${parentKey!!.propertyTranslationPath()}.properties.$name"
} else {
"features.$name"
}
}
}

View File

@ -27,8 +27,7 @@ class UserInterfaceTweaks : ConfigContainer() {
"ngs_search_icon_container" "ngs_search_icon_container"
) )
val storyViewerOverride = unique("story_viewer_override", "DISCOVER_PLAYBACK_SEEKBAR", "VERTICAL_STORY_VIEWER") val storyViewerOverride = unique("story_viewer_override", "DISCOVER_PLAYBACK_SEEKBAR", "VERTICAL_STORY_VIEWER")
val friendFeedMenuButtons = multiple("friend_feed_menu_buttons", "auto_download_blacklist", "anti_auto_save", "stealth_mode", "conversation_info") val friendFeedMenuButtons = multiple("friend_feed_menu_buttons", "auto_download_blacklist", "anti_auto_save", "stealth_mode", "conversation_info")
val enableFriendFeedMenuBar = boolean("enable_friend_feed_menu_bar")
val friendFeedMenuPosition = integer("friend_feed_menu_position", defaultValue = 1) val friendFeedMenuPosition = integer("friend_feed_menu_position", defaultValue = 1)
val enableFriendFeedMenuBar = boolean("enable_friend_feed_menu_bar")
} }