Manager app stutters and lag fixes
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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) }
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user