diff --git a/app/src/main/kotlin/me/eternal/purrfectsnap/ui/setup/screens/impl/MappingsScreen.kt b/app/src/main/kotlin/me/eternal/purrfectsnap/ui/setup/screens/impl/MappingsScreen.kt index 107fa5b4..45f3fd66 100644 --- a/app/src/main/kotlin/me/eternal/purrfectsnap/ui/setup/screens/impl/MappingsScreen.kt +++ b/app/src/main/kotlin/me/eternal/purrfectsnap/ui/setup/screens/impl/MappingsScreen.kt @@ -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 diff --git a/build.gradle.kts b/build.gradle.kts index 59829c76..f312ab5c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -33,8 +33,8 @@ tasks.register("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. diff --git a/core/src/main/kotlin/me/eternal/purrfectsnap/core/SecurityFeatures.kt b/core/src/main/kotlin/me/eternal/purrfectsnap/core/SecurityFeatures.kt index 574aa883..3a94b6e6 100644 --- a/core/src/main/kotlin/me/eternal/purrfectsnap/core/SecurityFeatures.kt +++ b/core/src/main/kotlin/me/eternal/purrfectsnap/core/SecurityFeatures.kt @@ -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( diff --git a/gradle.properties b/gradle.properties index 916bb56d..e28dc4bf 100644 --- a/gradle.properties +++ b/gradle.properties @@ -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