Sync push

This commit is contained in:
OmniaX-Dev 2026-06-24 07:54:59 +02:00
parent 9eb2bc7fda
commit d4e6b2e8cc
3 changed files with 41 additions and 18 deletions

1
.gitignore vendored
View file

@ -13,6 +13,7 @@
.externalNativeBuild .externalNativeBuild
.cxx .cxx
local.properties local.properties
/app/release
# Signing / secrets # Signing / secrets
*.jks *.jks

View file

@ -1,5 +1,8 @@
package com.keylightpiano.pianotimeline.ui package com.keylightpiano.pianotimeline.ui
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.spring
import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.basicMarquee import androidx.compose.foundation.basicMarquee
@ -31,10 +34,12 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
@ -42,6 +47,7 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.keylightpiano.pianotimeline.R import com.keylightpiano.pianotimeline.R
import com.keylightpiano.pianotimeline.domain.Recording import com.keylightpiano.pianotimeline.domain.Recording
import kotlinx.coroutines.launch
private val NotesGreen = Color(0xFF25B300) private val NotesGreen = Color(0xFF25B300)
private val NewBadgeRed = Color(0xFFE53935) private val NewBadgeRed = Color(0xFFE53935)
@ -145,15 +151,38 @@ fun RecordingItem(
} }
} }
// Favorite star // Favorite star with a spring "pop" on each tap.
IconButton(onClick = onFavoriteClick, modifier = Modifier.size(36.dp)) { val starScale = remember { Animatable(1f) }
val starScope = rememberCoroutineScope()
IconButton(
onClick = {
onFavoriteClick()
starScope.launch {
// Pop up then settle back with a springy overshoot.
starScale.snapTo(0.6f)
starScale.animateTo(
targetValue = 1f,
animationSpec = spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessMedium
)
)
}
},
modifier = Modifier.size(36.dp)
) {
Icon( Icon(
imageVector = if (recording.isFavorite) Icons.Filled.Star imageVector = if (recording.isFavorite) Icons.Filled.Star
else Icons.Outlined.StarBorder, else Icons.Outlined.StarBorder,
contentDescription = stringResource(if (recording.isFavorite) R.string.cd_unfavorite else R.string.cd_favorite), contentDescription = stringResource(if (recording.isFavorite) R.string.cd_unfavorite else R.string.cd_favorite),
tint = if (recording.isFavorite) Color(0xFFFFC107) tint = if (recording.isFavorite) Color(0xFFFFC107)
else MaterialTheme.colorScheme.onSurfaceVariant, else MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.size(20.dp) modifier = Modifier
.size(20.dp)
.graphicsLayer {
scaleX = starScale.value
scaleY = starScale.value
}
) )
} }

View file

@ -51,7 +51,6 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.runtime.withFrameNanos
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
@ -111,20 +110,13 @@ fun RecordingListScreen(
wasRefreshing = isRefreshing wasRefreshing = isRefreshing
} }
// Preserve scroll position across a favorite toggle (which reorders the list). // Favoriting reorders the list (favorites float to the top). We let the
// When the user stars/unstars an item, we snapshot the current top item's // LazyColumn's animateItem() handle the visual feedback — the toggled card
// index+offset, then restore it after the list settles so the viewport // visibly slides to its new position rather than vanishing under the finger.
// doesn't jump to follow the moved item. // We intentionally do NOT force-restore scroll here; the viewport stays put
val coroutineScope = rememberCoroutineScope() // while items animate to their new spots.
val onFavoriteToggle: (Recording) -> Unit = { recording -> val onFavoriteToggle: (Recording) -> Unit = { recording ->
val anchorIndex = listState.firstVisibleItemIndex
val anchorOffset = listState.firstVisibleItemScrollOffset
vm.toggleFavorite(recording) vm.toggleFavorite(recording)
coroutineScope.launch {
// Wait one frame for the reordered list to be applied, then restore.
withFrameNanos { }
listState.scrollToItem(anchorIndex, anchorOffset)
}
} }
// Deep link: when the VM requests scrolling to a specific entry, find its // Deep link: when the VM requests scrolling to a specific entry, find its
@ -202,7 +194,7 @@ fun RecordingListScreen(
Icon( Icon(
imageVector = Icons.Default.Search, imageVector = Icons.Default.Search,
contentDescription = stringResource(R.string.cd_search_filter), contentDescription = stringResource(R.string.cd_search_filter),
tint = if (hasActiveFilter) MaterialTheme.colorScheme.primary tint = if (hasActiveFilter || showControls) MaterialTheme.colorScheme.primary
else MaterialTheme.colorScheme.onSurfaceVariant else MaterialTheme.colorScheme.onSurfaceVariant
) )
} }
@ -418,7 +410,8 @@ fun RecordingListScreen(
} else null, } else null,
onCopyLink = { onCopyLink = {
copyLink(context, vm.audioShareUrl(recording)) copyLink(context, vm.audioShareUrl(recording))
} },
modifier = Modifier.animateItem()
) )
} }
} }