This commit is contained in:
Vitor Pamplona
2026-05-14 12:05:17 -04:00
parent 0a330f18b0
commit 93c6010e19
11 changed files with 35 additions and 25 deletions
@@ -35,6 +35,7 @@ import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.unit.dp
import com.vitorpamplona.amethyst.service.playback.composable.MediaControllerState
import java.lang.ref.WeakReference
import kotlin.math.abs
/**
@@ -58,8 +59,10 @@ private var winner: TrackedVideo? = null
// Cached result of view.getGlobalVisibleRect. The compose root rarely changes
// across a scroll burst; caching avoids N native calls per frame.
// The view is held weakly: it is only used for an identity check, never
// dereferenced, and a strong static reference would leak the Activity.
private val cachedRootRect = Rect()
private var cachedRootRectView: View? = null
private var cachedRootRectView: WeakReference<View>? = null
private var cachedRootRectVisible: Boolean = false
private var cachedRootRectTimeNs: Long = 0L
private const val ROOT_RECT_CACHE_TTL_NS = 8_000_000L // ~half a frame at 60fps
@@ -191,11 +194,11 @@ private fun electNewWinner() {
*/
private fun cachedGlobalVisibleRect(view: View): Rect? {
val now = System.nanoTime()
if (cachedRootRectView === view && (now - cachedRootRectTimeNs) < ROOT_RECT_CACHE_TTL_NS) {
if (cachedRootRectView?.get() === view && (now - cachedRootRectTimeNs) < ROOT_RECT_CACHE_TTL_NS) {
return if (cachedRootRectVisible) cachedRootRect else null
}
cachedRootRectVisible = view.getGlobalVisibleRect(cachedRootRect)
cachedRootRectView = view
cachedRootRectView = WeakReference(view)
cachedRootRectTimeNs = now
return if (cachedRootRectVisible) cachedRootRect else null
}
@@ -285,7 +285,6 @@ class CallActivity : AppCompatActivity() {
}
private fun enterPipIfActive() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
val callManager = CallSessionBridge.callManager ?: return
val state = callManager.state.value
val isActive =
@@ -45,8 +45,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.FilterQuality
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalWindowInfo
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
@@ -107,17 +106,10 @@ private fun LoadedPdfPreviewCard(
) {
val sharePopupExpanded = remember { mutableStateOf(false) }
val density = LocalDensity.current
val configuration = LocalConfiguration.current
val containerWidthPx = LocalWindowInfo.current.containerSize.width
val targetWidthPx =
remember(density, configuration) {
val screenPx =
with(density) {
configuration.screenWidthDp.dp
.toPx()
.toInt()
}
screenPx.coerceAtMost(THUMBNAIL_MAX_DIM_PX).coerceAtLeast(1)
remember(containerWidthPx) {
containerWidthPx.coerceAtMost(THUMBNAIL_MAX_DIM_PX).coerceAtLeast(1)
}
@Suppress("ProduceStateDoesNotAssignValue")
@@ -24,7 +24,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.State
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.mutableLongStateOf
import androidx.compose.runtime.produceState
import com.vitorpamplona.quartz.utils.TimeUtils
import kotlinx.coroutines.delay
@@ -35,7 +35,7 @@ private const val TICK_INTERVAL_MS = 30_000L
// every TimeAgo on screen reads from it. Because TimeAgo wraps the formatted string in
// `derivedStateOf`, the Text only recomposes when the displayed string actually changes
// (e.g. crossing 1m → 2m) — not on every tick.
val LocalNowSeconds = compositionLocalOf<State<Long>> { mutableStateOf(TimeUtils.now()) }
val LocalNowSeconds = compositionLocalOf<State<Long>> { mutableLongStateOf(TimeUtils.now()) }
@Composable
fun NowProvider(content: @Composable () -> Unit) {
@@ -46,6 +46,7 @@ import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
@@ -76,7 +77,7 @@ fun MarmotGroupListScreen(
nav: INav,
) {
var groupList by remember { mutableStateOf(listOf<Pair<HexKey, MarmotGroupChatroom>>()) }
var selectedTab by remember { mutableStateOf(0) }
var selectedTab by remember { mutableIntStateOf(0) }
// Load group list
LaunchedEffect(Unit) {
@@ -48,7 +48,7 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
@@ -435,7 +435,7 @@ private fun RoomHeader(
*/
@Composable
private fun CachedListenerCount(roomATag: String) {
var count by remember(roomATag) { mutableStateOf(0) }
var count by remember(roomATag) { mutableIntStateOf(0) }
LaunchedEffect(roomATag) {
val filter =
Filter(
@@ -47,6 +47,7 @@ import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
@@ -129,7 +130,7 @@ internal fun NestFullScreen(
// Tab selection survives configuration changes and PIP transitions.
// Stored as ordinal so rememberSaveable can persist it without a
// custom Saver.
var selectedTabIndex by rememberSaveable { mutableStateOf(0) }
var selectedTabIndex by rememberSaveable { mutableIntStateOf(0) }
val isHost = accountViewModel.account.signer.pubKey == event.pubKey
val myPubkey = accountViewModel.account.signer.pubKey
@@ -63,6 +63,7 @@ import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
@@ -672,7 +673,7 @@ private fun HandRaiseBadge(modifier: Modifier = Modifier) {
Box(
modifier =
modifier
.offset(x = 2.dp, y = (-2).dp + offsetY.dp)
.offset { IntOffset(2.dp.roundToPx(), ((-2).dp + offsetY.dp).roundToPx()) }
.size(MAX_BADGE_SIZE)
.background(MaterialTheme.colorScheme.tertiary, CircleShape),
contentAlignment = Alignment.Center,
@@ -38,7 +38,7 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
@@ -93,7 +93,7 @@ private fun ZapChip(zap: RoomZap) {
// + fade in lockstep with how close it is to being evicted. Re-key
// on the event id so the animation restarts whenever a fresh zap
// replaces the value at this slot.
var progress by remember(zap.eventId) { mutableStateOf(0f) }
var progress by remember(zap.eventId) { mutableFloatStateOf(0f) }
LaunchedEffect(zap.eventId) {
val ageMs = (System.currentTimeMillis() / 1000L - zap.createdAtSec).coerceAtLeast(0L) * 1000L
val remaining = (ZAP_WINDOW_MS - ageMs).coerceAtLeast(0L)
@@ -428,6 +428,7 @@
<string name="scheduled_posts_logout_toast_zero">Desconectado</string>
<plurals name="scheduled_posts_logout_toast">
<item quantity="one">Desconectado · %d post agendado excluído</item>
<item quantity="many">Desconectado · %d de posts agendados excluídos</item>
<item quantity="other">Desconectado · %d posts agendados excluídos</item>
</plurals>
<string name="scheduled_posts_notification_sent_title">Post agendado publicado</string>
@@ -445,18 +446,22 @@
<string name="scheduled_posts_status_cancelled">Cancelado</string>
<plurals name="scheduled_posts_logout_warning">
<item quantity="one">Você tem %d post agendado que ainda não foi publicado. Sair excluirá esse post permanentemente.</item>
<item quantity="many">Você tem %d de posts agendados que ainda não foram publicados. Sair excluirá esses posts permanentemente.</item>
<item quantity="other">Você tem %d posts agendados que ainda não foram publicados. Sair excluirá esses posts permanentemente.</item>
</plurals>
<plurals name="scheduled_posts_subtitle_queued">
<item quantity="one">%d na fila</item>
<item quantity="many">%d na fila</item>
<item quantity="other">%d na fila</item>
</plurals>
<plurals name="scheduled_posts_subtitle_due_suffix">
<item quantity="one"> · %d em 1h</item>
<item quantity="many"> · %d em 1h</item>
<item quantity="other"> · %d em 1h</item>
</plurals>
<plurals name="scheduled_posts_relay_count">
<item quantity="one">para %d relay</item>
<item quantity="many">para %d de relays</item>
<item quantity="other">para %d relays</item>
</plurals>
<string name="scheduled_posts_event_id_copied">ID do post copiado</string>
@@ -616,6 +621,7 @@
<string name="nest_leave_host_close_failed">Não foi possível marcar a sala como fechada. Saindo mesmo assim — a sala será fechada automaticamente.</string>
<plurals name="nest_listener_count">
<item quantity="one">%1$d ouvinte</item>
<item quantity="many">%1$d de ouvintes</item>
<item quantity="other">%1$d ouvintes</item>
</plurals>
<string name="nest_live_chip">AO VIVO</string>
@@ -2266,26 +2272,32 @@
<string name="last_seen_never">Nunca visto</string>
<plurals name="duration_minutes">
<item quantity="one">%1$d minuto</item>
<item quantity="many">%1$d de minutos</item>
<item quantity="other">%1$d minutos</item>
</plurals>
<plurals name="duration_hours">
<item quantity="one">%1$d hora</item>
<item quantity="many">%1$d de horas</item>
<item quantity="other">%1$d horas</item>
</plurals>
<plurals name="duration_days">
<item quantity="one">%1$d dia</item>
<item quantity="many">%1$d de dias</item>
<item quantity="other">%1$d dias</item>
</plurals>
<plurals name="duration_weeks">
<item quantity="one">%1$d semana</item>
<item quantity="many">%1$d de semanas</item>
<item quantity="other">%1$d semanas</item>
</plurals>
<plurals name="duration_months">
<item quantity="one">%1$d mês</item>
<item quantity="many">%1$d de meses</item>
<item quantity="other">%1$d meses</item>
</plurals>
<plurals name="duration_years">
<item quantity="one">%1$d ano</item>
<item quantity="many">%1$d de anos</item>
<item quantity="other">%1$d anos</item>
</plurals>
<string name="event_sync_less_than_until">&lt;%1$s</string>
@@ -42,6 +42,7 @@
<locale android:name="ks-IN"/>
<locale android:name="ku-TR"/>
<locale android:name="lt-LT"/>
<locale android:name="lv-LV"/>
<locale android:name="ne-NP"/>
<locale android:name="nl"/>
<locale android:name="nl-BE"/>