Add location reordering support and improve page transitions

- Add DAO and repository methods for reordering locations by display order
- Make PageIndicator clickable with ripple effect for future reorder UI
- Replace SunCard alpha fade with dark scrim overlay for smoother transitions
- Add Claude Code custom commands
This commit is contained in:
Paweł Orzech 2026-01-31 12:15:09 +01:00
parent ecd734582c
commit 0512ee674d
No known key found for this signature in database
7 changed files with 106 additions and 26 deletions

View file

@ -0,0 +1,15 @@
---
allowed-tools: Bash(git add:*), Bash(git status:*), Bash(git commit:*)
description: Create a git commit
---
## Context
- Current git status: !`git status`
- Current git diff (staged and unstaged changes): !`git diff HEAD`
- Current branch: !`git branch --show-current`
- Recent commits: !`git log --oneline -10`
## Your task
Based on the above changes, create a single git commit. Then push it.

View file

@ -0,0 +1,15 @@
---
allowed-tools: Bash(git add:*), Bash(git status:*), Bash(git commit:*)
description: Do migration tests first. Then: update changelog, update readme, update version in gradle files, create a git commit and push it
---
## Context
- Current git status: !`git status`
- Current git diff (staged and unstaged changes): !`git diff HEAD`
- Current branch: !`git branch --show-current`
- Recent commits: !`git log --oneline -10`
## Your task
Do migration tests first. Then: update changelog, update readme, update version in gradle files, create a git commit and push it

View file

@ -4,6 +4,7 @@ import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Update
import kotlinx.coroutines.flow.Flow
@Dao
@ -23,4 +24,10 @@ interface LocationDao {
@Query("DELETE FROM locations WHERE isCurrentLocation = 1")
suspend fun deleteCurrentLocation()
@Update
suspend fun updateLocations(locations: List<LocationEntity>)
@Query("SELECT * FROM locations ORDER BY displayOrder ASC, id ASC")
suspend fun getAllLocationsOnce(): List<LocationEntity>
}

View file

@ -23,4 +23,16 @@ class LocationRepository @Inject constructor(
locationDao.deleteCurrentLocation()
locationDao.insertLocation(location)
}
suspend fun reorderLocations(fromIndex: Int, toIndex: Int) {
val locations = locationDao.getAllLocationsOnce().toMutableList()
if (fromIndex !in locations.indices || toIndex !in locations.indices) return
val item = locations.removeAt(fromIndex)
locations.add(toIndex, item)
val updated = locations.mapIndexed { i, e -> e.copy(displayOrder = i) }
locationDao.updateLocations(updated)
}
suspend fun getAllLocationsOnce(): List<LocationEntity> =
locationDao.getAllLocationsOnce()
}

View file

@ -97,4 +97,10 @@ class MainViewModel @Inject constructor(
repository.deleteLocation(id)
}
}
fun reorderLocations(fromIndex: Int, toIndex: Int) {
viewModelScope.launch {
repository.reorderLocations(fromIndex, toIndex)
}
}
}

View file

@ -4,14 +4,20 @@ import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.spring
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
@ -22,36 +28,47 @@ import androidx.compose.ui.unit.dp
fun PageIndicator(
pageCount: Int,
currentPage: Int,
modifier: Modifier = Modifier
modifier: Modifier = Modifier,
onClick: () -> Unit = {}
) {
if (pageCount <= 1) return
Row(
modifier = modifier,
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically
Box(
modifier = modifier
.clip(RoundedCornerShape(16.dp))
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(color = Color.White.copy(alpha = 0.3f)),
onClick = onClick
)
.padding(horizontal = 12.dp, vertical = 8.dp)
) {
repeat(pageCount) { index ->
val isSelected = index == currentPage
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically
) {
repeat(pageCount) { index ->
val isSelected = index == currentPage
val width by animateDpAsState(
targetValue = if (isSelected) 24.dp else 8.dp,
animationSpec = spring(),
label = "indicator_width"
)
val width by animateDpAsState(
targetValue = if (isSelected) 24.dp else 8.dp,
animationSpec = spring(),
label = "indicator_width"
)
val color by animateColorAsState(
targetValue = if (isSelected) Color.White else Color.White.copy(alpha = 0.4f),
label = "indicator_color"
)
val color by animateColorAsState(
targetValue = if (isSelected) Color.White else Color.White.copy(alpha = 0.4f),
label = "indicator_color"
)
Box(
modifier = Modifier
.width(width)
.height(8.dp)
.clip(CircleShape)
.background(color)
)
Box(
modifier = Modifier
.width(width)
.height(8.dp)
.clip(CircleShape)
.background(color)
)
}
}
}
}

View file

@ -129,9 +129,9 @@ fun SunCard(
val isSunsetWindow = location.isDaytime && location.sunProgress > 0.92f
val particleActive = isSunriseWindow || isSunsetWindow
// Page transition: scale + fade (replaces parallax translationX)
// Page transition: scale + dark scrim (no alpha fade to avoid "lightening" effect)
val scale = lerp(0.92f, 1f, 1f - pageOffset.absoluteValue.coerceAtMost(1f))
val pageFade = lerp(0.6f, 1f, 1f - pageOffset.absoluteValue.coerceAtMost(1f))
val scrimAlpha = lerp(0f, 0.2f, pageOffset.absoluteValue.coerceAtMost(1f))
BoxWithConstraints(
modifier = modifier
@ -139,7 +139,6 @@ fun SunCard(
.graphicsLayer {
scaleX = scale
scaleY = scale
alpha = pageFade
}
.background(brush = Brush.verticalGradient(displayGradient))
) {
@ -419,6 +418,15 @@ fun SunCard(
)
}
}
// Dark scrim overlay for page transition depth effect
if (scrimAlpha > 0.01f) {
Box(
modifier = Modifier
.fillMaxSize()
.background(Color.Black.copy(alpha = scrimAlpha))
)
}
}
if (showDeleteDialog) {