From 9d18d0c927899fe9b0ff00cbeb23a428daef438f Mon Sep 17 00:00:00 2001 From: OneWay Date: Sat, 1 Aug 2026 14:29:27 +0300 Subject: [PATCH] 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. --- app/build.gradle.kts | 4 +- .../repository/DictionaryDownloader.kt | 2 + .../research/ui/main/components/MainTopBar.kt | 6 +- .../settings/components/DictionaryListItem.kt | 60 +++++++++++++------ .../components/DictionaryManagement.kt | 9 ++- app/src/main/res/values-ru/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + .../metadata/android/en-US/changelogs/6.txt | 2 + .../metadata/android/ru-RU/changelogs/6.txt | 2 + 9 files changed, 62 insertions(+), 25 deletions(-) create mode 100644 fastlane/metadata/android/en-US/changelogs/6.txt create mode 100644 fastlane/metadata/android/ru-RU/changelogs/6.txt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 667c10e..5e669d1 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -92,8 +92,8 @@ android { applicationId = "com.example.research" minSdk = project.property("minSdk").toString().toInt() targetSdk = project.property("targetSdk").toString().toInt() - versionCode = 5 - versionName = "1.2.1" + versionCode = 6 + versionName = "1.3.0" vectorDrawables { useSupportLibrary = true diff --git a/app/src/main/java/com/example/research/feature/download/repository/DictionaryDownloader.kt b/app/src/main/java/com/example/research/feature/download/repository/DictionaryDownloader.kt index 469d9b7..06d193c 100644 --- a/app/src/main/java/com/example/research/feature/download/repository/DictionaryDownloader.kt +++ b/app/src/main/java/com/example/research/feature/download/repository/DictionaryDownloader.kt @@ -343,6 +343,8 @@ class DictionaryDownloader( context.getString(R.string.download_connection_reset) e is SecurityException -> context.getString(R.string.download_no_write_permission) + e.isNetworkError() -> + context.getString(R.string.download_network_failed) else -> context.getString(R.string.download_error) } diff --git a/app/src/main/java/com/example/research/ui/main/components/MainTopBar.kt b/app/src/main/java/com/example/research/ui/main/components/MainTopBar.kt index e8b703b..411b102 100644 --- a/app/src/main/java/com/example/research/ui/main/components/MainTopBar.kt +++ b/app/src/main/java/com/example/research/ui/main/components/MainTopBar.kt @@ -4,6 +4,7 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row 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.windowInsetsPadding @@ -135,7 +136,9 @@ private fun SearchField( Icon( painter = searchIcon, contentDescription = null, - modifier = Modifier.size(24.dp) + modifier = Modifier + .offset(x = 4.dp) + .size(24.dp) ) }, trailingIcon = if (searchState.text.isNotEmpty()) { @@ -146,6 +149,7 @@ private fun SearchField( onClearQuery() }, modifier = Modifier + .padding(end = 12.dp) .testTag("clear_search") .semantics { testTagsAsResourceId = true } ) { diff --git a/app/src/main/java/com/example/research/ui/settings/components/DictionaryListItem.kt b/app/src/main/java/com/example/research/ui/settings/components/DictionaryListItem.kt index 5123a33..d06eae6 100644 --- a/app/src/main/java/com/example/research/ui/settings/components/DictionaryListItem.kt +++ b/app/src/main/java/com/example/research/ui/settings/components/DictionaryListItem.kt @@ -30,6 +30,7 @@ import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.draw.scale import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer @@ -81,7 +82,8 @@ fun DictionaryListItem( dictionary: Dictionary, onToggle: () -> Unit, onDelete: () -> Unit, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, + canDelete: Boolean = true ) { val haptic = LocalHapticFeedback.current val density = LocalDensity.current @@ -93,7 +95,9 @@ fun DictionaryListItem( val offsetAnim = remember { Animatable(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) { if (isDeleteRevealed) { @@ -101,6 +105,13 @@ fun DictionaryListItem( } } + LaunchedEffect(canDelete) { + if (!canDelete) { + rawOffset = 0f + offsetAnim.snapTo(0f) + } + } + val currentOnDelete by rememberUpdatedState(onDelete) val currentOnToggle by rememberUpdatedState(onToggle) @@ -121,6 +132,8 @@ fun DictionaryListItem( Box( modifier = modifier .fillMaxWidth() + .height(DictionaryItemHeight) + .clipToBounds() .testTag("dictionary_item") ) { Row( @@ -129,7 +142,8 @@ fun DictionaryListItem( .height(DictionaryItemHeight) .background(MaterialTheme.colorScheme.surfaceVariant) .graphicsLayer { translationX = offsetAnim.value } - .pointerInput(maxSwipePx, swipeThresholdPx) { + .pointerInput(canDelete, maxSwipePx, swipeThresholdPx) { + if (!canDelete) return@pointerInput detectHorizontalDragGestures( onDragStart = { haptic.performHapticFeedback(HapticFeedbackType.TextHandleMove) @@ -222,25 +236,11 @@ fun DictionaryListItem( } if (isDeleteRevealed) { - IconButton( - onClick = { - haptic.performHapticFeedback(HapticFeedbackType.TextHandleMove) - currentOnDelete() - scope.launch { offsetAnim.snapTo(0f) } - rawOffset = 0f - }, + Box( modifier = Modifier .padding(end = ItemHorizontalPadding) - .graphicsLayer { translationX = -offsetAnim.value } .size(IconButtonSize) - ) { - Icon( - painter = painterResource(R.drawable.ic_delete), - contentDescription = stringResource(R.string.dictionary_delete), - tint = DeleteIconColor, - modifier = Modifier.size(IconSize) - ) - } + ) } else { Switch( 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) + ) + } + } } } diff --git a/app/src/main/java/com/example/research/ui/settings/components/DictionaryManagement.kt b/app/src/main/java/com/example/research/ui/settings/components/DictionaryManagement.kt index c7bd253..8f2feae 100644 --- a/app/src/main/java/com/example/research/ui/settings/components/DictionaryManagement.kt +++ b/app/src/main/java/com/example/research/ui/settings/components/DictionaryManagement.kt @@ -166,7 +166,8 @@ fun DictionaryManagement( onDeleteDictionary = onDeleteDictionary, modifier = Modifier .fillMaxWidth() - .then(backgroundModifier) + .then(backgroundModifier), + canDelete = !isInProgress ) if (!isInProgress) { @@ -246,7 +247,8 @@ private fun DictionaryListSection( dictionaries: List, onToggleDictionary: (String) -> Unit, onDeleteDictionary: (Dictionary) -> Unit, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, + canDelete: Boolean = true ) { Column( modifier = modifier, @@ -256,7 +258,8 @@ private fun DictionaryListSection( DictionaryListItem( dictionary = dictionary, onToggle = { onToggleDictionary(dictionary.path) }, - onDelete = { onDeleteDictionary(dictionary) } + onDelete = { onDeleteDictionary(dictionary) }, + canDelete = canDelete ) } } diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index fe73e33..333d78f 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -7,6 +7,7 @@ Отмена ОК Ошибка загрузки словарей + Нет связи с сервером словарей. Проверьте сеть и попробуйте снова Язык Английский Русский diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3f93d6b..ad62b8a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -7,6 +7,7 @@ Cancel OK Dictionary download failed + No connection to the dictionary server. Check your network and try again Language English Russian diff --git a/fastlane/metadata/android/en-US/changelogs/6.txt b/fastlane/metadata/android/en-US/changelogs/6.txt new file mode 100644 index 0000000..40cee1b --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/6.txt @@ -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 diff --git a/fastlane/metadata/android/ru-RU/changelogs/6.txt b/fastlane/metadata/android/ru-RU/changelogs/6.txt new file mode 100644 index 0000000..0f6e286 --- /dev/null +++ b/fastlane/metadata/android/ru-RU/changelogs/6.txt @@ -0,0 +1,2 @@ +- Словари больше нельзя удалить во время обновления — раньше при этом терялся источник обновления +- Исправлен выход иконки словаря за границы строки при свайпе