feat: Initial commit!

This commit is contained in:
ΞTΞRNAL
2025-10-03 17:10:09 +05:30
parent b720b58886
commit 0676212a38
279 changed files with 449644 additions and 2215 deletions

View File

@@ -1,12 +1,12 @@
{
{
"setup": {
"dialogs": {
"select_language": "Select Language",
"select_save_folder_button": "Select Folder",
"save_folder": "SnapEnhance requires Storage permissions to download and Save Media from Snapchat.\nPlease choose the location where media should be downloaded to."
"save_folder": "PurrfectSnap requires Storage permissions to download and Save Media from Snapchat.\nPlease choose the location where media should be downloaded to."
},
"mappings": {
"generate_failure_no_snapchat": "SnapEnhance was unable to detect Snapchat, please try reinstalling Snapchat.",
"generate_failure_no_snapchat": "PurrfectSnap was unable to detect Snapchat, please try reinstalling Snapchat.",
"generate_failure": "An error occurred while trying to generate mappings, please try again.",
"dialog": "Generating Mappings, this may take a while, please be patient..."
},
@@ -65,11 +65,11 @@
"empty_hint": "(blank)"
},
"home": {
"update_title": "SnapEnhance Update",
"update_title": "PurrfectSnap Update",
"update_button": "Download",
"update_content": "Version {version} is Available!",
"debug_build_summary_title": "You are running a debug build of SnapEnhance",
"version_title": "v{versionName} · by rhunk",
"debug_build_summary_title": "You are running a debug build of PurrfectSnap",
"version_title": "v{versionName} · forked by Eternal",
"debug_build_summary_date": "Build date: {date} ({days} days ago)",
"debug_build_summary_content": "Version {versionName} ({versionCode})",
"quick_actions_title": "Quick Actions"
@@ -174,7 +174,7 @@
},
"scripting_warning": {
"title": "Warning",
"content": "SnapEnhance includes a scripting tool, allowing the execution of user-defined code on your device. Use extreme caution and only install modules from known, reliable sources. Unauthorised or unverified modules may pose security risks to your system."
"content": "PurrfectSnap includes a scripting tool, allowing the execution of user-defined code on your device. Use extreme caution and only install modules from known, reliable sources. Unauthorised or unverified modules may pose security risks to your system."
},
"reset_config": {
"title": "Reset config",
@@ -287,7 +287,7 @@
},
"change_language": {
"name": "Change Language",
"description": "Change the Language of SnapEnhance"
"description": "Change the Language of PurrfectSnap"
},
"logger_history": {
"name": "Logger History",
@@ -901,7 +901,7 @@
},
"composer_logs": {
"name": "Composer Logs",
"description": "Redirects console logs of Composer to SnapEnhance"
"description": "Redirects console logs of Composer to PurrfectSnap"
},
"show_first_created_username": {
"description": "Shows the first created username next to the current username in the profile page",
@@ -1055,7 +1055,7 @@
},
"scripting": {
"name": "Scripting",
"description": "Run custom scripts to extend SnapEnhance",
"description": "Run custom scripts to extend PurrfectSnap",
"properties": {
"developer_mode": {
"name": "Developer Mode",
@@ -1223,7 +1223,7 @@
},
"auto_reload": {
"snapchat_only": "Snapchat Only",
"all": "All (Snapchat + SnapEnhance)"
"all": "All (Snapchat + PurrfectSnap)"
},
"edit_text_override": {
"multi_line_chat_input": "Multi Line Chat Input",

View File

@@ -1,13 +1,13 @@
{
{
"setup": {
"dialogs": {
"select_language": "Select Language",
"save_folder": "SnapEnhance requires Storage permissions to download and Save Media from Snapchat.\nPlease choose the location where media should be downloaded to.",
"save_folder": "PurrfectSnap requires Storage permissions to download and Save Media from Snapchat.\nPlease choose the location where media should be downloaded to.",
"select_save_folder_button": "Select Folder"
},
"mappings": {
"dialog": "Generating Mappings, this may take a while ...",
"generate_failure_no_snapchat": "SnapEnhance was unable to detect Snapchat, please try reinstalling Snapchat.",
"generate_failure_no_snapchat": "PurrfectSnap was unable to detect Snapchat, please try reinstalling Snapchat.",
"generate_failure": "An error occurred while trying to generate mappings, please try again."
},
"permissions": {
@@ -46,11 +46,11 @@
},
"sections": {
"home": {
"version_title": "v{versionName} · by rhunk",
"update_title": "SnapEnhance Update",
"version_title": "v{versionName} · forked by Eternal",
"update_title": "PurrfectSnap Update",
"update_content": "Version {version} is available!",
"update_button": "Download",
"debug_build_summary_title": "You are running a debug build of SnapEnhance",
"debug_build_summary_title": "You are running a debug build of PurrfectSnap",
"debug_build_summary_content": "Version {versionName} ({versionCode})",
"debug_build_summary_date": "Build date: {date} ({days} days ago)",
"quick_actions_title": "Quick Actions"
@@ -71,7 +71,13 @@
"message_logger_summary": "{messageCount} messages\n{storyCount} stories",
"export_button": "Export",
"clear_button": "Clear",
"view_logger_history_button": "View Logger History"
"view_logger_history_button": "View Logger History",
"updates_title": "Updates",
"auto_update_check": "Auto check for updates",
"update_check_frequency": "Update check frequency",
"update_check_frequency_daily": "Daily",
"update_check_frequency_weekly": "Weekly",
"update_check_frequency_monthly": "Monthly"
},
"tasks": {
"no_tasks": "No tasks",
@@ -200,7 +206,7 @@
},
"scripting_warning": {
"title": "Warning",
"content": "SnapEnhance includes a scripting tool, allowing the execution of user-defined code on your device. Use extreme caution and only install modules from known, reliable sources. Unauthorized or unverified modules may pose security risks to your system."
"content": "PurrfectSnap includes a scripting tool, allowing the execution of user-defined code on your device. Use extreme caution and only install modules from known, reliable sources. Unauthorized or unverified modules may pose security risks to your system."
},
"reset_config": {
"title": "Reset config",
@@ -314,7 +320,7 @@
},
"change_language": {
"name": "Change Language",
"description": "Change the language of SnapEnhance"
"description": "Change the language of PurrfectSnap"
},
"file_imports": {
"name": "File Imports",
@@ -469,6 +475,10 @@
}
}
},
"force_amoled_theme": {
"name": "Amoled Dark Mode",
"description": "Uses a true black background for the dark theme"
},
"map_friend_nametags": {
"name": "Enhanced Friend Map Nametags",
"description": "Improves the Nametags of friends on the Snapmap"
@@ -544,6 +554,14 @@
"prevent_forced_keyboard": {
"name": "Prevent Forced Keyboard",
"description": "Prevents Snapchat from automatically popping up the keyboard when you open a conversation"
},
"settings_menu": {
"name": "Settings Menu",
"description": "Changes the way you open the PurrfectSnap menu",
"options": {
"default": "Default",
"legacy": "Legacy"
}
}
}
},
@@ -979,7 +997,7 @@
},
"composer_logs": {
"name": "Composer Logs",
"description": "Redirects console logs of Composer to SnapEnhance"
"description": "Redirects console logs of Composer to PurrfectSnap"
}
}
},
@@ -1139,7 +1157,7 @@
},
"scripting": {
"name": "Scripting",
"description": "Run custom scripts to extend SnapEnhance",
"description": "Run custom scripts to extend PurrfectSnap",
"properties": {
"developer_mode": {
"name": "Developer Mode",
@@ -1305,7 +1323,7 @@
},
"auto_reload": {
"snapchat_only": "Snapchat Only",
"all": "All (Snapchat + SnapEnhance)"
"all": "All (Snapchat + PurrfectSnap)"
},
"edit_text_override": {
"multi_line_chat_input": "Multi Line Chat Input",

View File

@@ -56,4 +56,16 @@ class Global : ConfigContainer() {
val disableTelecomFramework = boolean("disable_telecom_framework") { requireRestart() }
val hideActiveMusic = boolean("hide_active_music") { requireRestart() }
val disableSnapSplitting = boolean("disable_snap_splitting") { addNotices(FeatureNotice.UNSTABLE) }
}
inner class UpdateSettings : ConfigContainer() {
val autoUpdateCheck = boolean("auto_update_check")
val updateCheckFrequency = unique("update_check_frequency", "daily", "weekly", "monthly")
}
inner class UISettings : ConfigContainer() {
val hapticFeedback = boolean("haptic_feedback", true)
}
val updateSettings = container("update_settings", UpdateSettings()) { addFlags(ConfigFlag.HIDDEN) }
val uiSettings = container("ui_settings", UISettings()) { addFlags(ConfigFlag.HIDDEN) }
}

View File

@@ -29,6 +29,7 @@ class UserInterfaceTweaks : ConfigContainer() {
val friendFeedMessagePreview = container("friend_feed_message_preview", FriendFeedMessagePreview()) { requireRestart() }
val snapPreview = boolean("snap_preview") { addNotices(FeatureNotice.UNSTABLE); requireRestart() }
val bootstrapOverride = container("bootstrap_override", BootstrapOverride()) { requireRestart() }
val forceAmoledTheme = boolean("force_amoled_theme") { requireRestart() }
val mapFriendNameTags = boolean("map_friend_nametags") { requireRestart() }
val preventMessageListAutoScroll = boolean("prevent_message_list_auto_scroll") { requireRestart(); addNotices(FeatureNotice.UNSTABLE) }
val streakExpirationInfo = boolean("streak_expiration_info") { requireRestart() }
@@ -58,4 +59,5 @@ class UserInterfaceTweaks : ConfigContainer() {
requireRestart(); addNotices(FeatureNotice.BAN_RISK, FeatureNotice.INTERNAL_BEHAVIOR)
}
val preventForcedKeyboard = boolean("prevent_forced_keyboard") { requireRestart() }
val settingsMenu = unique("settings_menu", "default", "legacy") { requireRestart() }.apply { set("default") }
}

View File

@@ -0,0 +1,6 @@
package me.rhunk.snapenhance.common.data
enum class ExportType {
BULK,
SINGLE
}

View File

@@ -0,0 +1,6 @@
package me.rhunk.snapenhance.common.data
data class ExportedTrackerData(
val type: ExportType,
val rules: List<TrackerRule>
)

View File

@@ -150,6 +150,9 @@ data class TrackerRule(
val id: Int,
val enabled: Boolean,
val name: String,
val author: String? = null,
val events: List<TrackerRuleEvent>? = null,
val scopes: Map<String, TrackerScopeType>? = null
): Parcelable
@Parcelize

View File

@@ -0,0 +1,7 @@
package me.rhunk.snapenhance.common.data
interface TrackerDataManager {
fun getExportedTrackerData(): ExportedTrackerData
fun getExportedTrackerData(ruleId: Int): ExportedTrackerData?
fun importTrackerData(data: ExportedTrackerData)
}

View File

@@ -10,7 +10,9 @@ private val rhinoAndroidHelper = RhinoAndroidHelper(null as File?)
fun contextScope(shouldOptimize: Boolean = false, f: Context.() -> Any?): Any? {
val context = rhinoAndroidHelper.enterContext().apply {
languageVersion = Context.VERSION_ES6
optimizationLevel = if (!shouldOptimize) -1 else 0
// Deprecated: optimizationLevel. No longer set in new Rhino/Java.
// If you have a modern way to tweak optimization, use it here.
// optimizationLevel = if (!shouldOptimize) -1 else 0 <-- REMOVE THIS LINE
}
try {
return context.f().let {
@@ -49,7 +51,7 @@ fun ScriptableObject.putFunction(name: String, proxy: Scriptable.(Array<out Any?
}
fun scriptableObject(name: String? = "ScriptableObject", f: ScriptableObject.() -> Unit): ScriptableObject {
return object: ScriptableObject() {
return object : ScriptableObject() {
override fun getClassName() = name
}.apply(f)
}
}

View File

@@ -2,14 +2,14 @@ package me.rhunk.snapenhance.common.ui
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
// ---------- Light color palette ----------
val md_theme_light_primary = Color(0xFF6750A4)
val md_theme_light_onPrimary = Color(0xFFFFFFFF)
val md_theme_light_primaryContainer = Color(0xFFE9DDFF)
@@ -40,6 +40,7 @@ val md_theme_light_surfaceTint = Color(0xFF6750A4)
val md_theme_light_outlineVariant = Color(0xFFCAC4CF)
val md_theme_light_scrim = Color(0xFF000000)
// ---------- Dark color palette ----------
val md_theme_dark_primary = Color(0xFFCFBCFF)
val md_theme_dark_onPrimary = Color(0xFF381E72)
val md_theme_dark_primaryContainer = Color(0xFF4F378A)
@@ -70,10 +71,13 @@ val md_theme_dark_surfaceTint = Color(0xFFCFBCFF)
val md_theme_dark_outlineVariant = Color(0xFF49454E)
val md_theme_dark_scrim = Color(0xFF000000)
// ---------- AMOLED (true black dark) palette (only crucial overrides) ----------
val md_theme_amoled_background = Color(0xFF000000)
val md_theme_amoled_surface = Color(0xFF000000)
val md_theme_amoled_onBackground = Color(0xFFE6E1E6)
val md_theme_amoled_onSurface = Color(0xFFE6E1E6)
val seed = Color(0xFF6750A4)
// Material3 ColorScheme for light theme
private val LightThemeColors = lightColorScheme(
primary = md_theme_light_primary,
onPrimary = md_theme_light_onPrimary,
@@ -106,7 +110,8 @@ private val LightThemeColors = lightColorScheme(
scrim = md_theme_light_scrim
)
private val DarkThemeColors = lightColorScheme(
// Material3 ColorScheme for dark theme
private val DarkThemeColors = darkColorScheme(
primary = md_theme_dark_primary,
onPrimary = md_theme_dark_onPrimary,
primaryContainer = md_theme_dark_primaryContainer,
@@ -141,22 +146,41 @@ private val DarkThemeColors = lightColorScheme(
@Composable
fun AppMaterialTheme(
isDarkTheme: Boolean = isSystemInDarkTheme(),
themeMode: ThemeMode? = null,
content: @Composable () -> Unit
) {
val context = LocalContext.current
val dynamicColor = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
val colorScheme = when {
dynamicColor && isDarkTheme -> {
dynamicDarkColorScheme(LocalContext.current)
val effectiveThemeMode = themeMode ?: if (isDarkTheme) ThemeMode.DARK else ThemeMode.LIGHT
val colorScheme = when (effectiveThemeMode) {
ThemeMode.AMOLED -> {
val baseScheme = if (dynamicColor) dynamicDarkColorScheme(context) else DarkThemeColors
baseScheme.copy(
background = md_theme_amoled_background,
onBackground = md_theme_amoled_onBackground,
surface = md_theme_amoled_surface,
onSurface = md_theme_amoled_onSurface
)
}
dynamicColor && !isDarkTheme -> {
dynamicLightColorScheme(LocalContext.current)
ThemeMode.DARK -> if (dynamicColor) dynamicDarkColorScheme(context) else DarkThemeColors
ThemeMode.LIGHT -> if (dynamicColor) dynamicLightColorScheme(context) else LightThemeColors
ThemeMode.SYSTEM -> when {
isSystemInDarkTheme() && dynamicColor -> dynamicDarkColorScheme(context)
!isSystemInDarkTheme() && dynamicColor -> dynamicLightColorScheme(context)
isSystemInDarkTheme() -> DarkThemeColors
else -> LightThemeColors
}
!isDarkTheme -> LightThemeColors
else -> DarkThemeColors
}
val shapes = Shapes(
extraSmall = RoundedCornerShape(24.dp),
small = RoundedCornerShape(24.dp),
medium = RoundedCornerShape(24.dp),
large = RoundedCornerShape(24.dp),
extraLarge = RoundedCornerShape(24.dp)
)
MaterialTheme(
colorScheme = colorScheme,
shapes = shapes,
content = content
)
}
}

View File

@@ -0,0 +1,77 @@
package me.rhunk.snapenhance.common.ui
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Brightness4
import androidx.compose.material.icons.filled.DarkMode
import androidx.compose.material.icons.filled.LightMode
import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.InvertColors // AMOLED icon
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
@Composable
fun ThemeChooserDialog(
selected: ThemeMode,
onSelect: (ThemeMode) -> Unit,
onDismiss: () -> Unit
) {
AlertDialog(
onDismissRequest = onDismiss,
confirmButton = {},
title = {
Text("Choose App Theme", style = MaterialTheme.typography.titleMedium)
},
text = {
Column {
ThemeMode.values().forEachIndexed { idx, mode ->
val (icon, label) = when (mode) {
ThemeMode.LIGHT -> Icons.Filled.LightMode to "Light"
ThemeMode.DARK -> Icons.Filled.DarkMode to "Dark"
ThemeMode.SYSTEM -> Icons.Filled.Brightness4 to "System Default"
ThemeMode.AMOLED -> Icons.Filled.InvertColors to "AMOLED"
}
Row(
modifier = Modifier
.fillMaxWidth()
.clickable {
onSelect(mode)
onDismiss()
}
.background(
if (mode == selected) MaterialTheme.colorScheme.secondary.copy(alpha = 0.12f)
else MaterialTheme.colorScheme.surface,
MaterialTheme.shapes.small
)
.padding(12.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = icon,
contentDescription = label,
tint = if (mode == selected) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurface,
modifier = Modifier.size(28.dp)
)
Spacer(Modifier.width(20.dp))
Text(label, color = if (mode == selected) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurface)
Spacer(Modifier.weight(1f))
if (mode == selected) {
Icon(
imageVector = Icons.Filled.Check,
contentDescription = "Selected",
tint = MaterialTheme.colorScheme.primary
)
}
}
// Add a spacer between all buttons except the last
if (idx < ThemeMode.values().lastIndex) Spacer(Modifier.height(8.dp))
}
}
}
)
}

View File

@@ -0,0 +1,8 @@
package me.rhunk.snapenhance.common.ui
enum class ThemeMode(val displayName: String) {
SYSTEM("System Default"),
LIGHT("Light"),
DARK("Dark"),
AMOLED("AMOLED")
}

View File

@@ -0,0 +1,28 @@
package me.rhunk.snapenhance.common.ui
import android.content.Context
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
val Context.themeDataStore by preferencesDataStore("theme_prefs")
object ThemePreferences {
private val THEME_KEY = stringPreferencesKey("theme_mode")
fun getThemeModeFlow(context: Context): Flow<ThemeMode> =
context.themeDataStore.data.map { prefs ->
when (prefs[THEME_KEY]) {
ThemeMode.LIGHT.name -> ThemeMode.LIGHT
ThemeMode.DARK.name -> ThemeMode.DARK
ThemeMode.AMOLED.name -> ThemeMode.AMOLED
else -> ThemeMode.SYSTEM
}
}
suspend fun setThemeMode(context: Context, mode: ThemeMode) {
context.themeDataStore.edit { it[THEME_KEY] = mode.name }
}
}

View File

@@ -17,8 +17,8 @@ object SQLiteDatabaseHelper {
}
cursor.close()
val newColumns = columns.filter {
existingColumns.none { existingColumn -> it.startsWith(existingColumn) }
val newColumns = columns.filter { column ->
!column.uppercase().startsWith("PRIMARY KEY") && existingColumns.none { existingColumn -> column.startsWith(existingColumn) }
}
if (newColumns.isEmpty()) return@forEach