From c72a72cf7661b754fcc8bd37f20a74d5aa24a619 Mon Sep 17 00:00:00 2001 From: Maxim Date: Tue, 14 Jul 2026 14:39:32 +0400 Subject: [PATCH 1/4] refactored constants and fixed code blocking threads --- .../writer/core/TargetTranslation.kt | 4 ++-- .../writer/core/TranslationWordsType.kt | 6 ++++++ .../writer/ui/translate/TranslateItem.kt | 14 +++++++++----- .../translate/review/DefaultReviewModeComponent.kt | 4 ++-- .../writer/usecases/ValidateProject.kt | 8 ++------ 5 files changed, 21 insertions(+), 15 deletions(-) create mode 100644 shared/src/commonMain/kotlin/org/bibletranslationtools/writer/core/TranslationWordsType.kt diff --git a/shared/src/commonMain/kotlin/org/bibletranslationtools/writer/core/TargetTranslation.kt b/shared/src/commonMain/kotlin/org/bibletranslationtools/writer/core/TargetTranslation.kt index be5818c..051be28 100644 --- a/shared/src/commonMain/kotlin/org/bibletranslationtools/writer/core/TargetTranslation.kt +++ b/shared/src/commonMain/kotlin/org/bibletranslationtools/writer/core/TargetTranslation.kt @@ -4,6 +4,7 @@ import org.bibletranslationtools.logger.Logger import org.bibletranslationtools.resourcecatalog.library.models.TargetLanguage import org.bibletranslationtools.resourcecatalog.library.models.Translation import org.bibletranslationtools.resourcecontainer.ContainerTools +import org.bibletranslationtools.resourcecontainer.Resource import org.bibletranslationtools.resourcecontainer.ResourceContainer import org.bibletranslationtools.writer.DirectoryProvider import org.bibletranslationtools.writer.Platform @@ -714,7 +715,6 @@ class TargetTranslation private constructor( const val LICENSE_FILE = "LICENSE.md" const val APPLICATION_NAME = "ts-android" - const val OBS_PROJECT_TYPE = "obs" fun generateTargetTranslationId( targetLanguageSlug: String, @@ -730,7 +730,7 @@ class TargetTranslation private constructor( } fun isObsProject(projectId: String): Boolean = - OBS_PROJECT_TYPE.equals(projectId, ignoreCase = true) + Resource.OBS_SLUG.equals(projectId, ignoreCase = true) suspend fun open(targetTranslationDir: File, onError: (suspend () -> Unit)? = null): TargetTranslation? { if (targetTranslationDir.exists()) { diff --git a/shared/src/commonMain/kotlin/org/bibletranslationtools/writer/core/TranslationWordsType.kt b/shared/src/commonMain/kotlin/org/bibletranslationtools/writer/core/TranslationWordsType.kt new file mode 100644 index 0000000..804d4f3 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/bibletranslationtools/writer/core/TranslationWordsType.kt @@ -0,0 +1,6 @@ +package org.bibletranslationtools.writer.core + +import java.util.regex.Pattern + +const val WORDS_CHAPTER = "01" +val WORD_PATTERN: Pattern = Pattern.compile("#+([^\\n]+)\\n+([\\s\\S]*)") \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/bibletranslationtools/writer/ui/translate/TranslateItem.kt b/shared/src/commonMain/kotlin/org/bibletranslationtools/writer/ui/translate/TranslateItem.kt index 2648fa6..1fc8799 100644 --- a/shared/src/commonMain/kotlin/org/bibletranslationtools/writer/ui/translate/TranslateItem.kt +++ b/shared/src/commonMain/kotlin/org/bibletranslationtools/writer/ui/translate/TranslateItem.kt @@ -1,5 +1,6 @@ package org.bibletranslationtools.writer.ui.translate +import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable import androidx.compose.ui.text.AnnotatedString import btt_writer.shared.generated.resources.Res @@ -17,7 +18,7 @@ import org.bibletranslationtools.writer.core.ProjectTypeClass import org.bibletranslationtools.writer.core.TranslationHelp import org.bibletranslationtools.writer.ui.translate.review.TargetMode import org.bibletranslationtools.writer.usecases.ParseMergeConflicts -import org.bibletranslationtools.writer.utils.getStringBlocking +import org.jetbrains.compose.resources.stringResource interface Swipable { @@ -69,17 +70,18 @@ abstract class TranslateItem { // "Project Title", " Title", " :") — // never from pt/ct content, which for helps projects holds JSON open val targetTitle: String + @Composable get() { val language = chunk.target.targetLanguage.name val book = chunk.source.project.name.trim() val chapter = chunk.chapterSlug.toIntOrNull() ?: chunk.chapterSlug + return when { chunk.isProjectTitle -> - "${getStringBlocking(Res.string.project_title)} - $language" - chunk.isChapterTitle -> - "${getStringBlocking(Res.string.project_chapter_title, book, chapter)} - $language" + "${stringResource(Res.string.project_title)} - $language" + chunk.isChapterTitle -> "${stringResource(Res.string.project_chapter_title, book, chapter)} - $language" chunk.isChapterReference -> - "$book $chapter ${getStringBlocking(Res.string.reference)} - $language" + "$book $chapter ${stringResource(Res.string.reference)} - $language" else -> { val verseSpan = Frame.parseVerseTitle(sourceText, chunk.sourceTranslationFormat) val span = verseSpan.ifEmpty { @@ -163,6 +165,7 @@ data class ReadItem( } override val targetTitle: String + @Composable get() = "${sourceTitle.trim()} - ${chunk.target.targetLanguage.name}" } @@ -210,6 +213,7 @@ data class ReviewItem( get() = customSourceTitle ?: super.sourceTitle override val targetTitle: String + @Composable get() = if (projectTypeClass == ProjectTypeClass.EXTANT) { // words are titled by the word itself, not chapter:verse "$sourceTitle - ${chunk.target.targetLanguage.name}" diff --git a/shared/src/commonMain/kotlin/org/bibletranslationtools/writer/ui/translate/review/DefaultReviewModeComponent.kt b/shared/src/commonMain/kotlin/org/bibletranslationtools/writer/ui/translate/review/DefaultReviewModeComponent.kt index 24165d1..fe430b0 100644 --- a/shared/src/commonMain/kotlin/org/bibletranslationtools/writer/ui/translate/review/DefaultReviewModeComponent.kt +++ b/shared/src/commonMain/kotlin/org/bibletranslationtools/writer/ui/translate/review/DefaultReviewModeComponent.kt @@ -52,6 +52,8 @@ import org.bibletranslationtools.writer.core.TranslationFormat import org.bibletranslationtools.writer.core.TranslationHelp import org.bibletranslationtools.writer.core.TranslationViewMode import org.bibletranslationtools.writer.core.Translator +import org.bibletranslationtools.writer.core.WORDS_CHAPTER +import org.bibletranslationtools.writer.core.WORD_PATTERN import org.bibletranslationtools.writer.core.launchWithProgress import org.bibletranslationtools.writer.data.Preference import org.bibletranslationtools.writer.data.getPref @@ -94,8 +96,6 @@ class DefaultReviewModeComponent( companion object { private const val TAG = "ReviewModeComponent" - private const val WORDS_CHAPTER = "01" - private val WORD_PATTERN: Pattern = Pattern.compile("#+([^\\n]+)\\n+([\\s\\S]*)") } private val preference: Preference by inject() diff --git a/shared/src/commonMain/kotlin/org/bibletranslationtools/writer/usecases/ValidateProject.kt b/shared/src/commonMain/kotlin/org/bibletranslationtools/writer/usecases/ValidateProject.kt index 5c47232..0656127 100644 --- a/shared/src/commonMain/kotlin/org/bibletranslationtools/writer/usecases/ValidateProject.kt +++ b/shared/src/commonMain/kotlin/org/bibletranslationtools/writer/usecases/ValidateProject.kt @@ -14,10 +14,11 @@ import org.bibletranslationtools.writer.core.TranslationFormat import org.bibletranslationtools.writer.core.TranslationHelp import org.bibletranslationtools.writer.core.Translator import org.bibletranslationtools.writer.core.Validation +import org.bibletranslationtools.writer.core.WORDS_CHAPTER +import org.bibletranslationtools.writer.core.WORD_PATTERN import org.bibletranslationtools.writer.utils.StringUtilities import org.bibletranslationtools.writer.utils.sortedNumerically import org.jetbrains.compose.resources.getString -import java.util.regex.Pattern class ValidateProject( private val catalogClient: ResourceCatalogClient, @@ -411,9 +412,4 @@ class ValidateProject( val title = container.readChunk(chapterSlug, chunkSlug) return title.trim() + " - " + type } - - companion object { - private const val WORDS_CHAPTER = "01" - private val WORD_PATTERN: Pattern = Pattern.compile("#+([^\\n]+)\\n+([\\s\\S]*)") - } } \ No newline at end of file From 5f1b1be9e875ecd4ea19912e58249feb846f4607 Mon Sep 17 00:00:00 2001 From: Maxim Date: Tue, 14 Jul 2026 16:19:49 +0400 Subject: [PATCH 2/4] implemented capability to change resource type from project info dialog. --- .../composeResources/values/strings.xml | 1 + .../writer/core/TargetTranslation.kt | 10 ++ .../dialogs/project/ProjectDetailsDialog.kt | 122 +++++++++++++++++- .../writer/ui/home/HomeComponent.kt | 105 +++++++++++++++ .../writer/ui/home/TranslationListScreen.kt | 14 ++ .../integration/ui/home/FakeHomeComponent.kt | 3 + .../translate/chunk/ChunkModeSectionTest.kt | 2 - .../ui/translate/read/ReadModeSectionTest.kt | 4 +- .../translate/review/ReviewModeSectionTest.kt | 5 +- 9 files changed, 252 insertions(+), 14 deletions(-) diff --git a/shared/src/commonMain/composeResources/values/strings.xml b/shared/src/commonMain/composeResources/values/strings.xml index 418de55..7199b76 100644 --- a/shared/src/commonMain/composeResources/values/strings.xml +++ b/shared/src/commonMain/composeResources/values/strings.xml @@ -140,6 +140,7 @@ Regular Unlocked Literal Bible Unlocked Dynamic Bible + Resource Options Open Bible Stories Notes Questions diff --git a/shared/src/commonMain/kotlin/org/bibletranslationtools/writer/core/TargetTranslation.kt b/shared/src/commonMain/kotlin/org/bibletranslationtools/writer/core/TargetTranslation.kt index 051be28..c6b749e 100644 --- a/shared/src/commonMain/kotlin/org/bibletranslationtools/writer/core/TargetTranslation.kt +++ b/shared/src/commonMain/kotlin/org/bibletranslationtools/writer/core/TargetTranslation.kt @@ -532,6 +532,16 @@ class TargetTranslation private constructor( targetLanguageName = targetLanguage.name } + fun changeResourceType(resourceSlug: String) { + if (translationType != ResourceType.TEXT) return + val resourceName = getResourceName(resourceSlug) + manifestAccessor.save( + manifest.copy(resource = Manifest.Resource(resourceSlug, resourceName)) + ) + this.resourceSlug = resourceSlug + this.resourceName = resourceName + } + fun unlockRepo(): Boolean { var cleaned = false val gitDir = File(path.absolutePath, ".git") diff --git a/shared/src/commonMain/kotlin/org/bibletranslationtools/writer/ui/dialogs/project/ProjectDetailsDialog.kt b/shared/src/commonMain/kotlin/org/bibletranslationtools/writer/ui/dialogs/project/ProjectDetailsDialog.kt index 90261bc..12e9a5a 100644 --- a/shared/src/commonMain/kotlin/org/bibletranslationtools/writer/ui/dialogs/project/ProjectDetailsDialog.kt +++ b/shared/src/commonMain/kotlin/org/bibletranslationtools/writer/ui/dialogs/project/ProjectDetailsDialog.kt @@ -1,5 +1,6 @@ package org.bibletranslationtools.writer.ui.dialogs.project +import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -49,11 +50,18 @@ import btt_writer.shared.generated.resources.label_unknown import btt_writer.shared.generated.resources.print import btt_writer.shared.generated.resources.progress import btt_writer.shared.generated.resources.publish +import btt_writer.shared.generated.resources.reg_type +import btt_writer.shared.generated.resources.resource_options import btt_writer.shared.generated.resources.target_language +import btt_writer.shared.generated.resources.title_cancel import btt_writer.shared.generated.resources.translators import btt_writer.shared.generated.resources.type_label +import btt_writer.shared.generated.resources.udb_type +import btt_writer.shared.generated.resources.ulb_type +import org.bibletranslationtools.resourcecontainer.Resource import org.bibletranslationtools.writer.DirectoryProvider import org.bibletranslationtools.writer.core.NativeSpeaker +import org.bibletranslationtools.writer.core.ResourceType import org.bibletranslationtools.writer.core.TextStyleType import org.bibletranslationtools.writer.core.TranslationType import org.bibletranslationtools.writer.core.Typography @@ -74,6 +82,7 @@ fun ProjectDetailsDialog( project: TranslationItem, onDismiss: () -> Unit, onChangeLanguage: () -> Unit, + onChangeResourceType: (String) -> Unit, onDelete: () -> Unit, onPublish: () -> Unit, onExport: (Boolean) -> Unit @@ -141,6 +150,10 @@ fun ProjectDetailsDialog( } var showContributorsDialog by rememberSaveable { mutableStateOf(false) } var showDeleteDialog by rememberSaveable { mutableStateOf(false) } + var showResourceOptions by rememberSaveable { mutableStateOf(false) } + + val canChangeResourceType = project.translation.translationType == ResourceType.TEXT && + project.translation.resourceSlug != Resource.OBS_SLUG Dialog(onDismissRequest = onDismiss) { Surface( @@ -196,11 +209,31 @@ fun ProjectDetailsDialog( } } - DetailRow( - label = stringResource(Res.string.type_label), - value = project.translation.resourceSlug?.uppercase() - ?: project.translation.translationType.title - ) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = stringResource(Res.string.type_label), + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + + Text( + text = project.translation.resourceSlug?.uppercase() + ?: project.translation.translationType.title, + modifier = Modifier.weight(1f) + ) + + if (canChangeResourceType) { + TextButton(onClick = { showResourceOptions = true }) { + Text( + text = stringResource(Res.string.label_change), + fontWeight = FontWeight.Bold + ) + } + } + } DetailRow( label = stringResource(Res.string.progress), @@ -305,6 +338,85 @@ fun ProjectDetailsDialog( onDismiss = { showDeleteDialog = false } ) } + + if (showResourceOptions) { + ResourceOptionsDialog( + currentResourceSlug = project.translation.resourceSlug, + onSelect = { slug -> + showResourceOptions = false + onChangeResourceType(slug) + }, + onDismiss = { showResourceOptions = false } + ) + } +} + +@Composable +private fun ResourceOptionsDialog( + currentResourceSlug: String?, + onSelect: (String) -> Unit, + onDismiss: () -> Unit +) { + val options = listOf( + Resource.ULB_SLUG to stringResource(Res.string.ulb_type), + Resource.UDB_SLUG to stringResource(Res.string.udb_type), + Resource.REGULAR_SLUG to stringResource(Res.string.reg_type) + ) + + Dialog(onDismissRequest = onDismiss) { + Surface( + modifier = Modifier.fillMaxWidth(0.8f) + .padding(32.dp), + shape = RoundedCornerShape(8.dp), + color = MaterialTheme.colorScheme.surface + ) { + Column(modifier = Modifier.fillMaxWidth()) { + Text( + text = stringResource(Res.string.resource_options), + style = MaterialTheme.typography.titleLarge, + modifier = Modifier + .align(Alignment.CenterHorizontally) + .padding(24.dp) + ) + + options.forEach { (slug, name) -> + val isCurrent = slug == currentResourceSlug + Column( + modifier = Modifier + .fillMaxWidth() + .then( + if (isCurrent) { + Modifier.background( + MaterialTheme.colorScheme.surfaceVariant + ) + } else { + Modifier.clickable { onSelect(slug) } + } + ) + ) { + Text( + text = name, + fontWeight = FontWeight.Bold, + modifier = Modifier.padding( + horizontal = 24.dp, + vertical = 16.dp + ) + ) + HorizontalDivider() + } + } + + TextButton( + onClick = onDismiss, + modifier = Modifier + .align(Alignment.End) + .padding(8.dp) + ) { + Text(stringResource(Res.string.title_cancel)) + } + } + } + } } private fun getTranslatorNames(contributors: List): String { diff --git a/shared/src/commonMain/kotlin/org/bibletranslationtools/writer/ui/home/HomeComponent.kt b/shared/src/commonMain/kotlin/org/bibletranslationtools/writer/ui/home/HomeComponent.kt index 0b3d0f4..e93b7cd 100644 --- a/shared/src/commonMain/kotlin/org/bibletranslationtools/writer/ui/home/HomeComponent.kt +++ b/shared/src/commonMain/kotlin/org/bibletranslationtools/writer/ui/home/HomeComponent.kt @@ -2,9 +2,11 @@ package org.bibletranslationtools.writer.ui.home import btt_writer.shared.generated.resources.Res import btt_writer.shared.generated.resources.duplicate_target_translation +import btt_writer.shared.generated.resources.error import btt_writer.shared.generated.resources.exporting import btt_writer.shared.generated.resources.loading import btt_writer.shared.generated.resources.log_out +import btt_writer.shared.generated.resources.warn_existing_target_translation import com.arkivanov.decompose.ComponentContext import com.arkivanov.decompose.router.slot.ChildSlot import com.arkivanov.decompose.router.slot.SlotNavigation @@ -42,6 +44,7 @@ import org.bibletranslationtools.writer.core.Profile import org.bibletranslationtools.writer.core.Progress import org.bibletranslationtools.writer.core.ProgressManager import org.bibletranslationtools.writer.core.ProgressOwner +import org.bibletranslationtools.writer.core.ResourceType import org.bibletranslationtools.writer.core.TargetTranslation import org.bibletranslationtools.writer.core.TaskHandle import org.bibletranslationtools.writer.core.Translator @@ -64,6 +67,7 @@ import org.bibletranslationtools.writer.ui.dialogs.update.UpdateLibraryComponent import org.bibletranslationtools.writer.ui.navigation.RootComponent import org.bibletranslationtools.writer.usecases.BackupRC import org.bibletranslationtools.writer.usecases.GogsLogout +import org.bibletranslationtools.writer.usecases.MergeTargetTranslation import org.bibletranslationtools.writer.usecases.TranslationProgress import org.jetbrains.compose.resources.getString import org.koin.core.component.KoinComponent @@ -109,6 +113,9 @@ interface HomeComponent { suspend fun getLastOpened(): TargetTranslation? fun deleteProject(project: TranslationItem) + fun changeResourceType(project: TranslationItem, resourceSlug: String) + fun confirmResourceMerge() + fun dismissResourceMerge() fun changeProjectSort(sort: ProjectSort) fun changeBookSort(sort: BookSort) fun showProjectInfo(item: TranslationItem) @@ -144,11 +151,18 @@ interface HomeComponent { val translations: List ) + data class ResourceMerge( + val project: TranslationItem, + val destination: TargetTranslation, + val message: String + ) + data class HomeState( val translations: List = emptyList(), val projectSort: ProjectSort = ProjectSort.ProjectThenLanguage, val bookSort: BookSort = BookSort.BibleOrder, val projectInfo: TranslationItem? = null, + val resourceMerge: ResourceMerge? = null, val scrollToTopTrigger: Int = 0 ) @@ -219,6 +233,7 @@ class DefaultHomeComponent( private val profile: Profile by inject() private val gogsLogout: GogsLogout by inject() private val backupRC: BackupRC by inject() + private val mergeTargetTranslation: MergeTargetTranslation by inject() private val catalogClient: ResourceCatalogClient by inject() private val platform: Platform by inject() @@ -340,6 +355,96 @@ class DefaultHomeComponent( } } + override fun changeResourceType(project: TranslationItem, resourceSlug: String) { + launchWithProgress { + val translation = project.translation + if (translation.resourceSlug == resourceSlug) return@launchWithProgress + + val newId = TargetTranslation.generateTargetTranslationId( + translation.targetLanguageId, + translation.projectId, + ResourceType.TEXT, + resourceSlug + ) + val existing = withContext(Dispatchers.IO) { getTargetTranslation(newId) } + + if (existing != null) { + val message = getString( + Res.string.warn_existing_target_translation, + project.name, + translation.targetLanguageName + ) + _state.update { + it.copy( + projectInfo = null, + resourceMerge = HomeComponent.ResourceMerge(project, existing, message) + ) + } + } else { + withContext(Dispatchers.IO) { + val oldId = translation.id + translation.changeResourceType(resourceSlug) + translation.normalizePath() + moveTargetTranslationAppSettings(oldId, translation.id) + } + hideProjectInfo() + loadProjects() + } + } + } + + override fun confirmResourceMerge() { + val merge = _state.value.resourceMerge ?: return + launchWithProgress { + _state.update { it.copy(resourceMerge = null) } + val result = withContext(Dispatchers.IO) { + mergeTargetTranslation.execute( + merge.destination, + merge.project.translation, + true + ) + } + + when (result.status) { + MergeTargetTranslation.Status.MERGE_CONFLICTS -> { + loadProjects() + openProject(merge.destination.id, true) + } + MergeTargetTranslation.Status.SUCCESS -> loadProjects() + else -> { + val error = getString(Res.string.error) + _event.send(HomeComponent.Event.SnackbarMessage(error)) + } + } + } + } + + override fun dismissResourceMerge() { + _state.update { it.copy(resourceMerge = null) } + } + + private fun moveTargetTranslationAppSettings( + targetTranslationId: String, + newTargetTranslationId: String + ) { + val sources = preference.getOpenSourceTranslations(targetTranslationId) + for (source in sources) { + preference.addOpenSourceTranslation(newTargetTranslationId, source) + } + + val source = preference.getSelectedSourceTranslationId(targetTranslationId) + preference.setSelectedSourceTranslation(newTargetTranslationId, source) + + val lastFocusChapterId = preference.getLastFocusChapterId(targetTranslationId) + val lastFocusFrameId = preference.getLastFocusFrameId(targetTranslationId) + preference.setLastFocus(newTargetTranslationId, lastFocusChapterId, lastFocusFrameId) + + val lastViewMode = preference.getLastViewMode(targetTranslationId) + preference.setLastViewMode(newTargetTranslationId, lastViewMode) + + preference.clearTargetTranslationSettings(targetTranslationId) + } + override fun changeProjectSort(sort: ProjectSort) { preference.setPref(SORT_BY_PROJECT, sort.ordinal) diff --git a/shared/src/commonMain/kotlin/org/bibletranslationtools/writer/ui/home/TranslationListScreen.kt b/shared/src/commonMain/kotlin/org/bibletranslationtools/writer/ui/home/TranslationListScreen.kt index b2b5b1c..de89832 100644 --- a/shared/src/commonMain/kotlin/org/bibletranslationtools/writer/ui/home/TranslationListScreen.kt +++ b/shared/src/commonMain/kotlin/org/bibletranslationtools/writer/ui/home/TranslationListScreen.kt @@ -47,8 +47,10 @@ import btt_writer.shared.generated.resources.sort_progress_then_project import btt_writer.shared.generated.resources.sort_project_then_language import btt_writer.shared.generated.resources.sort_projects import btt_writer.shared.generated.resources.type_label +import btt_writer.shared.generated.resources.warn_existing_target_translation_label import org.bibletranslationtools.writer.core.Typography import org.bibletranslationtools.writer.ui.components.PlatformPullToRefresh +import org.bibletranslationtools.writer.ui.dialogs.ConfirmDialog import org.bibletranslationtools.writer.ui.dialogs.project.ProjectDetailsDialog import org.jetbrains.compose.resources.stringResource import org.koin.compose.koinInject @@ -178,6 +180,9 @@ fun TranslationListScreen( component.hideProjectInfo() onChangeLanguage(project) }, + onChangeResourceType = { resourceSlug -> + component.changeResourceType(project, resourceSlug) + }, onDelete = { component.deleteProject(project) }, @@ -191,6 +196,15 @@ fun TranslationListScreen( } ) } + + state.resourceMerge?.let { merge -> + ConfirmDialog( + title = stringResource(Res.string.warn_existing_target_translation_label), + message = merge.message, + onConfirm = component::confirmResourceMerge, + onDismiss = component::dismissResourceMerge + ) + } } @OptIn(ExperimentalMaterial3Api::class) diff --git a/shared/src/commonTest/kotlin/org/bibletranslationtools/writer/integration/ui/home/FakeHomeComponent.kt b/shared/src/commonTest/kotlin/org/bibletranslationtools/writer/integration/ui/home/FakeHomeComponent.kt index 1194ddf..59e67c3 100644 --- a/shared/src/commonTest/kotlin/org/bibletranslationtools/writer/integration/ui/home/FakeHomeComponent.kt +++ b/shared/src/commonTest/kotlin/org/bibletranslationtools/writer/integration/ui/home/FakeHomeComponent.kt @@ -34,6 +34,9 @@ class FakeHomeComponent : HomeComponent { override suspend fun getLastOpened(): TargetTranslation? = null override fun deleteProject(project: TranslationItem) {} + override fun changeResourceType(project: TranslationItem, resourceSlug: String) {} + override fun confirmResourceMerge() {} + override fun dismissResourceMerge() {} override fun changeProjectSort(sort: ProjectSort) {} override fun changeBookSort(sort: BookSort) {} override fun showProjectInfo(item: TranslationItem) {} diff --git a/shared/src/commonTest/kotlin/org/bibletranslationtools/writer/integration/ui/translate/chunk/ChunkModeSectionTest.kt b/shared/src/commonTest/kotlin/org/bibletranslationtools/writer/integration/ui/translate/chunk/ChunkModeSectionTest.kt index 99f6975..75adb3a 100644 --- a/shared/src/commonTest/kotlin/org/bibletranslationtools/writer/integration/ui/translate/chunk/ChunkModeSectionTest.kt +++ b/shared/src/commonTest/kotlin/org/bibletranslationtools/writer/integration/ui/translate/chunk/ChunkModeSectionTest.kt @@ -50,7 +50,6 @@ class ChunkModeSectionTest : ScreenTestBase() { val mockChunkItem = mockk(relaxed = true) { every { id } returns "chunk_item_1" every { sourceTitle } returns "Mock Source Title" - every { targetTitle } returns "Mock Target Title" every { renderedSourceText } returns AnnotatedString("Mock Source Text") every { renderedTargetText } returns AnnotatedString("") every { sourceOnTop } returns false @@ -106,7 +105,6 @@ class ChunkModeSectionTest : ScreenTestBase() { val mockChunkItem = mockk(relaxed = true) { every { id } returns "chunk_item_1" every { sourceTitle } returns "Mock Source Title" - every { targetTitle } returns "Mock Target Title" every { renderedSourceText } returns AnnotatedString("Mock Source Text") every { renderedTargetText } returns AnnotatedString("") every { sourceOnTop } returns false diff --git a/shared/src/commonTest/kotlin/org/bibletranslationtools/writer/integration/ui/translate/read/ReadModeSectionTest.kt b/shared/src/commonTest/kotlin/org/bibletranslationtools/writer/integration/ui/translate/read/ReadModeSectionTest.kt index 846ce07..b5db72b 100644 --- a/shared/src/commonTest/kotlin/org/bibletranslationtools/writer/integration/ui/translate/read/ReadModeSectionTest.kt +++ b/shared/src/commonTest/kotlin/org/bibletranslationtools/writer/integration/ui/translate/read/ReadModeSectionTest.kt @@ -47,7 +47,6 @@ class ReadModeSectionTest : ScreenTestBase() { val mockReadItem = mockk(relaxed = true) { every { id } returns "read_item_1" every { sourceTitle } returns "Mock Source Title" - every { targetTitle } returns "Mock Target Title" every { renderedSourceText } returns AnnotatedString("Mock Source Text") every { renderedTargetText } returns AnnotatedString("") // blank to trigger begin translating button every { sourceOnTop } returns false @@ -75,7 +74,7 @@ class ReadModeSectionTest : ScreenTestBase() { ) } - onNodeWithText("Mock Target Title", substring = true).assertIsDisplayed() + onNodeWithText("Mock Source Title", substring = true).assertIsDisplayed() onNodeWithText(getStringBlocking(Res.string.begin_translating)).performClick() assertEquals("3", beginTranslationCalledWith) @@ -93,7 +92,6 @@ class ReadModeSectionTest : ScreenTestBase() { val mockReadItem = mockk(relaxed = true) { every { id } returns "read_item_1" every { sourceTitle } returns "Mock Source Title" - every { targetTitle } returns "Mock Target Title" every { renderedSourceText } returns AnnotatedString("Mock Source Text") every { renderedTargetText } returns AnnotatedString("Existing translation text") every { sourceOnTop } returns false diff --git a/shared/src/commonTest/kotlin/org/bibletranslationtools/writer/integration/ui/translate/review/ReviewModeSectionTest.kt b/shared/src/commonTest/kotlin/org/bibletranslationtools/writer/integration/ui/translate/review/ReviewModeSectionTest.kt index 01cf564..ced2574 100644 --- a/shared/src/commonTest/kotlin/org/bibletranslationtools/writer/integration/ui/translate/review/ReviewModeSectionTest.kt +++ b/shared/src/commonTest/kotlin/org/bibletranslationtools/writer/integration/ui/translate/review/ReviewModeSectionTest.kt @@ -58,7 +58,6 @@ class ReviewModeSectionTest : ScreenTestBase() { val mockReviewItem = mockk(relaxed = true) { every { id } returns "review_item_1" every { sourceTitle } returns "Mock Source Title" - every { targetTitle } returns "Mock Target Title" every { renderedSourceText } returns AnnotatedString("Mock Source Text") every { renderedTargetText } returns AnnotatedString("Mock Target Text") every { targetMode } returns TargetMode.MARKER @@ -93,7 +92,7 @@ class ReviewModeSectionTest : ScreenTestBase() { ) } - onNodeWithText("Mock Target Title", substring = true).assertIsDisplayed() + onNodeWithText("Mock Source Title", substring = true).assertIsDisplayed() onNodeWithContentDescription("toggle edit").performClick() assertTrue(toggleEditCalled) @@ -118,7 +117,6 @@ class ReviewModeSectionTest : ScreenTestBase() { val mockReviewItem = mockk(relaxed = true) { every { id } returns "review_item_1" every { sourceTitle } returns "Mock Source Title" - every { targetTitle } returns "Mock Target Title" every { renderedSourceText } returns AnnotatedString("Mock Source Text") every { renderedTargetText } returns AnnotatedString("Mock Target Text") every { targetMode } returns TargetMode.EDIT @@ -177,7 +175,6 @@ class ReviewModeSectionTest : ScreenTestBase() { val mockReviewItem = mockk(relaxed = true) { every { id } returns "review_item_1" every { sourceTitle } returns "Mock Source Title" - every { targetTitle } returns "Mock Target Title" every { renderedSourceText } returns AnnotatedString("Mock Source Text") every { renderedTargetText } returns AnnotatedString("Mock Target Text") every { targetMode } returns TargetMode.MARKER From 17d0f094433df5fb9d9c770113f47c76ede093e0 Mon Sep 17 00:00:00 2001 From: Maxim Date: Tue, 14 Jul 2026 19:31:26 +0400 Subject: [PATCH 3/4] bump version, upgrade libs --- gradle/libs.versions.toml | 6 +++--- gradle/wrapper/gradle-wrapper.properties | 5 ++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 96dd2d5..cea0c3d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,13 +1,13 @@ [versions] agp = "9.2.1" -kotlin = "2.4.0" +kotlin = "2.4.10" android-compileSdk = "37" android-minSdk = "26" android-targetSdk = "37" app-version-name = "2.0.0" -app-version-code = "58" +app-version-code = "59" androidx-activity = "1.13.0" -androidx-lifecycle = "2.10.0" +androidx-lifecycle = "2.11.0" composeHotReload = "1.1.1" composeMultiplatform = "1.11.1" kotlinx-coroutines = "1.11.0" diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index c61a118..7befe19 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,6 @@ +#Tue Jul 14 19:25:38 GET 2026 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip -networkTimeout=10000 -validateDistributionUrl=true +distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From c2e36461bd0d18cf45a4e5dddcf520e0a7e21c45 Mon Sep 17 00:00:00 2001 From: Maxim Date: Wed, 15 Jul 2026 14:45:54 +0400 Subject: [PATCH 4/4] refactoring --- gradle/libs.versions.toml | 4 +-- .../writer/data/Preference.kt | 22 +++++++++++++ .../writer/ui/home/HomeComponent.kt | 33 +++++-------------- .../newtranslation/NewTranslationComponent.kt | 32 +++++------------- 4 files changed, 41 insertions(+), 50 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index cea0c3d..f848c3f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -18,7 +18,7 @@ iconsExtended = "1.7.3" koin = "4.2.2" jgit = "5.13.5.202508271544-r" -bcprovJdk18on = "1.84" +bcprovJdk18on = "1.85" jmx = "1.2.1" kotlinx-io = "0.9.1" @@ -31,7 +31,7 @@ preferences-core = "1.2.1" filekit = "0.14.2" htmlconverter = "1.1.1" foundation = "1.11.4" -oshi-core = "7.3.2" +oshi-core = "7.4.0" logger = "3.0.3" foreground = "0.1.0" diff --git a/shared/src/commonMain/kotlin/org/bibletranslationtools/writer/data/Preference.kt b/shared/src/commonMain/kotlin/org/bibletranslationtools/writer/data/Preference.kt index 1c8c441..b3cabe7 100644 --- a/shared/src/commonMain/kotlin/org/bibletranslationtools/writer/data/Preference.kt +++ b/shared/src/commonMain/kotlin/org/bibletranslationtools/writer/data/Preference.kt @@ -363,6 +363,28 @@ class Preference(private val settings: ObservableSettings) { ) } + fun moveTargetTranslationAppSettings( + targetTranslationId: String, + newTargetTranslationId: String + ) { + val sources = getOpenSourceTranslations(targetTranslationId) + for (source in sources) { + addOpenSourceTranslation(newTargetTranslationId, source) + } + + val source = getSelectedSourceTranslationId(targetTranslationId) + setSelectedSourceTranslation(newTargetTranslationId, source) + + val lastFocusChapterId = getLastFocusChapterId(targetTranslationId) + val lastFocusFrameId = getLastFocusFrameId(targetTranslationId) + setLastFocus(newTargetTranslationId, lastFocusChapterId, lastFocusFrameId) + + val lastViewMode = getLastViewMode(targetTranslationId) + setLastViewMode(newTargetTranslationId, lastViewMode) + + clearTargetTranslationSettings(targetTranslationId) + } + /** * Removes all settings for a target translation * @param targetTranslationId diff --git a/shared/src/commonMain/kotlin/org/bibletranslationtools/writer/ui/home/HomeComponent.kt b/shared/src/commonMain/kotlin/org/bibletranslationtools/writer/ui/home/HomeComponent.kt index e93b7cd..ab32c29 100644 --- a/shared/src/commonMain/kotlin/org/bibletranslationtools/writer/ui/home/HomeComponent.kt +++ b/shared/src/commonMain/kotlin/org/bibletranslationtools/writer/ui/home/HomeComponent.kt @@ -377,7 +377,11 @@ class DefaultHomeComponent( _state.update { it.copy( projectInfo = null, - resourceMerge = HomeComponent.ResourceMerge(project, existing, message) + resourceMerge = HomeComponent.ResourceMerge( + project = project, + destination = existing, + message = message + ) ) } } else { @@ -385,7 +389,10 @@ class DefaultHomeComponent( val oldId = translation.id translation.changeResourceType(resourceSlug) translation.normalizePath() - moveTargetTranslationAppSettings(oldId, translation.id) + preference.moveTargetTranslationAppSettings( + targetTranslationId = oldId, + newTargetTranslationId = translation.id + ) } hideProjectInfo() loadProjects() @@ -423,28 +430,6 @@ class DefaultHomeComponent( _state.update { it.copy(resourceMerge = null) } } - private fun moveTargetTranslationAppSettings( - targetTranslationId: String, - newTargetTranslationId: String - ) { - val sources = preference.getOpenSourceTranslations(targetTranslationId) - for (source in sources) { - preference.addOpenSourceTranslation(newTargetTranslationId, source) - } - - val source = preference.getSelectedSourceTranslationId(targetTranslationId) - preference.setSelectedSourceTranslation(newTargetTranslationId, source) - - val lastFocusChapterId = preference.getLastFocusChapterId(targetTranslationId) - val lastFocusFrameId = preference.getLastFocusFrameId(targetTranslationId) - preference.setLastFocus(newTargetTranslationId, lastFocusChapterId, lastFocusFrameId) - - val lastViewMode = preference.getLastViewMode(targetTranslationId) - preference.setLastViewMode(newTargetTranslationId, lastViewMode) - - preference.clearTargetTranslationSettings(targetTranslationId) - } - override fun changeProjectSort(sort: ProjectSort) { preference.setPref(SORT_BY_PROJECT, sort.ordinal) diff --git a/shared/src/commonMain/kotlin/org/bibletranslationtools/writer/ui/newtranslation/NewTranslationComponent.kt b/shared/src/commonMain/kotlin/org/bibletranslationtools/writer/ui/newtranslation/NewTranslationComponent.kt index ca0c523..5eb271c 100644 --- a/shared/src/commonMain/kotlin/org/bibletranslationtools/writer/ui/newtranslation/NewTranslationComponent.kt +++ b/shared/src/commonMain/kotlin/org/bibletranslationtools/writer/ui/newtranslation/NewTranslationComponent.kt @@ -190,7 +190,10 @@ class DefaultNewTranslationComponent( val resourceSlug = sourceTranslation.resourceSlug val existingTranslation = getTargetTranslation( TargetTranslation.generateTargetTranslationId( - targetLanguage.slug, projectId, ResourceType.TEXT, resourceSlug + targetLanguageSlug = targetLanguage.slug, + projectSlug = projectId, + resourceType = ResourceType.TEXT, + resourceSlug = resourceSlug ) ) @@ -212,7 +215,10 @@ class DefaultNewTranslationComponent( sourceTranslation.changeTargetLanguage(targetLanguage) sourceTranslation.normalizePath() val newId = sourceTranslation.id - moveTargetTranslationAppSettings(originalId, newId) + preference.moveTargetTranslationAppSettings( + originalId, + newId + ) withContext(Dispatchers.Main) { onResult(NewTranslationComponent.Result.Success) } @@ -557,28 +563,6 @@ class DefaultNewTranslationComponent( return translator.getTargetTranslation(translationId) } - private fun moveTargetTranslationAppSettings( - targetTranslationId: String, - newTargetTranslationId: String - ) { - val sources = preference.getOpenSourceTranslations(targetTranslationId) - for (source in sources) { - preference.addOpenSourceTranslation(newTargetTranslationId, source) - } - - val source = preference.getSelectedSourceTranslationId(targetTranslationId) - preference.setSelectedSourceTranslation(newTargetTranslationId, source) - - val lastFocusChapterId = preference.getLastFocusChapterId(targetTranslationId) - val lastFocusFrameId = preference.getLastFocusFrameId(targetTranslationId) - preference.setLastFocus(newTargetTranslationId, lastFocusChapterId, lastFocusFrameId) - - val lastViewMode = preference.getLastViewMode(targetTranslationId) - preference.setLastViewMode(newTargetTranslationId, lastViewMode) - - preference.clearTargetTranslationSettings(targetTranslationId) - } - private suspend fun createTargetTranslation( projectId: String, resourceType: ResourceType,