From 9e22030a3f62ebb1149ce4ad7a2d6d96ae732d3d Mon Sep 17 00:00:00 2001 From: Sergejs Luhmirins Date: Mon, 6 Oct 2025 14:55:23 +0300 Subject: [PATCH 01/10] Restore battery optimisation check for the service notification --- .../com/simprints/core/workers/SimCoroutineWorker.kt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/infra/core/src/main/java/com/simprints/core/workers/SimCoroutineWorker.kt b/infra/core/src/main/java/com/simprints/core/workers/SimCoroutineWorker.kt index 1c95968046..1fa629a9a5 100644 --- a/infra/core/src/main/java/com/simprints/core/workers/SimCoroutineWorker.kt +++ b/infra/core/src/main/java/com/simprints/core/workers/SimCoroutineWorker.kt @@ -7,6 +7,7 @@ import android.app.NotificationManager import android.content.Context import android.content.pm.ServiceInfo import android.os.Build +import android.os.PowerManager import androidx.core.app.NotificationCompat import androidx.work.CoroutineWorker import androidx.work.Data @@ -58,6 +59,9 @@ abstract class SimCoroutineWorker( protected suspend fun showProgressNotification() { try { + if (isFollowingBatteryOptimizations(context)) { + return + } setForeground(getForegroundInfo()) } catch (setForegroundException: Throwable) { // Setting foreground (showing the notification) may be restricted by the system @@ -120,6 +124,11 @@ abstract class SimCoroutineWorker( } } + private fun isFollowingBatteryOptimizations(context: Context): Boolean = context + .getSystemService(PowerManager::class.java) + .isIgnoringBatteryOptimizations(context.packageName) + .not() + private companion object { private const val WORKER_FOREGROUND_NOTIFICATION_ID = 2 private const val WORKER_FOREGROUND_NOTIFICATION_CHANNEL_ID = From 7d45c4052d2f5673e4a1165e998a5a80a34874f6 Mon Sep 17 00:00:00 2001 From: Sergejs Luhmirins Date: Mon, 6 Oct 2025 14:57:37 +0300 Subject: [PATCH 02/10] Improve sample upload logging to get more context of upload run --- .../images/remote/firebase/FirebaseSampleUploader.kt | 6 ++++-- .../images/remote/signedurl/SignedUrlSampleUploader.kt | 10 ++++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/infra/images/src/main/java/com/simprints/infra/images/remote/firebase/FirebaseSampleUploader.kt b/infra/images/src/main/java/com/simprints/infra/images/remote/firebase/FirebaseSampleUploader.kt index 82276db1e3..9017636d1b 100644 --- a/infra/images/src/main/java/com/simprints/infra/images/remote/firebase/FirebaseSampleUploader.kt +++ b/infra/images/src/main/java/com/simprints/infra/images/remote/firebase/FirebaseSampleUploader.kt @@ -43,7 +43,7 @@ internal class FirebaseSampleUploader @Inject constructor( } var allImagesUploaded = true - Simber.i("Starting sample upload to Firebase storage") + Simber.i("Starting sample upload to Firebase storage", tag = SAMPLE_UPLOAD) val bucketUrl = configManager.getProject(projectId).imageBucket val rootRef = FirebaseStorage .getInstance(firebaseApp, bucketUrl) @@ -52,6 +52,8 @@ internal class FirebaseSampleUploader @Inject constructor( val urlRequestScope = eventRepository.createEventScope(type = EventScopeType.SAMPLE_UP_SYNC) val sampleReferences = localDataSource.listImages(projectId) + Simber.i("Images to upload ${sampleReferences.size}", tag = SAMPLE_UPLOAD) + sampleReferences.forEachIndexed { index, imageRef -> Simber.i("Reading sample file: ${imageRef.relativePath.parts.last()}", tag = SAMPLE_UPLOAD) @@ -84,7 +86,7 @@ internal class FirebaseSampleUploader @Inject constructor( } } catch (t: Throwable) { allImagesUploaded = false - Simber.e("Failed to upload images", t, tag = SYNC) + Simber.e("Failed to upload images", t, tag = SAMPLE_UPLOAD) } } eventRepository.closeEventScope(urlRequestScope, EventScopeEndCause.WORKFLOW_ENDED) diff --git a/infra/images/src/main/java/com/simprints/infra/images/remote/signedurl/SignedUrlSampleUploader.kt b/infra/images/src/main/java/com/simprints/infra/images/remote/signedurl/SignedUrlSampleUploader.kt index 9bbe229ed6..10649e8e5d 100644 --- a/infra/images/src/main/java/com/simprints/infra/images/remote/signedurl/SignedUrlSampleUploader.kt +++ b/infra/images/src/main/java/com/simprints/infra/images/remote/signedurl/SignedUrlSampleUploader.kt @@ -33,7 +33,7 @@ internal class SignedUrlSampleUploader @Inject constructor( val batchSize = getBatchSize() val urlRequestScope = eventRepository.createEventScope(type = EventScopeType.SAMPLE_UP_SYNC) - Simber.i("Starting image upload in batches of $batchSize (Scope ID: ${urlRequestScope.id}") + Simber.i("Starting image upload in batches of $batchSize (Scope ID: ${urlRequestScope.id})", tag = SAMPLE_UPLOAD) var sampleIndex = 0 var samplesSize = 0 val sampleReferenceBatches = localDataSource @@ -44,6 +44,8 @@ internal class SignedUrlSampleUploader @Inject constructor( // cases where there are large amounts of files and the coroutine is being interrupted, // even if the result is that some requested batches are not at max size. .chunked(batchSize) + Simber.i("Images to upload: $samplesSize", tag = SAMPLE_UPLOAD) + for (batch in sampleReferenceBatches) { if (!coroutineContext.isActive) { // Do not process next batch if coroutine is being cancelled @@ -73,7 +75,7 @@ internal class SignedUrlSampleUploader @Inject constructor( // Fetch upload urls for each image val sampleIdToUrlMap = fetchUploadUrlsPerSample(projectId, batchUploadData) - Simber.i("${sampleIdToUrlMap.size} signed URLs fetched") + Simber.i("${sampleIdToUrlMap.size} signed URLs fetched", tag = SAMPLE_UPLOAD) for (sample in batchUploadData) { if (!coroutineContext.isActive) { @@ -81,7 +83,7 @@ internal class SignedUrlSampleUploader @Inject constructor( allImagesUploaded = false break } - Simber.i("Uploading ${sample.sampleId}") + Simber.i("Uploading ${sample.sampleId}", tag = SAMPLE_UPLOAD) progressCallback?.invoke(sampleIndex++, samplesSize) val url = sampleIdToUrlMap[sample.sampleId] @@ -96,7 +98,7 @@ internal class SignedUrlSampleUploader @Inject constructor( if (success) { localDataSource.deleteImage(sample.imageRef) metadataStore.deleteMetadata(sample.imageRef.relativePath) - Simber.i("Uploaded ${sample.sampleId} successfully") + Simber.i("Uploaded ${sample.sampleId} successfully", tag = SAMPLE_UPLOAD) } else { allImagesUploaded = false } From 47aeb87926de56871d8edf4f00e937f51b4a945e Mon Sep 17 00:00:00 2001 From: Sergejs Luhmirins Date: Mon, 6 Oct 2025 15:24:22 +0300 Subject: [PATCH 03/10] Fix worker tests --- ...serLocationIntoCurrentSessionWorkerTest.kt | 7 ++++++- .../RealmToRoomMigrationWorkerTest.kt | 6 ++++++ .../CommCareEventSyncDownloaderWorkerTest.kt | 17 +++++++++-------- ...printsEventDownSyncDownloaderWorkerTest.kt | 7 ++++++- .../master/EventEndSyncReporterWorkerTest.kt | 14 ++++++++------ .../EventStartSyncReporterWorkerTest.kt | 7 ++++++- .../sync/master/EventSyncMasterWorkerTest.kt | 11 ++++++++--- .../workers/EventUpSyncUploaderWorkerTest.kt | 17 +++++++++-------- .../worker/DeviceConfigDownSyncWorkerTest.kt | 8 +++++++- .../worker/ProjectConfigDownSyncWorkerTest.kt | 7 ++++++- .../enrolments/EnrolmentRecordWorkerTest.kt | 7 ++++++- .../infra/sync/files/FileUpSyncWorkerTest.kt | 19 +++++++++++++------ .../firmware/FirmwareFileUpdateWorkerTest.kt | 8 +++++++- 13 files changed, 97 insertions(+), 38 deletions(-) diff --git a/feature/setup/src/test/java/com/simprints/feature/setup/location/StoreUserLocationIntoCurrentSessionWorkerTest.kt b/feature/setup/src/test/java/com/simprints/feature/setup/location/StoreUserLocationIntoCurrentSessionWorkerTest.kt index 3178bcf052..6a0f9dc67f 100644 --- a/feature/setup/src/test/java/com/simprints/feature/setup/location/StoreUserLocationIntoCurrentSessionWorkerTest.kt +++ b/feature/setup/src/test/java/com/simprints/feature/setup/location/StoreUserLocationIntoCurrentSessionWorkerTest.kt @@ -1,5 +1,6 @@ package com.simprints.feature.setup.location +import android.os.PowerManager import com.simprints.infra.events.sampledata.createSessionScope import com.simprints.infra.events.session.SessionEventRepository import com.simprints.testtools.common.coroutines.TestCoroutineRule @@ -33,7 +34,11 @@ internal class StoreUserLocationIntoCurrentSessionWorkerTest { coEvery { eventRepository.getCurrentSessionScope() } returns createSessionScope() worker = StoreUserLocationIntoCurrentSessionWorker( - mockk(relaxed = true), + mockk(relaxed = true) { + every { getSystemService(any()) } returns mockk { + every { isIgnoringBatteryOptimizations(any()) } returns true + } + }, mockk(relaxed = true), eventRepository, locationManager, diff --git a/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/repository/local/migration/RealmToRoomMigrationWorkerTest.kt b/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/repository/local/migration/RealmToRoomMigrationWorkerTest.kt index cf34308977..c9d4160ef6 100644 --- a/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/repository/local/migration/RealmToRoomMigrationWorkerTest.kt +++ b/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/repository/local/migration/RealmToRoomMigrationWorkerTest.kt @@ -1,6 +1,7 @@ package com.simprints.infra.enrolment.records.repository.local.migration import android.content.Context +import android.os.PowerManager import androidx.work.ListenableWorker.Result import androidx.work.WorkerParameters import com.google.common.truth.Truth.assertThat @@ -13,6 +14,7 @@ import io.mockk.MockKAnnotations import io.mockk.Runs import io.mockk.coEvery import io.mockk.coVerify +import io.mockk.every import io.mockk.impl.annotations.InjectMockKs import io.mockk.impl.annotations.MockK import io.mockk.just @@ -53,6 +55,10 @@ class RealmToRoomMigrationWorkerTest { @Before fun setUp() { MockKAnnotations.init(this) + + every { appContext.getSystemService(any()) } returns mockk { + every { isIgnoringBatteryOptimizations(any()) } returns true + } } @Test diff --git a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/down/workers/CommCareEventSyncDownloaderWorkerTest.kt b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/down/workers/CommCareEventSyncDownloaderWorkerTest.kt index fbdc370549..48c3ef4bb3 100644 --- a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/down/workers/CommCareEventSyncDownloaderWorkerTest.kt +++ b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/down/workers/CommCareEventSyncDownloaderWorkerTest.kt @@ -1,12 +1,13 @@ package com.simprints.infra.eventsync.sync.down.workers -import androidx.test.ext.junit.runners.AndroidJUnit4 +import android.os.PowerManager +import androidx.test.ext.junit.runners.* import androidx.work.ListenableWorker import androidx.work.WorkInfo import androidx.work.WorkInfo.State.RUNNING import androidx.work.WorkInfo.State.SUCCEEDED import androidx.work.workDataOf -import com.google.common.truth.Truth.assertThat +import com.google.common.truth.Truth.* import com.simprints.core.tools.json.JsonHelper import com.simprints.infra.config.store.ConfigRepository import com.simprints.infra.enrolment.records.repository.local.migration.RealmToRoomMigrationFlagsStore @@ -22,12 +23,8 @@ import com.simprints.infra.eventsync.sync.down.workers.BaseEventDownSyncDownload import com.simprints.infra.eventsync.sync.down.workers.BaseEventDownSyncDownloaderWorker.Companion.OUTPUT_DOWN_SYNC import com.simprints.infra.eventsync.sync.down.workers.BaseEventDownSyncDownloaderWorker.Companion.PROGRESS_DOWN_SYNC import com.simprints.testtools.common.coroutines.TestCoroutineRule -import io.mockk.MockKAnnotations -import io.mockk.coEvery -import io.mockk.coVerify -import io.mockk.every +import io.mockk.* import io.mockk.impl.annotations.MockK -import io.mockk.mockk import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Rule @@ -68,7 +65,11 @@ internal class CommCareEventSyncDownloaderWorkerTest { MockKAnnotations.init(this, relaxed = true) eventDownSyncDownloaderWorker = CommCareEventSyncDownloaderWorker( - mockk(relaxed = true), + mockk(relaxed = true) { + every { getSystemService(any()) } returns mockk { + every { isIgnoringBatteryOptimizations(any()) } returns true + } + }, mockk(relaxed = true) { every { inputData } returns workDataOf( INPUT_DOWN_SYNC_OPS to JsonHelper.toJson(projectDownSyncScope.operations.first()), diff --git a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/down/workers/SimprintsEventDownSyncDownloaderWorkerTest.kt b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/down/workers/SimprintsEventDownSyncDownloaderWorkerTest.kt index d56304da6d..ad960f1cf8 100644 --- a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/down/workers/SimprintsEventDownSyncDownloaderWorkerTest.kt +++ b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/down/workers/SimprintsEventDownSyncDownloaderWorkerTest.kt @@ -1,5 +1,6 @@ package com.simprints.infra.eventsync.sync.down.workers +import android.os.PowerManager import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.work.ListenableWorker import androidx.work.WorkInfo @@ -75,7 +76,11 @@ internal class SimprintsEventDownSyncDownloaderWorkerTest { MockKAnnotations.init(this, relaxed = true) eventDownSyncDownloaderWorker = SimprintsEventDownSyncDownloaderWorker( - mockk(relaxed = true), + mockk(relaxed = true) { + every { getSystemService(any()) } returns mockk { + every { isIgnoringBatteryOptimizations(any()) } returns true + } + }, mockk(relaxed = true) { every { inputData } returns workDataOf( INPUT_DOWN_SYNC_OPS to JsonHelper.toJson(projectDownSyncScope.operations.first()), diff --git a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/master/EventEndSyncReporterWorkerTest.kt b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/master/EventEndSyncReporterWorkerTest.kt index 6d1e6b2479..518be080d7 100644 --- a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/master/EventEndSyncReporterWorkerTest.kt +++ b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/master/EventEndSyncReporterWorkerTest.kt @@ -1,8 +1,9 @@ package com.simprints.infra.eventsync.sync.master +import android.os.PowerManager import androidx.work.ListenableWorker import androidx.work.workDataOf -import com.google.common.truth.Truth.assertThat +import com.google.common.truth.Truth.* import com.simprints.core.tools.time.TimeHelper import com.simprints.core.tools.time.Timestamp import com.simprints.infra.events.EventRepository @@ -10,11 +11,8 @@ import com.simprints.infra.eventsync.sync.common.EventSyncCache import com.simprints.infra.eventsync.sync.master.EventEndSyncReporterWorker.Companion.EVENT_DOWN_SYNC_SCOPE_TO_CLOSE import com.simprints.infra.eventsync.sync.master.EventEndSyncReporterWorker.Companion.SYNC_ID_TO_MARK_AS_COMPLETED import com.simprints.testtools.common.coroutines.TestCoroutineRule -import io.mockk.MockKAnnotations -import io.mockk.coVerify -import io.mockk.every +import io.mockk.* import io.mockk.impl.annotations.MockK -import io.mockk.mockk import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Rule @@ -89,7 +87,11 @@ internal class EventEndSyncReporterWorkerTest { downScopeId: String?, upScopeId: String?, ) = EventEndSyncReporterWorker( - mockk(relaxed = true), + mockk(relaxed = true) { + every { getSystemService(any()) } returns mockk { + every { isIgnoringBatteryOptimizations(any()) } returns true + } + }, mockk(relaxed = true) { every { inputData } returns workDataOf( SYNC_ID_TO_MARK_AS_COMPLETED to syncId, diff --git a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/master/EventStartSyncReporterWorkerTest.kt b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/master/EventStartSyncReporterWorkerTest.kt index 7d273b5632..59c2f8674b 100644 --- a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/master/EventStartSyncReporterWorkerTest.kt +++ b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/master/EventStartSyncReporterWorkerTest.kt @@ -1,5 +1,6 @@ package com.simprints.infra.eventsync.sync.master +import android.os.PowerManager import androidx.work.ListenableWorker import androidx.work.workDataOf import com.google.common.truth.Truth.assertThat @@ -20,7 +21,11 @@ class EventStartSyncReporterWorkerTest { val testCoroutineRule = TestCoroutineRule() private val startSyncReportWorker = EventStartSyncReporterWorker( - mockk(relaxed = true), + mockk(relaxed = true) { + every { getSystemService(any()) } returns mockk { + every { isIgnoringBatteryOptimizations(any()) } returns true + } + }, mockk(relaxed = true) { every { inputData } returns INPUT_DATA }, diff --git a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/master/EventSyncMasterWorkerTest.kt b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/master/EventSyncMasterWorkerTest.kt index bb2b1b5c3d..cdf71375df 100644 --- a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/master/EventSyncMasterWorkerTest.kt +++ b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/master/EventSyncMasterWorkerTest.kt @@ -1,6 +1,7 @@ package com.simprints.infra.eventsync.sync.master import android.content.Context +import android.os.PowerManager import androidx.test.core.app.ApplicationProvider.* import androidx.test.ext.junit.runners.* import androidx.work.Configuration @@ -163,8 +164,9 @@ internal class EventSyncMasterWorkerTest { dispatcher = testCoroutineRule.testCoroutineDispatcher, securityManager = securityManager, eventRepository = eventRepository, - ) + ), ) + coEvery { masterWorker["showProgressNotification"]() } returns Unit } @@ -383,8 +385,11 @@ internal class EventSyncMasterWorkerTest { private fun canDownSyncFromCommCare(should: Boolean) { every { synchronizationConfiguration.down.simprints } returns null every { synchronizationConfiguration.down.commCare } returns - if (should) DownSynchronizationConfiguration.CommCareDownSynchronizationConfiguration - else null + if (should) { + DownSynchronizationConfiguration.CommCareDownSynchronizationConfiguration + } else { + null + } } private fun canUpSync(should: Boolean) { diff --git a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/up/workers/EventUpSyncUploaderWorkerTest.kt b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/up/workers/EventUpSyncUploaderWorkerTest.kt index ae452479ba..0f29d6813a 100644 --- a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/up/workers/EventUpSyncUploaderWorkerTest.kt +++ b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/up/workers/EventUpSyncUploaderWorkerTest.kt @@ -1,14 +1,15 @@ package com.simprints.infra.eventsync.sync.up.workers import android.content.Context +import android.os.PowerManager import androidx.arch.core.executor.testing.InstantTaskExecutorRule -import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.ext.junit.runners.* import androidx.work.ListenableWorker import androidx.work.WorkerFactory import androidx.work.WorkerParameters import androidx.work.testing.TestListenableWorkerBuilder import androidx.work.workDataOf -import com.google.common.truth.Truth.assertThat +import com.google.common.truth.Truth.* import com.simprints.core.tools.json.JsonHelper import com.simprints.infra.authstore.AuthStore import com.simprints.infra.authstore.exceptions.RemoteDbNotSignedInException @@ -28,12 +29,8 @@ import com.simprints.infra.eventsync.sync.up.workers.EventUpSyncUploaderWorker.C import com.simprints.infra.network.exceptions.BackendMaintenanceException import com.simprints.infra.network.exceptions.SyncCloudIntegrationException import com.simprints.testtools.common.coroutines.TestCoroutineRule -import io.mockk.MockKAnnotations -import io.mockk.coEvery -import io.mockk.coVerify -import io.mockk.every +import io.mockk.* import io.mockk.impl.annotations.MockK -import io.mockk.mockk import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runTest @@ -255,7 +252,11 @@ internal class EventUpSyncUploaderWorkerTest { scope: String?, eventScopeId: String? = "scopeId", ): EventUpSyncUploaderWorker = TestListenableWorkerBuilder( - mockk(), + mockk(relaxed = true) { + every { getSystemService(any()) } returns mockk { + every { isIgnoringBatteryOptimizations(any()) } returns true + } + }, workDataOf( INPUT_UP_SYNC to scope, INPUT_EVENT_UP_SYNC_SCOPE_ID to eventScopeId, diff --git a/infra/sync/src/test/java/com/simprints/infra/sync/config/worker/DeviceConfigDownSyncWorkerTest.kt b/infra/sync/src/test/java/com/simprints/infra/sync/config/worker/DeviceConfigDownSyncWorkerTest.kt index 434b61f6c3..46fb5f60df 100644 --- a/infra/sync/src/test/java/com/simprints/infra/sync/config/worker/DeviceConfigDownSyncWorkerTest.kt +++ b/infra/sync/src/test/java/com/simprints/infra/sync/config/worker/DeviceConfigDownSyncWorkerTest.kt @@ -1,5 +1,6 @@ package com.simprints.infra.sync.config.worker +import android.os.PowerManager import androidx.work.ListenableWorker import com.google.common.truth.Truth.assertThat import com.simprints.infra.config.store.models.DeviceState @@ -11,6 +12,7 @@ import com.simprints.testtools.common.coroutines.TestCoroutineRule import io.mockk.MockKAnnotations import io.mockk.coEvery import io.mockk.coVerify +import io.mockk.every import io.mockk.impl.annotations.MockK import io.mockk.mockk import io.mockk.verify @@ -39,7 +41,11 @@ class DeviceConfigDownSyncWorkerTest { MockKAnnotations.init(this, relaxed = true) deviceConfigWorker = DeviceConfigDownSyncWorker( - context = mockk(), + context = mockk(relaxed = true) { + every { getSystemService(any()) } returns mockk { + every { isIgnoringBatteryOptimizations(any()) } returns true + } + }, params = mockk(relaxed = true), configManager = configManager, logoutUseCase = logoutUseCase, diff --git a/infra/sync/src/test/java/com/simprints/infra/sync/config/worker/ProjectConfigDownSyncWorkerTest.kt b/infra/sync/src/test/java/com/simprints/infra/sync/config/worker/ProjectConfigDownSyncWorkerTest.kt index 5f6b048477..81c5f6ac1d 100644 --- a/infra/sync/src/test/java/com/simprints/infra/sync/config/worker/ProjectConfigDownSyncWorkerTest.kt +++ b/infra/sync/src/test/java/com/simprints/infra/sync/config/worker/ProjectConfigDownSyncWorkerTest.kt @@ -1,5 +1,6 @@ package com.simprints.infra.sync.config.worker +import android.os.PowerManager import androidx.work.ListenableWorker import com.google.common.truth.Truth.assertThat import com.simprints.infra.authstore.AuthStore @@ -48,7 +49,11 @@ class ProjectConfigDownSyncWorkerTest { MockKAnnotations.init(this, relaxed = true) projectConfigDownSyncWorker = ProjectConfigDownSyncWorker( - context = mockk(), + context = mockk(relaxed = true) { + every { getSystemService(any()) } returns mockk { + every { isIgnoringBatteryOptimizations(any()) } returns true + } + }, params = mockk(relaxed = true), authStore = authStore, configManager = configManager, diff --git a/infra/sync/src/test/java/com/simprints/infra/sync/enrolments/EnrolmentRecordWorkerTest.kt b/infra/sync/src/test/java/com/simprints/infra/sync/enrolments/EnrolmentRecordWorkerTest.kt index c9a47c6558..6936f2922c 100644 --- a/infra/sync/src/test/java/com/simprints/infra/sync/enrolments/EnrolmentRecordWorkerTest.kt +++ b/infra/sync/src/test/java/com/simprints/infra/sync/enrolments/EnrolmentRecordWorkerTest.kt @@ -1,5 +1,6 @@ package com.simprints.infra.sync.enrolments +import android.os.PowerManager import androidx.work.WorkerParameters import androidx.work.workDataOf import com.google.common.truth.Truth.assertThat @@ -31,7 +32,11 @@ class EnrolmentRecordWorkerTest { ) } private val worker = EnrolmentRecordWorker( - mockk(relaxed = true), + mockk(relaxed = true) { + every { getSystemService(any()) } returns mockk { + every { isIgnoringBatteryOptimizations(any()) } returns true + } + }, params, repository, configManager, diff --git a/infra/sync/src/test/java/com/simprints/infra/sync/files/FileUpSyncWorkerTest.kt b/infra/sync/src/test/java/com/simprints/infra/sync/files/FileUpSyncWorkerTest.kt index bc7ae66401..f6a66c7a0b 100644 --- a/infra/sync/src/test/java/com/simprints/infra/sync/files/FileUpSyncWorkerTest.kt +++ b/infra/sync/src/test/java/com/simprints/infra/sync/files/FileUpSyncWorkerTest.kt @@ -1,5 +1,6 @@ package com.simprints.infra.sync.files +import android.os.PowerManager import androidx.work.ListenableWorker.Result import com.google.common.truth.Truth import com.simprints.fingerprint.infra.imagedistortionconfig.ImageDistortionConfigRepo @@ -46,7 +47,11 @@ class FileUpSyncWorkerTest { every { authStore.signedInProjectId } returns PROJECT_ID fileUpSyncWorker = FileUpSyncWorker( - mockk(relaxed = true), + mockk(relaxed = true) { + every { getSystemService(any()) } returns mockk { + every { isIgnoringBatteryOptimizations(any()) } returns true + } + }, mockk(relaxed = true), imageRepository, imageDistortionConfigRepo, @@ -162,10 +167,12 @@ class FileUpSyncWorkerTest { // Then coVerify(exactly = 1) { imageRepository.uploadStoredImagesAndDelete(PROJECT_ID, any()) } - Truth.assertThat(progressValues).containsExactly( - 2 to 10, - 5 to 10, - 10 to 10, - ).inOrder() + Truth + .assertThat(progressValues) + .containsExactly( + 2 to 10, + 5 to 10, + 10 to 10, + ).inOrder() } } diff --git a/infra/sync/src/test/java/com/simprints/infra/sync/firmware/FirmwareFileUpdateWorkerTest.kt b/infra/sync/src/test/java/com/simprints/infra/sync/firmware/FirmwareFileUpdateWorkerTest.kt index ff589eb701..1ce795a329 100644 --- a/infra/sync/src/test/java/com/simprints/infra/sync/firmware/FirmwareFileUpdateWorkerTest.kt +++ b/infra/sync/src/test/java/com/simprints/infra/sync/firmware/FirmwareFileUpdateWorkerTest.kt @@ -1,5 +1,6 @@ package com.simprints.infra.sync.firmware +import android.os.PowerManager import androidx.work.ListenableWorker.Result.Retry import androidx.work.ListenableWorker.Result.Success import com.google.common.truth.Truth.assertThat @@ -8,6 +9,7 @@ import com.simprints.testtools.common.coroutines.TestCoroutineRule import io.mockk.MockKAnnotations import io.mockk.coEvery import io.mockk.coJustRun +import io.mockk.every import io.mockk.impl.annotations.MockK import io.mockk.mockk import kotlinx.coroutines.test.runTest @@ -30,7 +32,11 @@ class FirmwareFileUpdateWorkerTest { MockKAnnotations.init(this, relaxed = true) worker = FirmwareFileUpdateWorker( - mockk(relaxed = true), + mockk(relaxed = true) { + every { getSystemService(any()) } returns mockk { + every { isIgnoringBatteryOptimizations(any()) } returns true + } + }, mockk(relaxed = true), firmwareRepository, testCoroutineRule.testCoroutineDispatcher, From 96ebeef3898a11b19fd3cb4a10ba9dbe738d0249 Mon Sep 17 00:00:00 2001 From: Marinov Date: Wed, 8 Oct 2025 20:03:05 +0300 Subject: [PATCH 04/10] Merge data layer sync info along with buttons' in order to react to data layer updates --- .../feature/dashboard/settings/syncinfo/SyncInfoViewModel.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoViewModel.kt b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoViewModel.kt index 820b6d6324..62cf5c7cf6 100644 --- a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoViewModel.kt +++ b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoViewModel.kt @@ -126,11 +126,10 @@ internal class SyncInfoViewModel @Inject constructor( } merge( + dataLayerDrivenSyncInfoFlow, eventSyncButtonResponsiveSyncInfo, imageSyncButtonResponsiveSyncInfo, - ).onStart { - emit(dataLayerDrivenSyncInfoFlow.firstOrNull() ?: SyncInfo()) - }.distinctUntilChanged() + ).distinctUntilChanged() .flowOn(ioDispatcher) .asLiveData(viewModelScope.coroutineContext) } From 1af9f27ad4dc08f53fb5dcaad6da81f1273f9053 Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Mon, 6 Oct 2025 08:41:24 +0000 Subject: [PATCH 05/10] Translate strings.xml in fr 100% translated source file: 'strings.xml' on 'fr'. --- .../src/main/res/values-fr/strings.xml | 76 ++++++++++++------- 1 file changed, 47 insertions(+), 29 deletions(-) diff --git a/infra/resources/src/main/res/values-fr/strings.xml b/infra/resources/src/main/res/values-fr/strings.xml index 5c13694971..080f591f29 100644 --- a/infra/resources/src/main/res/values-fr/strings.xml +++ b/infra/resources/src/main/res/values-fr/strings.xml @@ -320,28 +320,58 @@ Utilisateur actuel: %1$s + Informations sur la synchronisation + Statut de la synchronisation - Synchronisation terminée - Synchronisation incomplète - Connexion - Synchronisation… %1$s - Synchroniser maintenant - La synchronisation a échoué. Veuillez contacter votre superviseur - Veuillez sélectionner les modules à synchroniser - Modules - Veuillez activer la connexion Internet dans les réglages Réglages - Trop de modules ont été téléchargés. + Vous devez vous connecter pour synchroniser. - Dernière synchronisation : %1$s - Tous enregistrements téléchargés - - %1$d enregistrement à envoyer - %1$d enregistrements à envoyer - %1$d enregistrements à envoyer - + Synchronisation des enregistrements + Sync d\'images + Modules sélectionnés + + Synchroniser manuellement les enregistrements en appuyant sur ce bouton. Plus d\'infos + Envoyer manuellement les images au serveur en appuyant sur ce bouton. Plus d\'infos + Les enregistrements des modules sélectionnés vont être synchronisés. Plus d\'infos + Synchroniser depuis CommCare requiert des permissions depuis Réglages systeme + Synchroniser requiert une connection internet. Réglages systeme + Sélectionner des modules pour synchroniser. Selectionner des modules + Sync échouée. Plus d\'infos + + Synchroniser manuellement les enregistrements en appuyant sur ce bouton. Votre appareil va aussi régulièrement essayer de synchroniser les enregistrements en tache de fond. + Synchroniser manuellement les images en appuyant sur ce bouton. Votre appareil va aussi régulièrement essayer de synchroniser les images en tache de fond. Veuillez noter que la synchronisation des images a besoin d\'une bonne connection internet. + Les enregistrements des modules sélectionnés vont être synchronisés sur votre appareil. Appuyez au dessus / au dessous pour sélectionner les modules à synchroniser sur votre appareil. + La synchronisation a échoué. Veuillez contacter votre superviseur + Trop de modules ont été téléchargés. + OK + + Enregistrements au total sur votre appareil + Enregistrements à uploader + Enregistrements à synchroniser + Images à envoyer + + Synchroniser les enregistrements maintenant + Synchronisation + Synchroniser les images maintenant + Arrêter la synchronisation des images Réessayez + Sélectionner les modules + + %1$s synchro en attente… + %1$s synchro terminée + %1$s synchro en cours… + %1$s synchro: %2$d sur %3$d… + + Item + Enregistrements & Évènements + Image + + Enregistrements totaux + + Synchronisation en cours + Synchronisation incomplète + Synchronisation terminée, déconnexion en cours… Activité : %1$s @@ -389,18 +419,6 @@ Alerte audio Auto-capture - - Nombre total d\'enregistrements - Informations sur la synchronisation - Sélectionner les modules - Enregistrements à envoyer - Enregistrements à télécharger ou à supprimer - Enregistrements à supprimer - Nombre total d\'enregistrements sur l\'appareil - Modules sélectionnés - Images à envoyer - Synchronisation… - Sélection invalide. Veuillez sélectionner au moins un module. Sélection invalide. Veuillez ne pas sélectionner plus de %1$d modules. From 4169db706dfca09be97224b57210f2ea940ac352 Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Wed, 8 Oct 2025 07:21:27 +0000 Subject: [PATCH 06/10] Translate strings.xml in hi 100% translated source file: 'strings.xml' on 'hi'. --- .../src/main/res/values-hi/strings.xml | 83 ++++++++++++------- 1 file changed, 51 insertions(+), 32 deletions(-) diff --git a/infra/resources/src/main/res/values-hi/strings.xml b/infra/resources/src/main/res/values-hi/strings.xml index b25e4d3c59..9772a83bfc 100644 --- a/infra/resources/src/main/res/values-hi/strings.xml +++ b/infra/resources/src/main/res/values-hi/strings.xml @@ -315,27 +315,58 @@ वर्तमान उपयोगकर्ता: %1$s - सिंक की स्थिति - सिंक सफल हुआ - सिंक असफल - कनेक्ट हो रहा है - सिंक हो रहा है......%1$s - अभी सिंक करें - सिंक असफल रहा। कृपया अपने सुपरवाईज़र से सम्पर्क करें - कृपया सिंक करने के लिए मॉड्यूल चुनें - मॉड्यूल्स - कृपया सेटिंग में जाकर इंटरनेट को चालू करें - सेटिंग - बहुत सारे मॉड्यूल डाउनलोड किए जा चुके हैं. - सिंक करने के लिए आपको फिर से लॉग इन करना होगा - - अंतिम बार सिंक: %1$s - सभी रिकॉर्ड अपलोड किए गए - - %1$d अपलोड करने हेतु रिकार्ड - %1$dअपलोड करने हेतु रिकार्ड - + सिंक जानकारी + + सिंक स्थिति + सेटिंग्स + + सिंक करने के लिए आपको पुनः लॉग इन करना होगा + + रिकॉर्ड सिंक + छवि सिंक + चयनित मॉड्यूल + + इस बटन को दबाकर रिकॉर्ड्स को मैन्युअल रूप से सिंक्रोनाइज़ करें। अधिक जानकारी + इस बटन को दबाकर छवियों को मैन्युअल रूप से क्लाउड पर भेजें। अधिक जानकारी + चयनित मॉड्यूल के रिकॉर्ड सिंक्रनाइज़ किए जाएँगे। अधिक जानकारी + CommCare से सिंक करने के लिए सिस्टम सेटिंग्स में अनुमति की आवश्यकता होती है + सिंक के लिए इंटरनेट कनेक्शन ज़रूरी है। कनेक्शन सेटिंग + सिंक के लिए मॉड्यूल का चयन आवश्यक है. मॉड्यूल चुनें + समन्वयन विफल. अधिक जानकारी + + इस बटन को दबाकर रिकॉर्ड्स को मैन्युअल रूप से सिंक्रोनाइज़ करें। आपका डिवाइस बैकग्राउंड में भी नियमित रूप से रिकॉर्ड्स को सिंक्रोनाइज़ करने का प्रयास करेगा। + इस बटन को दबाकर छवियों को क्लाउड पर मैन्युअल रूप से भेजें। आपका डिवाइस नियमित रूप से पृष्ठभूमि में भी छवियां भेजने का प्रयास करेगा। ध्यान दें कि छवि समन्वयन के लिए एक मजबूत इंटरनेट कनेक्शन की आवश्यकता हो सकती है। + चयनित मॉड्यूल के रिकॉर्ड आपके डिवाइस से सिंक्रोनाइज़ हो जाएँगे। अपने डिवाइस से सिंक्रोनाइज़ करने के लिए मॉड्यूल चुनने हेतु नीचे/ऊपर क्लिक करें। + समन्वयन विफल. कृपया अपने पर्यवेक्षक से संपर्क करें + बहुत सारे मॉड्यूल डाउनलोड हो गए हैं | + ठीक + + डिवाइस पर कुल रिकॉर्ड + अपलोड करने हेतु रिकार्ड + सिंक्रनाइज़ करने के लिए रिकॉर्ड + अपलोड करने के लिए चित्र + + अभी सिंक करें + सिंक हो रहा है + अभी छवियाँ सिंक करें + छवियों का समन्वयन रोकें पुनः प्रयास करें + मॉड्यूल का चुनाव करें + + %1$sसिंक लंबित… + %1$s सिंक सफल हुआ + %1$s समन्वयन प्रगति पर है + %1$sसिंक: %2$d का %3$d… + + वस्तु + रिकॉर्ड & इवेंट + छवि + + कुल रिकोर्ड्स + + समन्वयन प्रगति पर है + सिंक अपूर्ण + सिंक पूरा हो गया, आपको लॉग आउट किया जा रहा है... गतिविधि: %1$s @@ -380,18 +411,6 @@ ऑडियो अलर्ट ऑटो-कैप्चर - - कुल रिकोर्ड्स - सिंक सम्बंधित जानकारी - मॉड्यूल का चुनाव करें - रिकोर्ड्स अपलोड किए जाने हैं - डाउनलोड करने या हटाने के लिए रिकॉर्ड - रिकोर्ड्स डिलीट किए जाने हैं - डिवाइस में कुल रिकोर्ड्स - चुने गए मॉड्यूल - अपलोड करने के लिए चित्र - सिंक हो रहा है … - अमान्य चुनाव। कृपया कम से कम एक मॉड्यूल चुनें अमान्य चुनाव। कृपया %1$d से अधिक मॉड्यूल्स नहीं चुनें। From 4da8ee2a911c7fd83f902fe76504b4df0b5d8c79 Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Wed, 8 Oct 2025 08:17:55 +0000 Subject: [PATCH 07/10] Translate strings.xml in bn 100% translated source file: 'strings.xml' on 'bn'. --- .../src/main/res/values-bn/strings.xml | 81 ++++++++++++------- 1 file changed, 50 insertions(+), 31 deletions(-) diff --git a/infra/resources/src/main/res/values-bn/strings.xml b/infra/resources/src/main/res/values-bn/strings.xml index 2603a5c484..60c02e6ff8 100644 --- a/infra/resources/src/main/res/values-bn/strings.xml +++ b/infra/resources/src/main/res/values-bn/strings.xml @@ -319,27 +319,58 @@ বর্তমান ব্যবহারকারী: %1$s + সিঙ্ক তথ্য + সিঙ্ক স্ট্যাটাস - সিঙ্ক হয়েছে - সিঙ্ক অসম্পূর্ণ - সংযুক্ত হচ্ছে - সিঙ্ক করা হচ্ছে… %1$s - সিঙ্ক করুন - সিঙ্ক অসফল হয়েছে - সিঙ্ক করতে মডিউল নির্বাচন করুন - মডিউল সমূহ - সেটিংস্‌ থেকে ইন্টারনেট সংযোগ চালু করুন - স্ক্যানার যোগ করুন - অনেক বেশি মডিউল ডাউনলোড করা হয়েছে - সিঙ্ক করতে পুনরায় লগইন করুন - - সর্বশেষ সিঙ্ক: %1$s - সমস্ত রেকর্ড আপলোড হয়েছে - - %1$dটি রেকর্ড আপলোড হওয়া বাকি - %1$dটি রেকর্ড আপলোড হওয়া বাকি - + সেটিংস + + সিঙ্ক করার জন্য আপনাকে আবার লগ ইন করতে হবে + + রেকর্ড সিঙ্ক + চিত্র সিঙ্ক + নির্বাচিত মডিউলগুলি + + এই বোতামটি টিপে রেকর্ডগুলি ম্যানুয়ালি সিঙ্ক্রোনাইজ করুন। আরও তথ্য + এই বোতামটি টিপে ম্যানুয়ালি ক্লাউডে ছবি পাঠান। আরও তথ্য + নির্বাচিত মডিউলগুলির রেকর্ডগুলি সিঙ্ক্রোনাইজ করা হবে। আরও তথ্য + CommCare থেকে সিঙ্ক করার জন্য সিস্টেম সেটিংসে অনুমতি প্রয়োজন। + সিঙ্কের জন্য একটি ইন্টারনেট সংযোগ প্রয়োজন। সংযোগ সেটিংস + সিঙ্কের জন্য মডিউল নির্বাচন করা প্রয়োজন। মডিউল নির্বাচন করুন। + সিঙ্ক ব্যর্থ হয়েছে। আরও তথ্য + + এই বোতামটি টিপে রেকর্ডগুলি ম্যানুয়ালি সিঙ্ক্রোনাইজ করুন। আপনার ডিভাইসটি নিয়মিতভাবে ব্যাকগ্রাউন্ডে রেকর্ডগুলি সিঙ্ক্রোনাইজ করার চেষ্টা করবে। + এই বোতামটি টিপে ক্লাউডে ছবিগুলি ম্যানুয়ালি পাঠান। আপনার ডিভাইসটি নিয়মিতভাবে ব্যাকগ্রাউন্ডে ছবি পাঠানোর চেষ্টা করবে। মনে রাখবেন যে ছবি সিঙ্ক করার জন্য একটি শক্তিশালী ইন্টারনেট সংযোগের প্রয়োজন হতে পারে। + নির্বাচিত মডিউলগুলির রেকর্ডগুলি আপনার ডিভাইসে সিঙ্ক্রোনাইজ করা হবে। আপনার ডিভাইসে সিঙ্ক্রোনাইজ করার জন্য মডিউলগুলি নির্বাচন করতে নীচে/উপরে ক্লিক করুন। + সিঙ্ক ব্যর্থ হয়েছে। অনুগ্রহ করে আপনার সুপারভাইজারের সাথে যোগাযোগ করুন। + অনেক বেশি মডিউল ডাউনলোড করা হয়েছে। + ঠিক আছে + + ডিভাইসে মোট রেকর্ড + আপলোড করার জন্য রেকর্ড + সিঙ্ক্রোনাইজ করার জন্য রেকর্ড + আপলোড করার জন্য ছবি + + এখনই রেকর্ড সিঙ্ক করুন + সিঙ্ক হচ্ছে + এখনই ছবি সিঙ্ক করুন + ছবি সিঙ্ক করা বন্ধ করুন আবার চেষ্টা করুন + মডিউল নির্বাচন করুন + + %1$s সিঙ্ক মুলতুবি আছে... + %1$s সিঙ্ক সম্পূর্ণ হয়েছে + %1$s সিঙ্ক চলছে... + %1$s সিঙ্ক: %2$d এর %3$d… + + আইটেম + ইভেন্ট রেকর্ড করুন + ভাবমূর্তি + + মোট রেকর্ড + + সিঙ্ক হচ্ছে + সিঙ্ক অসম্পূর্ণ + সিঙ্ক সম্পূর্ণ হয়েছে, আপনাকে লগ আউট করা হচ্ছে... কার্যক্রম তথ্য: %1$s @@ -384,18 +415,6 @@ অডিও সতর্কতা অটো-ক্যাপচার - - সকল রেকর্ড - তথ্য সিঙ্ক করুন - মডিউল নির্বাচন করুন - আপলোড এর জন্য প্রস্তুত রেকর্ড - ডাউনলোড বা মুছে ফেলার রেকর্ড - মুছে ফেলার জন্য প্রস্তুত রেকর্ড - ফোনে সংরক্ষিত সকল রেকর্ড - নির্বাচিত মডিউল - আপলোড বাকি - সিঙ্ক হচ্ছে… - অবৈধ নির্বাচন। অন্তত একটি মডিউল নির্বাচন করুন। অবৈধ নির্বাচন। %1$d এর বেশি মডিউল নির্বাচন করবেন না। From b8b173be97663dc5531b801529b38b255b3720f2 Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Thu, 9 Oct 2025 10:54:24 +0000 Subject: [PATCH 08/10] Translate strings.xml in am 100% translated source file: 'strings.xml' on 'am'. --- .../src/main/res/values-am/strings.xml | 84 ++++++++++++------- 1 file changed, 52 insertions(+), 32 deletions(-) diff --git a/infra/resources/src/main/res/values-am/strings.xml b/infra/resources/src/main/res/values-am/strings.xml index 7a56e25959..7ce98c158e 100644 --- a/infra/resources/src/main/res/values-am/strings.xml +++ b/infra/resources/src/main/res/values-am/strings.xml @@ -322,27 +322,59 @@ አሁን ያለ ተጠቃሚ: %1$s - መረጃውን መገናኘት - ሲንክ ማድረጉ ተጠናቋል - ሲንክ ማድረጉ አልተጠናቀቀም - በመገኛኘት ላይ ነው - በግንኙነት ላይ...%1$s - አሁን ግንኙነት ፍጠር - ሲንክ ማድረግ አልተቻለም እባክዎ የበላይ አካል ያግኙ - ሲንክ ለማድረግ ሞጁል ይምረጡ - ሞጁሎች - የስልኩ ሴቲንግ በመግባት የሞባይል ዳታ ያብሩ - ማስተካከያ - ከተፈቀደው ሞጁል በላይ ወርዷል - በቅድሚያ መግባት አለበት መረጃዉን ለመላክ - - መጨረሻ ጊዜ ወደ ሰርቨር የተላከው : %1$s - ሁሉም መዝገቦች ተጭነዋል - - %1$d ለመስቀል መዝገብ - %1$d ለመስቀል መዝገቦች - + የማመሳሰል መረጃ + + የማመሳሰል ሁኔታ + ቅንብሮች + + ለማመሳሰል እንደገና መግባት አለብህ + + ማመሳሰልን ይቅዱ + የምስል ማመሳሰል + የተመረጡ ሞጁሎች + + ይህን ቁልፍ በመጫን መዝገቦችን በእጅ ያመሳስሉ። ተጨማሪ መረጃ + ይህንን ቁልፍ በመጫን ምስሎችን በእጅ ወደ ደመናው ይላኩ። + ከተመረጡት ሞጁሎች ውስጥ ያሉት መዝገቦች ይጣጣማሉ. ለበለጠ መረጃ + ከCommCare ለማመሳሰል በስይስተም ቅንብሮች ውስጥ ፍቃድ ያስፈልገዋል ስይስተም ቅንብሮች + ለማመሳሰል ኢንተርኔት ግንኙነት ያስፈልገዋል። የኢንተርኔት ግንኙነት ቅንብሮች + ለማመሳሰል ሞጁሎች መመረጥ አለባቸው።ሞጁሎችን ይምረጡ + ማመሳሰል አልተሳካም። ለበለጠ መረጃ + + ይህንን ቁልፍ እራስዎ በመጫን መዝገቦችን ያመሳስሉ ። ሞባይልዎ በመደበኛነት መዛግብትን ከበስተጀርባ ለማመሳሰል ይሞክራል። + ምስሎቹን በእራስዎ ወደ ክላውድ ለመላክ ይህን ቁልፍ ይጫኑ። ሞባይልዎ በመደበኛነት ምስሎቹን ከበስተጀርባ ለማመሳሰል ይሞክራል። እባክዎን ያስተውሉ፣ ምስል ማመሳሰል ጠንካራ የኢንተርኔት ግንኙነት ሊፈልግ ይችላል። + . +ከተመረጡት ሞጁሎች ውስጥ ያሉት መዝገቦች ከሞባይልዎ ጋር ይጣጣማሉ። ከሞባይልዎ ጋር ለማመሳሰል ሞጁሎችን ለመምረጥ ከታች/ከላይ ይጫኑ። + ማመሳሰል አልተሳካም። እባክዎን ተቆጣጣሪዎን ያነጋግሩ + በጣም ብዙ ሞጁሎች ወርደዋል። + እሺ + + በሞባይልዎ ላይ ያሉ አጠቃላይ መዝገቦች። + የሚላኩ መዝገቦች። + መዝገቦችን ለማመሳሰል። + የሚላኩ ምስሎች + + መዝገቦችን አሁን ያመሳስሉ/ ይላኩ። + በማመሳሰል ላይ + አሁን ምስሎችን ያመሳስሉ/ ይላኩ። + ምስሎችን ማመሳሰል/መላክን አቁም እንደገና ይሞክሩ + ሞጁሎችን ይምረጡ + + %1$sማመሳሰል በመጠባበቅ ላይ… + %1$sማመሳሰል ተጠናቅቋል + %1$sማመሳሰል በሂደት ላይ… + %1$sአመሳስል፡ %2$dከ…%3$d + + ንጥል + መዝገብ & ክስተት + ምስል + + ጠቅላላ መዝገቦች + + ማመሳሰል በሂደት ላይ + ማመሳሰል ተጠናቅቋል + ማመሳሰል ተጠናቅቋል፣ እርስዎን በማስወጣት ላይ… የየዕለት ተግባር: %1$s @@ -387,18 +419,6 @@ የድምጽ ማንቂያ ራስ-ሰር ቀረጻ - - አጠቃላይ የተመዘገበ - ሲንክ የተደረገ መረጃ - ሞጅውሎችን ይምረጡ - የተመዘገበውን ለመጫን - ሪክርዶች ለማውረድ ወይም ለመሰረዝ - የተመዘገበውን ለመሰረዝ - በስልኩ አጠቃላይ የተመዘገበ - የተመረጡ ሞጁሎች - የሚጫኑ ምስሎች - በግንኙነት ላይ - የተሳሳተ ምርጫ፡ እባክዎት ካለው የምዕራፍ አማራጭ ውስጥ ቢያንስ አንድ ይምረጡ የተሳሳተ ምርጫ ፡ካሉት ምዕራፎች ውስጥ ከ %1$d በላይ መምረጥ አይችሉም From b27735adec7c080e6498614e6b7113965335f110 Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Thu, 9 Oct 2025 10:56:39 +0000 Subject: [PATCH 09/10] Translate strings.xml in am 100% translated source file: 'strings.xml' on 'am'. --- infra/resources/src/main/res/values-am/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infra/resources/src/main/res/values-am/strings.xml b/infra/resources/src/main/res/values-am/strings.xml index 7ce98c158e..e23aa9b1cb 100644 --- a/infra/resources/src/main/res/values-am/strings.xml +++ b/infra/resources/src/main/res/values-am/strings.xml @@ -334,7 +334,7 @@ የተመረጡ ሞጁሎች ይህን ቁልፍ በመጫን መዝገቦችን በእጅ ያመሳስሉ። ተጨማሪ መረጃ - ይህንን ቁልፍ በመጫን ምስሎችን በእጅ ወደ ደመናው ይላኩ። + ይህንን ቁልፍ በመጫን ምስሎችን በእራስዎ ወደ ክላውድ ይላኩ። ለበለጠ መረጃ ከተመረጡት ሞጁሎች ውስጥ ያሉት መዝገቦች ይጣጣማሉ. ለበለጠ መረጃ ከCommCare ለማመሳሰል በስይስተም ቅንብሮች ውስጥ ፍቃድ ያስፈልገዋል ስይስተም ቅንብሮች ለማመሳሰል ኢንተርኔት ግንኙነት ያስፈልገዋል። የኢንተርኔት ግንኙነት ቅንብሮች From 2ec20dc867accb70dd35f99db17801d1ecb60f42 Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Thu, 9 Oct 2025 11:11:44 +0000 Subject: [PATCH 10/10] Translate strings.xml in am_ET 100% translated source file: 'strings.xml' on 'am_ET'. --- .../src/main/res/values-am-rET/strings.xml | 84 ++++++++++++------- 1 file changed, 52 insertions(+), 32 deletions(-) diff --git a/infra/resources/src/main/res/values-am-rET/strings.xml b/infra/resources/src/main/res/values-am-rET/strings.xml index 25931a6424..b2eb63d2ce 100644 --- a/infra/resources/src/main/res/values-am-rET/strings.xml +++ b/infra/resources/src/main/res/values-am-rET/strings.xml @@ -319,27 +319,59 @@ አሁን ያለ ተጠቃሚ: %1$s - መረጃውን መገናኘት - ሲንክ ማድረጉ ተጠናቋል - ሲንክ ማድረጉ አልተጠናቀቀም - በመገኛኘት ላይ ነው - በግንኙነት ላይ...%1$s - አሁን ግንኙነት ፍጠር - ሲንክ ማድረግ አልተቻለም እባክዎ የበላይ አካል ያግኙ - ሲንክ ለማድረግ ሞጁል ይምረጡ - ሞጁሎች - የስልኩ ሴቲንግ በመግባት የሞባይል ዳታ ያብሩ - ማስተካከያ - ብዙ ሞጁሎች ወርደዋል - ሲንክ ለማድረግ በቅድሚያ log in ይደረግ - - ያለፈው ግንኙነት: %1$s - ሁሉም መዝገቦች ተጭነዋል። - - %1$d ለመስቀል መዝገብ - %1$d ለመስቀል መዝገቦች - + መዝገቦችን አሁን ያመሳስሉ/ ይላኩ። + + የማመሳሰል ሁኔታ + ለማመሳሰል ሞጁሎች መመረጥ አለባቸው።ሞጁሎችን ይምረጡ + + ለማመሳሰል እንደገና መግባት አለብህ + + ማመሳሰልን ይቅዱ + የምስል ማመሳሰል + የተመረጡ ሞጁሎች + + ይህን ቁልፍ በመጫን መዝገቦችን በእጅ ያመሳስሉ። ተጨማሪ መረጃ + ይህንን ቁልፍ በመጫን ምስሎችን በእራስዎ ወደ ክላውድ ይላኩ። ለበለጠ መረጃ + ከተመረጡት ሞጁሎች ውስጥ ያሉት መዝገቦች ይጣጣማሉ. ለበለጠ መረጃ + ከCommCare ለማመሳሰል በስይስተም ቅንብሮች ውስጥ ፍቃድ ያስፈልገዋል ስይስተም ቅንብሮች + ለማመሳሰል ኢንተርኔት ግንኙነት ያስፈልገዋል። የኢንተርኔት ግንኙነት ቅንብሮች + ለማመሳሰል ሞጁሎች መመረጥ አለባቸው።ሞጁሎችን ይምረጡ + ማመሳሰል አልተሳካም። ለበለጠ መረጃ + + ይህንን ቁልፍ እራስዎ በመጫን መዝገቦችን ያመሳስሉ ። ሞባይልዎ በመደበኛነት መዛግብትን ከበስተጀርባ ለማመሳሰል ይሞክራል። + ምስሎቹን በእራስዎ ወደ ክላውድ ለመላክ ይህን ቁልፍ ይጫኑ። ሞባይልዎ በመደበኛነት ምስሎቹን ከበስተጀርባ ለማመሳሰል ይሞክራል። እባክዎን ያስተውሉ፣ ምስል ማመሳሰል ጠንካራ የኢንተርኔት ግንኙነት ሊፈልግ ይችላል። + . +ከተመረጡት ሞጁሎች ውስጥ ያሉት መዝገቦች ከሞባይልዎ ጋር ይጣጣማሉ። ከሞባይልዎ ጋር ለማመሳሰል ሞጁሎችን ለመምረጥ ከታች/ከላይ ይጫኑ። + ማመሳሰል አልተሳካም። እባክዎን ተቆጣጣሪዎን ያነጋግሩ + በጣም ብዙ ሞጁሎች ወርደዋል። + እሺ + + በሞባይልዎ ላይ ያሉ አጠቃላይ መዝገቦች። + የሚላኩ መዝገቦች። + መዝገቦችን ለማመሳሰል። + የሚጫኑ ምስሎች + + መዝገቦችን አሁን ያመሳስሉ/ ይላኩ። + በማመሳሰል ላይ + አሁን ምስሎችን ያመሳስሉ/ ይላኩ። + ምስሎችን ማመሳሰል/መላክን አቁም እንደገና ይሞክሩ + ሞጁሎችን ይምረጡ + + %1$sማመሳሰል በመጠባበቅ ላይ… + %1$sማመሳሰል ተጠናቅቋል + %1$sማመሳሰል በሂደት ላይ… + %1$sአመሳስል፡ %2$dከ…%3$d + + ንጥል + መዝገብ & ክስተት + ምስል + + ጠቅላላ መዝገቦች + + ማመሳሰል በሂደት ላይ + ማመሳሰል ተጠናቅቋል + ማመሳሰል ተጠናቅቋል፣ እርስዎን በማስወጣት ላይ… የየዕለት ተግባር: %1$s @@ -384,18 +416,6 @@ የድምጽ ማንቂያ ራስ-ሰር ቀረጻ - - አጠቃላይ የተመዘገበ - ሲንክ የተደረገ መረጃ - ሞጅውሎችን ይምረጡ - የተመዘገበውን ለመጫን - ሪከርዶች ለማዉረድ ወይም ለመሰረዝ - የተመዘገበውን ለመሰረዝ - በስልኩ አጠቃላይ የተመዘገበ - የተመረጠ ሞጁል - የሚጫኑ ምስሎች - በግንኙነት ላይ - የተሳሳተ ምርጫ፡ እባክዎት ካለው የምዕራፍ አማራጭ ውስጥ ቢያንስ አንድ ይምረጡ የተሳሳተ ምርጫ ፡ካሉት ምዕራፎች ውስጥ ከ %1$d በላይ መምረጥ አይችሉም