2 Commits

Author SHA1 Message Date
ΞTΞRNAL
1e9ad8eb2b Add v1.5.6 changelog entry 2026-03-27 01:06:17 +05:30
ΞTΞRNAL
182e7eefeb v1.5.6 2026-03-27 01:05:17 +05:30
5 changed files with 210 additions and 123 deletions

View File

@@ -11,21 +11,14 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.foundation.rememberScrollState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Warning
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
@@ -34,15 +27,11 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import me.eternal.purrfectsnap.ui.manager.components.AestheticDialog
import me.eternal.purrfectsnap.ui.manager.theme.PurrfectPalette
import me.eternal.purrfectsnap.ui.setup.screens.SetupScreen
import me.eternal.purrfectsnap.ui.util.AlertDialogs
@@ -55,16 +44,7 @@ class MappingsScreen : SetupScreen() {
val translation = context.translation
var infoText by remember { mutableStateOf(null as String?) }
var isGenerating by remember { mutableStateOf(false) }
var showCompletionNotice by remember { mutableStateOf(false) }
var completionCountdown by remember { mutableIntStateOf(10) }
fun finishMappings() {
if (isFirstRunFlow) {
showCompletionNotice = true
} else {
goNext()
}
}
fun finishMappings() = goNext()
if (infoText != null) {
fun dismiss() {
@@ -87,103 +67,6 @@ class MappingsScreen : SetupScreen() {
}
}
LaunchedEffect(showCompletionNotice) {
if (showCompletionNotice) {
completionCountdown = 10
while (completionCountdown > 0) {
delay(1000)
completionCountdown--
}
}
}
if (showCompletionNotice) {
val confirmLabel = if (completionCountdown > 0) {
translation.format(
"setup.mappings.confirm_understand_timeout",
"seconds" to completionCountdown.toString()
)
} else {
translation["setup.mappings.confirm_understand"]
}
AestheticDialog(
onDismissRequest = { if (completionCountdown == 0) { showCompletionNotice = false; goNext() } },
title = translation["setup.mappings.notice_title"],
text = "",
icon = Icons.Filled.Warning,
confirmButtonText = confirmLabel,
onConfirm = { if (completionCountdown == 0) { showCompletionNotice = false; goNext() } },
confirmEnabled = completionCountdown == 0,
showCloseButton = false,
customContent = {
val bodyStyle = MaterialTheme.typography.bodyMedium.copy(
color = PurrfectPalette.textSecondary,
lineHeight = 18.sp
)
Surface(
shape = RoundedCornerShape(14.dp),
color = PurrfectPalette.cardOverlayColor,
tonalElevation = 0.dp,
shadowElevation = 0.dp,
border = BorderStroke(
1.dp,
Brush.linearGradient(
listOf(
PurrfectPalette.glowPrimary.copy(alpha = 0.55f),
PurrfectPalette.glowSecondary.copy(alpha = 0.35f)
)
)
)
) {
Column(
modifier = Modifier
.fillMaxWidth()
.heightIn(max = 360.dp)
.verticalScroll(rememberScrollState())
.padding(horizontal = 14.dp, vertical = 12.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(10.dp)
) {
Text(
text = translation["setup.mappings.notice_intro"],
style = bodyStyle,
modifier = Modifier.fillMaxWidth()
)
Text(
text = translation["setup.mappings.notice_step_1"],
style = bodyStyle,
modifier = Modifier.fillMaxWidth()
)
Text(
text = translation["setup.mappings.notice_step_2"],
style = bodyStyle,
modifier = Modifier.fillMaxWidth()
)
Text(
text = translation["setup.mappings.notice_step_3"],
style = bodyStyle,
modifier = Modifier.fillMaxWidth()
)
Text(
text = translation["setup.mappings.notice_rooted_title"],
style = MaterialTheme.typography.bodyMedium.copy(
color = Color.White,
fontWeight = FontWeight.SemiBold,
lineHeight = 18.sp
),
modifier = Modifier.fillMaxWidth()
)
Text(
text = translation["setup.mappings.notice_rooted_body"],
style = bodyStyle,
modifier = Modifier.fillMaxWidth()
)
}
}
}
)
}
LaunchedEffect(Unit) {
coroutineScope.launch(Dispatchers.IO) {
if (isGenerating) return@launch

View File

@@ -33,8 +33,8 @@ tasks.register<GetVersionTask>("getVersion") {
}
// You can still set these for legacy use by submodules or scripts:
rootProject.ext.set("appVersionName", providers.gradleProperty("APP_VERSION_NAME").orElse("1.5.5").get())
rootProject.ext.set("appVersionCode", providers.gradleProperty("APP_VERSION_CODE").orElse("302").get().toInt())
rootProject.ext.set("appVersionName", providers.gradleProperty("APP_VERSION_NAME").orElse("1.5.6").get())
rootProject.ext.set("appVersionCode", providers.gradleProperty("APP_VERSION_CODE").orElse("304").get().toInt())
rootProject.ext.set("applicationId", "me.eternal.purrfectsnap")
// buildHash: when PurrfectSnap or Snapchat is updated, mappings become outdated and auto-regenerate.
// Include version code so each release has a different hash; use random for uniqueness within same version.

View File

@@ -1,3 +1,6 @@
## v1.5.6
- New: Add "Can't login" hint directly in the login screen
## v1.5.5
- Fix: Custom Emoji now works for all devices!
- New: Story preview in story batch download dialog

View File

@@ -2,21 +2,36 @@ package me.eternal.purrfectsnap.core
import android.system.Os
import android.view.ViewGroup
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.HelpOutline
import androidx.compose.material.icons.filled.Info
import androidx.compose.material.icons.outlined.Cancel
import androidx.compose.material.icons.rounded.NotInterested
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.ui.window.Dialog
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import kotlinx.coroutines.delay
import me.eternal.purrfectsnap.common.bridge.FileHandleScope
import me.eternal.purrfectsnap.common.bridge.toWrapper
@@ -25,6 +40,8 @@ import me.eternal.purrfectsnap.common.config.VersionRequirement
import me.eternal.purrfectsnap.common.ui.createComposeView
import me.eternal.purrfectsnap.core.event.events.impl.UnaryCallEvent
import me.eternal.purrfectsnap.core.ui.CustomComposable
import me.eternal.purrfectsnap.core.ui.PurrfectOverlayPalette
import me.eternal.purrfectsnap.core.ui.PurrfectOverlayTheme
import me.eternal.purrfectsnap.core.util.dataBuilder
import me.eternal.purrfectsnap.core.util.hook.HookStage
import me.eternal.purrfectsnap.core.util.hook.hook
@@ -48,6 +65,162 @@ class SecurityFeatures(
transact(this, 0)?.toString(2)?.padStart(32, '0')?.count { it == '1' }
}
private fun isLoginSignupActivity() = context.mainActivity?.javaClass?.name?.endsWith("LoginSignupActivity") == true
@Composable
private fun LoginSignupHelpButton(onClick: () -> Unit) {
Box(
modifier = Modifier
.fillMaxSize()
.padding(top = 16.dp),
contentAlignment = Alignment.TopCenter
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier
.clip(RoundedCornerShape(999.dp))
.background(
Brush.horizontalGradient(
listOf(
PurrfectOverlayPalette.glowPrimary.copy(alpha = 0.92f),
PurrfectOverlayPalette.glowSecondary.copy(alpha = 0.62f)
)
),
RoundedCornerShape(999.dp)
)
.border(
BorderStroke(1.dp, PurrfectOverlayPalette.textPrimary.copy(alpha = 0.18f)),
RoundedCornerShape(999.dp)
)
.clickable(onClick = onClick)
.padding(horizontal = 12.dp, vertical = 8.dp)
) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.size(32.dp)
.clip(CircleShape)
.background(Color.Black.copy(alpha = 0.16f))
.border(BorderStroke(1.dp, PurrfectOverlayPalette.textPrimary.copy(alpha = 0.28f)), CircleShape)
) {
Icon(
imageVector = Icons.Filled.HelpOutline,
contentDescription = "Login Help",
tint = Color.White,
modifier = Modifier.size(18.dp)
)
}
Text(
text = "Can't Login?",
color = Color.White,
fontWeight = FontWeight.SemiBold,
fontSize = 14.sp
)
}
}
}
@Composable
private fun LoginSignupHelpDialog(onDismiss: () -> Unit) {
Dialog(onDismissRequest = onDismiss) {
PurrfectOverlayTheme {
val shape = RoundedCornerShape(20.dp)
Surface(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
shape = shape,
color = Color.Transparent,
border = BorderStroke(
1.dp,
Brush.linearGradient(
listOf(
PurrfectOverlayPalette.glowPrimary.copy(alpha = 0.55f),
PurrfectOverlayPalette.glowSecondary.copy(alpha = 0.35f)
)
)
),
shadowElevation = 0.dp,
tonalElevation = 0.dp
) {
Box(
modifier = Modifier
.background(PurrfectOverlayPalette.cardOverlay, shape)
.padding(20.dp)
) {
Column(
horizontalAlignment = Alignment.Start,
verticalArrangement = Arrangement.spacedBy(14.dp)
) {
Icon(
Icons.Filled.Info,
contentDescription = null,
tint = PurrfectOverlayPalette.textPrimary,
modifier = Modifier
.align(Alignment.CenterHorizontally)
.size(28.dp)
)
Text(
text = context.translation["setup.mappings.notice_title"],
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
color = PurrfectOverlayPalette.textPrimary,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
)
Text(
text = context.translation["setup.mappings.notice_intro"],
color = PurrfectOverlayPalette.textSecondary,
textAlign = TextAlign.Start
)
Text(
text = "For non-rooted users:",
color = PurrfectOverlayPalette.textPrimary,
fontWeight = FontWeight.SemiBold
)
Text(
text = context.translation["setup.mappings.notice_step_1"],
color = PurrfectOverlayPalette.textSecondary,
textAlign = TextAlign.Start
)
Text(
text = context.translation["setup.mappings.notice_step_2"],
color = PurrfectOverlayPalette.textSecondary,
textAlign = TextAlign.Start
)
Text(
text = context.translation["setup.mappings.notice_step_3"],
color = PurrfectOverlayPalette.textSecondary,
textAlign = TextAlign.Start
)
Text(
text = context.translation["setup.mappings.notice_rooted_title"],
color = PurrfectOverlayPalette.textPrimary,
fontWeight = FontWeight.SemiBold
)
Text(
text = context.translation["setup.mappings.notice_rooted_body"],
color = PurrfectOverlayPalette.textSecondary,
textAlign = TextAlign.Start
)
Button(
onClick = onDismiss,
colors = ButtonDefaults.buttonColors(
containerColor = PurrfectOverlayPalette.glowPrimary.copy(alpha = 0.92f),
contentColor = Color.Black
),
modifier = Modifier.align(Alignment.CenterHorizontally)
) {
Text("OK")
}
}
}
}
}
}
}
fun init() {
val snapchatVersionCode = context.androidContext.packageManager?.getPackageInfo(context.androidContext.packageName, 0)?.longVersionCode ?: throw IllegalStateException("Failed to get version code")
var shouldDisablePlugin = MOD_DETECTION_VERSION_CHECK.checkVersion(snapchatVersionCode)?.second == VersionRequirement.OLDER_REQUIRED
@@ -101,6 +274,34 @@ class SecurityFeatures(
}
}
lateinit var loginHelpComposable: CustomComposable
loginHelpComposable = {
var showDialog by remember { mutableStateOf(false) }
var isLoginScreen by remember { mutableStateOf(false) }
LaunchedEffect(Unit) {
while (true) {
val currentlyInLogin = isLoginSignupActivity()
isLoginScreen = currentlyInLogin
if (!currentlyInLogin) showDialog = false
delay(150)
}
}
if (isLoginScreen) {
LoginSignupHelpButton(
onClick = { showDialog = true }
)
}
if (isLoginScreen && showDialog) {
LoginSignupHelpDialog(
onDismiss = { showDialog = false }
)
}
}
context.inAppOverlay.addCustomComposable(loginHelpComposable)
if (!context.disablePlugin) return
val allowedEPs = listOf(

View File

@@ -7,8 +7,8 @@ org.gradle.configuration-cache=true
org.gradle.configuration-cache.problems=warn
nativeAbis=arm64-v8a
APP_VERSION_NAME=1.5.5
APP_VERSION_CODE=302
APP_VERSION_NAME=1.5.6
APP_VERSION_CODE=304
debug_build_hash=18fe2a814d0e2eb5
psIntegrityPinnedSha256=
EXPECTED_CERT_SHA256=0f188cb17d8ea4902ee15ce98f4928ba4226a4790fa3c52f62d776b08e46ca1c