Manager app stutters and lag fixes

This commit is contained in:
DarkKnight2122
2026-04-25 08:23:44 +05:30
parent cbe9754fba
commit d4a5a60cc1
11 changed files with 110 additions and 52 deletions

View File

@@ -5,6 +5,9 @@ import android.content.Intent
import android.os.IBinder
import android.os.ParcelFileDescriptor
import android.os.RemoteException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import me.eternal.purrfectsnap.RemoteSideContext
import me.eternal.purrfectsnap.SharedContextHolder
@@ -240,16 +243,18 @@ class BridgeService : Service() {
if (chunkIndex == totalChunks - 1) {
val finalFriends = friendAccumulator.toList()
val finalGroups = groupAccumulator.toList()
pendingSocialSnapshotCallback?.let { callback ->
pendingSocialSnapshotCallback = null
callback(finalFriends, finalGroups)
}
remoteSideContext.database.replaceMessagingData(finalFriends, finalGroups)
remoteSideContext.database.messagingDataFlow.tryEmit(finalFriends to finalGroups)
friendAccumulator.clear()
groupAccumulator.clear()
remoteSideContext.coroutineScope.launch(Dispatchers.IO) {
pendingSocialSnapshotCallback?.let { callback ->
pendingSocialSnapshotCallback = null
callback(finalFriends, finalGroups)
}
remoteSideContext.database.replaceMessagingData(finalFriends, finalGroups)
remoteSideContext.database.messagingDataFlow.tryEmit(finalFriends to finalGroups)
}
}
}

View File

