last commit before migration from GitHub
This commit is contained in:
@@ -268,7 +268,7 @@ class RemoteSideContext(
|
||||
Constants.SNAPCHAT_PACKAGE_NAME
|
||||
)
|
||||
if (intent == null) {
|
||||
shortToast("Can't execute action: Snapchat is not installed")
|
||||
shortToast(translation["toast_snapchat_not_installed"])
|
||||
return
|
||||
}
|
||||
intent.putExtra(EnumAction.ACTION_PARAMETER, action.key)
|
||||
|
||||
@@ -91,6 +91,7 @@ object UpdateDownloader {
|
||||
scope: CoroutineScope
|
||||
) {
|
||||
val context = remoteContext.androidContext
|
||||
val translation = remoteContext.translation.getCategory("manager.sections.home")
|
||||
val fetch = getInstance(remoteContext)
|
||||
val filePath = File(context.externalCacheDir, fileName).path
|
||||
remoteContext.log.info("Starting update download from $downloadUrl -> $filePath", TAG)
|
||||
@@ -107,7 +108,7 @@ object UpdateDownloader {
|
||||
|
||||
override fun onQueued(download: Download, waitingOnNetwork: Boolean) {
|
||||
downloadState.value = DownloadState.DOWNLOADING
|
||||
Toast.makeText(context, "Download started", Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(context, translation["update_download_started_toast"], Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onProgress(download: Download, etaInMilliSeconds: Long, downloadedBytesPerSecond: Long) {
|
||||
@@ -122,7 +123,7 @@ object UpdateDownloader {
|
||||
"Download completed -> ${downloadedFile.absolutePath} (${downloadedFile.length()} bytes)",
|
||||
TAG
|
||||
)
|
||||
Toast.makeText(context, "Download completed", Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(context, translation["update_download_completed_toast"], Toast.LENGTH_SHORT).show()
|
||||
val apkFile = resolveDownloadedApk(remoteContext, downloadedFile)
|
||||
val uri = FileProvider.getUriForFile(
|
||||
context,
|
||||
@@ -144,7 +145,7 @@ object UpdateDownloader {
|
||||
remoteContext.log.info("Cleaned downloaded update files", TAG)
|
||||
}
|
||||
}.onFailure {
|
||||
Toast.makeText(context, "Failed to install update. Check logs for more details.", Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(context, translation["update_install_failed_toast"], Toast.LENGTH_SHORT).show()
|
||||
remoteContext.log.error("Failed to install downloaded update", it, TAG)
|
||||
downloadState.value = DownloadState.FAILED
|
||||
}
|
||||
@@ -157,7 +158,11 @@ object UpdateDownloader {
|
||||
|
||||
override fun onError(download: Download, error: Error, throwable: Throwable?) {
|
||||
downloadState.value = DownloadState.FAILED
|
||||
Toast.makeText(context, "Download failed: $error", Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(
|
||||
context,
|
||||
translation.format("update_download_failed_toast", "error" to error.toString()),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
throwable?.let { remoteContext.log.error("Update download failed: $error", it, TAG) }
|
||||
?: remoteContext.log.error("Update download failed: $error", TAG)
|
||||
fetch.removeListener(this)
|
||||
|
||||
@@ -414,7 +414,7 @@ class TasksRootSection : Routes.Route() {
|
||||
IconButton(onClick = {
|
||||
showConfirmDialog = true
|
||||
}) {
|
||||
Icon(Icons.Filled.Delete, contentDescription = "Clear tasks")
|
||||
Icon(Icons.Filled.Delete, contentDescription = translation["clear_button_description"])
|
||||
}
|
||||
|
||||
if (showConfirmDialog) {
|
||||
@@ -917,7 +917,18 @@ class TasksRootSection : Routes.Route() {
|
||||
fontSize = 18.sp
|
||||
)
|
||||
Text(
|
||||
text = if (activeTasks.isNotEmpty()) "${activeTasks.size} active · ${recentTasks.size} recent" else "Idle · ${recentTasks.size} recent",
|
||||
text = if (activeTasks.isNotEmpty()) {
|
||||
translation.format(
|
||||
"summary_active",
|
||||
"active" to activeTasks.size.toString(),
|
||||
"recent" to recentTasks.size.toString()
|
||||
)
|
||||
} else {
|
||||
translation.format(
|
||||
"summary_idle",
|
||||
"recent" to recentTasks.size.toString()
|
||||
)
|
||||
},
|
||||
color = PurrfectPalette.textSecondary,
|
||||
fontSize = 12.sp
|
||||
)
|
||||
@@ -964,11 +975,16 @@ class TasksRootSection : Routes.Route() {
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
Icon(Icons.Filled.PlaylistAddCheckCircle, contentDescription = null, tint = Color.White)
|
||||
Text(text = "${activeTasks.size} running", color = Color.White, fontWeight = FontWeight.SemiBold, fontSize = 12.sp)
|
||||
Text(
|
||||
text = translation.format("running_count", "count" to activeTasks.size.toString()),
|
||||
color = Color.White,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
fontSize = 12.sp
|
||||
)
|
||||
}
|
||||
}
|
||||
IconButton(onClick = { showConfirmDialog = true }) {
|
||||
Icon(Icons.Filled.Delete, contentDescription = "Clear tasks", tint = Color.White)
|
||||
Icon(Icons.Filled.Delete, contentDescription = translation["clear_button_description"], tint = Color.White)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,7 +203,7 @@ class ManageRuleFeature : Routes.Route() {
|
||||
title = translation["clear_list_button"],
|
||||
text = translation["dialog_clear_confirmation_text"],
|
||||
icon = Icons.Default.DeleteSweep,
|
||||
confirmButtonText = "Clear",
|
||||
confirmButtonText = context.translation["clear"],
|
||||
dismissButtonText = context.translation["button.cancel"],
|
||||
onDismiss = { confirmationDialog = false },
|
||||
onConfirm = {
|
||||
@@ -390,7 +390,7 @@ class ManageRuleFeature : Routes.Route() {
|
||||
contentColor = Color.White
|
||||
)
|
||||
) {
|
||||
Text(text = "Clear")
|
||||
Text(text = context.translation["clear"])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,13 +65,7 @@ class HomeAbout : Routes.Route() {
|
||||
FontFamily(Font(R.font.avenir_next_medium, FontWeight.Medium))
|
||||
}
|
||||
val scrollState = rememberScrollState()
|
||||
val aboutStory = remember {
|
||||
"""
|
||||
PurrfectSnap was founded on 2nd of October, 2025, as a fork of SnapEnhance by ΞTΞRNAL with a vision to provide users the quality Snapchat experience they deserve. This app was just meant to be a minor update in the SnapEnhance repository, but it soon became a separate app wherein the contributors kept adding features. Then the developer <RSR/> joined the team, and this app soon became a huge success. We received much love and support and gained 1K+ downloads in just two days! We thank all users and contributors; without your support, we wouldn't have reached this place. We would also like to convey our huge thanks to rhunk, the lead developer of SnapEnhance, as without him, this app wouldn't even exist. We are immensely grateful to him. Lastly, we would like to thank all our admins, notably: CLASSIC GENIUS, Harry, SUJΛL, Zain & scrodingerspet, who were right there with us from the very beginning. We would also like to thank all testers, notably Leo & Toxic, who tested and reported bugs continuously. We are immensely grateful for your contribution.
|
||||
|
||||
|
||||
""".trimIndent()
|
||||
}
|
||||
val aboutStory = remember { translation["about_story"] }
|
||||
val pagePadding = 16.dp
|
||||
val bottomPadding = routes.bottomPadding +
|
||||
WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding() +
|
||||
@@ -82,7 +76,7 @@ class HomeAbout : Routes.Route() {
|
||||
val lastTapTime = remember { mutableLongStateOf(0L) }
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
context.shortToast("Tap 5 times in this screen to see some magic 😉!")
|
||||
context.shortToast(translation["about_magic_toast"])
|
||||
}
|
||||
|
||||
Box(
|
||||
@@ -97,7 +91,7 @@ class HomeAbout : Routes.Route() {
|
||||
.padding(bottom = bottomPadding)
|
||||
) {
|
||||
FloatingTopBar(
|
||||
title = routeInfo.translatedKey?.value ?: "About",
|
||||
title = routeInfo.translatedKey?.value ?: translation["manager.routes.home_about"],
|
||||
onBack = { routes.navController.popBackStack() }
|
||||
)
|
||||
|
||||
@@ -121,7 +115,7 @@ class HomeAbout : Routes.Route() {
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "PurrfectSnap",
|
||||
text = translation["about_title"],
|
||||
fontSize = 28.sp,
|
||||
fontWeight = FontWeight.ExtraBold,
|
||||
color = PurrfectPalette.textPrimary,
|
||||
@@ -143,14 +137,14 @@ class HomeAbout : Routes.Route() {
|
||||
}
|
||||
)
|
||||
Text(
|
||||
text = "An Xposed Module meant to enhance your Snapchat experience!",
|
||||
text = translation["about_tagline"],
|
||||
fontSize = 13.sp,
|
||||
color = PurrfectPalette.textSecondary,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Text(
|
||||
text = "Lead Developers",
|
||||
text = translation["about_lead_developers_title"],
|
||||
fontSize = 15.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = Color.White,
|
||||
@@ -196,7 +190,7 @@ class HomeAbout : Routes.Route() {
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "Our Story",
|
||||
text = translation["about_story_title"],
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = Color.White
|
||||
@@ -228,7 +222,7 @@ class HomeAbout : Routes.Route() {
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(
|
||||
text = "With love, PurrfectSnap Team",
|
||||
text = translation["about_thanks_title"],
|
||||
fontSize = 15.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = Color.White,
|
||||
@@ -242,7 +236,12 @@ class HomeAbout : Routes.Route() {
|
||||
) {
|
||||
Button(
|
||||
modifier = Modifier.weight(1f),
|
||||
onClick = { context.androidContext.openLink("https://github.com/particle-box/PurrfectSnap") },
|
||||
onClick = {
|
||||
context.androidContext.openLink(
|
||||
"https://github.com/particle-box/PurrfectSnap",
|
||||
context.translation["toast_open_link_failed"]
|
||||
)
|
||||
},
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = Color.White,
|
||||
contentColor = Color(0xFF1B152E)
|
||||
@@ -254,11 +253,16 @@ class HomeAbout : Routes.Route() {
|
||||
modifier = Modifier.size(18.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text(text = "GitHub", maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||
Text(text = translation["github_button"], maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||
}
|
||||
OutlinedButton(
|
||||
modifier = Modifier.weight(1f),
|
||||
onClick = { context.androidContext.openLink("https://t.me/purrfectsnap_official") },
|
||||
onClick = {
|
||||
context.androidContext.openLink(
|
||||
"https://t.me/purrfectsnap_official",
|
||||
context.translation["toast_open_link_failed"]
|
||||
)
|
||||
},
|
||||
border = BorderStroke(1.dp, Color.White.copy(alpha = 0.35f)),
|
||||
colors = ButtonDefaults.outlinedButtonColors(contentColor = Color.White)
|
||||
) {
|
||||
@@ -269,7 +273,7 @@ class HomeAbout : Routes.Route() {
|
||||
tint = Color.White
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text(text = "Telegram", maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||
Text(text = translation["telegram_button"], maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,7 +127,7 @@ class HomeLogs : Routes.Route() {
|
||||
}
|
||||
}
|
||||
readerResult.onFailure {
|
||||
context.longToast("Failed to read logs!")
|
||||
context.longToast(translation["read_logs_failed_toast"])
|
||||
}
|
||||
readerResult.getOrNull()?.let { reader ->
|
||||
logReader = reader
|
||||
|
||||
@@ -319,7 +319,7 @@ class HomeRootSection : Routes.Route() {
|
||||
) { routes.homeLogs.navigate() }
|
||||
TopBarActionChip(
|
||||
icon = Icons.Filled.Info,
|
||||
label = "About"
|
||||
label = translation["manager.routes.home_about"]
|
||||
) { routes.about.navigate() }
|
||||
}
|
||||
|
||||
@@ -453,7 +453,7 @@ class HomeRootSection : Routes.Route() {
|
||||
fontFamily = avenirNext
|
||||
)
|
||||
Text(
|
||||
text = "An Xposed Module meant to enhance your Snapchat experience",
|
||||
text = translation["hero_tagline"],
|
||||
color = Color.White.copy(alpha = 0.9f),
|
||||
fontSize = 15.sp,
|
||||
lineHeight = 20.sp,
|
||||
@@ -465,9 +465,9 @@ class HomeRootSection : Routes.Route() {
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp, Alignment.CenterHorizontally),
|
||||
verticalArrangement = Arrangement.spacedBy(10.dp)
|
||||
) {
|
||||
HeroBadge("Version: $versionName - $channelLabel")
|
||||
HeroBadge(translation.format("hero_version_label", "version" to versionName, "channel" to channelLabel))
|
||||
gitHashShort.takeIf { it.isNotBlank() && it.lowercase() != "unknown" }?.let {
|
||||
HeroBadge("Build: $it")
|
||||
HeroBadge(translation.format("hero_build_label", "build" to it))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -566,7 +566,7 @@ class HomeRootSection : Routes.Route() {
|
||||
tint = Color(0xFFA3F0C2)
|
||||
)
|
||||
Text(
|
||||
text = "Ready to install",
|
||||
text = translation["update_ready_label"],
|
||||
color = Color.White,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
@@ -611,7 +611,7 @@ class HomeRootSection : Routes.Route() {
|
||||
.background(if (isPurrAuraActive) PurrfectPalette.glowPrimary else Color(0xFF8C8CA3))
|
||||
)
|
||||
Text(
|
||||
text = if (isPurrAuraActive) "PurrAura Active!" else "PurrAura Inactive",
|
||||
text = if (isPurrAuraActive) translation["purr_aura_active_label"] else translation["purr_aura_inactive_label"],
|
||||
color = Color.White,
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 14.sp
|
||||
@@ -627,7 +627,7 @@ class HomeRootSection : Routes.Route() {
|
||||
) {
|
||||
Icon(Icons.Filled.Settings, contentDescription = null, tint = Color.White)
|
||||
Spacer(modifier = Modifier.width(6.dp))
|
||||
Text("Open Settings")
|
||||
Text(translation["open_settings_button"])
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -657,7 +657,7 @@ class HomeRootSection : Routes.Route() {
|
||||
) {
|
||||
Icon(Icons.AutoMirrored.Filled.Help, contentDescription = null, modifier = Modifier.size(18.dp))
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text(text = "Wiki", maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||
Text(text = translation["wiki_button"], maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||
}
|
||||
OutlinedButton(
|
||||
modifier = Modifier.weight(1f),
|
||||
@@ -672,7 +672,7 @@ class HomeRootSection : Routes.Route() {
|
||||
modifier = Modifier.size(18.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(6.dp))
|
||||
Text(text = "GitHub", maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||
Text(text = translation["github_button"], maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||
}
|
||||
ExternalLinkIcon(
|
||||
imageVector = ImageVector.vectorResource(id = R.drawable.ic_telegram),
|
||||
@@ -740,7 +740,7 @@ class HomeRootSection : Routes.Route() {
|
||||
context.database.getQuickTiles().filter { it.isNotBlank() }
|
||||
}
|
||||
val updateChannel = context.config.root.global.updateSettings.updateChannel.getNullable() ?: "stable"
|
||||
val channelLabel = if (updateChannel == "prerelease") "Pre-release" else "Stable"
|
||||
val channelLabel = if (updateChannel == "prerelease") translation["channel_label_prerelease"] else translation["channel_label_stable"]
|
||||
val latestUpdate by rememberAsyncMutableState(defaultValue = null, keys = arrayOf(updateChannel)) {
|
||||
val channel = if (updateChannel == "prerelease") Channel.PRERELEASE else Channel.STABLE
|
||||
Updater.getLatestRelease(channel)
|
||||
@@ -785,7 +785,7 @@ class HomeRootSection : Routes.Route() {
|
||||
if (abiName == null) {
|
||||
android.widget.Toast.makeText(
|
||||
context.androidContext,
|
||||
"Your device architecture is not supported for automatic updates.",
|
||||
translation["update_arch_not_supported_toast"],
|
||||
android.widget.Toast.LENGTH_LONG
|
||||
).show()
|
||||
} else {
|
||||
@@ -807,7 +807,10 @@ class HomeRootSection : Routes.Route() {
|
||||
"No matching update asset for arch=$abiName (available: ${latest.assetDownloads.keys})",
|
||||
"HomeRoot"
|
||||
)
|
||||
context.androidContext.openLink(latest.releaseUrl)
|
||||
context.androidContext.openLink(
|
||||
latest.releaseUrl,
|
||||
context.translation["toast_open_link_failed"]
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -926,7 +929,7 @@ class HomeRootSection : Routes.Route() {
|
||||
TopBarActionChip(
|
||||
icon = Icons.Filled.Notifications,
|
||||
label = null,
|
||||
contentDescription = "Announcements"
|
||||
contentDescription = translation["announcements_button_description"]
|
||||
) {
|
||||
showAnnouncementsDialog = true
|
||||
loadAnnouncements()
|
||||
@@ -949,9 +952,24 @@ class HomeRootSection : Routes.Route() {
|
||||
onUpdateAction = onUpdateButtonClick,
|
||||
channelLabel = channelLabel,
|
||||
isPurrAuraActive = isPurrAuraActive,
|
||||
onWikiClick = { context.androidContext.openLink("https://github.com/particle-box/PurrfectSnap/wiki") },
|
||||
onTelegramClick = { context.androidContext.openLink("https://t.me/purrfectsnap_official") },
|
||||
onGithubClick = { context.androidContext.openLink("https://github.com/particle-box/PurrfectSnap") },
|
||||
onWikiClick = {
|
||||
context.androidContext.openLink(
|
||||
"https://github.com/particle-box/PurrfectSnap/wiki",
|
||||
context.translation["toast_open_link_failed"]
|
||||
)
|
||||
},
|
||||
onTelegramClick = {
|
||||
context.androidContext.openLink(
|
||||
"https://t.me/purrfectsnap_official",
|
||||
context.translation["toast_open_link_failed"]
|
||||
)
|
||||
},
|
||||
onGithubClick = {
|
||||
context.androidContext.openLink(
|
||||
"https://github.com/particle-box/PurrfectSnap",
|
||||
context.translation["toast_open_link_failed"]
|
||||
)
|
||||
},
|
||||
authorName = "ETERNAL",
|
||||
onManageClick = { routes.settings.navigate() },
|
||||
avenirNext = avenirNext,
|
||||
@@ -1001,14 +1019,14 @@ class HomeRootSection : Routes.Route() {
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Text(
|
||||
text = "No quick tiles yet",
|
||||
text = translation["quick_actions_empty_title"],
|
||||
fontSize = 20.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = Color.White
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Text(
|
||||
text = "Design your dream grid with the actions you use the most.",
|
||||
text = translation["quick_actions_empty_subtitle"],
|
||||
fontSize = 14.sp,
|
||||
color = Color.White.copy(alpha = 0.75f),
|
||||
textAlign = TextAlign.Center
|
||||
@@ -1027,7 +1045,7 @@ class HomeRootSection : Routes.Route() {
|
||||
modifier = Modifier.size(20.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(6.dp))
|
||||
Text(text = "Add tile")
|
||||
Text(text = translation["quick_actions_add_tile_button"])
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -1048,7 +1066,7 @@ class HomeRootSection : Routes.Route() {
|
||||
overflow = TextOverflow.Clip
|
||||
)
|
||||
Text(
|
||||
text = "${selectedTiles.size} curated shortcuts",
|
||||
text = translation.format("quick_actions_count_label", "count" to selectedTiles.size.toString()),
|
||||
fontSize = 13.sp,
|
||||
color = Color.White.copy(alpha = 0.75f),
|
||||
textAlign = TextAlign.Center
|
||||
@@ -1069,7 +1087,7 @@ class HomeRootSection : Routes.Route() {
|
||||
modifier = Modifier.size(18.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(6.dp))
|
||||
Text(text = "Manage")
|
||||
Text(text = translation["quick_actions_manage_button"])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -661,7 +661,12 @@ class HomeSettings : Routes.Route() {
|
||||
}
|
||||
}.onFailure {
|
||||
context.log.error("Failed to open file", it)
|
||||
context.longToast("Failed to open file! ${it.localizedMessage}")
|
||||
context.longToast(
|
||||
translation.format(
|
||||
"open_file_failed_toast",
|
||||
"message" to (it.localizedMessage ?: "")
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
colors = sharedButtonColors,
|
||||
@@ -728,12 +733,22 @@ class HomeSettings : Routes.Route() {
|
||||
context.log.info("Imported message logger from $uri", "MessageLogger")
|
||||
}.onFailure {
|
||||
context.log.error("Failed to import message logger", it)
|
||||
context.longToast("Import failed: ${it.localizedMessage ?: it.message}")
|
||||
context.longToast(
|
||||
translation.format(
|
||||
"import_failed_toast",
|
||||
"message" to (it.localizedMessage ?: it.message ?: "")
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}.onFailure {
|
||||
context.log.error("Failed to launch import picker", it)
|
||||
context.longToast("Import failed: ${it.localizedMessage ?: it.message}")
|
||||
context.longToast(
|
||||
translation.format(
|
||||
"import_failed_toast",
|
||||
"message" to (it.localizedMessage ?: it.message ?: "")
|
||||
)
|
||||
)
|
||||
}
|
||||
}) {
|
||||
Text(translation["button.import"] ?: "Import")
|
||||
|
||||
@@ -183,7 +183,8 @@ fun ScriptCatalog(root: ScriptingRootSection) {
|
||||
Button(
|
||||
onClick = {
|
||||
context.androidContext.openLink(
|
||||
"https://github.com/particle-box/PurrfectSnap/blob/dev/app/src/main/kotlin/me/eternal/purrfectsnap/ui/manager/pages/scripting/ScriptRepos.md"
|
||||
"https://github.com/particle-box/PurrfectSnap/blob/dev/app/src/main/kotlin/me/eternal/purrfectsnap/ui/manager/pages/scripting/ScriptRepos.md",
|
||||
context.translation["toast_open_link_failed"]
|
||||
)
|
||||
},
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
|
||||
@@ -543,10 +543,24 @@ class ScriptingRootSection : Routes.Route() {
|
||||
if (scriptingFolder == null) showToast = true else showImportDialog = true
|
||||
},
|
||||
onOpenFolder = {
|
||||
if (scriptingFolder == null) showToast = true else scriptingFolder?.let { context.androidContext.openLink(it.uri.toString()) }
|
||||
if (scriptingFolder == null) {
|
||||
showToast = true
|
||||
} else {
|
||||
scriptingFolder?.let {
|
||||
context.androidContext.openLink(
|
||||
it.uri.toString(),
|
||||
context.translation["toast_open_link_failed"]
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
onManageRepos = { routes.manageScriptRepos.navigate() },
|
||||
onDocs = { context.androidContext.openLink("https://github.com/SnapEnhance/scripting-docs") },
|
||||
onDocs = {
|
||||
context.androidContext.openLink(
|
||||
"https://github.com/SnapEnhance/scripting-docs",
|
||||
context.translation["toast_open_link_failed"]
|
||||
)
|
||||
},
|
||||
folderSelected = scriptingFolder != null
|
||||
)
|
||||
Spacer(Modifier.height(12.dp))
|
||||
|
||||
@@ -192,7 +192,7 @@ class LoggedStories : Routes.Route() {
|
||||
content = context.imageLoader.diskCache?.openSnapshot(story.url)?.use {
|
||||
it.data.toFile().absolutePath
|
||||
} ?: run {
|
||||
context.shortToast("Failed to get file")
|
||||
context.shortToast(translation["failed_to_get_file"])
|
||||
return@Button
|
||||
},
|
||||
type = DownloadMediaType.LOCAL_MEDIA,
|
||||
|
||||
@@ -292,8 +292,7 @@ class SocialRootSection : Routes.Route() {
|
||||
IconButton(onClick = { searchQuery = "" }) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Close,
|
||||
contentDescription = context.translation["close_button_description"]
|
||||
?: "Clear search",
|
||||
contentDescription = translation["clear_search_button_description"],
|
||||
tint = Color.White
|
||||
)
|
||||
}
|
||||
@@ -532,12 +531,12 @@ class SocialRootSection : Routes.Route() {
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
StatPill(label = "Friends", value = friendCount)
|
||||
StatPill(label = "Groups", value = groupCount)
|
||||
StatPill(label = translation["friends_tab"], value = friendCount)
|
||||
StatPill(label = translation["groups_tab"], value = groupCount)
|
||||
IconButton(onClick = onSearchToggle) {
|
||||
Icon(
|
||||
imageVector = if (searchActive) Icons.Filled.Close else Icons.Filled.Search,
|
||||
contentDescription = if (searchActive) "Close search" else "Search",
|
||||
contentDescription = if (searchActive) translation["close_search_button_description"] else translation["search_button_description"],
|
||||
tint = Color.White
|
||||
)
|
||||
}
|
||||
|
||||
@@ -248,7 +248,9 @@ class FriendTrackerManagerRoot : Routes.Route() {
|
||||
routes.friendTrackerConfigJsonForImport = content
|
||||
routes.friendTrackerConfigImport.navigate()
|
||||
}.onFailure {
|
||||
context.longToast("Failed to read file: ${it.message}")
|
||||
context.longToast(
|
||||
translation.format("read_file_failed_toast", "message" to (it.message ?: ""))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -591,7 +593,9 @@ class FriendTrackerManagerRoot : Routes.Route() {
|
||||
routes.friendTrackerConfigJsonForImport = content
|
||||
routes.friendTrackerConfigImport.navigate()
|
||||
}.onFailure {
|
||||
context.longToast("Failed to read file: ${it.message}")
|
||||
context.longToast(
|
||||
translation.format("read_file_failed_toast", "message" to (it.message ?: ""))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,6 +206,7 @@ class SetupActivity : ComponentActivity() {
|
||||
|
||||
setContent {
|
||||
val context = LocalContext.current
|
||||
val translation = setupContext.translation
|
||||
val navController = rememberNavController()
|
||||
var canGoNext by remember { mutableStateOf(false) }
|
||||
var lastRoute by rememberSaveable { mutableStateOf("") }
|
||||
@@ -226,16 +227,16 @@ class SetupActivity : ComponentActivity() {
|
||||
if (shouldShowAbiWarning) {
|
||||
AestheticDialog(
|
||||
onDismissRequest = {},
|
||||
title = "Wrong APK installed",
|
||||
title = translation["setup.activity.wrong_apk_title"],
|
||||
text = "",
|
||||
icon = Icons.Filled.Warning,
|
||||
confirmButtonText = "Close",
|
||||
confirmButtonText = translation["setup.activity.close_button"],
|
||||
onConfirm = { (context as? Activity)?.finishAffinity() },
|
||||
showCloseButton = false,
|
||||
opaque = true,
|
||||
customContent = {
|
||||
Text(
|
||||
text = "Your device is armv8, please download the armv8 apk, not armv7.",
|
||||
text = translation["setup.activity.wrong_apk_message"],
|
||||
color = PurrfectPalette.textSecondary,
|
||||
lineHeight = 18.sp
|
||||
)
|
||||
@@ -344,7 +345,14 @@ class SetupActivity : ComponentActivity() {
|
||||
.background(Color.Transparent)
|
||||
) {
|
||||
if (showImportantDialog) {
|
||||
val confirmLabel = if (importantTimeout > 0) "I understand (${importantTimeout}s)" else "I understand"
|
||||
val confirmLabel = if (importantTimeout > 0) {
|
||||
translation.format(
|
||||
"setup.activity.important_confirm_timeout",
|
||||
"seconds" to importantTimeout.toString()
|
||||
)
|
||||
} else {
|
||||
translation["setup.activity.important_confirm"]
|
||||
}
|
||||
AestheticDialog(
|
||||
onDismissRequest = {
|
||||
if (importantTimeout == 0) {
|
||||
@@ -352,7 +360,7 @@ class SetupActivity : ComponentActivity() {
|
||||
setupPrefs.edit().putBoolean("setup_important_notice_shown", true).apply()
|
||||
}
|
||||
},
|
||||
title = "Important!",
|
||||
title = translation["setup.activity.important_title"],
|
||||
text = "",
|
||||
icon = Icons.Filled.Warning,
|
||||
confirmButtonText = confirmLabel,
|
||||
@@ -366,7 +374,7 @@ class SetupActivity : ComponentActivity() {
|
||||
showCloseButton = false,
|
||||
customContent = {
|
||||
Text(
|
||||
text = "If you have used SnapEnhance or any other mod besides PurrfectSnap, we recommend uninstalling everything and staying on stock Snapchat for one week. Then switch to PurrfectSnap after next Friday.",
|
||||
text = translation["setup.activity.important_message"],
|
||||
color = PurrfectPalette.textSecondary,
|
||||
lineHeight = 18.sp
|
||||
)
|
||||
@@ -471,50 +479,50 @@ private fun SetupScreen.meta(context: RemoteSideContext): SetupStepMeta {
|
||||
return when (this) {
|
||||
is PickLanguageScreen -> SetupStepMeta(
|
||||
route = route,
|
||||
title = translation["setup.dialogs.select_language"] ?: "Choose your language",
|
||||
subtitle = "Tune PurrfectSnap to speak your voice before anything else.",
|
||||
title = translation["setup.dialogs.select_language"],
|
||||
subtitle = translation["setup.activity.language_subtitle"],
|
||||
icon = Icons.Filled.Language
|
||||
)
|
||||
|
||||
is InstallModeScreen -> SetupStepMeta(
|
||||
route = route,
|
||||
title = "Choose your device",
|
||||
subtitle = "Pick the path that matches how you'll install PurrfectSnap.",
|
||||
title = translation["setup.activity.install_mode_title"],
|
||||
subtitle = translation["setup.activity.install_mode_subtitle"],
|
||||
icon = Icons.Filled.VerifiedUser
|
||||
)
|
||||
|
||||
is PermissionsScreen -> SetupStepMeta(
|
||||
route = route,
|
||||
title = translation["setup.permissions.dialog"] ?: "Essential permissions",
|
||||
subtitle = "Grant the essentials so overlays, downloads, and alerts stay reliable.",
|
||||
title = translation["setup.permissions.dialog"],
|
||||
subtitle = translation["setup.activity.permissions_subtitle"],
|
||||
icon = Icons.Filled.VerifiedUser
|
||||
)
|
||||
|
||||
is PatchSnapchatScreen -> SetupStepMeta(
|
||||
route = route,
|
||||
title = "Auto Patcher",
|
||||
subtitle = "Streamlined download, patch, and install with a single flow.",
|
||||
title = translation["setup.activity.patch_title"],
|
||||
subtitle = translation["setup.activity.patch_subtitle"],
|
||||
icon = Icons.Filled.Download
|
||||
)
|
||||
|
||||
is RootInstallSnapchatScreen -> SetupStepMeta(
|
||||
route = route,
|
||||
title = "Snapchat Installer",
|
||||
subtitle = "Download and install the recommended Snapchat build.",
|
||||
title = translation["setup.activity.root_install_title"],
|
||||
subtitle = translation["setup.activity.root_install_subtitle"],
|
||||
icon = Icons.Filled.Download
|
||||
)
|
||||
|
||||
is SaveFolderScreen -> SetupStepMeta(
|
||||
route = route,
|
||||
title = translation["setup.dialogs.save_folder"] ?: "Where should we save?",
|
||||
subtitle = "Pick your personal vault so snaps land exactly where you expect.",
|
||||
title = translation["setup.dialogs.save_folder"],
|
||||
subtitle = translation["setup.activity.save_folder_subtitle"],
|
||||
icon = Icons.Filled.Folder
|
||||
)
|
||||
|
||||
is MappingsScreen -> SetupStepMeta(
|
||||
route = route,
|
||||
title = translation["setup.mappings.dialog"] ?: "Mapping your Snapchat",
|
||||
subtitle = "We calibrate everything to your install so the magic works flawlessly.",
|
||||
title = translation["setup.mappings.dialog"],
|
||||
subtitle = translation["setup.activity.mappings_subtitle"],
|
||||
icon = Icons.Filled.AutoAwesome
|
||||
)
|
||||
|
||||
@@ -648,7 +656,11 @@ private fun SetupHeader(
|
||||
tint = Color.White
|
||||
)
|
||||
Text(
|
||||
text = "Step ${currentIndex + 1} of $total",
|
||||
text = translation.format(
|
||||
"setup.activity.step_counter",
|
||||
"current" to (currentIndex + 1).toString(),
|
||||
"total" to total.toString()
|
||||
),
|
||||
color = Color.White,
|
||||
fontWeight = FontWeight.Medium,
|
||||
fontSize = 13.sp
|
||||
@@ -723,6 +735,7 @@ private fun StepBadgesRow(steps: List<SetupStepMeta>, currentStep: Int) {
|
||||
|
||||
@Composable
|
||||
private fun StepBadge(step: SetupStepMeta, state: StepState) {
|
||||
val translation = SharedContextHolder.remote(LocalContext.current).translation
|
||||
val baseColor = when (state) {
|
||||
StepState.COMPLETE -> PurrfectPalette.glowSecondary
|
||||
StepState.ACTIVE -> PurrfectPalette.glowPrimary
|
||||
@@ -774,9 +787,9 @@ private fun StepBadge(step: SetupStepMeta, state: StepState) {
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
val hint = when (state) {
|
||||
StepState.COMPLETE -> "Checked off"
|
||||
StepState.ACTIVE -> "In progress"
|
||||
StepState.UPCOMING -> "Ready next"
|
||||
StepState.COMPLETE -> translation["setup.activity.step_complete"]
|
||||
StepState.ACTIVE -> translation["setup.activity.step_active"]
|
||||
StepState.UPCOMING -> translation["setup.activity.step_upcoming"]
|
||||
}
|
||||
Text(
|
||||
text = hint,
|
||||
@@ -868,6 +881,7 @@ private fun NextButton(
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val translation = SharedContextHolder.remote(LocalContext.current).translation
|
||||
val alpha by animateFloatAsState(targetValue = if (enabled) 1f else 0.6f, label = "NextButtonAlpha")
|
||||
val gradient = Brush.horizontalGradient(
|
||||
listOf(
|
||||
@@ -903,7 +917,11 @@ private fun NextButton(
|
||||
horizontalArrangement = Arrangement.spacedBy(10.dp)
|
||||
) {
|
||||
Text(
|
||||
text = if (isFinalStep) "Finish setup" else "Continue",
|
||||
text = if (isFinalStep) {
|
||||
translation["setup.activity.finish_button"]
|
||||
} else {
|
||||
translation["setup.activity.continue_button"]
|
||||
},
|
||||
color = Color.White,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
fontSize = 16.sp
|
||||
|
||||
@@ -98,10 +98,14 @@ class InstallModeScreen(
|
||||
}
|
||||
|
||||
if (showGuides) {
|
||||
val confirmLabel = if (timeout > 0) "I understand (${timeout}s)" else "I understand"
|
||||
val confirmLabel = if (timeout > 0) {
|
||||
context.translation.format("setup.install_mode.confirm_timeout", "seconds" to timeout.toString())
|
||||
} else {
|
||||
context.translation["setup.install_mode.confirm"]
|
||||
}
|
||||
AestheticDialog(
|
||||
onDismissRequest = { if (timeout == 0) showGuides = false },
|
||||
title = "Please note!",
|
||||
title = context.translation["setup.install_mode.notice_title"],
|
||||
text = "",
|
||||
icon = Icons.Filled.Warning,
|
||||
confirmButtonText = confirmLabel,
|
||||
@@ -138,39 +142,39 @@ class InstallModeScreen(
|
||||
verticalArrangement = Arrangement.spacedBy(10.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "Select the type of device you have: rooted or non-rooted. If you are unsure, choose Non-root and continue.",
|
||||
text = context.translation["setup.install_mode.notice_intro"],
|
||||
style = bodyStyle,
|
||||
textAlign = TextAlign.Start,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Text(
|
||||
text = "Non-rooted devices",
|
||||
text = context.translation["setup.install_mode.notice_non_root_title"],
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = Color.White,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
textAlign = TextAlign.Start
|
||||
)
|
||||
Text(
|
||||
text = "Select Non-root and the app will handle everything. Tap Install Patched Snapchat when it appears. After it installs, do not open Snapchat yet. Continue the PurrfectSnap setup; once it finishes, you can open Snapchat and enjoy.",
|
||||
text = context.translation["setup.install_mode.notice_non_root_body"],
|
||||
style = bodyStyle,
|
||||
textAlign = TextAlign.Start,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Text(
|
||||
text = "Rooted devices",
|
||||
text = context.translation["setup.install_mode.notice_root_title"],
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = Color.White,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
textAlign = TextAlign.Start
|
||||
)
|
||||
Text(
|
||||
text = "Make sure you have flashed LSPosed first. We recommend JingMatrix LSPosed or LSPosed Irena. After you select Root, the app will install the recommended Snapchat version. Do not open it yet; continue the PurrfectSnap setup. When setup finishes, enable PurrfectSnap in LSPosed and reboot your phone. Then start using Snapchat. We highly recommend detaching Snapchat from the Play Store with the Zygisk Detach module to prevent auto-updates.",
|
||||
text = context.translation["setup.install_mode.notice_root_body"],
|
||||
style = bodyStyle,
|
||||
textAlign = TextAlign.Start,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Text(
|
||||
text = "If you run into any installation issues, the solution will appear here. Please read it carefully.",
|
||||
text = context.translation["setup.install_mode.notice_issues_hint"],
|
||||
style = bodyStyle,
|
||||
textAlign = TextAlign.Start,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
@@ -178,9 +182,9 @@ class InstallModeScreen(
|
||||
Text(
|
||||
text = buildAnnotatedString {
|
||||
withStyle(SpanStyle(fontWeight = FontWeight.Bold, color = Color.White)) {
|
||||
append("Note: ")
|
||||
append(context.translation["setup.install_mode.notice_note_prefix"])
|
||||
}
|
||||
append("New Accounts easily get locked! It is recommended to use an older account with PurrfectSnap.")
|
||||
append(context.translation["setup.install_mode.notice_note_body"])
|
||||
},
|
||||
style = bodyStyle,
|
||||
textAlign = TextAlign.Start,
|
||||
@@ -194,8 +198,8 @@ class InstallModeScreen(
|
||||
|
||||
SetupCard {
|
||||
StepTitle(
|
||||
title = "Choose your device",
|
||||
subtitle = "If you don't know, select Non-rooted device and proceed.",
|
||||
title = context.translation["setup.install_mode.step_title"],
|
||||
subtitle = context.translation["setup.install_mode.step_subtitle"],
|
||||
modifier = Modifier.align(Alignment.CenterHorizontally)
|
||||
)
|
||||
Column(
|
||||
@@ -203,8 +207,8 @@ class InstallModeScreen(
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
ModeOption(
|
||||
title = "Rooted device",
|
||||
subtitle = "Use Lsposed and skip auto patching.",
|
||||
title = context.translation["setup.install_mode.root_option_title"],
|
||||
subtitle = context.translation["setup.install_mode.root_option_subtitle"],
|
||||
icon = Icons.Filled.VerifiedUser,
|
||||
accent = Brush.horizontalGradient(
|
||||
listOf(
|
||||
@@ -219,8 +223,8 @@ class InstallModeScreen(
|
||||
}
|
||||
)
|
||||
ModeOption(
|
||||
title = "Non-rooted device",
|
||||
subtitle = "Use included auto patcher to install patched Snapchat.",
|
||||
title = context.translation["setup.install_mode.non_root_option_title"],
|
||||
subtitle = context.translation["setup.install_mode.non_root_option_subtitle"],
|
||||
icon = Icons.Filled.Shield,
|
||||
accent = Brush.horizontalGradient(
|
||||
listOf(
|
||||
@@ -262,7 +266,7 @@ class InstallModeScreen(
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Text(
|
||||
text = "Skip Auto Setup",
|
||||
text = context.translation["setup.install_mode.skip_auto_setup"],
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = Color.White,
|
||||
|
||||
@@ -52,6 +52,7 @@ class MappingsScreen : SetupScreen() {
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val translation = context.translation
|
||||
var infoText by remember { mutableStateOf(null as String?) }
|
||||
var isGenerating by remember { mutableStateOf(false) }
|
||||
var showCompletionNotice by remember { mutableStateOf(false) }
|
||||
@@ -98,13 +99,16 @@ class MappingsScreen : SetupScreen() {
|
||||
|
||||
if (showCompletionNotice) {
|
||||
val confirmLabel = if (completionCountdown > 0) {
|
||||
"I understand (${completionCountdown}s)"
|
||||
translation.format(
|
||||
"setup.mappings.confirm_understand_timeout",
|
||||
"seconds" to completionCountdown.toString()
|
||||
)
|
||||
} else {
|
||||
"I understand"
|
||||
translation["setup.mappings.confirm_understand"]
|
||||
}
|
||||
AestheticDialog(
|
||||
onDismissRequest = { if (completionCountdown == 0) { showCompletionNotice = false; goNext() } },
|
||||
title = "Please note!",
|
||||
title = translation["setup.mappings.notice_title"],
|
||||
text = "",
|
||||
icon = Icons.Filled.Warning,
|
||||
confirmButtonText = confirmLabel,
|
||||
@@ -141,27 +145,27 @@ class MappingsScreen : SetupScreen() {
|
||||
verticalArrangement = Arrangement.spacedBy(10.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "If you see the \"Account temporarily disabled\" error while logging in, do not worry. Follow these steps in order:",
|
||||
text = translation["setup.mappings.notice_intro"],
|
||||
style = bodyStyle,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Text(
|
||||
text = "1. Reopen Snapchat and log in. This fixes it most of the time.",
|
||||
text = translation["setup.mappings.notice_step_1"],
|
||||
style = bodyStyle,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Text(
|
||||
text = "2. If it still fails, tap the login button repeatedly. This usually covers the next chunk.",
|
||||
text = translation["setup.mappings.notice_step_2"],
|
||||
style = bodyStyle,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Text(
|
||||
text = "3. If it still fails, clear Snapchat's data, disable any VPN, and log in again.",
|
||||
text = translation["setup.mappings.notice_step_3"],
|
||||
style = bodyStyle,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Text(
|
||||
text = "For rooted users:",
|
||||
text = translation["setup.mappings.notice_rooted_title"],
|
||||
style = MaterialTheme.typography.bodyMedium.copy(
|
||||
color = Color.White,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
@@ -170,7 +174,7 @@ class MappingsScreen : SetupScreen() {
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Text(
|
||||
text = "Reopen Snapchat and log in. If it still fails, disable PurrfectSnap in LSPosed, log in, then re-enable PurrfectSnap.",
|
||||
text = translation["setup.mappings.notice_rooted_body"],
|
||||
style = bodyStyle,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
@@ -193,7 +197,11 @@ class MappingsScreen : SetupScreen() {
|
||||
|
||||
if (warnings.isNotEmpty()) {
|
||||
isGenerating = false
|
||||
infoText = "${warnings.size} warning(s) occurred while generating mappings:\n\n${warnings.joinToString("\n")}".also {
|
||||
infoText = translation.format(
|
||||
"setup.mappings.warnings_info",
|
||||
"count" to warnings.size.toString(),
|
||||
"warnings" to warnings.joinToString("\n")
|
||||
).also {
|
||||
context.log.warn(it)
|
||||
}
|
||||
return@launch
|
||||
@@ -216,7 +224,7 @@ class MappingsScreen : SetupScreen() {
|
||||
subtitle = null
|
||||
)
|
||||
DialogText(
|
||||
text = "This only takes a moment. Keep the app open while magic happens!"
|
||||
text = translation["setup.mappings.progress_hint"]
|
||||
)
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
Surface(
|
||||
|
||||
@@ -71,6 +71,7 @@ import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import me.eternal.purrfectsnap.common.bridge.wrapper.LocaleWrapper
|
||||
import me.eternal.purrfectsnap.setup.patch.AutoPatchServer
|
||||
import me.eternal.purrfectsnap.setup.patch.LSPatch
|
||||
import me.eternal.purrfectsnap.ui.manager.components.AestheticDialog
|
||||
@@ -93,7 +94,8 @@ class PatchSnapchatScreen : SetupScreen() {
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val logs = remember { mutableStateListOf("Auto Patcher is ready.") }
|
||||
val translation = context.translation
|
||||
val logs = remember { mutableStateListOf(translation["setup.patch.ready_log"]) }
|
||||
@Suppress("DEPRECATION")
|
||||
val clipboard = LocalClipboardManager.current
|
||||
var progress by remember { mutableFloatStateOf(-1f) }
|
||||
@@ -146,7 +148,7 @@ class PatchSnapchatScreen : SetupScreen() {
|
||||
repeat(80) {
|
||||
if (isSnapchatInstalledAfter(patchStartedAt)) {
|
||||
installVerified = true
|
||||
pushLog("Snapchat install confirmed. You're cleared to continue.")
|
||||
pushLog(translation["setup.patch.install_confirmed_log"])
|
||||
return@launch
|
||||
}
|
||||
delay(1200)
|
||||
@@ -179,7 +181,12 @@ class PatchSnapchatScreen : SetupScreen() {
|
||||
|
||||
suspend fun downloadSnapchatFromAutoPatchServer(): File? = withContext(Dispatchers.IO) {
|
||||
val latestApk = autoPatchServer.fetchLatestSnapchatApk() ?: return@withContext null
|
||||
pushStatus("Downloading recommended Snapchat version (${latestApk.tagName})...")
|
||||
pushStatus(
|
||||
translation.format(
|
||||
"setup.patch.download_recommended_status",
|
||||
"version" to latestApk.tagName
|
||||
)
|
||||
)
|
||||
|
||||
okHttpClient.newCall(Request.Builder().url(latestApk.downloadUrl).build()).execute().use { response ->
|
||||
if (!response.isSuccessful) return@withContext null
|
||||
@@ -223,22 +230,27 @@ class PatchSnapchatScreen : SetupScreen() {
|
||||
downloadFinished = false
|
||||
patchStartedAt = System.currentTimeMillis()
|
||||
logs.clear()
|
||||
pushLog("Starting Auto Patcher for recommended Snapchat version.")
|
||||
pushLog(translation["setup.patch.starting_log"])
|
||||
runCatching {
|
||||
if (isSnapchatInstalled()) {
|
||||
pushStatus("Snapchat is installed. Please uninstall it first (don't keep data), then start Auto Patcher again.")
|
||||
throw IllegalStateException("Snapchat still installed. Uninstall it first, to continue.")
|
||||
pushStatus(translation["setup.patch.uninstall_prompt_status"])
|
||||
throw IllegalStateException(translation["setup.patch.uninstall_prompt_error"])
|
||||
}
|
||||
val modulePath = context.androidContext.packageManager.getPackageInfo(
|
||||
context.androidContext.packageName, 0
|
||||
).applicationInfo?.sourceDir ?: throw IllegalStateException("Module apk not found")
|
||||
pushStatus("Fetching recommended Snapchat APK...")
|
||||
).applicationInfo?.sourceDir ?: throw IllegalStateException(translation["setup.patch.module_apk_not_found_error"])
|
||||
pushStatus(translation["setup.patch.fetching_apk_status"])
|
||||
val downloaded = downloadSnapchatFromAutoPatchServer()
|
||||
?: throw IllegalStateException("Download failed")
|
||||
?: throw IllegalStateException(translation["setup.patch.download_failed_error"])
|
||||
downloadedApkPath = downloaded.absolutePath
|
||||
pushStatus("Download completed: ${downloaded.name}")
|
||||
pushStatus(
|
||||
translation.format(
|
||||
"setup.patch.download_completed_status",
|
||||
"fileName" to downloaded.name
|
||||
)
|
||||
)
|
||||
downloadFinished = true
|
||||
pushStatus("Starting patch powered by Jingmatrix Lspatch")
|
||||
pushStatus(translation["setup.patch.starting_patch_status"])
|
||||
val lsPatch = LSPatch(
|
||||
context.androidContext,
|
||||
mapOf(context.androidContext.packageName to File(modulePath)),
|
||||
@@ -247,16 +259,22 @@ class PatchSnapchatScreen : SetupScreen() {
|
||||
)
|
||||
val outputs = withContext(Dispatchers.IO) { lsPatch.patchSplits(listOf(downloaded)) }
|
||||
val patched = outputs["base.apk"] ?: outputs.values.firstOrNull()
|
||||
?: throw IllegalStateException("Patched apk not produced")
|
||||
?: throw IllegalStateException(translation["setup.patch.patched_not_produced_error"])
|
||||
patchedApkPath = patched.absolutePath
|
||||
pushStatus("Patched build ready. Install to finish.")
|
||||
pushStatus(translation["setup.patch.patched_ready_status"])
|
||||
}.onFailure {
|
||||
val message = it.message ?: it.toString()
|
||||
error = it.message ?: it.toString()
|
||||
it.stackTraceToString()
|
||||
.lineSequence()
|
||||
.filter { line -> line.isNotBlank() }
|
||||
.forEach { line -> pushLog(line) }
|
||||
pushStatus("Failed: ${it.message}")
|
||||
pushStatus(
|
||||
translation.format(
|
||||
"setup.patch.failed_status",
|
||||
"message" to message
|
||||
)
|
||||
)
|
||||
}
|
||||
isRunning = false
|
||||
progress = -1f
|
||||
@@ -282,7 +300,7 @@ class PatchSnapchatScreen : SetupScreen() {
|
||||
fun markAlreadyInstalled() {
|
||||
installRequested = false
|
||||
installVerified = true
|
||||
pushLog("Marked as installed manually. You're cleared to continue.")
|
||||
pushLog(translation["setup.patch.mark_installed_log"])
|
||||
}
|
||||
|
||||
val accent = remember {
|
||||
@@ -297,10 +315,10 @@ class PatchSnapchatScreen : SetupScreen() {
|
||||
if (showIssuesDialog) {
|
||||
AestheticDialog(
|
||||
onDismissRequest = { showIssuesDialog = false },
|
||||
title = "Facing issues?",
|
||||
title = translation["setup.patch.issues_title"],
|
||||
text = "",
|
||||
icon = Icons.Filled.Info,
|
||||
confirmButtonText = "Got it",
|
||||
confirmButtonText = translation["setup.patch.issues_confirm"],
|
||||
onConfirm = { showIssuesDialog = false },
|
||||
showCloseButton = false,
|
||||
customContent = {
|
||||
@@ -316,36 +334,36 @@ class PatchSnapchatScreen : SetupScreen() {
|
||||
verticalArrangement = Arrangement.spacedBy(10.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "How to fix installation errors",
|
||||
text = translation["setup.patch.issues_heading"],
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = Color.White,
|
||||
textAlign = TextAlign.Start,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Text(
|
||||
text = "Issue: App cannot be installed because it conflicts with an existing package.",
|
||||
text = translation["setup.patch.issues_conflict_issue"],
|
||||
style = bodyStyle,
|
||||
textAlign = TextAlign.Start
|
||||
)
|
||||
Text(
|
||||
text = "Fix: Download Snapchat from the Play Store and uninstall it without keeping data. Run Auto Patcher again. If it still does not work, run:",
|
||||
text = translation["setup.patch.issues_conflict_fix"],
|
||||
style = bodyStyle,
|
||||
textAlign = TextAlign.Start
|
||||
)
|
||||
Text(
|
||||
text = "adb uninstall com.snapchat.android",
|
||||
text = translation["setup.patch.issues_adb_command"],
|
||||
style = bodyStyle,
|
||||
textAlign = TextAlign.Start,
|
||||
softWrap = false,
|
||||
modifier = Modifier.horizontalScroll(rememberScrollState())
|
||||
)
|
||||
Text(
|
||||
text = "Issue: App not installed because the package appears to be invalid.",
|
||||
text = translation["setup.patch.issues_invalid_issue"],
|
||||
style = bodyStyle,
|
||||
textAlign = TextAlign.Start
|
||||
)
|
||||
Text(
|
||||
text = "Fix: Download and install JingMatrix LSPatch, then patch a Snapchat version (any one) from this range, i.e. between 13.65.1.0 and 13.71.0.51, in Integrated mode. Select Embed Modules and embed the PurrfectSnap APK. Then choose Skip auto setup during PurrfectSnap setup to skip Auto Patcher.",
|
||||
text = translation["setup.patch.issues_invalid_fix"],
|
||||
style = bodyStyle,
|
||||
textAlign = TextAlign.Start
|
||||
)
|
||||
@@ -356,12 +374,12 @@ class PatchSnapchatScreen : SetupScreen() {
|
||||
|
||||
SetupCard {
|
||||
StepTitle(
|
||||
title = "Auto Patcher",
|
||||
title = translation["setup.patch.title"],
|
||||
subtitle = null,
|
||||
modifier = Modifier.align(Alignment.CenterHorizontally),
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
JingmatrixBadge(accent)
|
||||
JingmatrixBadge(accent, translation)
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
shape = RoundedCornerShape(24.dp),
|
||||
@@ -392,9 +410,12 @@ class PatchSnapchatScreen : SetupScreen() {
|
||||
val isPatching = isRunning && downloadFinished && !isDownloading
|
||||
Text(
|
||||
text = when {
|
||||
isDownloading -> "Downloading Snapchat ${(progress * 100).toInt()}%"
|
||||
isPatching -> "Patching..."
|
||||
else -> "Initializing..."
|
||||
isDownloading -> translation.format(
|
||||
"setup.patch.status_downloading",
|
||||
"percent" to (progress * 100).toInt().toString()
|
||||
)
|
||||
isPatching -> translation["setup.patch.status_patching"]
|
||||
else -> translation["setup.patch.status_initializing"]
|
||||
},
|
||||
color = PurrfectPalette.textPrimary,
|
||||
fontWeight = FontWeight.Medium
|
||||
@@ -426,9 +447,10 @@ class PatchSnapchatScreen : SetupScreen() {
|
||||
logs = logs,
|
||||
pulse = logPulse,
|
||||
accent = accent,
|
||||
translation = translation,
|
||||
onCopy = {
|
||||
clipboard.setText(AnnotatedString(logs.joinToString("\n")))
|
||||
pushLog("Logs copied to clipboard.")
|
||||
pushLog(translation["setup.patch.logs_copied"])
|
||||
}
|
||||
)
|
||||
|
||||
@@ -463,7 +485,7 @@ class PatchSnapchatScreen : SetupScreen() {
|
||||
tint = Color.White
|
||||
)
|
||||
Text(
|
||||
text = "Patched APK installed",
|
||||
text = translation["setup.patch.install_success"],
|
||||
color = Color.White,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
@@ -472,7 +494,7 @@ class PatchSnapchatScreen : SetupScreen() {
|
||||
} else {
|
||||
if (patchedApk == null) {
|
||||
GradientActionButton(
|
||||
label = "Start auto patch",
|
||||
label = translation["setup.patch.start_button"],
|
||||
icon = Icons.Filled.Download,
|
||||
onClick = { startPatch() },
|
||||
enabled = !isRunning
|
||||
@@ -480,7 +502,7 @@ class PatchSnapchatScreen : SetupScreen() {
|
||||
}
|
||||
if (patchedApk != null) {
|
||||
GradientActionButton(
|
||||
label = "Install patched Snapchat",
|
||||
label = translation["setup.patch.install_button"],
|
||||
icon = Icons.Filled.Verified,
|
||||
onClick = { installPatchedApk() },
|
||||
enabled = true
|
||||
@@ -509,7 +531,7 @@ class PatchSnapchatScreen : SetupScreen() {
|
||||
tint = Color.White.copy(alpha = 0.9f)
|
||||
)
|
||||
Text(
|
||||
text = "Facing issues?",
|
||||
text = translation["setup.patch.issues_title"],
|
||||
color = Color.White,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
@@ -539,7 +561,7 @@ class PatchSnapchatScreen : SetupScreen() {
|
||||
tint = Color.White.copy(alpha = 0.9f)
|
||||
)
|
||||
Text(
|
||||
text = "Already Installed?",
|
||||
text = translation["setup.patch.already_installed_button"],
|
||||
color = Color.White,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
@@ -555,7 +577,10 @@ class PatchSnapchatScreen : SetupScreen() {
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun JingmatrixBadge(accent: Brush) {
|
||||
private fun JingmatrixBadge(
|
||||
accent: Brush,
|
||||
translation: LocaleWrapper
|
||||
) {
|
||||
Surface(
|
||||
shape = RoundedCornerShape(18.dp),
|
||||
color = Color.White.copy(alpha = 0.06f),
|
||||
@@ -581,7 +606,7 @@ private fun JingmatrixBadge(accent: Brush) {
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = "Powered by Jingmatrix Lspatch",
|
||||
text = translation["setup.patch.powered_by_label"],
|
||||
color = Color.White,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
@@ -647,6 +672,7 @@ private fun LogsPanel(
|
||||
logs: List<String>,
|
||||
pulse: Float,
|
||||
accent: Brush,
|
||||
translation: LocaleWrapper,
|
||||
onCopy: () -> Unit
|
||||
) {
|
||||
var expanded by rememberSaveable { mutableStateOf(false) }
|
||||
@@ -685,7 +711,7 @@ private fun LogsPanel(
|
||||
horizontalArrangement = Arrangement.spacedBy(6.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "Logs",
|
||||
text = translation["setup.patch.logs_title"],
|
||||
color = Color.White,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
@@ -715,7 +741,7 @@ private fun LogsPanel(
|
||||
modifier = Modifier.size(14.dp)
|
||||
)
|
||||
Text(
|
||||
text = "Copy",
|
||||
text = translation["setup.patch.copy_button"],
|
||||
color = Color.White,
|
||||
fontWeight = FontWeight.Medium,
|
||||
fontSize = 12.sp
|
||||
@@ -727,7 +753,10 @@ private fun LogsPanel(
|
||||
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||
logs.forEach { line ->
|
||||
Text(
|
||||
text = "- $line",
|
||||
text = translation.format(
|
||||
"setup.patch.log_line_prefix",
|
||||
"line" to line
|
||||
),
|
||||
color = PurrfectPalette.textPrimary,
|
||||
fontSize = 13.sp,
|
||||
lineHeight = 16.sp
|
||||
|
||||
@@ -73,9 +73,9 @@ class PermissionsScreen : SetupScreen() {
|
||||
|
||||
private fun descriptionFor(key: String): String {
|
||||
return when (key) {
|
||||
"notification_access" -> "Alerts you the second downloads finish."
|
||||
"battery_optimization" -> "Keeps background tasks alive without being killed."
|
||||
"display_over_other_apps" -> "Enables floating overlays while you are in Snapchat."
|
||||
"notification_access" -> context.translation["setup.permissions.notification_access_description"]
|
||||
"battery_optimization" -> context.translation["setup.permissions.battery_optimization_description"]
|
||||
"display_over_other_apps" -> context.translation["setup.permissions.display_over_other_apps_description"]
|
||||
else -> ""
|
||||
}
|
||||
}
|
||||
@@ -178,7 +178,7 @@ class PermissionsScreen : SetupScreen() {
|
||||
modifier = Modifier.padding(end = 6.dp)
|
||||
)
|
||||
Text(
|
||||
text = "Granted",
|
||||
text = context.translation["setup.permissions.granted_label"],
|
||||
color = Color.White,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
|
||||
@@ -151,7 +151,7 @@ class PickLanguageScreen : SetupScreen() {
|
||||
modifier = Modifier.weight(1f)
|
||||
) {
|
||||
Text(
|
||||
text = "Current selection",
|
||||
text = context.translation["setup.pick_language.current_selection"],
|
||||
fontSize = 14.sp,
|
||||
color = PurrfectPalette.textSecondary
|
||||
)
|
||||
@@ -180,9 +180,9 @@ class PickLanguageScreen : SetupScreen() {
|
||||
contentColor = Color.White
|
||||
)
|
||||
) {
|
||||
Text(text = "Browse languages")
|
||||
Text(text = context.translation["setup.pick_language.browse_languages"])
|
||||
}
|
||||
DialogText(text = "You can change this anytime from PurrfectSnap settings.")
|
||||
DialogText(text = context.translation["setup.pick_language.change_anytime_hint"])
|
||||
}
|
||||
|
||||
if (isDialog) {
|
||||
@@ -196,7 +196,7 @@ class PickLanguageScreen : SetupScreen() {
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
StepTitle(
|
||||
title = "Available Languages",
|
||||
title = context.translation["setup.pick_language.available_languages"],
|
||||
subtitle = null,
|
||||
modifier = Modifier.align(Alignment.CenterHorizontally),
|
||||
textAlign = TextAlign.Center
|
||||
|
||||
@@ -66,6 +66,7 @@ import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import me.eternal.purrfectsnap.common.bridge.wrapper.LocaleWrapper
|
||||
import me.eternal.purrfectsnap.setup.patch.AutoPatchServer
|
||||
import me.eternal.purrfectsnap.ui.manager.theme.PurrfectPalette
|
||||
import me.eternal.purrfectsnap.ui.setup.screens.SetupScreen
|
||||
@@ -86,7 +87,8 @@ class RootInstallSnapchatScreen : SetupScreen() {
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val logs = remember { mutableStateListOf("Snapchat installer is ready.") }
|
||||
val translation = context.translation
|
||||
val logs = remember { mutableStateListOf(translation["setup.root_install.ready_log"]) }
|
||||
@Suppress("DEPRECATION")
|
||||
val clipboard = LocalClipboardManager.current
|
||||
var progress by remember { mutableFloatStateOf(-1f) }
|
||||
@@ -136,7 +138,7 @@ class RootInstallSnapchatScreen : SetupScreen() {
|
||||
repeat(80) {
|
||||
if (isSnapchatInstalledAfter(downloadStartedAt)) {
|
||||
installVerified = true
|
||||
pushLog("Snapchat install confirmed. You're cleared to continue.")
|
||||
pushLog(translation["setup.root_install.install_confirmed_log"])
|
||||
return@launch
|
||||
}
|
||||
delay(1200)
|
||||
@@ -168,7 +170,12 @@ class RootInstallSnapchatScreen : SetupScreen() {
|
||||
|
||||
suspend fun downloadSnapchatFromAutoPatchServer(): File? = withContext(Dispatchers.IO) {
|
||||
val latestApk = autoPatchServer.fetchLatestSnapchatApk() ?: return@withContext null
|
||||
pushStatus("Downloading recommended Snapchat version (${latestApk.tagName})...")
|
||||
pushStatus(
|
||||
translation.format(
|
||||
"setup.root_install.download_recommended_status",
|
||||
"version" to latestApk.tagName
|
||||
)
|
||||
)
|
||||
|
||||
okHttpClient.newCall(Request.Builder().url(latestApk.downloadUrl).build()).execute().use { response ->
|
||||
if (!response.isSuccessful) return@withContext null
|
||||
@@ -218,7 +225,7 @@ class RootInstallSnapchatScreen : SetupScreen() {
|
||||
fun markAlreadyInstalled() {
|
||||
installRequested = false
|
||||
installVerified = true
|
||||
pushLog("Marked as installed manually. You're cleared to continue.")
|
||||
pushLog(translation["setup.root_install.mark_installed_log"])
|
||||
}
|
||||
|
||||
fun startDownloadAndInstall() {
|
||||
@@ -233,27 +240,38 @@ class RootInstallSnapchatScreen : SetupScreen() {
|
||||
downloadFinished = false
|
||||
downloadStartedAt = System.currentTimeMillis()
|
||||
logs.clear()
|
||||
pushLog("Starting Snapchat download for rooted install.")
|
||||
pushLog(translation["setup.root_install.start_download_log"])
|
||||
runCatching {
|
||||
if (isSnapchatInstalled()) {
|
||||
pushStatus("Snapchat is installed. Please uninstall it first (don't keep data), then try again.")
|
||||
throw IllegalStateException("Snapchat still installed. Uninstall it first, to continue.")
|
||||
pushStatus(translation["setup.root_install.uninstall_prompt_status"])
|
||||
throw IllegalStateException(translation["setup.root_install.uninstall_prompt_error"])
|
||||
}
|
||||
pushStatus("Fetching recommended Snapchat APK...")
|
||||
pushStatus(translation["setup.root_install.fetching_apk_status"])
|
||||
val downloaded = downloadSnapchatFromAutoPatchServer()
|
||||
?: throw IllegalStateException("Download failed")
|
||||
?: throw IllegalStateException(translation["setup.root_install.download_failed_error"])
|
||||
downloadedApkPath = downloaded.absolutePath
|
||||
pushStatus("Download completed: ${downloaded.name}")
|
||||
pushStatus(
|
||||
translation.format(
|
||||
"setup.root_install.download_completed_status",
|
||||
"fileName" to downloaded.name
|
||||
)
|
||||
)
|
||||
downloadFinished = true
|
||||
pushStatus("Launching installer...")
|
||||
pushStatus(translation["setup.root_install.launching_installer_status"])
|
||||
installDownloadedApk()
|
||||
}.onFailure {
|
||||
val message = it.message ?: it.toString()
|
||||
error = it.message ?: it.toString()
|
||||
it.stackTraceToString()
|
||||
.lineSequence()
|
||||
.filter { line -> line.isNotBlank() }
|
||||
.forEach { line -> pushLog(line) }
|
||||
pushStatus("Failed: ${it.message}")
|
||||
pushStatus(
|
||||
translation.format(
|
||||
"setup.root_install.failed_status",
|
||||
"message" to message
|
||||
)
|
||||
)
|
||||
}
|
||||
isRunning = false
|
||||
progress = -1f
|
||||
@@ -271,7 +289,7 @@ class RootInstallSnapchatScreen : SetupScreen() {
|
||||
|
||||
SetupCard {
|
||||
StepTitle(
|
||||
title = "Snapchat Installer",
|
||||
title = translation["setup.root_install.title"],
|
||||
subtitle = null,
|
||||
modifier = Modifier.align(Alignment.CenterHorizontally),
|
||||
textAlign = TextAlign.Center
|
||||
@@ -305,9 +323,12 @@ class RootInstallSnapchatScreen : SetupScreen() {
|
||||
val isDownloading = progress >= 0f
|
||||
Text(
|
||||
text = if (isDownloading) {
|
||||
"Downloading Snapchat ${(progress * 100).toInt()}%"
|
||||
translation.format(
|
||||
"setup.root_install.status_downloading",
|
||||
"percent" to (progress * 100).toInt().toString()
|
||||
)
|
||||
} else {
|
||||
"Preparing installer..."
|
||||
translation["setup.root_install.status_preparing"]
|
||||
},
|
||||
color = PurrfectPalette.textPrimary,
|
||||
fontWeight = FontWeight.Medium
|
||||
@@ -339,9 +360,10 @@ class RootInstallSnapchatScreen : SetupScreen() {
|
||||
logs = logs,
|
||||
pulse = logPulse,
|
||||
accent = accent,
|
||||
translation = translation,
|
||||
onCopy = {
|
||||
clipboard.setText(AnnotatedString(logs.joinToString("\n")))
|
||||
pushLog("Logs copied to clipboard.")
|
||||
pushLog(translation["setup.root_install.logs_copied"])
|
||||
}
|
||||
)
|
||||
|
||||
@@ -376,7 +398,7 @@ class RootInstallSnapchatScreen : SetupScreen() {
|
||||
tint = Color.White
|
||||
)
|
||||
Text(
|
||||
text = "Snapchat installed",
|
||||
text = translation["setup.root_install.install_success"],
|
||||
color = Color.White,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
@@ -385,7 +407,7 @@ class RootInstallSnapchatScreen : SetupScreen() {
|
||||
} else {
|
||||
if (downloadedApk == null) {
|
||||
GradientActionButton(
|
||||
label = "Download Snapchat",
|
||||
label = translation["setup.root_install.download_button"],
|
||||
icon = Icons.Filled.Download,
|
||||
onClick = { startDownloadAndInstall() },
|
||||
enabled = !isRunning
|
||||
@@ -393,7 +415,7 @@ class RootInstallSnapchatScreen : SetupScreen() {
|
||||
}
|
||||
if (downloadedApk != null) {
|
||||
GradientActionButton(
|
||||
label = "Install Snapchat",
|
||||
label = translation["setup.root_install.install_button"],
|
||||
icon = Icons.Filled.Verified,
|
||||
onClick = { installDownloadedApk() },
|
||||
enabled = true
|
||||
@@ -422,7 +444,7 @@ class RootInstallSnapchatScreen : SetupScreen() {
|
||||
tint = Color.White.copy(alpha = 0.9f)
|
||||
)
|
||||
Text(
|
||||
text = "Already Installed?",
|
||||
text = translation["setup.root_install.already_installed_button"],
|
||||
color = Color.White,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
@@ -495,6 +517,7 @@ private fun LogsPanel(
|
||||
logs: List<String>,
|
||||
pulse: Float,
|
||||
accent: Brush,
|
||||
translation: LocaleWrapper,
|
||||
onCopy: () -> Unit
|
||||
) {
|
||||
var expanded by rememberSaveable { mutableStateOf(false) }
|
||||
@@ -533,7 +556,7 @@ private fun LogsPanel(
|
||||
horizontalArrangement = Arrangement.spacedBy(6.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "Logs",
|
||||
text = translation["setup.root_install.logs_title"],
|
||||
color = Color.White,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
@@ -563,7 +586,7 @@ private fun LogsPanel(
|
||||
modifier = Modifier.size(14.dp)
|
||||
)
|
||||
Text(
|
||||
text = "Copy",
|
||||
text = translation["setup.root_install.copy_button"],
|
||||
color = Color.White,
|
||||
fontWeight = FontWeight.Medium,
|
||||
fontSize = 12.sp
|
||||
@@ -575,7 +598,10 @@ private fun LogsPanel(
|
||||
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||
logs.forEach { line ->
|
||||
Text(
|
||||
text = "- $line",
|
||||
text = translation.format(
|
||||
"setup.root_install.log_line_prefix",
|
||||
"line" to line
|
||||
),
|
||||
color = PurrfectPalette.textPrimary,
|
||||
fontSize = 13.sp,
|
||||
lineHeight = 16.sp
|
||||
|
||||
@@ -58,7 +58,7 @@ class SaveFolderScreen : SetupScreen() {
|
||||
title = context.translation["setup.dialogs.save_folder"],
|
||||
subtitle = null
|
||||
)
|
||||
DialogText(text = "Please choose the location where media should be downloaded to.")
|
||||
DialogText(text = context.translation["setup.save_folder.description"])
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
shape = RoundedCornerShape(22.dp),
|
||||
@@ -99,12 +99,12 @@ class SaveFolderScreen : SetupScreen() {
|
||||
modifier = Modifier.weight(1f)
|
||||
) {
|
||||
Text(
|
||||
text = "Destination",
|
||||
text = context.translation["setup.save_folder.destination_label"],
|
||||
fontSize = 13.sp,
|
||||
color = PurrfectPalette.textSecondary
|
||||
)
|
||||
Text(
|
||||
text = if (currentFolder.isBlank()) "System default" else currentFolder,
|
||||
text = if (currentFolder.isBlank()) context.translation["setup.save_folder.system_default_label"] else currentFolder,
|
||||
fontSize = 15.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = Color.White,
|
||||
@@ -162,7 +162,7 @@ class SaveFolderScreen : SetupScreen() {
|
||||
),
|
||||
border = BorderStroke(1.dp, Color.White.copy(alpha = 0.18f))
|
||||
) {
|
||||
Text(text = "Use default location")
|
||||
Text(text = context.translation["setup.save_folder.use_default_location_button"])
|
||||
}
|
||||
|
||||
if (showNoPickerDialog) {
|
||||
@@ -204,13 +204,13 @@ class SaveFolderScreen : SetupScreen() {
|
||||
}
|
||||
}
|
||||
Text(
|
||||
text = "Folder picker unavailable",
|
||||
text = context.translation["setup.save_folder.no_picker_title"],
|
||||
fontSize = 18.sp,
|
||||
fontWeight = FontWeight.ExtraBold,
|
||||
color = Color.White
|
||||
)
|
||||
Text(
|
||||
text = "Some cloned/dual-app environments block the system folder picker. You can continue using the system default save location, or open the app outside clone mode to select a custom folder.",
|
||||
text = context.translation["setup.save_folder.no_picker_message"],
|
||||
fontSize = 14.sp,
|
||||
lineHeight = 18.sp,
|
||||
textAlign = androidx.compose.ui.text.style.TextAlign.Center,
|
||||
@@ -230,7 +230,7 @@ class SaveFolderScreen : SetupScreen() {
|
||||
),
|
||||
border = BorderStroke(1.dp, Color.White.copy(alpha = 0.14f))
|
||||
) {
|
||||
Text("Cancel")
|
||||
Text(context.translation["button.cancel"])
|
||||
}
|
||||
Button(
|
||||
onClick = {
|
||||
@@ -248,7 +248,7 @@ class SaveFolderScreen : SetupScreen() {
|
||||
contentColor = Color.White
|
||||
)
|
||||
) {
|
||||
Text("Use default")
|
||||
Text(context.translation["setup.save_folder.use_default_button"])
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -256,7 +256,7 @@ class SaveFolderScreen : SetupScreen() {
|
||||
}
|
||||
}
|
||||
DialogText(
|
||||
text = "PurrfectSnap requires Storage permissions to download and Save Media from Snapchat."
|
||||
text = context.translation["setup.save_folder.permission_hint"]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -326,7 +326,7 @@ class AlertDialogs(
|
||||
Button(
|
||||
onClick = {
|
||||
if (fieldValue.text.isNotEmpty() && property.key.params.inputCheck?.invoke(fieldValue.text) == false) {
|
||||
Toast.makeText(context, "Invalid input! Make sure you entered a valid value.", Toast.LENGTH_SHORT).show() //TODO: i18n
|
||||
Toast.makeText(context, translation["invalid_input_toast"], Toast.LENGTH_SHORT).show()
|
||||
return@Button
|
||||
}
|
||||
|
||||
|
||||
@@ -1,21 +1,160 @@
|
||||
{
|
||||
"setup": {
|
||||
"activity": {
|
||||
"wrong_apk_title": "Wrong APK installed",
|
||||
"wrong_apk_message": "Your device is armv8, please download the armv8 apk, not armv7.",
|
||||
"close_button": "Close",
|
||||
"important_confirm_timeout": "I understand ({seconds}s)",
|
||||
"important_confirm": "I understand",
|
||||
"important_title": "Important!",
|
||||
"important_message": "If you have used SnapEnhance or any other mod besides PurrfectSnap, we recommend uninstalling everything and staying on stock Snapchat for one week. Then switch to PurrfectSnap after next Friday.",
|
||||
"language_subtitle": "Tune PurrfectSnap to speak your voice before anything else.",
|
||||
"install_mode_title": "Choose your device",
|
||||
"install_mode_subtitle": "Pick the path that matches how you'll install PurrfectSnap.",
|
||||
"permissions_subtitle": "Grant the essentials so overlays, downloads, and alerts stay reliable.",
|
||||
"patch_title": "Auto Patcher",
|
||||
"patch_subtitle": "Streamlined download, patch, and install with a single flow.",
|
||||
"root_install_title": "Snapchat Installer",
|
||||
"root_install_subtitle": "Download and install the recommended Snapchat build.",
|
||||
"save_folder_subtitle": "Pick your personal vault so snaps land exactly where you expect.",
|
||||
"mappings_subtitle": "We calibrate everything to your install so the magic works flawlessly.",
|
||||
"step_counter": "Step {current} of {total}",
|
||||
"step_complete": "Checked off",
|
||||
"step_active": "In progress",
|
||||
"step_upcoming": "Ready next",
|
||||
"finish_button": "Finish setup",
|
||||
"continue_button": "Continue"
|
||||
},
|
||||
"dialogs": {
|
||||
"select_language": "Select Language",
|
||||
"save_folder": "Choose where to save downloads",
|
||||
"select_save_folder_button": "Select Folder"
|
||||
},
|
||||
"install_mode": {
|
||||
"confirm_timeout": "I understand ({seconds}s)",
|
||||
"confirm": "I understand",
|
||||
"notice_title": "Please note!",
|
||||
"notice_intro": "Select the type of device you have: rooted or non-rooted. If you are unsure, choose Non-root and continue.",
|
||||
"notice_non_root_title": "Non-rooted devices",
|
||||
"notice_non_root_body": "Select Non-root and the app will handle everything. Tap Install Patched Snapchat when it appears. After it installs, do not open Snapchat yet. Continue the PurrfectSnap setup; once it finishes, you can open Snapchat and enjoy.",
|
||||
"notice_root_title": "Rooted devices",
|
||||
"notice_root_body": "Make sure you have flashed LSPosed first. We recommend JingMatrix LSPosed or LSPosed Irena. After you select Root, the app will install the recommended Snapchat version. Do not open it yet; continue the PurrfectSnap setup. When setup finishes, enable PurrfectSnap in LSPosed and reboot your phone. Then start using Snapchat. We highly recommend detaching Snapchat from the Play Store with the Zygisk Detach module to prevent auto-updates.",
|
||||
"notice_issues_hint": "If you run into any installation issues, the solution will appear here. Please read it carefully.",
|
||||
"notice_note_prefix": "Note: ",
|
||||
"notice_note_body": "New Accounts easily get locked! It is recommended to use an older account with PurrfectSnap.",
|
||||
"step_title": "Choose your device",
|
||||
"step_subtitle": "If you don't know, select Non-rooted device and proceed.",
|
||||
"root_option_title": "Rooted device",
|
||||
"root_option_subtitle": "Use Lsposed and skip auto patching.",
|
||||
"non_root_option_title": "Non-rooted device",
|
||||
"non_root_option_subtitle": "Use included auto patcher to install patched Snapchat.",
|
||||
"skip_auto_setup": "Skip Auto Setup"
|
||||
},
|
||||
"mappings": {
|
||||
"dialog": "Generating Mappings...",
|
||||
"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."
|
||||
"generate_failure": "An error occurred while trying to generate mappings, please try again.",
|
||||
"confirm_understand_timeout": "I understand ({seconds}s)",
|
||||
"confirm_understand": "I understand",
|
||||
"notice_title": "Please note!",
|
||||
"notice_intro": "If you see the \"Account temporarily disabled\" error while logging in, do not worry. Follow these steps in order:",
|
||||
"notice_step_1": "1. Reopen Snapchat and log in. This fixes it most of the time.",
|
||||
"notice_step_2": "2. If it still fails, tap the login button repeatedly. This usually covers the next chunk.",
|
||||
"notice_step_3": "3. If it still fails, clear Snapchat's data, disable any VPN, and log in again.",
|
||||
"notice_rooted_title": "For rooted users:",
|
||||
"notice_rooted_body": "Reopen Snapchat and log in. If it still fails, disable PurrfectSnap in LSPosed, log in, then re-enable PurrfectSnap.",
|
||||
"warnings_info": "{count} warning(s) occurred while generating mappings:\n\n{warnings}",
|
||||
"progress_hint": "This only takes a moment. Keep the app open while magic happens!"
|
||||
},
|
||||
"patch": {
|
||||
"title": "Auto Patcher",
|
||||
"ready_log": "Auto Patcher is ready.",
|
||||
"install_confirmed_log": "Snapchat install confirmed. You're cleared to continue.",
|
||||
"download_recommended_status": "Downloading recommended Snapchat version ({version})...",
|
||||
"starting_log": "Starting Auto Patcher for recommended Snapchat version.",
|
||||
"uninstall_prompt_status": "Snapchat is installed. Please uninstall it first (don't keep data), then start Auto Patcher again.",
|
||||
"uninstall_prompt_error": "Snapchat still installed. Uninstall it first, to continue.",
|
||||
"module_apk_not_found_error": "Module apk not found",
|
||||
"fetching_apk_status": "Fetching recommended Snapchat APK...",
|
||||
"download_failed_error": "Download failed",
|
||||
"download_completed_status": "Download completed: {fileName}",
|
||||
"starting_patch_status": "Starting patch powered by Jingmatrix Lspatch",
|
||||
"patched_not_produced_error": "Patched apk not produced",
|
||||
"patched_ready_status": "Patched build ready. Install to finish.",
|
||||
"failed_status": "Failed: {message}",
|
||||
"mark_installed_log": "Marked as installed manually. You're cleared to continue.",
|
||||
"issues_title": "Facing issues?",
|
||||
"issues_confirm": "Got it",
|
||||
"issues_heading": "How to fix installation errors",
|
||||
"issues_conflict_issue": "Issue: App cannot be installed because it conflicts with an existing package.",
|
||||
"issues_conflict_fix": "Fix: Download Snapchat from the Play Store and uninstall it without keeping data. Run Auto Patcher again. If it still does not work, run:",
|
||||
"issues_adb_command": "adb uninstall com.snapchat.android",
|
||||
"issues_invalid_issue": "Issue: App not installed because the package appears to be invalid.",
|
||||
"issues_invalid_fix": "Fix: Download and install JingMatrix LSPatch, then patch a Snapchat version (any one) from this range, i.e. between 13.65.1.0 and 13.71.0.51, in Integrated mode. Select Embed Modules and embed the PurrfectSnap APK. Then choose Skip auto setup during PurrfectSnap setup to skip Auto Patcher.",
|
||||
"status_downloading": "Downloading Snapchat {percent}%",
|
||||
"status_patching": "Patching...",
|
||||
"status_initializing": "Initializing...",
|
||||
"logs_copied": "Logs copied to clipboard.",
|
||||
"install_success": "Patched APK installed",
|
||||
"start_button": "Start auto patch",
|
||||
"install_button": "Install patched Snapchat",
|
||||
"already_installed_button": "Already Installed?",
|
||||
"powered_by_label": "Powered by Jingmatrix Lspatch",
|
||||
"logs_title": "Logs",
|
||||
"copy_button": "Copy",
|
||||
"log_line_prefix": "- {line}"
|
||||
},
|
||||
"permissions": {
|
||||
"dialog": "Complete these essentials to continue:",
|
||||
"notification_access": "Notification Access",
|
||||
"battery_optimization": "Battery Optimization",
|
||||
"display_over_other_apps": "Display Over Other Apps",
|
||||
"request_button": "Request"
|
||||
"request_button": "Request",
|
||||
"notification_access_description": "Alerts you the second downloads finish.",
|
||||
"battery_optimization_description": "Keeps background tasks alive without being killed.",
|
||||
"display_over_other_apps_description": "Enables floating overlays while you are in Snapchat.",
|
||||
"granted_label": "Granted"
|
||||
},
|
||||
"pick_language": {
|
||||
"current_selection": "Current selection",
|
||||
"browse_languages": "Browse languages",
|
||||
"change_anytime_hint": "You can change this anytime from PurrfectSnap settings.",
|
||||
"available_languages": "Available Languages"
|
||||
},
|
||||
"root_install": {
|
||||
"title": "Snapchat Installer",
|
||||
"ready_log": "Snapchat installer is ready.",
|
||||
"install_confirmed_log": "Snapchat install confirmed. You're cleared to continue.",
|
||||
"download_recommended_status": "Downloading recommended Snapchat version ({version})...",
|
||||
"mark_installed_log": "Marked as installed manually. You're cleared to continue.",
|
||||
"start_download_log": "Starting Snapchat download for rooted install.",
|
||||
"uninstall_prompt_status": "Snapchat is installed. Please uninstall it first (don't keep data), then try again.",
|
||||
"uninstall_prompt_error": "Snapchat still installed. Uninstall it first, to continue.",
|
||||
"fetching_apk_status": "Fetching recommended Snapchat APK...",
|
||||
"download_failed_error": "Download failed",
|
||||
"download_completed_status": "Download completed: {fileName}",
|
||||
"launching_installer_status": "Launching installer...",
|
||||
"failed_status": "Failed: {message}",
|
||||
"status_downloading": "Downloading Snapchat {percent}%",
|
||||
"status_preparing": "Preparing installer...",
|
||||
"logs_copied": "Logs copied to clipboard.",
|
||||
"install_success": "Snapchat installed",
|
||||
"download_button": "Download Snapchat",
|
||||
"install_button": "Install Snapchat",
|
||||
"already_installed_button": "Already Installed?",
|
||||
"logs_title": "Logs",
|
||||
"copy_button": "Copy",
|
||||
"log_line_prefix": "- {line}"
|
||||
},
|
||||
"save_folder": {
|
||||
"description": "Please choose the location where media should be downloaded to.",
|
||||
"destination_label": "Destination",
|
||||
"system_default_label": "System default",
|
||||
"use_default_location_button": "Use default location",
|
||||
"no_picker_title": "Folder picker unavailable",
|
||||
"no_picker_message": "Some cloned/dual-app environments block the system folder picker. You can continue using the system default save location, or open the app outside clone mode to select a custom folder.",
|
||||
"use_default_button": "Use default",
|
||||
"permission_hint": "PurrfectSnap requires Storage permissions to download and Save Media from Snapchat."
|
||||
}
|
||||
},
|
||||
"scopes": {
|
||||
@@ -61,10 +200,44 @@
|
||||
"update_title": "PurrfectSnap Update",
|
||||
"update_content": "Version {version} is available!",
|
||||
"update_button": "Download",
|
||||
"hero_tagline": "An Xposed Module meant to enhance your Snapchat experience",
|
||||
"hero_version_label": "Version: {version} - {channel}",
|
||||
"hero_build_label": "Build: {build}",
|
||||
"update_ready_label": "Ready to install",
|
||||
"purr_aura_active_label": "PurrAura Active!",
|
||||
"purr_aura_inactive_label": "PurrAura Inactive",
|
||||
"open_settings_button": "Open Settings",
|
||||
"wiki_button": "Wiki",
|
||||
"github_button": "GitHub",
|
||||
"telegram_button": "Telegram",
|
||||
"channel_label_stable": "Stable",
|
||||
"channel_label_prerelease": "Pre-release",
|
||||
"announcements_button_description": "Announcements",
|
||||
"update_arch_not_supported_toast": "Your device architecture is not supported for automatic updates.",
|
||||
"update_download_started_toast": "Download started",
|
||||
"update_download_completed_toast": "Download completed",
|
||||
"update_install_failed_toast": "Failed to install update. Check logs for more details.",
|
||||
"update_download_failed_toast": "Download failed: {error}",
|
||||
"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"
|
||||
"quick_actions_title": "Quick Actions",
|
||||
"quick_actions_empty_title": "No quick tiles yet",
|
||||
"quick_actions_empty_subtitle": "Design your dream grid with the actions you use the most.",
|
||||
"quick_actions_add_tile_button": "Add tile",
|
||||
"quick_actions_manage_button": "Manage",
|
||||
"quick_actions_count_label": "{count} curated shortcuts"
|
||||
},
|
||||
"home_about": {
|
||||
"about_title": "PurrfectSnap",
|
||||
"about_tagline": "An Xposed Module meant to enhance your Snapchat experience!",
|
||||
"about_lead_developers_title": "Lead Developers",
|
||||
"about_story_title": "Our Story",
|
||||
"about_story": "PurrfectSnap was founded on 2nd of October, 2025, as a fork of SnapEnhance by IzTIzRNAL with a vision to provide users the quality Snapchat experience they deserve. This app was just meant to be a minor update in the SnapEnhance repository, but it soon became a separate app wherein the contributors kept adding features. Then the developer <RSR/> joined the team, and this app soon became a huge success. We received much love and support and gained 1K+ downloads in just two days! We thank all users and contributors; without your support, we wouldn't have reached this place. We would also like to convey our huge thanks to rhunk, the lead developer of SnapEnhance, as without him, this app wouldn't even exist. We are immensely grateful to him. Lastly, we would like to thank all our admins, notably: CLASSIC GENIUS, Harry, SUJI>L, Zain & scrodingerspet, who were right there with us from the very beginning. We would also like to thank all testers, notably Leo & Toxic, who tested and reported bugs continuously. We are immensely grateful for your contribution.",
|
||||
"about_thanks_title": "With love, PurrfectSnap Team",
|
||||
"about_magic_toast": "Tap 5 times in this screen to see some magic 😉!",
|
||||
"github_button": "GitHub",
|
||||
"telegram_button": "Telegram"
|
||||
},
|
||||
"home_logs": {
|
||||
"no_logs_hint": "No logs available",
|
||||
@@ -72,7 +245,8 @@
|
||||
"export_logs_button": "Export Logs",
|
||||
"saving_logs_toast": "Saving logs, this may take a while ...",
|
||||
"saved_logs_success_toast": "Logs saved successfully",
|
||||
"saved_logs_failure_toast": "Failed to save logs"
|
||||
"saved_logs_failure_toast": "Failed to save logs",
|
||||
"read_logs_failed_toast": "Failed to read logs!"
|
||||
},
|
||||
"home_settings": {
|
||||
"actions_title": "Actions",
|
||||
@@ -119,11 +293,17 @@
|
||||
"test_mode_label": "Enable PurrAura",
|
||||
"disable_feature_loading_label": "Disable Feature Loading",
|
||||
"disable_auto_mapper_label": "Disable Auto Mapper",
|
||||
"disable_bypass_indicator_label": "Disable Bypass Indicator"
|
||||
"disable_bypass_indicator_label": "Disable Bypass Indicator",
|
||||
"open_file_failed_toast": "Failed to open file! {message}",
|
||||
"import_failed_toast": "Import failed: {message}"
|
||||
},
|
||||
"tasks": {
|
||||
"no_tasks": "No tasks",
|
||||
"merge_button": "Merge",
|
||||
"summary_active": "{active} active \u00b7 {recent} recent",
|
||||
"summary_idle": "Idle \u00b7 {recent} recent",
|
||||
"running_count": "{count} running",
|
||||
"clear_button_description": "Clear tasks",
|
||||
"failed_to_open_file": "Failed to open file",
|
||||
"merge_files_toast": "Merging {count} files",
|
||||
"remove_selected_tasks_title": "Are you sure you want to remove selected tasks?",
|
||||
@@ -167,6 +347,9 @@
|
||||
"social": {
|
||||
"friends_tab": "Friends",
|
||||
"groups_tab": "Groups",
|
||||
"search_button_description": "Search",
|
||||
"close_search_button_description": "Close search",
|
||||
"clear_search_button_description": "Clear search",
|
||||
"empty_hint": "Your list is empty for now",
|
||||
"friends_empty_title": "No friends added yet",
|
||||
"groups_empty_title": "No groups synced yet",
|
||||
@@ -317,6 +500,7 @@
|
||||
"import_button": "Import",
|
||||
"import_from_url_button": "Import from URL",
|
||||
"import_script_from_url_title": "Import Script from URL",
|
||||
"import_failed": "Import failed: {message}",
|
||||
"import_script_warning": "Only install scripts from sources you trust.",
|
||||
"installed_scripts_tab": "Installed",
|
||||
"manage_repos_button": "Manage repos",
|
||||
@@ -420,6 +604,7 @@
|
||||
"delete_rule_dialog_text": "Are you sure you want to delete this rule?",
|
||||
"no_repos_added": "No repositories added",
|
||||
"import_dialog_title": "Import Rules",
|
||||
"read_file_failed_toast": "Failed to read file: {message}",
|
||||
"bulk_import_button": "Bulk import",
|
||||
"individual_import_button": "Single import",
|
||||
"invalid_import_type_dialog_title": "Invalid import",
|
||||
@@ -753,6 +938,10 @@
|
||||
"name": "Allow Duplicate",
|
||||
"description": "Allows the same media to be downloaded multiple times"
|
||||
},
|
||||
"file_hash_check": {
|
||||
"name": "File Hash Check",
|
||||
"description": "Verify downloaded media with file hashes"
|
||||
},
|
||||
"merge_overlays": {
|
||||
"name": "Merge Overlays",
|
||||
"description": "Combines the Text and the media of a Snap into a single file"
|
||||
@@ -2834,6 +3023,8 @@
|
||||
"native_hooks_send_failure_toast": "Failed to send! Please enable Native Hooks in the settings.",
|
||||
"no_participants_to_encrypt_toast": "You don't have any friends in this conversation to encrypt messages with!",
|
||||
"encryption_failed_toast": "Failed to encrypt message! Check logcat for more details.",
|
||||
"missing_friend_id_toast": "Can't find friendId for conversationId {conversationId}",
|
||||
"key_exchange_failed_toast": "Can't create key exchange for friendId {friendId}",
|
||||
"accept_public_key_success_toast": "Public key successfully accepted!",
|
||||
"accept_secret_key_success_toast": "Done! You can now send and receive encrypted messages with this friend.",
|
||||
"accept_public_key_failure_toast": "Failed to accept public key",
|
||||
@@ -2845,6 +3036,21 @@
|
||||
"incoming_pk_message": "You just received a public key request. Click below to accept it.",
|
||||
"incoming_secret_message": "Your friend just accepted your public key. Click below to accept the secret."
|
||||
},
|
||||
"account_switcher_ui": {
|
||||
"already_logged_in": "Already logged in as {username}",
|
||||
"login_failed_toast": "Failed to login. Check logs for more info.",
|
||||
"logged_out_toast": "Logged out",
|
||||
"data_not_found_toast": "Account data not found",
|
||||
"restore_failed_toast": "Failed to restore account data",
|
||||
"logged_in_as_toast": "Logged in as {username}",
|
||||
"backup_success_toast": "Account backed up!",
|
||||
"backup_failure_toast": "Failed to backup account. Check logs for more info.",
|
||||
"import_success_toast": "Imported {username}!",
|
||||
"import_failure_toast": "Failed to import account: {message}",
|
||||
"export_success_toast": "Account exported!",
|
||||
"export_failed_toast": "Failed to export account. Check logs for more info.",
|
||||
"forced_logout_toast": "Removed account due to forced logout"
|
||||
},
|
||||
"auto_open_snaps": {
|
||||
"title": "Auto Open Snaps",
|
||||
"priority_title": "Auto Open Snaps (Priority)",
|
||||
@@ -3004,7 +3210,8 @@
|
||||
"auto_delete_sent_messages": {
|
||||
"countdown_toast": "Message will be deleted in {time}",
|
||||
"delete_success_toast": "Message deleted successfully",
|
||||
"delete_failed_toast": "Failed to delete message"
|
||||
"delete_failed_toast": "Failed to delete message",
|
||||
"queue_cleared_toast": "Auto delete queue cleared"
|
||||
},
|
||||
"translation_position": {
|
||||
"above": "Above",
|
||||
@@ -3243,6 +3450,9 @@
|
||||
"cancel": "Cancel",
|
||||
"documentation": "Documentation"
|
||||
},
|
||||
"button": {
|
||||
"cancel": "Cancel"
|
||||
},
|
||||
"common": {
|
||||
"cancel": "Cancel",
|
||||
"add": "Add",
|
||||
@@ -3309,6 +3519,32 @@
|
||||
"failed_to_fetch_message": "Failed to fetch message: {error}",
|
||||
"failed_to_edit_message": "Failed to edit message: {error}"
|
||||
},
|
||||
"toast_snapchat_not_installed": "Can't execute action: Snapchat is not installed",
|
||||
"invalid_input_toast": "Invalid input! Make sure you entered a valid value.",
|
||||
"toast_async_task_failed": "Async task failed: {message}",
|
||||
"toast_snapchat_crashed": "Snapchat has crashed! Please check logs for more details.",
|
||||
"toast_init_features_failed": "Failed to initialize features! Some functionality may not work properly.",
|
||||
"toast_init_script_runtime_failed": "Failed to initialize script runtime!",
|
||||
"toast_database_corrupted": "Database {path} is corrupted! Restarting ...",
|
||||
"toast_feature_init_failed": "Failed to init feature {feature}! Check logcat for more details.",
|
||||
"toast_updating_purrfectsnap": "Updating PurrfectSnap. Please wait...",
|
||||
"toast_update_purrfectsnap_failed": "Failed to update PurrfectSnap. Please check logcat for more details.",
|
||||
"toast_purrfectsnap_updated": "PurrfectSnap updated!",
|
||||
"toast_export_memories_failed": "Failed to export memories",
|
||||
"toast_exported_to_path": "Exported to {path}",
|
||||
"toast_open_memories_db_failed": "Failed to open memories database",
|
||||
"toast_friend_add_unavailable": "Failed to add friend: FriendRelationshipChanger instance not available",
|
||||
"toast_friend_add_failed": "Failed to add friend: {message}",
|
||||
"toast_friends_exported": "Exported {count} friends!",
|
||||
"toast_friends_import_failed": "Failed to import friends: {message}",
|
||||
"toast_translation_service_unavailable": "Translation service temporarily unavailable",
|
||||
"toast_send_message_failed": "Failed to send message: {error}",
|
||||
"toast_mark_conversation_read_failed": "Failed to mark conversation as read",
|
||||
"toast_fetch_conversation_failed": "Failed to fetch conversation",
|
||||
"toast_open_snap_failed": "Failed to open snap",
|
||||
"toast_mark_message_read_failed": "Failed to mark message as read. Check logs for more details",
|
||||
"toast_open_conversation_first": "You must open a conversation first",
|
||||
"toast_open_link_failed": "Failed to open link",
|
||||
"ai_response_style": {
|
||||
"casual": "Casual",
|
||||
"formal": "Formal",
|
||||
@@ -3347,7 +3583,3 @@
|
||||
"openrouter": "OpenRouter"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -33,11 +33,26 @@ class LocaleWrapper(
|
||||
lateinit var loadedLocale: Locale
|
||||
|
||||
private fun load(locale: String, pfd: ParcelFileDescriptor) {
|
||||
loadedLocale = if (locale.contains("_")) {
|
||||
val split = locale.split("_")
|
||||
Locale.Builder().setLanguage(split[0]).setRegion(split[1]).build()
|
||||
} else {
|
||||
Locale.Builder().setLanguage(locale).build()
|
||||
loadedLocale = when (locale) {
|
||||
"zh_SIMPLIFIED" -> Locale.SIMPLIFIED_CHINESE
|
||||
else -> {
|
||||
if (locale.contains("_")) {
|
||||
val split = locale.split("_", limit = 2)
|
||||
val language = split[0]
|
||||
val region = split.getOrNull(1)
|
||||
runCatching {
|
||||
val builder = Locale.Builder().setLanguage(language)
|
||||
if (!region.isNullOrBlank() && region.length in 2..3) {
|
||||
builder.setRegion(region)
|
||||
}
|
||||
builder.build()
|
||||
}.getOrElse {
|
||||
Locale.forLanguageTag(locale.replace('_', '-'))
|
||||
}
|
||||
} else {
|
||||
Locale.forLanguageTag(locale)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val translations = AutoCloseInputStream(pfd).use {
|
||||
|
||||
@@ -42,7 +42,7 @@ fun Context.getUrlFromClipboard(): String? {
|
||||
return getTextFromClipboard()?.takeIf { it.startsWith("http") }
|
||||
}
|
||||
|
||||
fun Context.openLink(url: String, shouldThrow: Boolean = false) {
|
||||
fun Context.openLink(url: String, failureMessage: String, shouldThrow: Boolean = false) {
|
||||
runCatching {
|
||||
startActivity(Intent(Intent.ACTION_VIEW).apply {
|
||||
data = url.toUri()
|
||||
@@ -50,7 +50,7 @@ fun Context.openLink(url: String, shouldThrow: Boolean = false) {
|
||||
})
|
||||
}.onFailure {
|
||||
if (shouldThrow) throw it
|
||||
Toast.makeText(this, "Failed to open link", Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(this, failureMessage, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -100,7 +100,7 @@ class ModContext(
|
||||
runCatching {
|
||||
runnable()
|
||||
}.onFailure {
|
||||
longToast("Async task failed: " + it.message)
|
||||
longToast(translation.format("toast_async_task_failed", "message" to (it.message ?: "")))
|
||||
log.error("Async task failed", it)
|
||||
}
|
||||
}
|
||||
@@ -139,7 +139,7 @@ class ModContext(
|
||||
|
||||
fun logCritical(message: Any?, throwable: Throwable = Throwable()) {
|
||||
log.error(message ?: "Snapchat crash", throwable)
|
||||
longToast(message ?: "Snapchat has crashed! Please check logs for more details.")
|
||||
longToast(message ?: translation["toast_snapchat_crashed"])
|
||||
}
|
||||
|
||||
private fun delayForceCloseApp(delay: Long) = Handler(Looper.getMainLooper()).postDelayed({
|
||||
|
||||
@@ -198,7 +198,7 @@ class PurrfectSnap {
|
||||
log.verbose("Features initialized successfully")
|
||||
}.onFailure { throwable ->
|
||||
log.error("Failed to initialize features", throwable)
|
||||
longToast("Failed to initialize features! Some functionality may not work properly.")
|
||||
longToast(appContext.translation["toast_init_features_failed"])
|
||||
// Continue with other initializations even if features fail
|
||||
}
|
||||
|
||||
@@ -209,7 +209,7 @@ class PurrfectSnap {
|
||||
log.verbose("Script runtime initialized successfully")
|
||||
}.onFailure { throwable ->
|
||||
log.error("Failed to initialize script runtime", throwable)
|
||||
longToast("Failed to initialize script runtime!")
|
||||
longToast(appContext.translation["toast_init_script_runtime_failed"])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -299,13 +299,15 @@ class ExportMemories : AbstractAction() {
|
||||
val exportedPath = runCatching { outputTarget.finalize(outputZip) }
|
||||
.getOrElse { error ->
|
||||
context.log.error("Failed to finalize memories export", error)
|
||||
context.longToast("Failed to export memories")
|
||||
context.longToast(context.translation["toast_export_memories_failed"])
|
||||
return
|
||||
}
|
||||
if (outputZip.parentFile == context.androidContext.cacheDir) {
|
||||
outputZip.delete()
|
||||
}
|
||||
context.longToast("Exported to $exportedPath")
|
||||
context.longToast(
|
||||
context.translation.format("toast_exported_to_path", "path" to exportedPath)
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@@ -863,7 +865,7 @@ class ExportMemories : AbstractAction() {
|
||||
}.getOrNull()
|
||||
|
||||
if (database == null) {
|
||||
context.longToast("Failed to open memories database")
|
||||
context.longToast(context.translation["toast_open_memories_db_failed"])
|
||||
return@launch
|
||||
}
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ class ManageFriendList : AbstractAction() {
|
||||
private fun addFriend(userId: String) {
|
||||
val friendRelationshipChangerInstance = context.feature(AddFriendSourceSpoof::class).friendRelationshipChangerInstance
|
||||
?: run {
|
||||
context.longToast("Failed to add friend: FriendRelationshipChanger instance not available")
|
||||
context.longToast(context.translation["toast_friend_add_unavailable"])
|
||||
return
|
||||
}
|
||||
|
||||
@@ -153,7 +153,9 @@ class ManageFriendList : AbstractAction() {
|
||||
}
|
||||
}.onFailure {
|
||||
context.log.error("Failed to add friend $userId", it)
|
||||
context.longToast("Failed to add friend: ${it.message}")
|
||||
context.longToast(
|
||||
context.translation.format("toast_friend_add_failed", "message" to (it.message ?: ""))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -173,7 +175,9 @@ class ManageFriendList : AbstractAction() {
|
||||
context.androidContext.contentResolver.openOutputStream(data)?.bufferedWriter()?.use { writer ->
|
||||
userIds.forEach { writer.write(it); writer.newLine() }
|
||||
}
|
||||
context.longToast("Exported ${userIds.size} friends!")
|
||||
context.longToast(
|
||||
context.translation.format("toast_friends_exported", "count" to userIds.size.toString())
|
||||
)
|
||||
}
|
||||
context.mainActivity?.startActivityForResult(
|
||||
Intent.createChooser(Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
|
||||
@@ -303,7 +307,12 @@ class ManageFriendList : AbstractAction() {
|
||||
fetchedFriends = context.androidContext.contentResolver.openInputStream(data)?.bufferedReader()?.readLines()?.filter { it.matches(uuidRegex) }?.map { it.trim() }?.toMutableList() ?: mutableListOf()
|
||||
}.onFailure {
|
||||
context.log.error("Failed to import friends", it)
|
||||
context.longToast("Failed to import friends: ${it.message}")
|
||||
context.longToast(
|
||||
context.translation.format(
|
||||
"toast_friends_import_failed",
|
||||
"message" to (it.message ?: "")
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
context.mainActivity?.startActivityForResult(Intent.createChooser(Intent(Intent.ACTION_GET_CONTENT).apply { type = "*/*" }, "Select a file"), pendingPickerAction!!.first)
|
||||
|
||||
@@ -99,7 +99,12 @@ class DatabaseAccess(
|
||||
context.log.error("Failed to execute query $query", it)
|
||||
return@onFailure
|
||||
}
|
||||
context.longToast("Database ${this.path} is corrupted! Restarting ...")
|
||||
context.longToast(
|
||||
context.translation.format(
|
||||
"toast_database_corrupted",
|
||||
"path" to this.path
|
||||
)
|
||||
)
|
||||
context.androidContext.deleteDatabase(this.path)
|
||||
context.crash("Database ${this.path} is corrupted!", it)
|
||||
}.getOrNull()
|
||||
@@ -616,4 +621,4 @@ class DatabaseAccess(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,7 +164,12 @@ class FeatureManager(
|
||||
}
|
||||
}.onFailure {
|
||||
context.log.error("Failed to init feature ${feature.key}", it)
|
||||
context.longToast("Failed to init feature ${feature.key}! Check logcat for more details.")
|
||||
context.longToast(
|
||||
context.translation.format(
|
||||
"toast_feature_init_failed",
|
||||
"feature" to feature.key
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ import java.util.zip.ZipOutputStream
|
||||
import kotlin.random.Random
|
||||
|
||||
class AccountSwitcher: Feature("Account Switcher") {
|
||||
private val translation by lazy { context.translation.getCategory("account_switcher_ui") }
|
||||
private var exportCallback: Pair<Int, String>? = null // requestCode -> userId
|
||||
private var importRequestCode: Int? = null
|
||||
|
||||
@@ -107,7 +108,9 @@ class AccountSwitcher: Feature("Account Switcher") {
|
||||
onClick = {
|
||||
runCatching {
|
||||
if (!isLoginActivity && context.database.myUserId == user.first) {
|
||||
context.shortToast("Already logged in as ${user.second}")
|
||||
context.shortToast(
|
||||
translation.format("already_logged_in", "username" to user.second)
|
||||
)
|
||||
return@runCatching
|
||||
}
|
||||
|
||||
@@ -117,7 +120,7 @@ class AccountSwitcher: Feature("Account Switcher") {
|
||||
|
||||
login(userId = user.first, username = user.second)
|
||||
}.onFailure {
|
||||
context.shortToast("Failed to login. Check logs for more info.")
|
||||
context.shortToast(translation["login_failed_toast"])
|
||||
context.log.error("Failed to login", it)
|
||||
}
|
||||
}
|
||||
@@ -259,7 +262,7 @@ class AccountSwitcher: Feature("Account Switcher") {
|
||||
|
||||
private fun logout() {
|
||||
context.androidContext.dataDir.resolve( "shared_prefs/user_session_shared_pref.xml").takeIf { it.exists() }?.delete()
|
||||
context.shortToast("Logged out")
|
||||
context.shortToast(translation["logged_out_toast"])
|
||||
context.softRestartApp()
|
||||
}
|
||||
|
||||
@@ -268,7 +271,7 @@ class AccountSwitcher: Feature("Account Switcher") {
|
||||
ParcelFileDescriptor.AutoCloseInputStream(pfd).use { it.readBytes() }
|
||||
}
|
||||
if (accountData == null) {
|
||||
context.shortToast("Account data not found")
|
||||
context.shortToast(translation["data_not_found_toast"])
|
||||
return
|
||||
}
|
||||
|
||||
@@ -312,12 +315,12 @@ class AccountSwitcher: Feature("Account Switcher") {
|
||||
zipInputStream.close()
|
||||
} catch (e: Exception) {
|
||||
context.log.error("Failed to restore account data", e)
|
||||
context.shortToast("Failed to restore account data")
|
||||
context.shortToast(translation["restore_failed_toast"])
|
||||
return
|
||||
}
|
||||
|
||||
context.log.debug("Account data restored")
|
||||
context.shortToast("Logged in as $username")
|
||||
context.shortToast(translation.format("logged_in_as_toast", "username" to username))
|
||||
context.softRestartApp()
|
||||
}
|
||||
|
||||
@@ -390,9 +393,9 @@ class AccountSwitcher: Feature("Account Switcher") {
|
||||
context.database.getFriendInfo(context.database.myUserId)?.mutableUsername ?: "Unknown username",
|
||||
getCurrentAccountData()
|
||||
)
|
||||
context.shortToast("Account backed up!")
|
||||
context.shortToast(translation["backup_success_toast"])
|
||||
}.onFailure {
|
||||
context.shortToast("Failed to backup account. Check logs for more info.")
|
||||
context.shortToast(translation["backup_failure_toast"])
|
||||
context.log.error("Failed to backup account", it)
|
||||
}
|
||||
}
|
||||
@@ -465,11 +468,13 @@ class AccountSwitcher: Feature("Account Switcher") {
|
||||
it.toParcelFileDescriptor(context.coroutineScope)
|
||||
)
|
||||
}
|
||||
context.shortToast("Imported $username!")
|
||||
context.shortToast(translation.format("import_success_toast", "username" to username))
|
||||
updateUsers()
|
||||
}
|
||||
}.onFailure {
|
||||
context.shortToast("Failed to import account: ${it.message}")
|
||||
context.shortToast(
|
||||
translation.format("import_failure_toast", "message" to (it.message ?: ""))
|
||||
)
|
||||
context.log.error("Failed to import account", it)
|
||||
}
|
||||
|
||||
@@ -522,10 +527,10 @@ class AccountSwitcher: Feature("Account Switcher") {
|
||||
it.copyTo(outputStream)
|
||||
}
|
||||
}
|
||||
context.shortToast("Account exported!")
|
||||
context.shortToast(translation["export_success_toast"])
|
||||
}
|
||||
}.onFailure {
|
||||
context.shortToast("Failed to export account. Check logs for more info.")
|
||||
context.shortToast(translation["export_failed_toast"])
|
||||
context.log.error("Failed to export account", it)
|
||||
}
|
||||
}
|
||||
@@ -539,10 +544,10 @@ class AccountSwitcher: Feature("Account Switcher") {
|
||||
runCatching {
|
||||
val accountStorage = context.bridgeClient.getAccountStorage()
|
||||
|
||||
if (accountStorage.isAccountExists(context.database.myUserId)) {
|
||||
accountStorage.removeAccount(context.database.myUserId)
|
||||
context.shortToast("Removed account due to forced logout")
|
||||
}
|
||||
if (accountStorage.isAccountExists(context.database.myUserId)) {
|
||||
accountStorage.removeAccount(context.database.myUserId)
|
||||
context.shortToast(translation["forced_logout_toast"])
|
||||
}
|
||||
}
|
||||
return@hook
|
||||
}
|
||||
|
||||
@@ -75,12 +75,22 @@ class EndToEndEncryption : MessagingRuleFeature(
|
||||
|
||||
private fun askForKeys(conversationId: String) {
|
||||
val friendId = context.database.getDMOtherParticipant(conversationId) ?: run {
|
||||
context.longToast("Can't find friendId for conversationId $conversationId")
|
||||
context.longToast(
|
||||
translation.format(
|
||||
"missing_friend_id_toast",
|
||||
"conversationId" to conversationId
|
||||
)
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
val publicKey = e2eeInterface.createKeyExchange(friendId) ?: run {
|
||||
context.longToast("Can't create key exchange for friendId $friendId")
|
||||
context.longToast(
|
||||
translation.format(
|
||||
"key_exchange_failed_toast",
|
||||
"friendId" to friendId
|
||||
)
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -131,7 +141,12 @@ class EndToEndEncryption : MessagingRuleFeature(
|
||||
|
||||
private fun handlePublicKeyRequest(conversationId: String, publicKey: ByteArray) {
|
||||
val friendId = context.database.getDMOtherParticipant(conversationId) ?: run {
|
||||
context.longToast("Can't find friendId for conversationId $conversationId")
|
||||
context.longToast(
|
||||
translation.format(
|
||||
"missing_friend_id_toast",
|
||||
"conversationId" to conversationId
|
||||
)
|
||||
)
|
||||
return
|
||||
}
|
||||
warnKeyOverwrite(friendId) {
|
||||
@@ -151,7 +166,12 @@ class EndToEndEncryption : MessagingRuleFeature(
|
||||
|
||||
private fun handleSecretResponse(conversationId: String, secret: ByteArray) {
|
||||
val friendId = context.database.getDMOtherParticipant(conversationId) ?: run {
|
||||
context.longToast("Can't find friendId for conversationId $conversationId")
|
||||
context.longToast(
|
||||
translation.format(
|
||||
"missing_friend_id_toast",
|
||||
"conversationId" to conversationId
|
||||
)
|
||||
)
|
||||
return
|
||||
}
|
||||
warnKeyOverwrite(friendId) {
|
||||
@@ -561,4 +581,4 @@ class EndToEndEncryption : MessagingRuleFeature(
|
||||
}
|
||||
|
||||
override fun getRuleState() = RuleState.WHITELIST
|
||||
}
|
||||
}
|
||||
|
||||
@@ -396,7 +396,7 @@ class AutoDeleteSentMessages : MessagingRuleFeature("Auto Delete Sent Messages",
|
||||
notificationManager.cancel(9999)
|
||||
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
context.shortToast("Auto delete queue cleared")
|
||||
context.shortToast(translation["queue_cleared_toast"])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ class MessageTranslator : Feature("Instant Translation") {
|
||||
if (config.pauseOnError.get()) {
|
||||
isPaused = true
|
||||
context.log.warn("Translation paused due to errors")
|
||||
context.shortToast("Translation service temporarily unavailable")
|
||||
context.shortToast(context.translation["toast_translation_service_unavailable"])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,7 +192,12 @@ class Notifications : Feature("Notifications") {
|
||||
val myUser = context.database.myUserId.let { context.database.getFriendInfo(it) } ?: return@subscribe
|
||||
|
||||
context.messageSender.sendChatMessage(listOf(SnapUUID(conversationId)), input, onError = {
|
||||
context.longToast("Failed to send message: $it")
|
||||
context.longToast(
|
||||
context.translation.format(
|
||||
"toast_send_message_failed",
|
||||
"error" to it.toString()
|
||||
)
|
||||
)
|
||||
context.coroutineScope.launch(coroutineDispatcher) {
|
||||
appendNotificationText("Failed to send message: $it")
|
||||
}
|
||||
@@ -221,7 +226,7 @@ class Notifications : Feature("Notifications") {
|
||||
onResult = {
|
||||
if (it != null) {
|
||||
context.log.error("Failed to mark conversation as read: $it")
|
||||
context.shortToast("Failed to mark conversation as read")
|
||||
context.shortToast(context.translation["toast_mark_conversation_read_failed"])
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -245,7 +250,7 @@ class Notifications : Feature("Notifications") {
|
||||
},
|
||||
onError = {
|
||||
context.log.error("Failed to fetch conversation: $it")
|
||||
context.shortToast("Failed to fetch conversation")
|
||||
context.shortToast(context.translation["toast_fetch_conversation_failed"])
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -257,13 +262,13 @@ class Notifications : Feature("Notifications") {
|
||||
conversationManager.updateMessage(conversationId, clientMessageId, MessageUpdate.READ) {
|
||||
if (it != null) {
|
||||
context.log.error("Failed to open snap: $it")
|
||||
context.shortToast("Failed to open snap")
|
||||
context.shortToast(context.translation["toast_open_snap_failed"])
|
||||
}
|
||||
}
|
||||
}
|
||||
}.onFailure {
|
||||
context.log.error("Failed to mark message as read", it)
|
||||
context.shortToast("Failed to mark message as read. Check logs for more details")
|
||||
context.shortToast(context.translation["toast_mark_message_read_failed"])
|
||||
}
|
||||
notificationManager.cancel(notificationId)
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ class ConversationToolbox : Feature("Conversation Toolbox") {
|
||||
|
||||
private fun openToolbox() {
|
||||
val openedConversationId = context.feature(Messaging::class).openedConversationUUID?.toString() ?: run {
|
||||
context.shortToast("You must open a conversation first")
|
||||
context.shortToast(context.translation["toast_open_conversation_first"])
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -59,19 +59,19 @@ object LSPatchUpdater {
|
||||
}
|
||||
|
||||
context.log.verbose("updating", TAG)
|
||||
context.shortToast("Updating PurrfectSnap. Please wait...")
|
||||
context.shortToast(context.translation["toast_updating_purrfectsnap"])
|
||||
// copy embedded module to cache
|
||||
runCatching {
|
||||
seAppApk.copyTo(embeddedModule, overwrite = true)
|
||||
}.onFailure {
|
||||
seAppApk.delete()
|
||||
context.log.error("Failed to copy embedded module", it, TAG)
|
||||
context.longToast("Failed to update PurrfectSnap. Please check logcat for more details.")
|
||||
context.longToast(context.translation["toast_update_purrfectsnap_failed"])
|
||||
context.forceCloseApp()
|
||||
return
|
||||
}
|
||||
|
||||
context.longToast("PurrfectSnap updated!")
|
||||
context.longToast(context.translation["toast_purrfectsnap_updated"])
|
||||
context.log.verbose("updated", TAG)
|
||||
context.softRestartApp()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user