Merge pull request #129 from imCrest/dev
Sync: Latest improvements and multiple fixes by Sujal
This commit is contained in:
@@ -779,11 +779,14 @@ class FeaturesRootSection : Routes.Route() {
|
||||
DataProcessors.Type.STRING, DataProcessors.Type.INTEGER, DataProcessors.Type.FLOAT -> {
|
||||
val isMessageListProperty = property.key.name.endsWith("_messages")
|
||||
val isSleepWindowProperty = property.key.name.contains("sleep_window")
|
||||
val isSnapchatPlusPurchaseDateProperty = property.key.name == "snapchat_plus_purchase_date"
|
||||
|
||||
if (isMessageListProperty) {
|
||||
alertDialogs.MessageListPropertyDialog(property) { showDialog = false }
|
||||
} else if (isSleepWindowProperty) {
|
||||
alertDialogs.AutoOpenScheduleDialog(property as PropertyPair<String>) { showDialog = false }
|
||||
} else if (isSnapchatPlusPurchaseDateProperty) {
|
||||
alertDialogs.DatePickerPropertyDialog(property) { showDialog = false }
|
||||
} else {
|
||||
alertDialogs.KeyboardInputDialog(property) { showDialog = false }
|
||||
}
|
||||
@@ -801,6 +804,7 @@ class FeaturesRootSection : Routes.Route() {
|
||||
)
|
||||
} else {
|
||||
val isMessageListProperty = property.key.name.endsWith("_messages")
|
||||
val isSnapchatPlusPurchaseDateProperty = property.key.name == "snapchat_plus_purchase_date"
|
||||
if (isMessageListProperty) {
|
||||
val messageCount = try {
|
||||
val messageList: List<String> = gson.fromJson(propertyValue.get().toString(), listTypeToken) ?: emptyList()
|
||||
@@ -822,6 +826,16 @@ class FeaturesRootSection : Routes.Route() {
|
||||
color = Color.White
|
||||
)
|
||||
}
|
||||
} else if (isSnapchatPlusPurchaseDateProperty) {
|
||||
Button(
|
||||
onClick = click,
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = PurrfectPalette.glowPrimary.copy(alpha = 0.28f),
|
||||
contentColor = Color.White
|
||||
)
|
||||
) {
|
||||
Text(translation["button.set"] ?: "Set")
|
||||
}
|
||||
} else {
|
||||
IconButton(onClick = click) {
|
||||
Icon(Icons.AutoMirrored.Filled.OpenInNew, contentDescription = null)
|
||||
|
||||
@@ -75,6 +75,10 @@ import org.osmdroid.views.overlay.Marker
|
||||
import org.osmdroid.views.overlay.MapEventsOverlay
|
||||
import org.osmdroid.views.overlay.Overlay
|
||||
import java.io.File
|
||||
import java.time.Instant
|
||||
import java.time.LocalDate
|
||||
import java.time.ZoneId
|
||||
import java.time.format.DateTimeFormatter
|
||||
import me.eternal.purrfectsnap.ui.util.purrfectSwitchColors
|
||||
import me.eternal.purrfectsnap.ui.util.Dialog as StandardDialog
|
||||
|
||||
@@ -512,6 +516,68 @@ class AlertDialogs(
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun DatePickerPropertyDialog(property: PropertyPair<*>, dismiss: () -> Unit = {}) {
|
||||
val context = LocalContext.current
|
||||
val zoneId = remember { ZoneId.systemDefault() }
|
||||
val initialSelectedDateMillis = remember(property.value.get()) {
|
||||
runCatching {
|
||||
LocalDate
|
||||
.parse(property.value.get().toString(), DateTimeFormatter.ISO_LOCAL_DATE)
|
||||
.atStartOfDay(zoneId)
|
||||
.toInstant()
|
||||
.toEpochMilli()
|
||||
}.getOrNull()
|
||||
}
|
||||
val datePickerState = rememberDatePickerState(initialSelectedDateMillis = initialSelectedDateMillis)
|
||||
|
||||
DefaultDialogCard {
|
||||
DatePicker(
|
||||
state = datePickerState,
|
||||
showModeToggle = true
|
||||
)
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(top = 10.dp)
|
||||
.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(10.dp, Alignment.End),
|
||||
) {
|
||||
Button(
|
||||
onClick = { dismiss() },
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = Color.White.copy(alpha = 0.08f),
|
||||
contentColor = Color.White
|
||||
)
|
||||
) {
|
||||
Text(text = translation["button.cancel"])
|
||||
}
|
||||
Button(
|
||||
onClick = {
|
||||
val selectedDate = datePickerState.selectedDateMillis?.let {
|
||||
Instant.ofEpochMilli(it).atZone(zoneId).toLocalDate()
|
||||
}
|
||||
|
||||
if (selectedDate == null) {
|
||||
Toast.makeText(context, translation["invalid_input_toast"], Toast.LENGTH_SHORT).show()
|
||||
return@Button
|
||||
}
|
||||
|
||||
property.value.setAny(selectedDate.format(DateTimeFormatter.ISO_LOCAL_DATE))
|
||||
dismiss()
|
||||
},
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = PurrfectPalette.glowPrimary.copy(alpha = 0.32f),
|
||||
contentColor = Color.White
|
||||
)
|
||||
) {
|
||||
Text(text = translation["button.ok"])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun RawInputDialog(onDismiss: () -> Unit, onConfirm: (value: String) -> Unit) {
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
@@ -1615,4 +1681,3 @@ class AlertDialogs(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1240,6 +1240,10 @@
|
||||
"name": "Settings Menu",
|
||||
"description": "Choose between the new and legacy settings menu layouts"
|
||||
},
|
||||
"chat_hold_kill_actions": {
|
||||
"name": "PurrfectSnap Chat Hold Kill",
|
||||
"description": "Hold the chat/settings header button to kill selected app(s). Leave all options off to disable."
|
||||
},
|
||||
"spoof_snap_score": {
|
||||
"name": "Spoof Snap Score",
|
||||
"description": "Spoof your Snap Score (local only)",
|
||||
@@ -1919,6 +1923,10 @@
|
||||
"name": "Snapchat Plus",
|
||||
"description": "Enables Snapchat Plus features\nSome Server-sided features may not work"
|
||||
},
|
||||
"snapchat_plus_purchase_date": {
|
||||
"name": "Snapchat Plus Purchase Date",
|
||||
"description": "Tap Save to choose a date from calendar (leave empty to use default)"
|
||||
},
|
||||
"media_upload_quality": {
|
||||
"name": "Media Upload Quality",
|
||||
"description": "Overrides the media upload quality",
|
||||
@@ -2905,6 +2913,10 @@
|
||||
"default": "Default",
|
||||
"legacy": "Legacy"
|
||||
},
|
||||
"chat_hold_kill_actions": {
|
||||
"kill_snapchat": "Kill Snapchat",
|
||||
"kill_purrfectsnap": "Kill PurrfectSnap"
|
||||
},
|
||||
"path_format": {
|
||||
"create_author_folder": "Create folder for each author",
|
||||
"create_source_folder": "Create folder for each media source type",
|
||||
@@ -3623,6 +3635,7 @@
|
||||
"cancel": "Cancel",
|
||||
"copy": "Copy",
|
||||
"save": "Save",
|
||||
"set": "Set",
|
||||
"open": "Open",
|
||||
"download": "Download",
|
||||
"import": "Import",
|
||||
|
||||
@@ -3,6 +3,8 @@ package me.eternal.purrfectsnap.common.config.impl
|
||||
import me.eternal.purrfectsnap.common.config.ConfigContainer
|
||||
import me.eternal.purrfectsnap.common.config.ConfigFlag
|
||||
import me.eternal.purrfectsnap.common.config.FeatureNotice
|
||||
import java.time.LocalDate
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
class Global : ConfigContainer() {
|
||||
companion object {
|
||||
@@ -46,6 +48,12 @@ class Global : ConfigContainer() {
|
||||
|
||||
val betterLocation = container("better_location", BetterLocationConfig())
|
||||
val snapchatPlus = unique("snapchat_plus", "not_subscribed", "basic", "ad_free") { requireRestart() }
|
||||
val snapchatPlusPurchaseDate = string("snapchat_plus_purchase_date", "") {
|
||||
requireRestart()
|
||||
inputCheck = {
|
||||
it.isBlank() || runCatching { LocalDate.parse(it, DateTimeFormatter.ISO_LOCAL_DATE) }.isSuccess
|
||||
}
|
||||
}
|
||||
val mediaUploadQualityConfig = container("media_upload_quality", MediaUploadQualityConfig())
|
||||
val performanceMode = container("performance_mode", PerformanceModeConfig()) { requireRestart() }.apply {
|
||||
profile.set("max")
|
||||
|
||||
@@ -63,6 +63,7 @@ class UserInterfaceTweaks : ConfigContainer() {
|
||||
}
|
||||
val preventForcedKeyboard = boolean("prevent_forced_keyboard") { requireRestart() }
|
||||
val settingsMenu = unique("settings_menu", "default", "legacy") { requireRestart() }.apply { set("default") }
|
||||
val chatHoldKillActions = multiple("chat_hold_kill_actions", "kill_snapchat", "kill_purrfectsnap") { requireRestart() }
|
||||
|
||||
inner class SpoofSnapScore : ConfigContainer(hasGlobalState = true) {
|
||||
val customSnapScore = string("custom_snap_score") {
|
||||
|
||||
@@ -8,9 +8,11 @@ import me.eternal.purrfectsnap.core.util.hook.HookStage
|
||||
import me.eternal.purrfectsnap.core.util.hook.hook
|
||||
import me.eternal.purrfectsnap.core.util.hook.hookConstructor
|
||||
import me.eternal.purrfectsnap.mapper.impl.PlusSubscriptionMapper
|
||||
import java.time.LocalDate
|
||||
import java.time.ZoneId
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
class SnapchatPlus: Feature("SnapchatPlus") {
|
||||
private val originalSubscriptionTime = (System.currentTimeMillis() - 7776000000L)
|
||||
private val expirationTimeMillis = (System.currentTimeMillis() + 15552000000L)
|
||||
|
||||
override fun init() {
|
||||
@@ -40,7 +42,24 @@ class SnapchatPlus: Feature("SnapchatPlus") {
|
||||
//subscription status
|
||||
set(statusField.getAsString()!!, 2)
|
||||
|
||||
set(originalSubscriptionTimeMillisField.getAsString()!!, originalSubscriptionTime)
|
||||
val fallbackOriginalSubscriptionTime = System.currentTimeMillis() - 7776000000L
|
||||
val customPurchaseDate = context.config.global.snapchatPlusPurchaseDate.get().trim()
|
||||
val customPurchaseDateMillis = if (customPurchaseDate.isNotEmpty()) {
|
||||
runCatching {
|
||||
LocalDate
|
||||
.parse(customPurchaseDate, DateTimeFormatter.ISO_LOCAL_DATE)
|
||||
.atStartOfDay(ZoneId.systemDefault())
|
||||
.toInstant()
|
||||
.toEpochMilli()
|
||||
}.getOrNull()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
set(
|
||||
originalSubscriptionTimeMillisField.getAsString()!!,
|
||||
customPurchaseDateMillis ?: fallbackOriginalSubscriptionTime
|
||||
)
|
||||
set(expirationTimeMillisField.getAsString()!!, expirationTimeMillis)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import android.view.ViewGroup
|
||||
import android.view.ViewGroup.MarginLayoutParams
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import me.eternal.purrfectsnap.core.event.events.impl.AddViewEvent
|
||||
import me.eternal.purrfectsnap.core.event.events.impl.BindViewEvent
|
||||
import me.eternal.purrfectsnap.core.features.Feature
|
||||
@@ -69,10 +70,157 @@ class UITweaks : Feature("UITweaks") {
|
||||
}
|
||||
}
|
||||
|
||||
private fun findSpotlightNavTarget(
|
||||
event: AddViewEvent,
|
||||
spotlightNavIds: Set<Int>,
|
||||
spotlightNavNames: Set<String>
|
||||
): View? {
|
||||
data class ViewMetadata(
|
||||
val view: View,
|
||||
val resourceEntryName: String?,
|
||||
val contentDescription: String?,
|
||||
val text: String?,
|
||||
val className: String
|
||||
)
|
||||
|
||||
fun resourceEntryNameOrNull(view: View): String? {
|
||||
val id = view.id
|
||||
if (id == View.NO_ID || id == 0) return null
|
||||
return runCatching { context.resources.getResourceEntryName(id) }.getOrNull()
|
||||
}
|
||||
|
||||
val markerKeywords = setOf("spotlight", "following", "discover")
|
||||
|
||||
val viewChain = buildList {
|
||||
var current: View? = event.view
|
||||
repeat(5) {
|
||||
current ?: return@repeat
|
||||
add(
|
||||
ViewMetadata(
|
||||
view = current!!,
|
||||
resourceEntryName = resourceEntryNameOrNull(current!!),
|
||||
contentDescription = current!!.contentDescription?.toString(),
|
||||
text = (current as? TextView)?.text?.toString(),
|
||||
className = current!!.javaClass.name
|
||||
)
|
||||
)
|
||||
current = current?.parent as? View
|
||||
}
|
||||
}
|
||||
|
||||
fun isExactMatch(metadata: ViewMetadata): Boolean {
|
||||
return metadata.view.id in spotlightNavIds ||
|
||||
metadata.resourceEntryName in spotlightNavNames
|
||||
}
|
||||
|
||||
fun hasMarker(metadata: ViewMetadata): Boolean {
|
||||
return listOfNotNull(
|
||||
metadata.resourceEntryName,
|
||||
metadata.contentDescription,
|
||||
metadata.text
|
||||
).any { value ->
|
||||
markerKeywords.any { keyword ->
|
||||
value.contains(keyword, ignoreCase = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun isNavigationLike(metadata: ViewMetadata): Boolean {
|
||||
val resourceEntryName = metadata.resourceEntryName.orEmpty()
|
||||
val className = metadata.className
|
||||
return resourceEntryName.contains("hova_nav", ignoreCase = true) ||
|
||||
resourceEntryName.contains("bottom_nav", ignoreCase = true) ||
|
||||
resourceEntryName.contains("nav", ignoreCase = true) ||
|
||||
resourceEntryName.contains("tab", ignoreCase = true) ||
|
||||
className.contains("navigation", ignoreCase = true) ||
|
||||
className.contains("bottom", ignoreCase = true) ||
|
||||
className.contains("tab", ignoreCase = true) ||
|
||||
className.contains("hova", ignoreCase = true)
|
||||
}
|
||||
|
||||
if (viewChain.none(::isExactMatch) && viewChain.none(::hasMarker)) {
|
||||
return null
|
||||
}
|
||||
|
||||
var sawSpotlightMarker = false
|
||||
viewChain.forEach { metadata ->
|
||||
if (isExactMatch(metadata) || hasMarker(metadata)) {
|
||||
sawSpotlightMarker = true
|
||||
}
|
||||
|
||||
if (sawSpotlightMarker && isNavigationLike(metadata)) {
|
||||
return metadata.view
|
||||
}
|
||||
}
|
||||
|
||||
return viewChain.firstOrNull(::isExactMatch)?.view
|
||||
}
|
||||
|
||||
private fun findSpotlightHeaderTabsTarget(view: View): View? {
|
||||
fun collectTextLabels(current: View, depth: Int = 0, maxDepth: Int = 2): List<String> {
|
||||
if (depth > maxDepth) return emptyList()
|
||||
|
||||
val ownText = listOfNotNull(
|
||||
current.contentDescription?.toString(),
|
||||
(current as? TextView)?.text?.toString()
|
||||
).filter { it.isNotBlank() }
|
||||
|
||||
if (current !is ViewGroup) return ownText
|
||||
|
||||
return ownText + current.children().flatMap { child ->
|
||||
collectTextLabels(child, depth + 1, maxDepth)
|
||||
}
|
||||
}
|
||||
|
||||
fun isHeaderMarkerText(value: String): Boolean {
|
||||
return value.contains("spotlight", ignoreCase = true) ||
|
||||
value.contains("discover", ignoreCase = true) ||
|
||||
value.contains("following", ignoreCase = true)
|
||||
}
|
||||
|
||||
val candidateChain = buildList {
|
||||
var current: View? = view
|
||||
repeat(6) {
|
||||
current ?: return@repeat
|
||||
add(current!!)
|
||||
current = current?.parent as? View
|
||||
}
|
||||
}
|
||||
|
||||
candidateChain.forEach { candidate ->
|
||||
val group = candidate as? ViewGroup ?: return@forEach
|
||||
if (group.childCount !in 2..4) return@forEach
|
||||
|
||||
val directMarkedChildren = group.children().count { child ->
|
||||
collectTextLabels(child).any(::isHeaderMarkerText)
|
||||
}
|
||||
|
||||
if (directMarkedChildren < 2) return@forEach
|
||||
|
||||
val texts = collectTextLabels(group)
|
||||
.map { it.trim() }
|
||||
.filter { it.isNotBlank() }
|
||||
.distinct()
|
||||
|
||||
val hasSpotlightOrDiscover = texts.any {
|
||||
it.contains("spotlight", ignoreCase = true) ||
|
||||
it.contains("discover", ignoreCase = true)
|
||||
}
|
||||
val hasFollowing = texts.any { it.contains("following", ignoreCase = true) }
|
||||
|
||||
if (hasSpotlightOrDiscover && hasFollowing) {
|
||||
return group
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun onActivityCreate() {
|
||||
val blockAds by context.config.global.blockAds
|
||||
val hiddenElements by context.config.userInterface.hideUiComponents
|
||||
val hideStorySuggestions by context.config.userInterface.hideStorySuggestions
|
||||
val disableSpotlight by context.config.userInterface.disableSpotlight
|
||||
val isImmersiveCamera by context.config.camera.immersiveCameraPreview
|
||||
|
||||
val displayMetrics = context.resources.displayMetrics
|
||||
@@ -80,14 +228,36 @@ class UITweaks : Feature("UITweaks") {
|
||||
|
||||
val chatNoteRecordButton = getId("chat_note_record_button", "id")
|
||||
val unreadHintButton = getId("unread_hint_button", "id")
|
||||
val spotlightNavIds = listOf(
|
||||
getId("hova_nav_spotlight", "id"),
|
||||
getId("ngs_hova_nav_spotlight", "id"),
|
||||
getId("hova_nav_spotlight_tab", "id"),
|
||||
getId("hova_nav_spotlight_button", "id"),
|
||||
getId("hova_nav_discover", "id"),
|
||||
getId("ngs_hova_nav_discover", "id"),
|
||||
getId("hova_nav_discover_tab", "id"),
|
||||
getId("hova_nav_discover_button", "id")
|
||||
).filter { it != 0 }.toSet()
|
||||
val spotlightNavNames = setOf(
|
||||
"hova_nav_spotlight",
|
||||
"ngs_hova_nav_spotlight",
|
||||
"hova_nav_spotlight_tab",
|
||||
"hova_nav_spotlight_button",
|
||||
"hova_nav_discover",
|
||||
"ngs_hova_nav_discover",
|
||||
"hova_nav_discover_tab",
|
||||
"hova_nav_discover_button"
|
||||
)
|
||||
|
||||
Resources::class.java.methods.first { it.name == "getDimensionPixelSize"}.hook(
|
||||
Resources::class.java.methods.first { it.name == "getDimensionPixelSize" }.hook(
|
||||
HookStage.AFTER,
|
||||
{ isImmersiveCamera }
|
||||
) { param ->
|
||||
val id = param.arg<Int>(0)
|
||||
if (id == getId("capri_viewfinder_default_corner_radius", "dimen") ||
|
||||
id == getId("ngs_hova_nav_larger_camera_button_size", "dimen")) {
|
||||
if (
|
||||
id == getId("capri_viewfinder_default_corner_radius", "dimen") ||
|
||||
id == getId("ngs_hova_nav_larger_camera_button_size", "dimen")
|
||||
) {
|
||||
param.setResult(0)
|
||||
}
|
||||
}
|
||||
@@ -96,12 +266,17 @@ class UITweaks : Feature("UITweaks") {
|
||||
if (event.view is FrameLayout) {
|
||||
fun removeView() {
|
||||
event.view.layoutParams = event.view.layoutParams?.apply {
|
||||
width = 0; height = 0
|
||||
width = 0
|
||||
height = 0
|
||||
} ?: return
|
||||
}
|
||||
|
||||
val viewModelString = event.prevModel.toString()
|
||||
val isMyStory by lazy { viewModelString.let { it.startsWith("StoryCarouselItemViewModel") && it.contains("storyId=") } }
|
||||
val isMyStory by lazy {
|
||||
viewModelString.let {
|
||||
it.startsWith("StoryCarouselItemViewModel") && it.contains("storyId=")
|
||||
}
|
||||
}
|
||||
|
||||
if (hideStorySuggestions.contains("hide_my_stories") && isMyStory) {
|
||||
removeView()
|
||||
@@ -110,6 +285,10 @@ class UITweaks : Feature("UITweaks") {
|
||||
}
|
||||
}
|
||||
|
||||
context.event.subscribe(BindViewEvent::class, { disableSpotlight }) { event ->
|
||||
findSpotlightHeaderTabsTarget(event.view)?.hideViewCompletely()
|
||||
}
|
||||
|
||||
context.event.subscribe(AddViewEvent::class) { event ->
|
||||
val viewId = event.view.id
|
||||
val view = event.view
|
||||
@@ -118,6 +297,15 @@ class UITweaks : Feature("UITweaks") {
|
||||
hideStorySection(event)
|
||||
}
|
||||
|
||||
findSpotlightNavTarget(event, spotlightNavIds, spotlightNavNames)?.takeIf { disableSpotlight }?.let { targetView ->
|
||||
targetView.hideViewCompletely()
|
||||
if (targetView !== view) {
|
||||
view.hideViewCompletely()
|
||||
}
|
||||
event.canceled = true
|
||||
return@subscribe
|
||||
}
|
||||
|
||||
if (isImmersiveCamera) {
|
||||
if (view.id == getId("edits_container", "id")) {
|
||||
Hooker.hookObjectMethod(View::class.java, view, "layout", HookStage.BEFORE) {
|
||||
@@ -134,7 +322,10 @@ class UITweaks : Feature("UITweaks") {
|
||||
}
|
||||
}
|
||||
|
||||
if (hiddenElements.contains("hide_billboard_prompt") && event.parent.javaClass.name.endsWith("BillboardFeedHeaderPromptComponent")) {
|
||||
if (
|
||||
hiddenElements.contains("hide_billboard_prompt") &&
|
||||
event.parent.javaClass.name.endsWith("BillboardFeedHeaderPromptComponent")
|
||||
) {
|
||||
hideView(event.parent)
|
||||
view.getValdiContext()?.componentContext?.get()?.dataBuilder {
|
||||
val dismissFunction = get<Any>("_onDismiss") ?: return@subscribe
|
||||
@@ -142,7 +333,11 @@ class UITweaks : Feature("UITweaks") {
|
||||
}
|
||||
}
|
||||
|
||||
if (event.parent.javaClass.name.endsWith("ConstraintLayout") && event.view is LinearLayout && hiddenElements.contains("hide_map_reactions")) {
|
||||
if (
|
||||
event.parent.javaClass.name.endsWith("ConstraintLayout") &&
|
||||
event.view is LinearLayout &&
|
||||
hiddenElements.contains("hide_map_reactions")
|
||||
) {
|
||||
val viewGroup = event.view as ViewGroup
|
||||
val children = viewGroup.children()
|
||||
|
||||
@@ -154,7 +349,10 @@ class UITweaks : Feature("UITweaks") {
|
||||
}
|
||||
}
|
||||
|
||||
if (event.parent.javaClass.name.endsWith("PreviewBottomToolbarView") && hiddenElements.contains("hide_post_to_story_buttons")) {
|
||||
if (
|
||||
event.parent.javaClass.name.endsWith("PreviewBottomToolbarView") &&
|
||||
hiddenElements.contains("hide_post_to_story_buttons")
|
||||
) {
|
||||
if (event.parent.childCount == 1) {
|
||||
event.view.hideViewCompletely()
|
||||
}
|
||||
@@ -163,7 +361,8 @@ class UITweaks : Feature("UITweaks") {
|
||||
if (viewId == getId("send_btn", "id") && hiddenElements.contains("hide_post_to_story_buttons")) {
|
||||
// hide previous view
|
||||
if (event.parent.childCount > 0) {
|
||||
val lastChild = event.parent.getChildAt(event.parent.childCount - 1)?.takeIf { it is LinearLayout } ?: return@subscribe
|
||||
val lastChild = event.parent.getChildAt(event.parent.childCount - 1)
|
||||
?.takeIf { it is LinearLayout } ?: return@subscribe
|
||||
context.log.verbose("Hiding post to story button")
|
||||
lastChild.hideViewCompletely()
|
||||
}
|
||||
@@ -174,7 +373,11 @@ class UITweaks : Feature("UITweaks") {
|
||||
|
||||
if (hiddenElements.contains("hide_live_location_share_button")) {
|
||||
chatInputBar?.onLayoutChange {
|
||||
chatInputBar!!.children().lastOrNull { it.javaClass.name.endsWith("AppCompatImageButton") && runCatching { it.resources.getResourceName(it.id) }.getOrNull() == null }
|
||||
chatInputBar!!.children()
|
||||
.lastOrNull {
|
||||
it.javaClass.name.endsWith("AppCompatImageButton") &&
|
||||
runCatching { it.resources.getResourceName(it.id) }.getOrNull() == null
|
||||
}
|
||||
?.hideViewCompletely()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
package me.eternal.purrfectsnap.core.ui.menu.impl
|
||||
|
||||
import android.app.ActivityManager
|
||||
import android.os.Process
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.ImageView
|
||||
import me.eternal.purrfectsnap.common.Constants
|
||||
import me.eternal.purrfectsnap.common.ui.OverlayType
|
||||
import me.eternal.purrfectsnap.core.event.events.impl.AddViewEvent
|
||||
import me.eternal.purrfectsnap.core.ui.menu.AbstractMenu
|
||||
import me.eternal.purrfectsnap.core.util.ktx.getDrawable
|
||||
import me.eternal.purrfectsnap.core.util.ktx.getStyledAttributes
|
||||
import me.eternal.purrfectsnap.core.util.ktx.vibrateLongPress
|
||||
|
||||
class SettingsGearInjector : AbstractMenu() {
|
||||
private val hovaHeaderAddFriendIconId by lazy {
|
||||
@@ -47,6 +51,9 @@ class SettingsGearInjector : AbstractMenu() {
|
||||
this@SettingsGearInjector.context.log.info("Gear icon clicked.", logTag)
|
||||
this@SettingsGearInjector.context.bridgeClient.openOverlay(OverlayType.SETTINGS)
|
||||
}
|
||||
setOnLongClickListener {
|
||||
this@SettingsGearInjector.handleChatHoldKillAction()
|
||||
}
|
||||
}
|
||||
|
||||
val layoutParams = FrameLayout.LayoutParams(
|
||||
@@ -88,4 +95,25 @@ class SettingsGearInjector : AbstractMenu() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleChatHoldKillAction(): Boolean {
|
||||
val selectedActions = context.config.userInterface.chatHoldKillActions.get()
|
||||
if (selectedActions.isEmpty()) return false
|
||||
|
||||
context.androidContext.vibrateLongPress()
|
||||
context.mainActivity?.vibrateLongPress()
|
||||
|
||||
if (selectedActions.contains("kill_purrfectsnap")) {
|
||||
runCatching {
|
||||
val activityManager = context.androidContext.getSystemService(ActivityManager::class.java)
|
||||
activityManager?.killBackgroundProcesses(Constants.MODULE_PACKAGE_NAME)
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedActions.contains("kill_snapchat")) {
|
||||
Process.killProcess(Process.myPid())
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
package me.eternal.purrfectsnap.core.ui.menu.impl
|
||||
|
||||
import android.app.ActivityManager
|
||||
import android.os.Process
|
||||
import android.view.View
|
||||
import android.widget.FrameLayout
|
||||
import me.eternal.purrfectsnap.common.Constants
|
||||
import me.eternal.purrfectsnap.common.ui.OverlayType
|
||||
import me.eternal.purrfectsnap.core.ui.menu.AbstractMenu
|
||||
import me.eternal.purrfectsnap.core.util.hook.HookStage
|
||||
import me.eternal.purrfectsnap.core.util.hook.hook
|
||||
import me.eternal.purrfectsnap.core.util.ktx.getId
|
||||
import me.eternal.purrfectsnap.core.util.ktx.vibrateLongPress
|
||||
|
||||
class SettingsMenu : AbstractMenu() {
|
||||
private val hovaHeaderSearchIconId by lazy {
|
||||
@@ -22,8 +26,32 @@ class SettingsMenu : AbstractMenu() {
|
||||
view.setOnClickListener {
|
||||
context.bridgeClient.openOverlay(OverlayType.SETTINGS)
|
||||
}
|
||||
view.setOnLongClickListener {
|
||||
handleChatHoldKillAction()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleChatHoldKillAction(): Boolean {
|
||||
val selectedActions = context.config.userInterface.chatHoldKillActions.get()
|
||||
if (selectedActions.isEmpty()) return false
|
||||
|
||||
context.androidContext.vibrateLongPress()
|
||||
context.mainActivity?.vibrateLongPress()
|
||||
|
||||
if (selectedActions.contains("kill_purrfectsnap")) {
|
||||
runCatching {
|
||||
val activityManager = context.androidContext.getSystemService(ActivityManager::class.java)
|
||||
activityManager?.killBackgroundProcesses(Constants.MODULE_PACKAGE_NAME)
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedActions.contains("kill_snapchat")) {
|
||||
Process.killProcess(Process.myPid())
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -338,4 +338,14 @@ case "$1" in
|
||||
esac
|
||||
|
||||
cd "$RUST_DIR"
|
||||
|
||||
# macOS CI runners may inject DYLD override variables that break Rust/cargo
|
||||
# processes with libc++abi symbol shim errors and bus error 10.
|
||||
if [[ "$HOST_TAG" == darwin-* ]]; then
|
||||
unset DYLD_INSERT_LIBRARIES
|
||||
unset DYLD_LIBRARY_PATH
|
||||
unset DYLD_FRAMEWORK_PATH
|
||||
unset DYLD_ROOT_PATH
|
||||
fi
|
||||
|
||||
rustup run "$TOOLCHAIN" cargo build --release --target "$1"
|
||||
|
||||
Reference in New Issue
Block a user