From 0512ee674d45961e6a2cfc02310e2615f2f02028 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Orzech?= Date: Sat, 31 Jan 2026 12:15:09 +0100 Subject: [PATCH] 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 --- .claude/commands/commit-push-pr.md | 15 +++++ .claude/commands/new-version.md | 15 +++++ .../com/sunzones/data/local/LocationDao.kt | 7 +++ .../data/repository/LocationRepository.kt | 12 ++++ .../com/sunzones/ui/main/MainViewModel.kt | 6 ++ .../ui/main/components/PageIndicator.kt | 63 ++++++++++++------- .../sunzones/ui/main/components/SunCard.kt | 14 ++++- 7 files changed, 106 insertions(+), 26 deletions(-) create mode 100644 .claude/commands/commit-push-pr.md create mode 100644 .claude/commands/new-version.md diff --git a/.claude/commands/commit-push-pr.md b/.claude/commands/commit-push-pr.md new file mode 100644 index 0000000..69009d5 --- /dev/null +++ b/.claude/commands/commit-push-pr.md @@ -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. diff --git a/.claude/commands/new-version.md b/.claude/commands/new-version.md new file mode 100644 index 0000000..d52b9fa --- /dev/null +++ b/.claude/commands/new-version.md @@ -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 diff --git a/app/src/main/java/com/sunzones/data/local/LocationDao.kt b/app/src/main/java/com/sunzones/data/local/LocationDao.kt index 3b3f6e4..4538f70 100644 --- a/app/src/main/java/com/sunzones/data/local/LocationDao.kt +++ b/app/src/main/java/com/sunzones/data/local/LocationDao.kt @@ -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) + + @Query("SELECT * FROM locations ORDER BY displayOrder ASC, id ASC") + suspend fun getAllLocationsOnce(): List } diff --git a/app/src/main/java/com/sunzones/data/repository/LocationRepository.kt b/app/src/main/java/com/sunzones/data/repository/LocationRepository.kt index 6827c96..1d4d065 100644 --- a/app/src/main/java/com/sunzones/data/repository/LocationRepository.kt +++ b/app/src/main/java/com/sunzones/data/repository/LocationRepository.kt @@ -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 = + locationDao.getAllLocationsOnce() } diff --git a/app/src/main/java/com/sunzones/ui/main/MainViewModel.kt b/app/src/main/java/com/sunzones/ui/main/MainViewModel.kt index 37c4674..159ee4a 100644 --- a/app/src/main/java/com/sunzones/ui/main/MainViewModel.kt +++ b/app/src/main/java/com/sunzones/ui/main/MainViewModel.kt @@ -97,4 +97,10 @@ class MainViewModel @Inject constructor( repository.deleteLocation(id) } } + + fun reorderLocations(fromIndex: Int, toIndex: Int) { + viewModelScope.launch { + repository.reorderLocations(fromIndex, toIndex) + } + } } diff --git a/app/src/main/java/com/sunzones/ui/main/components/PageIndicator.kt b/app/src/main/java/com/sunzones/ui/main/components/PageIndicator.kt index e5fc17a..93353fe 100644 --- a/app/src/main/java/com/sunzones/ui/main/components/PageIndicator.kt +++ b/app/src/main/java/com/sunzones/ui/main/components/PageIndicator.kt @@ -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) + ) + } } } } diff --git a/app/src/main/java/com/sunzones/ui/main/components/SunCard.kt b/app/src/main/java/com/sunzones/ui/main/components/SunCard.kt index 95f02cc..60cc63a 100644 --- a/app/src/main/java/com/sunzones/ui/main/components/SunCard.kt +++ b/app/src/main/java/com/sunzones/ui/main/components/SunCard.kt @@ -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) {