Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
286 changes: 286 additions & 0 deletions app/src/androidTest/java/com/nmc/android/AlbumsResourceTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,286 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2025 TSI-mc <surinder.kumar@t-systems.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

package com.nmc.android

import android.content.Context
import android.content.res.Configuration
import android.util.DisplayMetrics
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.owncloud.android.R
import junit.framework.TestCase.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import java.util.Locale

/**
* Test class to verify the strings and dimens customized in this branch PR for NMC
*/
@RunWith(AndroidJUnit4::class)
class AlbumsResourceTest {

private val baseContext = ApplicationProvider.getApplicationContext<Context>()

private val localizedStringMap = mapOf(
R.string.drawer_item_album to ExpectedLocalizedString(
translations = mapOf(
Locale.ENGLISH to "Albums",
Locale.GERMAN to "Alben"
)
), R.string.create_album to ExpectedLocalizedString(
translations = mapOf(
Locale.ENGLISH to "Create album",
Locale.GERMAN to "Album erstellen"
)
), R.string.create_album_dialog_title to ExpectedLocalizedString(
translations = mapOf(
Locale.ENGLISH to "New album",
Locale.GERMAN to "Neues Album"
)
), R.string.rename_album_dialog_title to ExpectedLocalizedString(
translations = mapOf(
Locale.ENGLISH to "Rename album",
Locale.GERMAN to "Album umbenennen"
)
), R.string.rename_dialog_button to ExpectedLocalizedString(
translations = mapOf(
Locale.ENGLISH to "Rename",
Locale.GERMAN to "Speichern"
)
), R.string.create_album_dialog_message to ExpectedLocalizedString(
translations = mapOf(
Locale.ENGLISH to "Enter your new Album name",
Locale.GERMAN to "Gib einen Namen für das Album ein"
)
), R.string.album_name_empty to ExpectedLocalizedString(
translations = mapOf(
Locale.ENGLISH to "Album name cannot be empty",
Locale.GERMAN to "Der Albumname darf nicht leer sein"
)
), R.string.hidden_album_name to ExpectedLocalizedString(
translations = mapOf(
Locale.ENGLISH to "Album name cannot start with invalid char",
Locale.GERMAN to "Der Albumname darf nicht mit einem ungültigen Zeichen beginnen"
)
), R.string.add_more to ExpectedLocalizedString(
translations = mapOf(
Locale.ENGLISH to "Add more",
Locale.GERMAN to "Mehr hinzufügen"
)
), R.string.album_rename to ExpectedLocalizedString(
translations = mapOf(
Locale.ENGLISH to "Rename Album",
Locale.GERMAN to "Album umbenennen"
)
), R.string.album_delete to ExpectedLocalizedString(
translations = mapOf(
Locale.ENGLISH to "Delete Album",
Locale.GERMAN to "Album löschen"
)
), R.string.album_delete_failed_message to ExpectedLocalizedString(
translations = mapOf(
Locale.ENGLISH to "Failed to delete few of the files.",
Locale.GERMAN to "Einige Dateien konnten nicht gelöscht werden."
)
), R.string.album_already_exists to ExpectedLocalizedString(
translations = mapOf(
Locale.ENGLISH to "Album already exists",
Locale.GERMAN to "Das Album existiert bereits"
)
), R.string.album_picker_toolbar_title to ExpectedLocalizedString(
translations = mapOf(
Locale.ENGLISH to "Pick Album",
Locale.GERMAN to "Album auswählen"
)
), R.string.media_picker_toolbar_title to ExpectedLocalizedString(
translations = mapOf(
Locale.ENGLISH to "Pick Media Files",
Locale.GERMAN to "Mediendateien auswählen"
)
), R.string.empty_albums_title to ExpectedLocalizedString(
translations = mapOf(
Locale.ENGLISH to "Create Albums for your Photos",
Locale.GERMAN to "Erstelle Alben für deine Fotos"
)
), R.string.empty_albums_message to ExpectedLocalizedString(
translations = mapOf(
Locale.ENGLISH to "You can organize all your photos in as many albums as you like. You haven\'t created an album yet.",
Locale.GERMAN to "Sie können all Ihre Fotos in beliebig vielen Alben organisieren. Bisher haben Sie noch kein Album erstellt."
)
), R.string.add_to_album to ExpectedLocalizedString(
translations = mapOf(
Locale.ENGLISH to "Add to Album",
Locale.GERMAN to "Zum Album hinzufügen"
)
), R.string.album_file_added_message to ExpectedLocalizedString(
translations = mapOf(
Locale.ENGLISH to "File added successfully",
Locale.GERMAN to "Datei erfolgreich hinzugefügt"
)
), R.string.empty_album_detailed_view_title to ExpectedLocalizedString(
translations = mapOf(
Locale.ENGLISH to "All that\'s missing are your photos",
Locale.GERMAN to "Es fehlen nur noch Ihre Fotos"
)
), R.string.empty_album_detailed_view_message to ExpectedLocalizedString(
translations = mapOf(
Locale.ENGLISH to "You can add as many photos as you like. A photo can also belong to more than one album.",
Locale.GERMAN to "Sie können so viele Fotos hinzufügen, wie Sie möchten. Ein Foto kann auch mehreren Alben zugeordnet werden."
)
),
R.string.add_photos to ExpectedLocalizedString(
translations = mapOf(
Locale.ENGLISH to "Add photos",
Locale.GERMAN to "Fotos hinzufügen"
)
), R.string.album_items_text to ExpectedLocalizedString(
translations = mapOf(
Locale.ENGLISH to "%d Items — %s",
Locale.GERMAN to "%d Elemente — %s"
)
), R.string.album_unsupported_file to ExpectedLocalizedString(
translations = mapOf(
Locale.ENGLISH to "Unsupported media",
Locale.GERMAN to "Nicht unterstützte Medien"
)
), R.string.album_upload_from_camera_roll to ExpectedLocalizedString(
translations = mapOf(
Locale.ENGLISH to "Upload from cameraroll",
Locale.GERMAN to "Dateien hochladen"
)
), R.string.album_upload_from_account to ExpectedLocalizedString(
translations = mapOf(
Locale.ENGLISH to "Select images from account",
Locale.GERMAN to "Dateien auswählen"
)
), R.string.album_rename_conflict to ExpectedLocalizedString(
translations = mapOf(
Locale.ENGLISH to "This name is already in use.",
Locale.GERMAN to "Dieser Name wird bereits verwendet."
)
), R.string.album_copy_file_conflict to ExpectedLocalizedString(
translations = mapOf(
Locale.ENGLISH to "Already exists.",
Locale.GERMAN to "Existiert bereits."
)
),
)

@Test
fun verifyLocalizedStrings() {
localizedStringMap.forEach { (stringRes, expected) ->
expected.translations.forEach { (locale, expectedText) ->

val config = Configuration(baseContext.resources.configuration)
config.setLocale(locale)

val localizedContext = baseContext.createConfigurationContext(config)
val actualText = localizedContext.getString(stringRes)

assertEquals(
"Mismatch for ${baseContext.resources.getResourceEntryName(stringRes)} in $locale",
expectedText,
actualText
)
}
}
}

data class ExpectedLocalizedString(val translations: Map<Locale, String>)

private val expectedDimenMap = mapOf(
R.dimen.album_list_image_width to ExpectedDimen(
default = 78f,
unit = DimenUnit.DP
),
R.dimen.album_list_image_height to ExpectedDimen(
default = 56f,
unit = DimenUnit.DP
),
R.dimen.album_grid_image_height to ExpectedDimen(
default = 140f,
unit = DimenUnit.DP
),
R.dimen.album_grid_image_corner_radius to ExpectedDimen(
default = 8f,
unit = DimenUnit.DP
),
R.dimen.album_list_image_corner_radius to ExpectedDimen(
default = 4f,
unit = DimenUnit.DP
),
R.dimen.album_grid_spacing to ExpectedDimen(
default = 4f,
unit = DimenUnit.DP
),
R.dimen.album_recycler_view_grid_padding to ExpectedDimen(
default = 8f,
unit = DimenUnit.DP
),
)

@Test
fun validateDefaultDimens() {
validateDimens(
configModifier = { it }, // no change → default values
) { it.default to it.unit }
}

@Test
fun validate_sw600dp_Dimens() {
validateDimens(configModifier = { config ->
config.smallestScreenWidthDp = 600
config
}) { it.alt to it.unit }
}

private fun validateDimens(
configModifier: (Configuration) -> Configuration,
selector: (ExpectedDimen) -> Pair<Float?, DimenUnit>
) {
val baseConfig = Configuration(baseContext.resources.configuration)
val testConfig = configModifier(baseConfig)
val testContext = baseContext.createConfigurationContext(testConfig)
val dm = testContext.resources.displayMetrics
val config = testContext.resources.configuration
expectedDimenMap.forEach { (resId, entry) ->
val (value, unit) = selector(entry)
val actualPx = testContext.resources.getDimension(resId)
value?.let {
val expectedPx = convertToPx(value, unit, dm, config)
assertEquals(
"Mismatch for ${testContext.resources.getResourceEntryName(resId)} ($unit)",
expectedPx,
actualPx,
0.01f
)
}
}
}

private fun convertToPx(
value: Float,
unit: DimenUnit,
dm: DisplayMetrics,
config: Configuration
): Float {
return when (unit) {
DimenUnit.DP -> value * dm.density
DimenUnit.SP -> value * dm.density * config.fontScale
DimenUnit.PX -> value
}
}

data class ExpectedDimen(
val default: Float,
val alt: Float? = null,
val unit: DimenUnit,
)

enum class DimenUnit { DP, SP, PX }
}
3 changes: 3 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -619,6 +619,9 @@
android:launchMode="singleTop"
android:theme="@style/Theme.ownCloud.Dialog.NoTitle"
android:windowSoftInputMode="adjustResize" />
<activity
android:name=".ui.activity.AlbumsPickerActivity"
android:exported="false" />
<activity
android:name=".ui.activity.ShareActivity"
android:exported="false"
Expand Down
20 changes: 20 additions & 0 deletions app/src/main/java/com/nextcloud/client/di/ComponentsModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import com.nextcloud.ui.ImageDetailFragment;
import com.nextcloud.ui.SetOnlineStatusBottomSheet;
import com.nextcloud.ui.SetStatusMessageBottomSheet;
import com.nextcloud.ui.albumItemActions.AlbumItemActionsBottomSheet;
import com.nextcloud.ui.composeActivity.ComposeActivity;
import com.nextcloud.ui.fileactions.FileActionsBottomSheet;
import com.nextcloud.ui.trashbinFileActions.TrashbinFileActionsBottomSheet;
Expand Down Expand Up @@ -82,6 +83,7 @@
import com.owncloud.android.ui.dialog.ChooseTemplateDialogFragment;
import com.owncloud.android.ui.dialog.ConfirmationDialogFragment;
import com.owncloud.android.ui.dialog.ConflictsResolveDialog;
import com.owncloud.android.ui.dialog.CreateAlbumDialogFragment;
import com.owncloud.android.ui.dialog.CreateFolderDialogFragment;
import com.owncloud.android.ui.dialog.ExpirationDatePickerDialogFragment;
import com.owncloud.android.ui.dialog.IndeterminateProgressDialog;
Expand Down Expand Up @@ -114,6 +116,9 @@
import com.owncloud.android.ui.fragment.OCFileListFragment;
import com.owncloud.android.ui.fragment.SharedListFragment;
import com.owncloud.android.ui.fragment.UnifiedSearchFragment;
import com.owncloud.android.ui.fragment.albums.AlbumItemsFragment;
import com.owncloud.android.ui.fragment.albums.AlbumsFragment;
import com.owncloud.android.ui.activity.AlbumsPickerActivity;
import com.owncloud.android.ui.fragment.contactsbackup.BackupFragment;
import com.owncloud.android.ui.fragment.contactsbackup.BackupListFragment;
import com.owncloud.android.ui.preview.FileDownloadFragment;
Expand Down Expand Up @@ -505,4 +510,19 @@ abstract class ComponentsModule {

@ContributesAndroidInjector
abstract SetStatusMessageBottomSheet setStatusMessageBottomSheet();

@ContributesAndroidInjector
abstract AlbumsPickerActivity albumsPickerActivity();

@ContributesAndroidInjector
abstract CreateAlbumDialogFragment createAlbumDialogFragment();

@ContributesAndroidInjector
abstract AlbumsFragment albumsFragment();

@ContributesAndroidInjector
abstract AlbumItemsFragment albumItemsFragment();

@ContributesAndroidInjector
abstract AlbumItemActionsBottomSheet albumItemActionsBottomSheet();
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import com.nextcloud.client.jobs.download.FileDownloadWorker
import com.nextcloud.client.jobs.metadata.MetadataWorker
import com.nextcloud.client.jobs.offlineOperations.OfflineOperationsWorker
import com.nextcloud.client.jobs.folderDownload.FolderDownloadWorker
import com.nextcloud.client.jobs.upload.AlbumFileUploadWorker
import com.nextcloud.client.jobs.upload.FileUploadWorker
import com.nextcloud.client.logger.Logger
import com.nextcloud.client.network.ConnectivityService
Expand Down Expand Up @@ -97,6 +98,7 @@ class BackgroundJobFactory @Inject constructor(
FilesExportWork::class -> createFilesExportWork(context, workerParameters)
FileUploadWorker::class -> createFilesUploadWorker(context, workerParameters)
FileDownloadWorker::class -> createFilesDownloadWorker(context, workerParameters)
AlbumFileUploadWorker::class -> createAlbumsFilesUploadWorker(context, workerParameters)
GeneratePdfFromImagesWork::class -> createPDFGenerateWork(context, workerParameters)
HealthStatusWork::class -> createHealthStatusWork(context, workerParameters)
TestJob::class -> createTestJob(context, workerParameters)
Expand Down Expand Up @@ -250,6 +252,20 @@ class BackgroundJobFactory @Inject constructor(
params
)

private fun createAlbumsFilesUploadWorker(context: Context, params: WorkerParameters): AlbumFileUploadWorker =
AlbumFileUploadWorker(
uploadsStorageManager,
connectivityService,
powerManagementService,
accountManager,
viewThemeUtils.get(),
localBroadcastManager.get(),
backgroundJobManager.get(),
preferences,
context,
params
)

private fun createPDFGenerateWork(context: Context, params: WorkerParameters): GeneratePdfFromImagesWork =
GeneratePdfFromImagesWork(
appContext = context,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,12 @@ interface BackgroundJobManager {
fun startNotificationJob(subject: String, signature: String)
fun startAccountRemovalJob(accountName: String, remoteWipe: Boolean)
fun startFilesUploadJob(user: User, uploadIds: LongArray, showSameFileAlreadyExistsNotification: Boolean)
fun startAlbumFilesUploadJob(
user: User,
uploadIds: LongArray,
albumName: String,
showSameFileAlreadyExistsNotification: Boolean
)
fun getFileUploads(user: User): LiveData<List<JobInfo>>
fun cancelFilesUploadJob(user: User)
fun isStartFileUploadJobScheduled(accountName: String): Boolean
Expand Down
Loading