Bump version to 1.3 (versionCode 4)
- Add CityListSheet for managing locations via page indicator tap - Add drag-to-reorder support in city list - Fix Polish translation: "Daylight w roku" → "Długość dnia w roku" - Fix PageIndicator position to avoid overlapping yearly chart - Remove "About" section from UI (simplified) - Add CHANGELOG.md
This commit is contained in:
parent
0512ee674d
commit
aede45cec2
9 changed files with 336 additions and 82 deletions
42
CHANGELOG.md
Normal file
42
CHANGELOG.md
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
# Changelog
|
||||
|
||||
## [1.3] - 2026-01-31
|
||||
|
||||
### Added
|
||||
- City management sheet - tap page indicator to manage, reorder and delete locations
|
||||
- Drag-to-reorder support in city list
|
||||
|
||||
### Changed
|
||||
- Page indicator moved higher to avoid overlapping yearly daylight chart
|
||||
- Simplified page indicator styling
|
||||
|
||||
### Fixed
|
||||
- Polish translation: "Daylight w roku" → "Długość dnia w roku"
|
||||
|
||||
### Removed
|
||||
- "About" section from cards and city list (simplified UI)
|
||||
|
||||
## [1.2] - 2025-01-XX
|
||||
|
||||
### Added
|
||||
- Location reordering support
|
||||
- Improved page transitions between locations
|
||||
|
||||
## [1.1] - 2025-01-XX
|
||||
|
||||
### Added
|
||||
- Sun data refresh on app resume
|
||||
- Periodic 60-second auto-refresh
|
||||
|
||||
### Fixed
|
||||
- Location name text alignment for multi-line names
|
||||
|
||||
## [1.0] - 2025-01-XX
|
||||
|
||||
### Added
|
||||
- Initial release
|
||||
- Multi-location sunrise/sunset tracking
|
||||
- Animated sun/moon arc
|
||||
- Time-of-day gradient backgrounds
|
||||
- Yearly daylight chart
|
||||
- 20 language translations
|
||||
|
|
@ -12,7 +12,7 @@ Track sunrise, sunset and daylight across multiple locations around the world. A
|
|||
|
||||
## Features
|
||||
|
||||
- **Multi-location tracking** -- swipe between saved locations to see real-time sun and moon data
|
||||
- **Multi-location tracking** -- swipe between saved locations to see real-time sun and moon data; tap page indicator to manage cities
|
||||
- **Sunrise & sunset times** with day/night length and countdown to next sunrise
|
||||
- **Animated sun/moon arc** -- a visual progress indicator showing where the sun (or moon) is in its path across the sky
|
||||
- **Moon phase & illumination** -- current lunar phase emoji and illumination percentage
|
||||
|
|
|
|||
|
|
@ -16,8 +16,8 @@ android {
|
|||
applicationId = "com.sunzones"
|
||||
minSdk = 26
|
||||
targetSdk = 35
|
||||
versionCode = 3
|
||||
versionName = "1.2"
|
||||
versionCode = 4
|
||||
versionName = "1.3"
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ import com.sunzones.R
|
|||
import com.sunzones.ui.addlocation.AddLocationSheet
|
||||
import com.sunzones.ui.main.animation.getTimeGradient
|
||||
import com.sunzones.ui.main.animation.lerpGradient
|
||||
import com.sunzones.ui.main.components.CityListSheet
|
||||
import com.sunzones.ui.main.components.PageIndicator
|
||||
import com.sunzones.ui.main.components.SunCard
|
||||
import com.sunzones.ui.theme.NightBottom
|
||||
|
|
@ -50,6 +51,7 @@ fun MainScreen(
|
|||
) {
|
||||
val uiState by viewModel.uiState.collectAsState()
|
||||
var showAddSheet by remember { mutableStateOf(false) }
|
||||
var showCityListSheet by remember { mutableStateOf(false) }
|
||||
|
||||
// Entrance animation — runs once on screen load, shared by all cards
|
||||
val entranceProgress = remember { Animatable(0f) }
|
||||
|
|
@ -132,10 +134,11 @@ fun MainScreen(
|
|||
PageIndicator(
|
||||
pageCount = uiState.locations.size,
|
||||
currentPage = pagerState.currentPage,
|
||||
onClick = { showCityListSheet = true },
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomCenter)
|
||||
.navigationBarsPadding()
|
||||
.padding(bottom = 80.dp)
|
||||
.padding(bottom = 120.dp)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -162,4 +165,16 @@ fun MainScreen(
|
|||
)
|
||||
}
|
||||
|
||||
if (showCityListSheet) {
|
||||
CityListSheet(
|
||||
locations = uiState.locations,
|
||||
onDismiss = { showCityListSheet = false },
|
||||
onDelete = { viewModel.deleteLocation(it) },
|
||||
onReorder = { from, to -> viewModel.reorderLocations(from, to) },
|
||||
onAddClick = {
|
||||
showCityListSheet = false
|
||||
showAddSheet = true
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,269 @@
|
|||
package com.sunzones.ui.main.components
|
||||
|
||||
import androidx.compose.animation.animateColorAsState
|
||||
import androidx.compose.animation.core.animateDpAsState
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.gestures.detectDragGesturesAfterLongPress
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.Add
|
||||
import androidx.compose.material.icons.rounded.Delete
|
||||
import androidx.compose.material.icons.rounded.DragHandle
|
||||
import androidx.compose.material.icons.rounded.MyLocation
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ModalBottomSheet
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.rememberModalBottomSheetState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableFloatStateOf
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.draw.shadow
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.zIndex
|
||||
import com.sunzones.R
|
||||
import com.sunzones.domain.model.SunLocation
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun CityListSheet(
|
||||
locations: List<SunLocation>,
|
||||
onDismiss: () -> Unit,
|
||||
onDelete: (Long) -> Unit,
|
||||
onReorder: (fromIndex: Int, toIndex: Int) -> Unit,
|
||||
onAddClick: () -> Unit
|
||||
) {
|
||||
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
|
||||
val haptic = LocalHapticFeedback.current
|
||||
val density = LocalDensity.current
|
||||
|
||||
val localList = remember { mutableStateListOf<SunLocation>() }
|
||||
LaunchedEffect(locations) {
|
||||
localList.clear()
|
||||
localList.addAll(locations)
|
||||
}
|
||||
|
||||
var draggedItemIndex by remember { mutableIntStateOf(-1) }
|
||||
var dragOffsetY by remember { mutableFloatStateOf(0f) }
|
||||
val itemHeight = with(density) { 56.dp.toPx() }
|
||||
|
||||
var locationToDelete by remember { mutableStateOf<SunLocation?>(null) }
|
||||
|
||||
if (locationToDelete != null) {
|
||||
AlertDialog(
|
||||
onDismissRequest = { locationToDelete = null },
|
||||
title = { Text(stringResource(R.string.delete_location_title)) },
|
||||
text = { Text(stringResource(R.string.delete_location_message, locationToDelete!!.name)) },
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
locationToDelete?.let { location ->
|
||||
val index = localList.indexOfFirst { it.id == location.id }
|
||||
if (index >= 0) {
|
||||
localList.removeAt(index)
|
||||
}
|
||||
onDelete(location.id)
|
||||
}
|
||||
locationToDelete = null
|
||||
}
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.delete),
|
||||
color = MaterialTheme.colorScheme.error
|
||||
)
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = { locationToDelete = null }) {
|
||||
Text(stringResource(R.string.cancel))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
ModalBottomSheet(
|
||||
onDismissRequest = onDismiss,
|
||||
sheetState = sheetState
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 24.dp)
|
||||
.padding(bottom = 32.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.manage_cities),
|
||||
style = MaterialTheme.typography.titleLarge
|
||||
)
|
||||
IconButton(onClick = onAddClick) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.Add,
|
||||
contentDescription = stringResource(R.string.add_location),
|
||||
tint = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
if (localList.isEmpty()) {
|
||||
Text(
|
||||
text = stringResource(R.string.empty_state),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(vertical = 24.dp)
|
||||
)
|
||||
} else {
|
||||
val listState = rememberLazyListState()
|
||||
|
||||
LazyColumn(
|
||||
state = listState,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
itemsIndexed(
|
||||
items = localList,
|
||||
key = { _, item -> item.id }
|
||||
) { index, location ->
|
||||
val isDragging = draggedItemIndex == index
|
||||
val elevation by animateDpAsState(
|
||||
targetValue = if (isDragging) 8.dp else 0.dp,
|
||||
label = "elevation"
|
||||
)
|
||||
val backgroundColor by animateColorAsState(
|
||||
targetValue = if (isDragging) {
|
||||
MaterialTheme.colorScheme.surfaceContainerHighest
|
||||
} else {
|
||||
MaterialTheme.colorScheme.surface
|
||||
},
|
||||
label = "backgroundColor"
|
||||
)
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.zIndex(if (isDragging) 1f else 0f)
|
||||
.offset {
|
||||
IntOffset(
|
||||
x = 0,
|
||||
y = if (isDragging) dragOffsetY.roundToInt() else 0
|
||||
)
|
||||
}
|
||||
.shadow(elevation, RoundedCornerShape(8.dp))
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.background(backgroundColor)
|
||||
.height(56.dp)
|
||||
.padding(start = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.DragHandle,
|
||||
contentDescription = stringResource(R.string.drag_to_reorder),
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier
|
||||
.size(24.dp)
|
||||
.pointerInput(Unit) {
|
||||
detectDragGesturesAfterLongPress(
|
||||
onDragStart = {
|
||||
draggedItemIndex = index
|
||||
dragOffsetY = 0f
|
||||
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
},
|
||||
onDrag = { change, dragAmount ->
|
||||
change.consume()
|
||||
dragOffsetY += dragAmount.y
|
||||
val targetIndex = (index + (dragOffsetY / itemHeight).roundToInt())
|
||||
.coerceIn(0, localList.size - 1)
|
||||
if (targetIndex != index && targetIndex != draggedItemIndex) {
|
||||
val item = localList.removeAt(draggedItemIndex)
|
||||
localList.add(targetIndex, item)
|
||||
dragOffsetY -= (targetIndex - draggedItemIndex) * itemHeight
|
||||
draggedItemIndex = targetIndex
|
||||
}
|
||||
},
|
||||
onDragEnd = {
|
||||
if (draggedItemIndex != index) {
|
||||
onReorder(index, draggedItemIndex)
|
||||
}
|
||||
draggedItemIndex = -1
|
||||
dragOffsetY = 0f
|
||||
},
|
||||
onDragCancel = {
|
||||
draggedItemIndex = -1
|
||||
dragOffsetY = 0f
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(12.dp))
|
||||
|
||||
Text(
|
||||
text = location.name,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
|
||||
if (location.isCurrentLocation) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.MyLocation,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.size(20.dp)
|
||||
)
|
||||
}
|
||||
|
||||
IconButton(
|
||||
onClick = { locationToDelete = location }
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.Delete,
|
||||
contentDescription = stringResource(R.string.delete),
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,6 @@ 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
|
||||
|
|
@ -14,10 +13,8 @@ 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
|
||||
|
|
@ -36,11 +33,7 @@ fun PageIndicator(
|
|||
Box(
|
||||
modifier = modifier
|
||||
.clip(RoundedCornerShape(16.dp))
|
||||
.clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = rememberRipple(color = Color.White.copy(alpha = 0.3f)),
|
||||
onClick = onClick
|
||||
)
|
||||
.clickable(onClick = onClick)
|
||||
.padding(horizontal = 12.dp, vertical = 8.dp)
|
||||
) {
|
||||
Row(
|
||||
|
|
|
|||
|
|
@ -1,14 +1,11 @@
|
|||
package com.sunzones.ui.main.components
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import androidx.compose.animation.core.LinearEasing
|
||||
import androidx.compose.animation.core.RepeatMode
|
||||
import androidx.compose.animation.core.animateFloat
|
||||
import androidx.compose.animation.core.infiniteRepeatable
|
||||
import androidx.compose.animation.core.rememberInfiniteTransition
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
|
|
@ -40,8 +37,6 @@ import androidx.compose.material3.Icon
|
|||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
|
|
@ -54,7 +49,6 @@ import androidx.compose.ui.draw.alpha
|
|||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
|
|
@ -87,7 +81,6 @@ fun SunCard(
|
|||
modifier: Modifier = Modifier
|
||||
) {
|
||||
var showDeleteDialog by remember { mutableStateOf(false) }
|
||||
var showAboutDialog by remember { mutableStateOf(false) }
|
||||
|
||||
val scrollState = rememberScrollState()
|
||||
|
||||
|
|
@ -393,23 +386,6 @@ fun SunCard(
|
|||
modifier = Modifier.padding(top = 8.dp)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
// Subtle about button
|
||||
Box(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.about_title),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = TextOnDarkSecondary.copy(alpha = 0.35f),
|
||||
modifier = Modifier
|
||||
.clickable { showAboutDialog = true }
|
||||
.padding(vertical = 12.dp, horizontal = 24.dp)
|
||||
)
|
||||
}
|
||||
|
||||
// Space for navigation bar + page indicator + FAB
|
||||
Spacer(
|
||||
modifier = Modifier
|
||||
|
|
@ -450,51 +426,6 @@ fun SunCard(
|
|||
)
|
||||
}
|
||||
|
||||
if (showAboutDialog) {
|
||||
val context = LocalContext.current
|
||||
val email = stringResource(R.string.about_email)
|
||||
Dialog(
|
||||
onDismissRequest = { showAboutDialog = false },
|
||||
properties = DialogProperties(usePlatformDefaultWidth = false)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 48.dp)
|
||||
.background(
|
||||
color = Color.Black.copy(alpha = 0.7f),
|
||||
shape = RoundedCornerShape(28.dp)
|
||||
)
|
||||
.padding(vertical = 32.dp, horizontal = 24.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(
|
||||
text = "\u2600\uFE0F",
|
||||
style = MaterialTheme.typography.displayLarge
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Text(
|
||||
text = stringResource(R.string.about_made_in),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = Color.White
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
TextButton(onClick = {
|
||||
context.startActivity(
|
||||
Intent(Intent.ACTION_SENDTO, Uri.parse("mailto:$email"))
|
||||
)
|
||||
}) {
|
||||
Text(
|
||||
text = stringResource(R.string.about_contact),
|
||||
color = SunGold
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -12,10 +12,12 @@
|
|||
<string name="delete_location_message">Usunąć %1$s z Twoich lokalizacji?</string>
|
||||
<string name="delete">Usuń</string>
|
||||
<string name="cancel">Anuluj</string>
|
||||
<string name="yearly_daylight">Daylight w roku</string>
|
||||
<string name="yearly_daylight">Długość dnia w roku</string>
|
||||
<string name="about_title">O aplikacji</string>
|
||||
<string name="about_contact">Napisz do mnie</string>
|
||||
<string name="ok">OK</string>
|
||||
<string name="night_length">Długość nocy</string>
|
||||
<string name="sunrise_in">Wschód za %1$s</string>
|
||||
<string name="manage_cities">Zarządzaj miastami</string>
|
||||
<string name="drag_to_reorder">Przeciągnij by zmienić kolejność</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -20,4 +20,6 @@
|
|||
<string name="ok">OK</string>
|
||||
<string name="night_length">Night length</string>
|
||||
<string name="sunrise_in">Sunrise in %1$s</string>
|
||||
<string name="manage_cities">Manage Cities</string>
|
||||
<string name="drag_to_reorder">Drag to reorder</string>
|
||||
</resources>
|
||||
|
|
|
|||
Loading…
Reference in a new issue