@@ -181,7 +181,6 @@ class FeaturesRootSection : Routes.Route() {
}
internal fun getRandomizedProfileSnapshot(): String {
context.config.load()
return context.config.root.experimental.spoof.randomizeDeviceProfile.currentProfileSnapshot.getNullable()
?.takeIf { it.isNotBlank() }
?: (context.translation["manager.dialogs.randomize_device_profile.empty"]

View File

@@ -279,8 +279,9 @@ class ManageScriptReposSection : Routes.Route() {
}
override val content: @Composable (androidx.navigation.NavBackStackEntry) -> Unit = {
val repositories by remember(refreshTrigger.value) {
mutableStateOf<List<String>>(runBlocking { context.database.getRepositories("script") })
var repositories by remember { mutableStateOf<List<String>>(emptyList()) }
LaunchedEffect(refreshTrigger.value) {
repositories = context.database.getRepositories("script")
}
val density = LocalDensity.current
val statusBarTopPadding = WindowInsets.statusBars.asPaddingValues().calculateTopPadding()

View File

@@ -225,24 +225,30 @@ class AddFriendDialog(
friends: List<MessagingFriendInfo>,
groups: List<MessagingGroupInfo>
) {
cachedFriends = context.sortSocialFriends(friends, pinnedIds = pinnedIds)
cachedGroups = groups.run {
if (pinnedIds != null) {
sortedBy { -pinnedIds.indexOf(it.conversationId) }
} else {
// Priority sort for whitelisted groups
val whitelistedIds = context.database.getRuleIds(MessagingRuleType.STEALTH.key).toSet()
sortedWith { a, b ->
val aSelected = whitelistedIds.contains(a.conversationId)
val bSelected = whitelistedIds.contains(b.conversationId)
if (aSelected != bSelected) if (aSelected) -1 else 1
else a.name.compareTo(b.name, ignoreCase = true)
coroutineScope.launch(Dispatchers.IO) {
val sortedFriends = context.sortSocialFriends(friends, pinnedIds = pinnedIds)
val sortedGroups = groups.run {
if (pinnedIds != null) {
sortedBy { -pinnedIds.indexOf(it.conversationId) }
} else {
// Priority sort for whitelisted groups
val whitelistedIds = context.database.getRuleIds(MessagingRuleType.STEALTH.key).toSet()
sortedWith { a, b ->
val aSelected = whitelistedIds.contains(a.conversationId)
val bSelected = whitelistedIds.contains(b.conversationId)
if (aSelected != bSelected) if (aSelected) -1 else 1
else a.name.compareTo(b.name, ignoreCase = true)
}
}
}
withContext(Dispatchers.Main) {
cachedFriends = sortedFriends
cachedGroups = sortedGroups
if (friends.isNotEmpty() || groups.isNotEmpty()) {
timeoutJob?.cancel()
hasFetchError = false
}
}
}
if (friends.isNotEmpty() || groups.isNotEmpty()) {
timeoutJob?.cancel()
hasFetchError = false
}
}

View File

@@ -35,6 +35,7 @@ import me.eternal.purrfectsnap.storage.getFriendInfo
import me.eternal.purrfectsnap.ui.manager.Routes
import me.eternal.purrfectsnap.ui.util.Dialog
import me.eternal.purrfectsnap.ui.util.coil.ImageRequestHelper
import me.eternal.purrfectsnap.common.ui.rememberAsyncMutableState
import java.io.File
import java.text.DateFormat
import java.util.Date
@@ -44,12 +45,11 @@ import kotlin.math.absoluteValue
class LoggedStories : Routes.Route() {
override val title: @Composable () -> Unit = {
val navBackStackEntry by routes.navController.currentBackStackEntryAsState()
val text = remember(navBackStackEntry) {
navBackStackEntry?.arguments?.getString("id")?.let {
context.database.getFriendInfo(it)?.displayName
}
val userId = navBackStackEntry?.arguments?.getString("id")
val displayName by rememberAsyncMutableState(defaultValue = null) {
userId?.let { context.database.getFriendInfo(it)?.displayName }
}
text?.let { Text(it, maxLines = 1, overflow = TextOverflow.Ellipsis) }
displayName?.let { Text(it, maxLines = 1, overflow = TextOverflow.Ellipsis) }
}
@OptIn(ExperimentalCoilApi::class, ExperimentalLayoutApi::class)
@@ -57,7 +57,9 @@ class LoggedStories : Routes.Route() {
val userId = navBackStackEntry.arguments?.getString("id") ?: return@content
val stories = remember { mutableStateListOf<StoryData>() }
val friendInfo = remember { context.database.getFriendInfo(userId) }
val friendInfo by rememberAsyncMutableState(defaultValue = null) {
context.database.getFriendInfo(userId)
}
var lastStoryTimestamp by remember { mutableLongStateOf(Long.MAX_VALUE) }
var selectedStory by remember { mutableStateOf<StoryData?>(null) }

View File

@@ -65,8 +65,13 @@ class SocialRootSection : Routes.Route() {
// Real-time synchronization from the bridge
context.database.messagingDataFlow.collect { (friends, groups) ->
friendList = context.sortSocialFriends(friends)
groupList = groups
withContext(Dispatchers.IO) {
val sortedFriends = context.sortSocialFriends(friends)
withContext(Dispatchers.Main) {
friendList = sortedFriends
groupList = groups
}
}
}
}
}

View File

@@ -444,8 +444,10 @@ fun HomeRootSection.AphelionHomeScreen(nav: NavBackStackEntry) {
}
hasInitialized -> storedTiles
else -> {
context.database.setQuickTiles(allQuickTileNames)
prefs.edit().putBoolean(HomeRootSection.QUICK_TILES_INITIALIZED_PREF, true).apply()
context.coroutineScope.launch(Dispatchers.IO) {
context.database.setQuickTiles(allQuickTileNames)
prefs.edit().putBoolean(HomeRootSection.QUICK_TILES_INITIALIZED_PREF, true).apply()
}
allQuickTileNames
}
}

View File

@@ -337,8 +337,10 @@ object LegacyTheme : ThemeContract {
}
hasInitializedQuickTiles -> storedTiles
else -> {
context.database.setQuickTiles(allQuickTileNames)
prefs.edit().putBoolean(QUICK_TILES_INITIALIZED_PREF, true).apply()
context.coroutineScope.launch(Dispatchers.IO) {
context.database.setQuickTiles(allQuickTileNames)
prefs.edit().putBoolean(QUICK_TILES_INITIALIZED_PREF, true).apply()
}
allQuickTileNames
}
}

View File

@@ -269,8 +269,9 @@ class ManageFriendTrackerReposSection: Routes.Route() {
}
override val content: @Composable (androidx.navigation.NavBackStackEntry) -> Unit = {
val repositories by remember(refreshTrigger.value) {
mutableStateOf<List<String>>(runBlocking { context.database.getRepositories("friend_tracker") })
var repositories by remember { mutableStateOf<List<String>>(emptyList()) }
LaunchedEffect(refreshTrigger.value) {
repositories = context.database.getRepositories("friend_tracker")
}
val density = LocalDensity.current
val statusBarTopPadding = WindowInsets.statusBars.asPaddingValues().calculateTopPadding()

View File

@@ -20,6 +20,7 @@ import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.MutableSharedFlow
import me.eternal.purrfectsnap.bridge.AutoOpenInterface
import me.eternal.purrfectsnap.common.config.PropertyValue
import me.eternal.purrfectsnap.common.config.ModConfig
import me.eternal.purrfectsnap.common.data.ContentType
import me.eternal.purrfectsnap.common.data.MessageState
import me.eternal.purrfectsnap.common.data.MessageUpdate
@@ -93,6 +94,7 @@ class AutoOpenSnaps: MessagingRuleFeature("Auto Open Snaps", MessagingRuleType.A
private val lastSaveTime = AtomicLong(System.currentTimeMillis())
private var isThermalThrottled = false
private var lastThermalThrottleAt = 0L
private var actionReceiver: BroadcastReceiver? = null
private fun logInfo(msg: String) = this@AutoOpenSnaps.context.log.info("[AutoOpenEngine] $msg")
private fun logError(msg: String, e: Throwable? = null) = if (e != null) this@AutoOpenSnaps.context.log.error("[AutoOpenEngine] $msg", e) else this@AutoOpenSnaps.context.log.error("[AutoOpenEngine] $msg")
@@ -111,6 +113,8 @@ class AutoOpenSnaps: MessagingRuleFeature("Auto Open Snaps", MessagingRuleType.A
}
override fun init() {
if (autoOpenConfig.globalState == false) return
restorePersistence()
createNotificationChannels()
@@ -477,7 +481,7 @@ class AutoOpenSnaps: MessagingRuleFeature("Auto Open Snaps", MessagingRuleType.A
}
private fun setupReceivers() {
val actionReceiver = object : BroadcastReceiver() {
actionReceiver = object : BroadcastReceiver() {
override fun onReceive(ctx: Context?, intent: Intent?) {
when (intent?.action) {
ACTION_PAUSE_RESUME -> { isPaused.set(!isPaused.get()); updateStatusNotification(force = true) }
@@ -501,8 +505,8 @@ class AutoOpenSnaps: MessagingRuleFeature("Auto Open Snaps", MessagingRuleType.A
addAction(Intent.ACTION_SCREEN_OFF)
addAction(Intent.ACTION_BATTERY_CHANGED)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) this@AutoOpenSnaps.context.androidContext.registerReceiver(actionReceiver, filter, Context.RECEIVER_NOT_EXPORTED)
else this@AutoOpenSnaps.context.androidContext.registerReceiver(actionReceiver, filter)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) this@AutoOpenSnaps.context.androidContext.registerReceiver(actionReceiver!!, filter, Context.RECEIVER_NOT_EXPORTED)
else this@AutoOpenSnaps.context.androidContext.registerReceiver(actionReceiver!!, filter)
}
private fun recordSpeedTimestamp() { synchronized(snapTimestamps) { snapTimestamps.addLast(System.currentTimeMillis()); if (snapTimestamps.size > 250) snapTimestamps.removeFirst() } }
@@ -510,9 +514,39 @@ class AutoOpenSnaps: MessagingRuleFeature("Auto Open Snaps", MessagingRuleType.A
private fun shutdownFeature() {
engineActive.set(false)
engineJob?.cancel()
releaseWakeLock()
cancelStatusNotification()
saveQueueToDisk()
// Permanently disable the feature in settings
autoOpenConfig.globalState = false
this@AutoOpenSnaps.context.coroutineScope.launch {
runCatching {
val field = context::class.java.getDeclaredField("_config").apply { isAccessible = true }
val modConfig = (field.get(context) as Lazy<*>).value as ModConfig
modConfig.writeConfig()
}
}
// Surgical clean-up: release resources and listeners
actionReceiver?.let {
runCatching { this@AutoOpenSnaps.context.androidContext.unregisterReceiver(it) }
}
actionReceiver = null
wakeLockCooldownJob?.cancel()
// Grace period for WakeLock release
this@AutoOpenSnaps.context.coroutineScope.launch {
delay(60000)
releaseWakeLock()
}
// Show final "Stopped" notice
val builder = Notification.Builder(this@AutoOpenSnaps.context.androidContext, "auto_open_status")
.setOngoing(false)
.setSmallIcon(android.R.drawable.ic_menu_close_clear_cancel)
.setContentTitle("Auto-Open")
.setContentText("Auto-Open Engine Disabled. Re-enable in settings.")
notificationManager.notify(STATUS_NOTIFICATION_ID, builder.build())
}
private fun cancelStatusNotification() = notificationManager.cancel(STATUS_NOTIFICATION_ID)

View File

@@ -108,9 +108,10 @@ class FriendFeedMessagePreview : Feature("FriendFeedMessagePreview") {
}
fetchMessages(conversationId) {
val fontMetrics = textPaint.fontMetrics
val universalTextSize = 12 * density
val fontMetrics = textPaint.apply { textSize = universalTextSize }.fontMetrics
val lineHeight = (fontMetrics.descent - fontMetrics.ascent).toInt()
val spacing = (2 * density).toInt()
val spacing = (4 * density).toInt()
val messages = messageCache[conversationId]
val previewContainerHeight = if (messages.isNullOrEmpty()) 0 else (messages.size * (lineHeight + spacing))
@@ -123,16 +124,16 @@ class FriendFeedMessagePreview : Feature("FriendFeedMessagePreview") {
}
ffItem.layoutParams = ffItem.layoutParams.apply {
height = feedEntryHeight + (safetyGap * 1.5f).toInt() + previewContainerHeight
height = feedEntryHeight + (safetyGap).toInt() + previewContainerHeight
}
cachedLayouts[conversationId] = frameLayout
frameLayout.addForegroundDrawable("ffItem", ShapeDrawable(object: Shape() {
override fun draw(canvas: Canvas, paint: Paint) {
val startY = feedEntryHeight.toFloat() + (1 * density).toInt()
paint.textSize = secondaryTextSize
paint.color = Color(context.userInterface.colorPrimary).copy(alpha = 0.7f).toArgb()
val startY = feedEntryHeight.toFloat() - (9 * density)
paint.textSize = universalTextSize
paint.color = Color(context.userInterface.colorPrimary).copy(alpha = 0.85f).toArgb()
paint.typeface = Typeface.DEFAULT
paint.isAntiAlias = true