Sync push
This commit is contained in:
parent
9eb2bc7fda
commit
d4e6b2e8cc
3 changed files with 41 additions and 18 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -13,6 +13,7 @@
|
||||||
.externalNativeBuild
|
.externalNativeBuild
|
||||||
.cxx
|
.cxx
|
||||||
local.properties
|
local.properties
|
||||||
|
/app/release
|
||||||
|
|
||||||
# Signing / secrets
|
# Signing / secrets
|
||||||
*.jks
|
*.jks
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue