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.
This commit is contained in:
@@ -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
|
||||||
|
|||||||
+2
@@ -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 }
|
||||||
) {
|
) {
|
||||||
|
|||||||
+40
-18
@@ -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)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-3
@@ -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
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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 @@
|
|||||||
|
- Словари больше нельзя удалить во время обновления — раньше при этом терялся источник обновления
|
||||||
|
- Исправлен выход иконки словаря за границы строки при свайпе
|
||||||
Reference in New Issue
Block a user