1 Commits
Author SHA1 Message Date
OneWay 9d18d0c927 Release 1.3.0
- Blocked swipe-to-delete on dictionary rows while a download, extract,
  import or indexing pass is running. Deleting mid-update removed the
  dictionary's source from preferences while the pipeline kept its own
  snapshot, so the post-update rescan re-created the dictionary with its
  update source permanently lost.
- Fixed the dictionary icon leaving the row while swiping to delete: the
  item now clips to its bounds and the delete button is a static overlay
  instead of a counter-translated child of the sliding row.
2026-08-02 11:09:47 +03:00
9 changed files with 62 additions and 25 deletions
+2 -2
View File
@@ -92,8 +92,8 @@ android {
applicationId = "com.example.research" applicationId = "com.example.research"
minSdk = project.property("minSdk").toString().toInt() minSdk = project.property("minSdk").toString().toInt()
targetSdk = project.property("targetSdk").toString().toInt() targetSdk = project.property("targetSdk").toString().toInt()
versionCode = 5 versionCode = 6
versionName = "1.2.1" versionName = "1.3.0"
vectorDrawables { vectorDrawables {
useSupportLibrary = true useSupportLibrary = true
@@ -343,6 +343,8 @@ class DictionaryDownloader(
context.getString(R.string.download_connection_reset) context.getString(R.string.download_connection_reset)
e is SecurityException -> e is SecurityException ->
context.getString(R.string.download_no_write_permission) context.getString(R.string.download_no_write_permission)
e.isNetworkError() ->
context.getString(R.string.download_network_failed)
else -> else ->
context.getString(R.string.download_error) context.getString(R.string.download_error)
} }
@@ -4,6 +4,7 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.layout.windowInsetsPadding
@@ -135,7 +136,9 @@ private fun SearchField(
Icon( Icon(
painter = searchIcon, painter = searchIcon,
contentDescription = null, contentDescription = null,
modifier = Modifier.size(24.dp) modifier = Modifier
.offset(x = 4.dp)
.size(24.dp)
) )
}, },
trailingIcon = if (searchState.text.isNotEmpty()) { trailingIcon = if (searchState.text.isNotEmpty()) {
@@ -146,6 +149,7 @@ private fun SearchField(
onClearQuery() onClearQuery()
}, },
modifier = Modifier modifier = Modifier
.padding(end = 12.dp)
.testTag("clear_search") .testTag("clear_search")
.semantics { testTagsAsResourceId = true } .semantics { testTagsAsResourceId = true }
) { ) {
@@ -30,6 +30,7 @@ import androidx.compose.runtime.rememberUpdatedState
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.draw.clipToBounds
import androidx.compose.ui.draw.scale import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.graphicsLayer
@@ -81,7 +82,8 @@ fun DictionaryListItem(
dictionary: Dictionary, dictionary: Dictionary,
onToggle: () -> Unit, onToggle: () -> Unit,
onDelete: () -> Unit, onDelete: () -> Unit,
modifier: Modifier = Modifier modifier: Modifier = Modifier,
canDelete: Boolean = true
) { ) {
val haptic = LocalHapticFeedback.current val haptic = LocalHapticFeedback.current
val density = LocalDensity.current val density = LocalDensity.current
@@ -93,7 +95,9 @@ fun DictionaryListItem(
val offsetAnim = remember { Animatable(0f) } val offsetAnim = remember { Animatable(0f) }
var rawOffset by remember { mutableFloatStateOf(0f) } var rawOffset by remember { mutableFloatStateOf(0f) }
val isDeleteRevealed by remember { derivedStateOf { rawOffset <= -swipeThresholdPx } } val isDeleteRevealed by remember(canDelete) {
derivedStateOf { canDelete && rawOffset <= -swipeThresholdPx }
}
LaunchedEffect(isDeleteRevealed) { LaunchedEffect(isDeleteRevealed) {
if (isDeleteRevealed) { if (isDeleteRevealed) {
@@ -101,6 +105,13 @@ fun DictionaryListItem(
} }
} }
LaunchedEffect(canDelete) {
if (!canDelete) {
rawOffset = 0f
offsetAnim.snapTo(0f)
}
}
val currentOnDelete by rememberUpdatedState(onDelete) val currentOnDelete by rememberUpdatedState(onDelete)
val currentOnToggle by rememberUpdatedState(onToggle) val currentOnToggle by rememberUpdatedState(onToggle)
@@ -121,6 +132,8 @@ fun DictionaryListItem(
Box( Box(
modifier = modifier modifier = modifier
.fillMaxWidth() .fillMaxWidth()
.height(DictionaryItemHeight)
.clipToBounds()
.testTag("dictionary_item") .testTag("dictionary_item")
) { ) {
Row( Row(
@@ -129,7 +142,8 @@ fun DictionaryListItem(
.height(DictionaryItemHeight) .height(DictionaryItemHeight)
.background(MaterialTheme.colorScheme.surfaceVariant) .background(MaterialTheme.colorScheme.surfaceVariant)
.graphicsLayer { translationX = offsetAnim.value } .graphicsLayer { translationX = offsetAnim.value }
.pointerInput(maxSwipePx, swipeThresholdPx) { .pointerInput(canDelete, maxSwipePx, swipeThresholdPx) {
if (!canDelete) return@pointerInput
detectHorizontalDragGestures( detectHorizontalDragGestures(
onDragStart = { onDragStart = {
haptic.performHapticFeedback(HapticFeedbackType.TextHandleMove) haptic.performHapticFeedback(HapticFeedbackType.TextHandleMove)
@@ -222,25 +236,11 @@ fun DictionaryListItem(
} }
if (isDeleteRevealed) { if (isDeleteRevealed) {
IconButton( Box(
onClick = {
haptic.performHapticFeedback(HapticFeedbackType.TextHandleMove)
currentOnDelete()
scope.launch { offsetAnim.snapTo(0f) }
rawOffset = 0f
},
modifier = Modifier modifier = Modifier
.padding(end = ItemHorizontalPadding) .padding(end = ItemHorizontalPadding)
.graphicsLayer { translationX = -offsetAnim.value }
.size(IconButtonSize) .size(IconButtonSize)
) { )
Icon(
painter = painterResource(R.drawable.ic_delete),
contentDescription = stringResource(R.string.dictionary_delete),
tint = DeleteIconColor,
modifier = Modifier.size(IconSize)
)
}
} else { } else {
Switch( Switch(
checked = dictionary.isActive, checked = dictionary.isActive,
@@ -257,5 +257,27 @@ fun DictionaryListItem(
) )
} }
} }
if (isDeleteRevealed) {
IconButton(
onClick = {
haptic.performHapticFeedback(HapticFeedbackType.TextHandleMove)
currentOnDelete()
scope.launch { offsetAnim.snapTo(0f) }
rawOffset = 0f
},
modifier = Modifier
.align(Alignment.CenterEnd)
.padding(end = ItemHorizontalPadding)
.size(IconButtonSize)
) {
Icon(
painter = painterResource(R.drawable.ic_delete),
contentDescription = stringResource(R.string.dictionary_delete),
tint = DeleteIconColor,
modifier = Modifier.size(IconSize)
)
}
}
} }
} }
@@ -166,7 +166,8 @@ fun DictionaryManagement(
onDeleteDictionary = onDeleteDictionary, onDeleteDictionary = onDeleteDictionary,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.then(backgroundModifier) .then(backgroundModifier),
canDelete = !isInProgress
) )
if (!isInProgress) { if (!isInProgress) {
@@ -246,7 +247,8 @@ private fun DictionaryListSection(
dictionaries: List<Dictionary>, dictionaries: List<Dictionary>,
onToggleDictionary: (String) -> Unit, onToggleDictionary: (String) -> Unit,
onDeleteDictionary: (Dictionary) -> Unit, onDeleteDictionary: (Dictionary) -> Unit,
modifier: Modifier = Modifier modifier: Modifier = Modifier,
canDelete: Boolean = true
) { ) {
Column( Column(
modifier = modifier, modifier = modifier,
@@ -256,7 +258,8 @@ private fun DictionaryListSection(
DictionaryListItem( DictionaryListItem(
dictionary = dictionary, dictionary = dictionary,
onToggle = { onToggleDictionary(dictionary.path) }, onToggle = { onToggleDictionary(dictionary.path) },
onDelete = { onDeleteDictionary(dictionary) } onDelete = { onDeleteDictionary(dictionary) },
canDelete = canDelete
) )
} }
} }
+1
View File
@@ -7,6 +7,7 @@
<string name="button_cancel_download">Отмена</string> <string name="button_cancel_download">Отмена</string>
<string name="action_ok">ОК</string> <string name="action_ok">ОК</string>
<string name="download_error">Ошибка загрузки словарей</string> <string name="download_error">Ошибка загрузки словарей</string>
<string name="download_network_failed">Нет связи с сервером словарей. Проверьте сеть и попробуйте снова</string>
<string name="language">Язык</string> <string name="language">Язык</string>
<string name="language_english">Английский</string> <string name="language_english">Английский</string>
<string name="language_russian">Русский</string> <string name="language_russian">Русский</string>
+1
View File
@@ -7,6 +7,7 @@
<string name="button_cancel_download">Cancel</string> <string name="button_cancel_download">Cancel</string>
<string name="action_ok">OK</string> <string name="action_ok">OK</string>
<string name="download_error">Dictionary download failed</string> <string name="download_error">Dictionary download failed</string>
<string name="download_network_failed">No connection to the dictionary server. Check your network and try again</string>
<string name="language">Language</string> <string name="language">Language</string>
<string name="language_english">English</string> <string name="language_english">English</string>
<string name="language_russian">Russian</string> <string name="language_russian">Russian</string>
@@ -0,0 +1,2 @@
- Dictionaries can no longer be deleted while an update is running, which used to lose their update source
- Fixed the dictionary icon sliding outside the row while swiping to delete
@@ -0,0 +1,2 @@
- Словари больше нельзя удалить во время обновления — раньше при этом терялся источник обновления
- Исправлен выход иконки словаря за границы строки при свайпе