diff --git a/account/src/main/kotlin/com/alfresco/content/account/Account.kt b/account/src/main/kotlin/com/alfresco/content/account/Account.kt index b10cc482d..1cf5a003c 100644 --- a/account/src/main/kotlin/com/alfresco/content/account/Account.kt +++ b/account/src/main/kotlin/com/alfresco/content/account/Account.kt @@ -19,12 +19,12 @@ data class Account( val myFiles: String? = null, ) { companion object { - private const val displayNameKey = "displayName" - private const val emailKey = "email" - private const val authTypeKey = "type" - private const val authConfigKey = "config" - private const val serverKey = "server" - private const val myFilesKey = "myFiles" + private const val DISPLAY_NAME_KEY = "displayName" + private const val EMAIL_KEY = "email" + private const val AUTH_TYPE_KEY = "type" + private const val AUTH_CONFIG_KEY = "config" + private const val SERVER_KEY = "server" + private const val MY_FILES_KEY = "myFiles" fun createAccount( context: Context, @@ -40,12 +40,12 @@ data class Account( val sharedSecure = SecureSharedPreferencesManager(context) val b = Bundle() - b.putString(authTypeKey, authType) - b.putString(authConfigKey, authConfig) - b.putString(serverKey, serverUrl) - b.putString(displayNameKey, KEY_DISPLAY_NAME) - b.putString(emailKey, KEY_EMAIL) - b.putString(myFilesKey, myFiles) + b.putString(AUTH_TYPE_KEY, authType) + b.putString(AUTH_CONFIG_KEY, authConfig) + b.putString(SERVER_KEY, serverUrl) + b.putString(DISPLAY_NAME_KEY, KEY_DISPLAY_NAME) + b.putString(EMAIL_KEY, KEY_EMAIL) + b.putString(MY_FILES_KEY, myFiles) val acc = AndroidAccount(id, context.getString(R.string.android_auth_account_type)) // Save credentials securely using the SecureSharedPreferencesManager @@ -54,7 +54,11 @@ data class Account( AccountManager.get(context).addAccountExplicitly(acc, KEY_PASSWORD, b) } - fun update(context: Context, id: String, authState: String) { + fun update( + context: Context, + id: String, + authState: String, + ) { val am = AccountManager.get(context) val acc = getAndroidAccount(context) val sharedSecure = SecureSharedPreferencesManager(context) @@ -81,16 +85,19 @@ data class Account( sharedSecure.saveCredentials(email, authState, displayName) am.setPassword(acc, KEY_PASSWORD) - am.setUserData(acc, displayNameKey, KEY_DISPLAY_NAME) - am.setUserData(acc, emailKey, KEY_EMAIL) - am.setUserData(acc, myFilesKey, myFiles) + am.setUserData(acc, DISPLAY_NAME_KEY, KEY_DISPLAY_NAME) + am.setUserData(acc, EMAIL_KEY, KEY_EMAIL) + am.setUserData(acc, MY_FILES_KEY, myFiles) if (acc?.name != id) { am.renameAccount(acc, id, null, null) } } - fun delete(context: Context, callback: () -> Unit) { + fun delete( + context: Context, + callback: () -> Unit, + ) { AccountManager.get(context) .removeAccount(getAndroidAccount(context), null, { callback() @@ -108,12 +115,12 @@ data class Account( return Account( acc.name, secureCredentials.second, - am.getUserData(acc, authTypeKey), - am.getUserData(acc, authConfigKey), - am.getUserData(acc, serverKey), + am.getUserData(acc, AUTH_TYPE_KEY), + am.getUserData(acc, AUTH_CONFIG_KEY), + am.getUserData(acc, SERVER_KEY), secureCredentials.third, secureCredentials.first, - am.getUserData(acc, myFilesKey), + am.getUserData(acc, MY_FILES_KEY), ) } return null diff --git a/account/src/main/kotlin/com/alfresco/content/account/Authenticator.kt b/account/src/main/kotlin/com/alfresco/content/account/Authenticator.kt index 1856f2e6f..6492da6e6 100644 --- a/account/src/main/kotlin/com/alfresco/content/account/Authenticator.kt +++ b/account/src/main/kotlin/com/alfresco/content/account/Authenticator.kt @@ -8,7 +8,6 @@ import android.os.Bundle class Authenticator(context: Context) : AbstractAccountAuthenticator(context) { - override fun getAuthTokenLabel(authTokenType: String?): String { throw UnsupportedOperationException() } diff --git a/account/src/main/kotlin/com/alfresco/content/account/AuthenticatorService.kt b/account/src/main/kotlin/com/alfresco/content/account/AuthenticatorService.kt index 878158d3f..c12083ee3 100644 --- a/account/src/main/kotlin/com/alfresco/content/account/AuthenticatorService.kt +++ b/account/src/main/kotlin/com/alfresco/content/account/AuthenticatorService.kt @@ -9,7 +9,6 @@ import android.os.IBinder * when started. */ class AuthenticatorService : Service() { - private lateinit var authenticator: Authenticator override fun onCreate() { diff --git a/account/src/main/kotlin/com/alfresco/content/account/SecureSharedPreferencesManager.kt b/account/src/main/kotlin/com/alfresco/content/account/SecureSharedPreferencesManager.kt index df07fedfa..e2fd45509 100644 --- a/account/src/main/kotlin/com/alfresco/content/account/SecureSharedPreferencesManager.kt +++ b/account/src/main/kotlin/com/alfresco/content/account/SecureSharedPreferencesManager.kt @@ -9,20 +9,25 @@ import androidx.security.crypto.MasterKey * Marked as SecureSharedPreferencesManager */ class SecureSharedPreferencesManager(private val context: Context) { + private val masterKey: MasterKey = + MasterKey.Builder(context, MasterKey.DEFAULT_MASTER_KEY_ALIAS) + .setKeyScheme(MasterKey.KeyScheme.AES256_GCM) + .build() - private val masterKey: MasterKey = MasterKey.Builder(context, MasterKey.DEFAULT_MASTER_KEY_ALIAS) - .setKeyScheme(MasterKey.KeyScheme.AES256_GCM) - .build() - - fun saveCredentials(email: String, password: String, displayName: String) { + fun saveCredentials( + email: String, + password: String, + displayName: String, + ) { try { - val encryptedSharedPreferences = EncryptedSharedPreferences.create( - context, - KEY_PREF_NAME, - masterKey, - EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, - EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM, - ) + val encryptedSharedPreferences = + EncryptedSharedPreferences.create( + context, + KEY_PREF_NAME, + masterKey, + EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, + EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM, + ) encryptedSharedPreferences.edit() .putString(KEY_EMAIL, email) @@ -38,13 +43,14 @@ class SecureSharedPreferencesManager(private val context: Context) { fun savePassword(password: String) { try { - val encryptedSharedPreferences = EncryptedSharedPreferences.create( - context, - KEY_PREF_NAME, - masterKey, - EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, - EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM, - ) + val encryptedSharedPreferences = + EncryptedSharedPreferences.create( + context, + KEY_PREF_NAME, + masterKey, + EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, + EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM, + ) encryptedSharedPreferences.edit() .putString(KEY_PASSWORD, password) @@ -58,13 +64,14 @@ class SecureSharedPreferencesManager(private val context: Context) { fun getSavedCredentials(): Triple? { try { - val encryptedSharedPreferences = EncryptedSharedPreferences.create( - context, - KEY_PREF_NAME, - masterKey, - EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, - EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM, - ) + val encryptedSharedPreferences = + EncryptedSharedPreferences.create( + context, + KEY_PREF_NAME, + masterKey, + EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, + EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM, + ) val email = encryptedSharedPreferences.getString(KEY_EMAIL, null) val password = encryptedSharedPreferences.getString(KEY_PASSWORD, null) diff --git a/actions/build.gradle b/actions/build.gradle index ffb4671e4..f7461acfe 100644 --- a/actions/build.gradle +++ b/actions/build.gradle @@ -1,7 +1,8 @@ plugins{ - id('com.android.library') - id('kotlin-android') - id('kotlin-kapt') + id 'com.android.library' + id 'kotlin-android' +// id('kotlin-kapt') + id 'com.google.devtools.ksp' } android { @@ -35,7 +36,7 @@ dependencies { implementation libs.epoxy.core implementation libs.mavericks - kapt libs.epoxy.processor + ksp libs.epoxy.processor // Testing testImplementation libs.junit diff --git a/actions/src/main/kotlin/com/alfresco/content/actions/Action.kt b/actions/src/main/kotlin/com/alfresco/content/actions/Action.kt index d8d295de2..cce42bf34 100644 --- a/actions/src/main/kotlin/com/alfresco/content/actions/Action.kt +++ b/actions/src/main/kotlin/com/alfresco/content/actions/Action.kt @@ -31,11 +31,13 @@ interface Action { val eventName: EventName suspend fun execute(context: Context): ParentEntry + suspend fun executeMulti(context: Context): Pair> { return Pair(entry, entries) } fun copy(_entry: ParentEntry): Action + fun copy(_entries: List): Action { return this } @@ -139,10 +141,12 @@ interface Action { } } - fun showToast(view: View, anchorView: View? = null) {} + fun showToast( + view: View, + anchorView: View? = null, + ) {} - fun maxFileNameInToast(view: View) = - view.context.resources.getInteger(R.integer.action_toast_file_name_max_length) + fun maxFileNameInToast(view: View) = view.context.resources.getInteger(R.integer.action_toast_file_name_max_length) data class Error(val message: String) @@ -150,7 +154,12 @@ interface Action { companion object { const val ERROR_FILE_SIZE_EXCEED = "File size exceed" - fun showActionToasts(scope: CoroutineScope, view: View?, anchorView: View? = null) { + + fun showActionToasts( + scope: CoroutineScope, + view: View?, + anchorView: View? = null, + ) { scope.on(block = showToast(view, anchorView)) scope.on { if (view != null) { @@ -159,7 +168,10 @@ interface Action { } } - private fun showToast(view: View?, anchorView: View?): suspend (value: T) -> Unit { + private fun showToast( + view: View?, + anchorView: View?, + ): suspend (value: T) -> Unit { return { action: T -> // Don't call on backstack views if (view != null) { diff --git a/actions/src/main/kotlin/com/alfresco/content/actions/ActionCaptureMedia.kt b/actions/src/main/kotlin/com/alfresco/content/actions/ActionCaptureMedia.kt index 7b57e83ee..94754f359 100644 --- a/actions/src/main/kotlin/com/alfresco/content/actions/ActionCaptureMedia.kt +++ b/actions/src/main/kotlin/com/alfresco/content/actions/ActionCaptureMedia.kt @@ -17,9 +17,13 @@ data class ActionCaptureMedia( override var entry: Entry, override val icon: Int = R.drawable.ic_action_capture_photo, override val title: Int = R.string.action_capture_media_title, - override val eventName: EventName = if (entry.uploadServer == UploadServerType.UPLOAD_TO_TASK) EventName.TaskCreateMedia else EventName.CreateMedia, + override val eventName: EventName = + if (entry.uploadServer == UploadServerType.UPLOAD_TO_TASK) { + EventName.TaskCreateMedia + } else { + EventName.CreateMedia + }, ) : Action { - private val repository = OfflineRepository() override suspend fun execute(context: Context): Entry { @@ -60,6 +64,8 @@ data class ActionCaptureMedia( override fun copy(_entry: ParentEntry): Action = copy(entry = _entry as Entry) - override fun showToast(view: View, anchorView: View?) = - Action.showToast(view, anchorView, R.string.action_upload_media_toast) + override fun showToast( + view: View, + anchorView: View?, + ) = Action.showToast(view, anchorView, R.string.action_upload_media_toast) } diff --git a/actions/src/main/kotlin/com/alfresco/content/actions/ActionCreateFolder.kt b/actions/src/main/kotlin/com/alfresco/content/actions/ActionCreateFolder.kt index 682de2ba7..18cb6b63a 100644 --- a/actions/src/main/kotlin/com/alfresco/content/actions/ActionCreateFolder.kt +++ b/actions/src/main/kotlin/com/alfresco/content/actions/ActionCreateFolder.kt @@ -18,25 +18,27 @@ data class ActionCreateFolder( override val title: Int = R.string.action_create_folder, override val eventName: EventName = EventName.CreateFolder, ) : Action { - override suspend fun execute(context: Context): Entry { val result = showCreateFolderDialog(context) ?: throw CancellationException("User Cancellation") return BrowseRepository().createFolder(result.name, result.description, entry.id, false) } - private suspend fun showCreateFolderDialog(context: Context) = withContext(Dispatchers.Main) { - suspendCoroutine { - CreateFolderDialog.Builder(context, false, entry.name) - .onSuccess { title, description -> - it.resume(CreateMetadata(title, description)) - } - .onCancel { it.resume(null) } - .show() + private suspend fun showCreateFolderDialog(context: Context) = + withContext(Dispatchers.Main) { + suspendCoroutine { + CreateFolderDialog.Builder(context, false, entry.name) + .onSuccess { title, description -> + it.resume(CreateMetadata(title, description)) + } + .onCancel { it.resume(null) } + .show() + } } - } override fun copy(_entry: ParentEntry): Action = copy(entry = _entry as Entry) - override fun showToast(view: View, anchorView: View?) = - Action.showToast(view, anchorView, R.string.action_create_folder_toast, entry.name) + override fun showToast( + view: View, + anchorView: View?, + ) = Action.showToast(view, anchorView, R.string.action_create_folder_toast, entry.name) } diff --git a/actions/src/main/kotlin/com/alfresco/content/actions/ActionCreateTask.kt b/actions/src/main/kotlin/com/alfresco/content/actions/ActionCreateTask.kt index 72b0bd3da..eb692bacb 100644 --- a/actions/src/main/kotlin/com/alfresco/content/actions/ActionCreateTask.kt +++ b/actions/src/main/kotlin/com/alfresco/content/actions/ActionCreateTask.kt @@ -27,16 +27,17 @@ data class ActionCreateTask( return TaskRepository().createTask(result.name, result.description) } - private suspend fun showCreateTaskDialog(context: Context) = withContext(Dispatchers.Main) { - suspendCoroutine { - CreateTaskDialog.Builder(context, false, CreateMetadata(entry.name, entry.description ?: "")) - .onSuccess { title, description -> - it.resume(CreateMetadata(title, description)) - } - .onCancel { it.resume(null) } - .show() + private suspend fun showCreateTaskDialog(context: Context) = + withContext(Dispatchers.Main) { + suspendCoroutine { + CreateTaskDialog.Builder(context, false, CreateMetadata(entry.name, entry.description ?: "")) + .onSuccess { title, description -> + it.resume(CreateMetadata(title, description)) + } + .onCancel { it.resume(null) } + .show() + } } - } override fun copy(_entry: ParentEntry): Action = copy(entry = _entry as TaskEntry) } diff --git a/actions/src/main/kotlin/com/alfresco/content/actions/ActionDelete.kt b/actions/src/main/kotlin/com/alfresco/content/actions/ActionDelete.kt index 6f7e6cad1..49049eadf 100644 --- a/actions/src/main/kotlin/com/alfresco/content/actions/ActionDelete.kt +++ b/actions/src/main/kotlin/com/alfresco/content/actions/ActionDelete.kt @@ -26,7 +26,6 @@ data class ActionDelete( override val title: Int = R.string.action_delete_title, override val eventName: EventName = EventName.MoveTrash, ) : Action { - override suspend fun execute(context: Context): Entry { try { withContext(Dispatchers.IO) { @@ -45,28 +44,29 @@ data class ActionDelete( return entry } - override suspend fun executeMulti(context: Context): Pair> = coroutineScope { - val entriesObj = entries.toMutableList() - try { - entriesObj.map { - async(Dispatchers.IO) { - delete(it) + override suspend fun executeMulti(context: Context): Pair> = + coroutineScope { + val entriesObj = entries.toMutableList() + try { + entriesObj.map { + async(Dispatchers.IO) { + delete(it) + } } + } catch (ex: KotlinNullPointerException) { + // no-op. expected for 204 + ex.printStackTrace() } - } catch (ex: KotlinNullPointerException) { - // no-op. expected for 204 - ex.printStackTrace() - } - // Cleanup associated upload if any - entriesObj.forEach { - if (it.type == Entry.Type.FILE) { - OfflineRepository().removeUpload(it.id) + // Cleanup associated upload if any + entriesObj.forEach { + if (it.type == Entry.Type.FILE) { + OfflineRepository().removeUpload(it.id) + } } - } - return@coroutineScope Pair(entry, entriesObj) - } + return@coroutineScope Pair(entry, entriesObj) + } private suspend inline fun delete(entry: Entry) { when (entry.type) { @@ -80,7 +80,10 @@ data class ActionDelete( override fun copy(_entries: List): Action = copy(entries = _entries) - override fun showToast(view: View, anchorView: View?) { + override fun showToast( + view: View, + anchorView: View?, + ) { if (entries.size > 1) { Action.showToast(view, anchorView, R.string.action_delete_multiple_toast, entries.size.toString()) } else { @@ -103,21 +106,25 @@ data class ActionRestore( return entry } - override suspend fun executeMulti(context: Context): Pair> = coroutineScope { - val entriesObj = entries.toMutableList() - entriesObj.map { - async(Dispatchers.IO) { - TrashCanRepository().restoreEntry(it) + override suspend fun executeMulti(context: Context): Pair> = + coroutineScope { + val entriesObj = entries.toMutableList() + entriesObj.map { + async(Dispatchers.IO) { + TrashCanRepository().restoreEntry(it) + } } + return@coroutineScope Pair(entry, entriesObj) } - return@coroutineScope Pair(entry, entriesObj) - } override fun copy(_entry: ParentEntry): Action = copy(entry = _entry as Entry) override fun copy(_entries: List): Action = copy(entries = _entries) - override fun showToast(view: View, anchorView: View?) { + override fun showToast( + view: View, + anchorView: View?, + ) { if (entries.size > 1) { Action.showToast(view, anchorView, R.string.action_restored_multiple_toast, entries.size.toString()) } else { @@ -133,7 +140,6 @@ data class ActionDeleteForever( override val title: Int = R.string.action_delete_forever_title, override val eventName: EventName = EventName.PermanentlyDelete, ) : Action { - override suspend fun execute(context: Context): Entry { if (showConfirmation(context)) { try { @@ -151,46 +157,50 @@ data class ActionDeleteForever( return entry } - override suspend fun executeMulti(context: Context): Pair> = coroutineScope { - val entriesObj = entries.toMutableList() - if (showConfirmation(context)) { - try { - entriesObj.map { - async(Dispatchers.IO) { - delete(it) + override suspend fun executeMulti(context: Context): Pair> = + coroutineScope { + val entriesObj = entries.toMutableList() + if (showConfirmation(context)) { + try { + entriesObj.map { + async(Dispatchers.IO) { + delete(it) + } } + } catch (ex: KotlinNullPointerException) { + // no-op. expected for 204 + ex.printStackTrace() } - } catch (ex: KotlinNullPointerException) { - // no-op. expected for 204 - ex.printStackTrace() + } else { + throw CancellationException() } - } else { - throw CancellationException() + return@coroutineScope Pair(entry, entriesObj) } - return@coroutineScope Pair(entry, entriesObj) - } - private suspend fun showConfirmation(context: Context) = withContext(Dispatchers.Main) { - suspendCoroutine { - MaterialAlertDialogBuilder(context) - .setTitle(context.getString(R.string.action_delete_confirmation_title)) - .setMessage( - if (entries.size > 1) { - context.getString( - R.string.action_delete_multiple_confirmation_message, - entries.size.toString(), - ) - } else context.getString(R.string.action_delete_confirmation_message, entry.name), - ) - .setNegativeButton(context.getString(R.string.action_delete_confirmation_negative)) { _, _ -> - it.resume(false) - } - .setPositiveButton(context.getString(R.string.action_delete_confirmation_positive)) { _, _ -> - it.resume(true) - } - .show() + private suspend fun showConfirmation(context: Context) = + withContext(Dispatchers.Main) { + suspendCoroutine { + MaterialAlertDialogBuilder(context) + .setTitle(context.getString(R.string.action_delete_confirmation_title)) + .setMessage( + if (entries.size > 1) { + context.getString( + R.string.action_delete_multiple_confirmation_message, + entries.size.toString(), + ) + } else { + context.getString(R.string.action_delete_confirmation_message, entry.name) + }, + ) + .setNegativeButton(context.getString(R.string.action_delete_confirmation_negative)) { _, _ -> + it.resume(false) + } + .setPositiveButton(context.getString(R.string.action_delete_confirmation_positive)) { _, _ -> + it.resume(true) + } + .show() + } } - } private suspend inline fun delete(entry: Entry) = TrashCanRepository().deleteForeverEntry(entry) @@ -198,7 +208,10 @@ data class ActionDeleteForever( override fun copy(_entries: List): Action = copy(entries = _entries) - override fun showToast(view: View, anchorView: View?) { + override fun showToast( + view: View, + anchorView: View?, + ) { if (entries.size > 1) { Action.showToast(view, anchorView, R.string.action_delete_forever_multiple_toast, entries.size.toString()) } else { diff --git a/actions/src/main/kotlin/com/alfresco/content/actions/ActionDownload.kt b/actions/src/main/kotlin/com/alfresco/content/actions/ActionDownload.kt index c1d2014d6..0786ffbc9 100644 --- a/actions/src/main/kotlin/com/alfresco/content/actions/ActionDownload.kt +++ b/actions/src/main/kotlin/com/alfresco/content/actions/ActionDownload.kt @@ -27,7 +27,6 @@ data class ActionDownload( override val title: Int = R.string.action_download_title, override val eventName: EventName = EventName.Download, ) : Action { - override suspend fun execute(context: Context): Entry { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q || PermissionFragment.requestPermission( @@ -56,33 +55,37 @@ data class ActionDownload( val filename = entry.name val mimeType = DocumentFile.fromFile(src).type - val legacyPath = uniqueFilePath( - Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), - filename, - ) + val legacyPath = + uniqueFilePath( + Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), + filename, + ) - val contentValues = ContentValues().apply { - put(MediaStore.MediaColumns.DISPLAY_NAME, filename) - put(MediaStore.MediaColumns.MIME_TYPE, mimeType) + val contentValues = + ContentValues().apply { + put(MediaStore.MediaColumns.DISPLAY_NAME, filename) + put(MediaStore.MediaColumns.MIME_TYPE, mimeType) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS) + put(MediaStore.MediaColumns.IS_PENDING, 1) + } else { + put(MediaStore.MediaColumns.DATA, legacyPath) + } + } + + val target = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS) - put(MediaStore.MediaColumns.IS_PENDING, 1) + MediaStore.Downloads.EXTERNAL_CONTENT_URI } else { - put(MediaStore.MediaColumns.DATA, legacyPath) + MediaStore.Files.getContentUri("external") } - } - val target = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - MediaStore.Downloads.EXTERNAL_CONTENT_URI - } else { - MediaStore.Files.getContentUri("external") - } - - val dest = resolver.insert( - target, - contentValues, - ) ?: throw FileNotFoundException("Could not create destination file.") + val dest = + resolver.insert( + target, + contentValues, + ) ?: throw FileNotFoundException("Could not create destination file.") try { resolver.openOutputStream(dest).use { output -> @@ -94,9 +97,10 @@ data class ActionDownload( } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - val updatedValues = ContentValues().apply { - put(MediaStore.Images.ImageColumns.IS_PENDING, 0) - } + val updatedValues = + ContentValues().apply { + put(MediaStore.Images.ImageColumns.IS_PENDING, 0) + } resolver.update(dest, updatedValues, null, null) } } catch (ex: Exception) { @@ -105,7 +109,10 @@ data class ActionDownload( } } - private fun uniqueFilePath(directory: File, fileName: String): String { + private fun uniqueFilePath( + directory: File, + fileName: String, + ): String { val fileExtension = fileName.substringAfterLast(".") val baseFileName = fileName.replace(".$fileExtension", "") @@ -134,12 +141,15 @@ data class ActionDownload( override fun copy(_entry: ParentEntry): Action = copy(entry = _entry as Entry) - override fun showToast(view: View, anchorView: View?) = - toastMessage.let { Action.showToast(view, anchorView, it) } + override fun showToast( + view: View, + anchorView: View?, + ) = toastMessage.let { Action.showToast(view, anchorView, it) } - private val toastMessage = if (entry.isSynced) { - R.string.action_export_toast - } else { - R.string.action_download_toast - } + private val toastMessage = + if (entry.isSynced) { + R.string.action_export_toast + } else { + R.string.action_download_toast + } } diff --git a/actions/src/main/kotlin/com/alfresco/content/actions/ActionExtension.kt b/actions/src/main/kotlin/com/alfresco/content/actions/ActionExtension.kt index d3a594d3c..3c9a5a646 100644 --- a/actions/src/main/kotlin/com/alfresco/content/actions/ActionExtension.kt +++ b/actions/src/main/kotlin/com/alfresco/content/actions/ActionExtension.kt @@ -25,7 +25,10 @@ interface ActionExtension { /** * execute declaration for uploading the files */ - suspend fun execute(context: Context, list: List): Entry + suspend fun execute( + context: Context, + list: List, + ): Entry /** * copied entry obj @@ -60,7 +63,10 @@ interface ActionExtension { /** * showing toast on execution complete */ - fun showToast(view: View, anchorView: View? = null) + fun showToast( + view: View, + anchorView: View? = null, + ) /** * Mark as Error @@ -73,11 +79,14 @@ interface ActionExtension { class Exception(string: String) : kotlin.Exception(string) companion object { - /** * show Message on coroutine scope using lifecycle */ - fun showActionExtensionToasts(scope: CoroutineScope, view: View?, anchorView: View? = null) { + fun showActionExtensionToasts( + scope: CoroutineScope, + view: View?, + anchorView: View? = null, + ) { scope.on(block = showToast(view, anchorView)) scope.on { if (view != null) { @@ -86,7 +95,10 @@ interface ActionExtension { } } - private fun showToast(view: View?, anchorView: View?): suspend (value: T) -> Unit { + private fun showToast( + view: View?, + anchorView: View?, + ): suspend (value: T) -> Unit { return { action: T -> // Don't call on backstack views if (view != null) { diff --git a/actions/src/main/kotlin/com/alfresco/content/actions/ActionFavorite.kt b/actions/src/main/kotlin/com/alfresco/content/actions/ActionFavorite.kt index 69bbd890c..a5eb884df 100644 --- a/actions/src/main/kotlin/com/alfresco/content/actions/ActionFavorite.kt +++ b/actions/src/main/kotlin/com/alfresco/content/actions/ActionFavorite.kt @@ -24,19 +24,25 @@ data class ActionAddFavorite( return entry.copy(isFavorite = true) } - override suspend fun executeMulti(context: Context): Pair> = coroutineScope { - val entriesObj = entries.toMutableList() - entriesObj.map { - async(Dispatchers.IO) { - repository.addFavorite(it) + override suspend fun executeMulti(context: Context): Pair> = + coroutineScope { + val entriesObj = entries.toMutableList() + entriesObj.map { + async(Dispatchers.IO) { + repository.addFavorite(it) + } } + return@coroutineScope Pair(entry, entriesObj.map { it.copy(isFavorite = true, isSelectedForMultiSelection = false) }) } - return@coroutineScope Pair(entry, entriesObj.map { it.copy(isFavorite = true, isSelectedForMultiSelection = false) }) - } override fun copy(_entry: ParentEntry): Action = copy(entry = _entry as Entry) + override fun copy(_entries: List): Action = copy(entries = _entries) - override fun showToast(view: View, anchorView: View?) { + + override fun showToast( + view: View, + anchorView: View?, + ) { if (entries.size > 1) { Action.showToast(view, anchorView, R.string.action_add_favorite_multiple_toast, entries.size.toString()) } else { @@ -63,24 +69,30 @@ data class ActionRemoveFavorite( return entry.copy(isFavorite = false) } - override suspend fun executeMulti(context: Context): Pair> = coroutineScope { - val entriesObj = entries.toMutableList() - try { - entriesObj.map { - async(Dispatchers.IO) { - repository.removeFavorite(it) + override suspend fun executeMulti(context: Context): Pair> = + coroutineScope { + val entriesObj = entries.toMutableList() + try { + entriesObj.map { + async(Dispatchers.IO) { + repository.removeFavorite(it) + } } + } catch (ex: KotlinNullPointerException) { + // no-op. expected for 204 + ex.printStackTrace() } - } catch (ex: KotlinNullPointerException) { - // no-op. expected for 204 - ex.printStackTrace() + return@coroutineScope Pair(entry, entriesObj.map { it.copy(isFavorite = false, isSelectedForMultiSelection = false) }) } - return@coroutineScope Pair(entry, entriesObj.map { it.copy(isFavorite = false, isSelectedForMultiSelection = false) }) - } override fun copy(_entry: ParentEntry): Action = copy(entry = _entry as Entry) + override fun copy(_entries: List): Action = copy(entries = _entries) - override fun showToast(view: View, anchorView: View?) { + + override fun showToast( + view: View, + anchorView: View?, + ) { if (entries.size > 1) { Action.showToast(view, anchorView, R.string.action_remove_favorite_multiple_toast, entries.size.toString()) } else { diff --git a/actions/src/main/kotlin/com/alfresco/content/actions/ActionListLoading.kt b/actions/src/main/kotlin/com/alfresco/content/actions/ActionListLoading.kt index ca99392f9..c05d3ae9d 100644 --- a/actions/src/main/kotlin/com/alfresco/content/actions/ActionListLoading.kt +++ b/actions/src/main/kotlin/com/alfresco/content/actions/ActionListLoading.kt @@ -6,13 +6,14 @@ import android.widget.FrameLayout import com.airbnb.epoxy.ModelView @ModelView(autoLayout = ModelView.Size.MATCH_WIDTH_MATCH_HEIGHT) -internal class ActionListLoading @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0, -) : FrameLayout(context, attrs, defStyleAttr) { - - init { - inflate(context, R.layout.view_action_list_loading, this) +internal class ActionListLoading + @JvmOverloads + constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + ) : FrameLayout(context, attrs, defStyleAttr) { + init { + inflate(context, R.layout.view_action_list_loading, this) + } } -} diff --git a/actions/src/main/kotlin/com/alfresco/content/actions/ActionListRow.kt b/actions/src/main/kotlin/com/alfresco/content/actions/ActionListRow.kt index 8973c3131..d2257d401 100644 --- a/actions/src/main/kotlin/com/alfresco/content/actions/ActionListRow.kt +++ b/actions/src/main/kotlin/com/alfresco/content/actions/ActionListRow.kt @@ -11,24 +11,26 @@ import com.airbnb.epoxy.ModelView import com.alfresco.content.actions.databinding.ViewActionListRowBinding @ModelView(autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT) -internal class ActionListRow @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0, -) : FrameLayout(context, attrs, defStyleAttr) { - private val binding = ViewActionListRowBinding.inflate(LayoutInflater.from(context), this, true) +internal class ActionListRow + @JvmOverloads + constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + ) : FrameLayout(context, attrs, defStyleAttr) { + private val binding = ViewActionListRowBinding.inflate(LayoutInflater.from(context), this, true) - @ModelProp(options = [ModelProp.Option.IgnoreRequireHashCode]) - fun setAction(action: Action) { - binding.apply { - parentTitle.contentDescription = resources.getString(action.title) - title.text = resources.getString(action.title) - icon.setImageDrawable(ResourcesCompat.getDrawable(resources, action.icon, context.theme)) + @ModelProp(options = [ModelProp.Option.IgnoreRequireHashCode]) + fun setAction(action: Action) { + binding.apply { + parentTitle.contentDescription = resources.getString(action.title) + title.text = resources.getString(action.title) + icon.setImageDrawable(ResourcesCompat.getDrawable(resources, action.icon, context.theme)) + } } - } - @CallbackProp - fun setClickListener(listener: OnClickListener?) { - setOnClickListener(listener) + @CallbackProp + fun setClickListener(listener: OnClickListener?) { + setOnClickListener(listener) + } } -} diff --git a/actions/src/main/kotlin/com/alfresco/content/actions/ActionMoveFilesFolders.kt b/actions/src/main/kotlin/com/alfresco/content/actions/ActionMoveFilesFolders.kt index 33da6287a..acb5a4e33 100644 --- a/actions/src/main/kotlin/com/alfresco/content/actions/ActionMoveFilesFolders.kt +++ b/actions/src/main/kotlin/com/alfresco/content/actions/ActionMoveFilesFolders.kt @@ -22,7 +22,6 @@ data class ActionMoveFilesFolders( override val title: Int = R.string.action_move_title, override val eventName: EventName = EventName.MoveToFolder, ) : Action { - override suspend fun execute(context: Context): Entry { val result = ActionMoveFragment.moveItem(context, entry) if (!result.isNullOrEmpty()) { @@ -35,25 +34,30 @@ data class ActionMoveFilesFolders( return entry } - override suspend fun executeMulti(context: Context): Pair> = coroutineScope { - val entriesObj = entries.toMutableList() - val result = ActionMoveFragment.moveItem(context, entry) - if (!result.isNullOrEmpty()) { - entriesObj.map { - async(Dispatchers.IO) { - BrowseRepository().moveNode(it.id, result) + override suspend fun executeMulti(context: Context): Pair> = + coroutineScope { + val entriesObj = entries.toMutableList() + val result = ActionMoveFragment.moveItem(context, entry) + if (!result.isNullOrEmpty()) { + entriesObj.map { + async(Dispatchers.IO) { + BrowseRepository().moveNode(it.id, result) + } } + } else { + throw CancellationException("User Cancellation") } - } else { - throw CancellationException("User Cancellation") + return@coroutineScope Pair(entry, entriesObj) } - return@coroutineScope Pair(entry, entriesObj) - } override fun copy(_entry: ParentEntry): Action = copy(entry = _entry as Entry) + override fun copy(_entries: List): Action = copy(entries = _entries) - override fun showToast(view: View, anchorView: View?) { + override fun showToast( + view: View, + anchorView: View?, + ) { if (entries.size > 1) { Action.showToast(view, anchorView, R.string.action_move_multiple_toast, entries.size.toString()) } else { diff --git a/actions/src/main/kotlin/com/alfresco/content/actions/ActionMoveFragment.kt b/actions/src/main/kotlin/com/alfresco/content/actions/ActionMoveFragment.kt index 603b81200..f3c69f5b7 100644 --- a/actions/src/main/kotlin/com/alfresco/content/actions/ActionMoveFragment.kt +++ b/actions/src/main/kotlin/com/alfresco/content/actions/ActionMoveFragment.kt @@ -25,9 +25,10 @@ class ActionMoveFragment : Fragment() { arguments?.let { bundle -> val entryObj = bundle.getParcelable(ENTRY_OBJ_KEY) as Entry? - requestLauncher = registerForActivityResult(MoveResultContract(entryObj)) { - viewModel.onResult?.resume(it, null) - } + requestLauncher = + registerForActivityResult(MoveResultContract(entryObj)) { + viewModel.onResult?.resume(it, null) + } } } @@ -48,9 +49,10 @@ class ActionMoveFragment : Fragment() { entry: Entry, ): String? { val fragment = ActionMoveFragment() - val bundle = Bundle().apply { - putParcelable(ENTRY_OBJ_KEY, entry) - } + val bundle = + Bundle().apply { + putParcelable(ENTRY_OBJ_KEY, entry) + } fragment.arguments = bundle return withNewFragment( context, diff --git a/actions/src/main/kotlin/com/alfresco/content/actions/ActionOffline.kt b/actions/src/main/kotlin/com/alfresco/content/actions/ActionOffline.kt index acd220797..21f786105 100644 --- a/actions/src/main/kotlin/com/alfresco/content/actions/ActionOffline.kt +++ b/actions/src/main/kotlin/com/alfresco/content/actions/ActionOffline.kt @@ -38,7 +38,10 @@ data class ActionAddOffline( override fun copy(_entries: List): Action = copy(entries = _entries) - override fun showToast(view: View, anchorView: View?) { + override fun showToast( + view: View, + anchorView: View?, + ) { if (entries.size > 1) { Action.showToast(view, anchorView, R.string.action_add_offline_multiple_toast, entries.size.toString()) } else { @@ -69,8 +72,13 @@ data class ActionRemoveOffline( } override fun copy(_entry: ParentEntry): Action = copy(entry = _entry as Entry) + override fun copy(_entries: List): Action = copy(entries = _entries) - override fun showToast(view: View, anchorView: View?) { + + override fun showToast( + view: View, + anchorView: View?, + ) { if (entries.size > 1) { Action.showToast(view, anchorView, R.string.action_remove_offline_multiple_toast, entries.size.toString()) } else { diff --git a/actions/src/main/kotlin/com/alfresco/content/actions/ActionOpenWith.kt b/actions/src/main/kotlin/com/alfresco/content/actions/ActionOpenWith.kt index ef2688b8a..8ab38c1c3 100644 --- a/actions/src/main/kotlin/com/alfresco/content/actions/ActionOpenWith.kt +++ b/actions/src/main/kotlin/com/alfresco/content/actions/ActionOpenWith.kt @@ -32,15 +32,15 @@ data class ActionOpenWith( override val eventName: EventName = EventName.OpenWith, val hasChooser: Boolean = false, ) : Action { - private var deferredDownload = AtomicReference?>(null) override suspend fun execute(context: Context): Entry { - val target = if (entry.isSynced) { - OfflineRepository().contentFile(entry) - } else { - fetchRemoteFile(context) - } + val target = + if (entry.isSynced) { + OfflineRepository().contentFile(entry) + } else { + fetchRemoteFile(context) + } return when (entry.uploadServer) { UploadServerType.DEFAULT -> { @@ -69,9 +69,10 @@ data class ActionOpenWith( val triple = getUriClientOutputFileData(context, entry) - val deferredDownload = GlobalScope.async(Dispatchers.IO) { - ContentDownloader.downloadFileTo(triple.first, triple.third.path, triple.second) - } + val deferredDownload = + GlobalScope.async(Dispatchers.IO) { + ContentDownloader.downloadFileTo(triple.first, triple.third.path, triple.second) + } this.deferredDownload.compareAndSet(null, deferredDownload) try { @@ -86,49 +87,59 @@ data class ActionOpenWith( return triple.third } - private fun getUriClientOutputFileData(context: Context, entry: Entry): Triple { + private fun getUriClientOutputFileData( + context: Context, + entry: Entry, + ): Triple { val triple: Triple when (entry.uploadServer) { UploadServerType.UPLOAD_TO_TASK -> { - triple = Triple( - TaskRepository().contentUri(entry), - TaskRepository().getHttpClient(), - TaskRepository().getContentDirectory(entry.fileName), - ) - } - - UploadServerType.UPLOAD_TO_PROCESS -> { - triple = if (!entry.sourceId.isNullOrEmpty()) { - Triple( - BrowseRepository().contentUri(entry), - null, - File(context.cacheDir, entry.name), - ) - } else { + triple = Triple( TaskRepository().contentUri(entry), TaskRepository().getHttpClient(), TaskRepository().getContentDirectory(entry.fileName), ) - } + } + + UploadServerType.UPLOAD_TO_PROCESS -> { + triple = + if (!entry.sourceId.isNullOrEmpty()) { + Triple( + BrowseRepository().contentUri(entry), + null, + File(context.cacheDir, entry.name), + ) + } else { + Triple( + TaskRepository().contentUri(entry), + TaskRepository().getHttpClient(), + TaskRepository().getContentDirectory(entry.fileName), + ) + } } else -> { - triple = Triple( - BrowseRepository().contentUri(entry), - null, - File(context.cacheDir, entry.name), - ) + triple = + Triple( + BrowseRepository().contentUri(entry), + null, + File(context.cacheDir, entry.name), + ) } } return triple } - private fun showFileChooserDialog(context: Context, file: File) { + private fun showFileChooserDialog( + context: Context, + file: File, + ) { val contentUri = FileProvider.getUriForFile(context, ContentDownloader.FILE_PROVIDER_AUTHORITY, file) - val intent = Intent(Intent.ACTION_VIEW) - .setData(contentUri) - .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + val intent = + Intent(Intent.ACTION_VIEW) + .setData(contentUri) + .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) val chooser = Intent.createChooser(intent, context.getString(R.string.action_open_with_title)) if (intent.resolveActivity(context.packageManager) != null) { @@ -141,25 +152,26 @@ data class ActionOpenWith( private suspend fun showProgressDialogAsync(context: Context) = GlobalScope.async(Dispatchers.Main) { suspendCancellableCoroutine { - val dialog = MaterialAlertDialogBuilder(context) - .setTitle(entry.name) - .setMessage( - context.getString(R.string.action_open_with_downloading), - ) - .setIcon( - ResourcesCompat.getDrawable( - context.resources, - MimeType.with(entry.mimeType).icon, - context.theme, - ), - ) - .setNegativeButton( - context.getString(R.string.action_open_with_cancel), - ) { _, _ -> - deferredDownload.get()?.cancel() - } - .setCancelable(false) - .show() + val dialog = + MaterialAlertDialogBuilder(context) + .setTitle(entry.name) + .setMessage( + context.getString(R.string.action_open_with_downloading), + ) + .setIcon( + ResourcesCompat.getDrawable( + context.resources, + MimeType.with(entry.mimeType).icon, + context.theme, + ), + ) + .setNegativeButton( + context.getString(R.string.action_open_with_cancel), + ) { _, _ -> + deferredDownload.get()?.cancel() + } + .setCancelable(false) + .show() it.invokeOnCancellation { dialog.dismiss() } @@ -168,5 +180,8 @@ data class ActionOpenWith( override fun copy(_entry: ParentEntry): Action = copy(entry = _entry as Entry) - override fun showToast(view: View, anchorView: View?) = Unit + override fun showToast( + view: View, + anchorView: View?, + ) = Unit } diff --git a/actions/src/main/kotlin/com/alfresco/content/actions/ActionPermission.kt b/actions/src/main/kotlin/com/alfresco/content/actions/ActionPermission.kt index 924d93443..c8f34244b 100644 --- a/actions/src/main/kotlin/com/alfresco/content/actions/ActionPermission.kt +++ b/actions/src/main/kotlin/com/alfresco/content/actions/ActionPermission.kt @@ -20,7 +20,6 @@ import kotlin.coroutines.cancellation.CancellationException * Marked as ActionPermission */ interface ActionPermission { - /** * It executed when we have read permission granted */ @@ -68,11 +67,14 @@ interface ActionPermission { class Error(val message: String) companion object { - /** * show Message on coroutine scope using lifecycle */ - fun showActionPermissionToasts(scope: CoroutineScope, view: View?, anchorView: View? = null) { + fun showActionPermissionToasts( + scope: CoroutineScope, + view: View?, + anchorView: View? = null, + ) { scope.on { if (view != null) { showToast(view, anchorView, it.message) @@ -120,7 +122,6 @@ interface ActionPermission { ) } - private fun permissionRationale(context: Context) = - context.getString(R.string.share_files_permissions_rationale) + private fun permissionRationale(context: Context) = context.getString(R.string.share_files_permissions_rationale) } } diff --git a/actions/src/main/kotlin/com/alfresco/content/actions/ActionStartProcess.kt b/actions/src/main/kotlin/com/alfresco/content/actions/ActionStartProcess.kt index 8d8397da3..819865926 100644 --- a/actions/src/main/kotlin/com/alfresco/content/actions/ActionStartProcess.kt +++ b/actions/src/main/kotlin/com/alfresco/content/actions/ActionStartProcess.kt @@ -16,7 +16,6 @@ data class ActionStartProcess( override val title: Int = R.string.action_start_workflow, override val eventName: EventName = EventName.StartWorkflow, ) : Action { - override suspend fun execute(context: Context): Entry { return entry } @@ -29,5 +28,8 @@ data class ActionStartProcess( override fun copy(_entries: List): Action = copy(entries = _entries) - override fun showToast(view: View, anchorView: View?) {} + override fun showToast( + view: View, + anchorView: View?, + ) {} } diff --git a/actions/src/main/kotlin/com/alfresco/content/actions/ActionUpdateFileFolder.kt b/actions/src/main/kotlin/com/alfresco/content/actions/ActionUpdateFileFolder.kt index f53d0dbeb..dc6e106b7 100644 --- a/actions/src/main/kotlin/com/alfresco/content/actions/ActionUpdateFileFolder.kt +++ b/actions/src/main/kotlin/com/alfresco/content/actions/ActionUpdateFileFolder.kt @@ -22,33 +22,36 @@ data class ActionUpdateFileFolder( override val title: Int = R.string.action_rename_file_folder, override val eventName: EventName = EventName.RenameNode, ) : Action { - override suspend fun execute(context: Context): Entry { val result = showCreateFolderDialog(context) ?: throw CancellationException("User Cancellation") - val nodeType = when (entry.type) { - Entry.Type.FOLDER -> "cm:folder" - else -> "cm:content" - } + val nodeType = + when (entry.type) { + Entry.Type.FOLDER -> "cm:folder" + else -> "cm:content" + } val name = if (entry.isFile) "${result.name}.${entry.name.nameAndExtension().second}" else result.name return BrowseRepository().updateFileFolder(name, result.description, entry.id, nodeType) } - private suspend fun showCreateFolderDialog(context: Context) = withContext(Dispatchers.Main) { - suspendCoroutine { - val name = if (entry.isFile) entry.name.nameAndExtension().first else entry.name - CreateFolderDialog.Builder(context, true, name) - .onSuccess { title, description -> - it.resume(CreateFolderMetadata(title, description)) - } - .onCancel { it.resume(null) } - .show() + private suspend fun showCreateFolderDialog(context: Context) = + withContext(Dispatchers.Main) { + suspendCoroutine { + val name = if (entry.isFile) entry.name.nameAndExtension().first else entry.name + CreateFolderDialog.Builder(context, true, name) + .onSuccess { title, description -> + it.resume(CreateFolderMetadata(title, description)) + } + .onCancel { it.resume(null) } + .show() + } } - } override fun copy(_entry: ParentEntry): Action = copy(entry = _entry as Entry) - override fun showToast(view: View, anchorView: View?) = - Action.showToast(view, anchorView, R.string.action_rename_file_folder_toast, entry.name) + override fun showToast( + view: View, + anchorView: View?, + ) = Action.showToast(view, anchorView, R.string.action_rename_file_folder_toast, entry.name) private data class CreateFolderMetadata( val name: String, diff --git a/actions/src/main/kotlin/com/alfresco/content/actions/ActionUpdateNameDescription.kt b/actions/src/main/kotlin/com/alfresco/content/actions/ActionUpdateNameDescription.kt index aca1efd8a..eafdb8461 100644 --- a/actions/src/main/kotlin/com/alfresco/content/actions/ActionUpdateNameDescription.kt +++ b/actions/src/main/kotlin/com/alfresco/content/actions/ActionUpdateNameDescription.kt @@ -26,20 +26,22 @@ data class ActionUpdateNameDescription( AnalyticsManager().taskEvent(eventName) return if (entry is ProcessEntry) { ProcessEntry.updateNameDescription(entry as ProcessEntry, result.name, result.description) - } else + } else { TaskEntry.updateTaskNameDescription(entry as TaskEntry, result.name, result.description) + } } - private suspend fun showCreateTaskDialog(context: Context) = withContext(Dispatchers.Main) { - suspendCoroutine { - CreateTaskDialog.Builder(context, true, getMetaData(entry)) - .onSuccess { title, description -> - it.resume(CreateMetadata(title, description)) - } - .onCancel { it.resume(null) } - .show() + private suspend fun showCreateTaskDialog(context: Context) = + withContext(Dispatchers.Main) { + suspendCoroutine { + CreateTaskDialog.Builder(context, true, getMetaData(entry)) + .onSuccess { title, description -> + it.resume(CreateMetadata(title, description)) + } + .onCancel { it.resume(null) } + .show() + } } - } override fun copy(_entry: ParentEntry): Action = copy(entry = _entry) diff --git a/actions/src/main/kotlin/com/alfresco/content/actions/ActionUploadExtensionFiles.kt b/actions/src/main/kotlin/com/alfresco/content/actions/ActionUploadExtensionFiles.kt index 570257663..3581f90c2 100644 --- a/actions/src/main/kotlin/com/alfresco/content/actions/ActionUploadExtensionFiles.kt +++ b/actions/src/main/kotlin/com/alfresco/content/actions/ActionUploadExtensionFiles.kt @@ -18,10 +18,12 @@ data class ActionUploadExtensionFiles( override var entry: Entry, override val title: Int = R.string.action_upload_files_title, ) : ActionExtension { - private val repository = OfflineRepository() - override suspend fun execute(context: Context, list: List): Entry { + override suspend fun execute( + context: Context, + list: List, + ): Entry { if (!list.isNullOrEmpty()) { withContext(Dispatchers.IO) { list.map { @@ -37,7 +39,10 @@ data class ActionUploadExtensionFiles( override fun copy(_entry: Entry): ActionExtension = copy(entry = _entry) - override fun showToast(view: View, anchorView: View?) { + override fun showToast( + view: View, + anchorView: View?, + ) { MaterialAlertDialogBuilder(view.context) .setTitle(view.resources.getString(R.string.action_upload_queue_title)) .setCancelable(false) diff --git a/actions/src/main/kotlin/com/alfresco/content/actions/ActionUploadFiles.kt b/actions/src/main/kotlin/com/alfresco/content/actions/ActionUploadFiles.kt index 9e85af298..a1d147477 100644 --- a/actions/src/main/kotlin/com/alfresco/content/actions/ActionUploadFiles.kt +++ b/actions/src/main/kotlin/com/alfresco/content/actions/ActionUploadFiles.kt @@ -24,9 +24,13 @@ data class ActionUploadFiles( override var entry: Entry, override val icon: Int = R.drawable.ic_action_upload, override val title: Int = R.string.action_upload_files_title, - override val eventName: EventName = if (entry.uploadServer == UploadServerType.UPLOAD_TO_TASK) EventName.TaskUploadFiles else EventName.UploadFiles, + override val eventName: EventName = + if (entry.uploadServer == UploadServerType.UPLOAD_TO_TASK) { + EventName.TaskUploadFiles + } else { + EventName.UploadFiles + }, ) : Action { - private val repository = OfflineRepository() override suspend fun execute(context: Context): Entry { @@ -36,7 +40,11 @@ data class ActionUploadFiles( UploadServerType.UPLOAD_TO_TASK, UploadServerType.UPLOAD_TO_PROCESS -> { result.forEach { val fileLength = DocumentFile.fromSingleUri(context, it)?.length() ?: 0L - if (GetMultipleContents.isFileSizeExceed(fileLength, if (entry.observerID.isNotEmpty()) MAX_FILE_SIZE_10 else MAX_FILE_SIZE_100)) { + if (GetMultipleContents.isFileSizeExceed( + fileLength, + if (entry.observerID.isNotEmpty()) MAX_FILE_SIZE_10 else MAX_FILE_SIZE_100, + ) + ) { throw CancellationException(ERROR_FILE_SIZE_EXCEED) } } @@ -64,7 +72,10 @@ data class ActionUploadFiles( override fun copy(_entry: ParentEntry): Action = copy(entry = _entry as Entry) - override fun showToast(view: View, anchorView: View?) { + override fun showToast( + view: View, + anchorView: View?, + ) { Action.showToast(view, anchorView, R.string.action_upload_media_toast) } diff --git a/actions/src/main/kotlin/com/alfresco/content/actions/ActionUploadMedia.kt b/actions/src/main/kotlin/com/alfresco/content/actions/ActionUploadMedia.kt index 1010a159a..0ab5f592d 100644 --- a/actions/src/main/kotlin/com/alfresco/content/actions/ActionUploadMedia.kt +++ b/actions/src/main/kotlin/com/alfresco/content/actions/ActionUploadMedia.kt @@ -19,9 +19,13 @@ data class ActionUploadMedia( override var entry: Entry, override val icon: Int = R.drawable.ic_action_upload_photo, override val title: Int = R.string.action_upload_photo_title, - override val eventName: EventName = if (entry.uploadServer == UploadServerType.UPLOAD_TO_TASK) EventName.TaskUploadMedia else EventName.UploadMedia, + override val eventName: EventName = + if (entry.uploadServer == UploadServerType.UPLOAD_TO_TASK) { + EventName.TaskUploadMedia + } else { + EventName.UploadMedia + }, ) : Action { - private val repository = OfflineRepository() override suspend fun execute(context: Context): Entry { @@ -31,7 +35,15 @@ data class ActionUploadMedia( UploadServerType.UPLOAD_TO_TASK, UploadServerType.UPLOAD_TO_PROCESS -> { result.forEach { val fileLength = DocumentFile.fromSingleUri(context, it)?.length() ?: 0L - if (GetMultipleContents.isFileSizeExceed(fileLength, if (entry.observerID.isNotEmpty()) GetMultipleContents.MAX_FILE_SIZE_10 else GetMultipleContents.MAX_FILE_SIZE_100)) { + if (GetMultipleContents.isFileSizeExceed( + fileLength, + if (entry.observerID.isNotEmpty()) { + GetMultipleContents.MAX_FILE_SIZE_10 + } else { + GetMultipleContents.MAX_FILE_SIZE_100 + }, + ) + ) { throw CancellationException(ERROR_FILE_SIZE_EXCEED) } } @@ -59,8 +71,10 @@ data class ActionUploadMedia( override fun copy(_entry: ParentEntry): Action = copy(entry = _entry as Entry) - override fun showToast(view: View, anchorView: View?) = - Action.showToast(view, anchorView, R.string.action_upload_media_toast) + override fun showToast( + view: View, + anchorView: View?, + ) = Action.showToast(view, anchorView, R.string.action_upload_media_toast) private companion object { val MIME_TYPES = arrayOf("image/*", "video/*") diff --git a/actions/src/main/kotlin/com/alfresco/content/actions/ContextualActionsBarFragment.kt b/actions/src/main/kotlin/com/alfresco/content/actions/ContextualActionsBarFragment.kt index 22a438554..3b2165b84 100644 --- a/actions/src/main/kotlin/com/alfresco/content/actions/ContextualActionsBarFragment.kt +++ b/actions/src/main/kotlin/com/alfresco/content/actions/ContextualActionsBarFragment.kt @@ -36,13 +36,17 @@ class ContextualActionsBarFragment : Fragment(), MavericksView, EntryListener { container: ViewGroup?, savedInstanceState: Bundle?, ): View { - view = LinearLayout(context).apply { - orientation = LinearLayout.HORIZONTAL - } + view = + LinearLayout(context).apply { + orientation = LinearLayout.HORIZONTAL + } return view } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { super.onViewCreated(view, savedInstanceState) lifecycleScope.on { @@ -53,16 +57,21 @@ class ContextualActionsBarFragment : Fragment(), MavericksView, EntryListener { viewModel.setEntryListener(this) } - override fun invalidate() = withState(viewModel) { - val entry = it.entries.first() + override fun invalidate() = + withState(viewModel) { + val entry = it.entries.first() - (requireActivity() as AppCompatActivity).supportActionBar?.title = entry.name + (requireActivity() as AppCompatActivity).supportActionBar?.title = entry.name - view.removeAllViews() - addButtons(view, it.topActions, entry) - } + view.removeAllViews() + addButtons(view, it.topActions, entry) + } - private fun addButtons(container: LinearLayout, actions: List, entry: Entry) { + private fun addButtons( + container: LinearLayout, + actions: List, + entry: Entry, + ) { container.addView(createSeparator()) for (action in actions) { @@ -87,10 +96,11 @@ class ContextualActionsBarFragment : Fragment(), MavericksView, EntryListener { private fun createButton(action: Action) = ImageButton(context).apply { - layoutParams = LinearLayout.LayoutParams( - LinearLayout.LayoutParams.WRAP_CONTENT, - LinearLayout.LayoutParams.WRAP_CONTENT, - ) + layoutParams = + LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT, + ) minimumWidth = resources.getDimension(R.dimen.action_button_min_touch_target_size).toInt() minimumHeight = minimumWidth when (action) { @@ -115,19 +125,21 @@ class ContextualActionsBarFragment : Fragment(), MavericksView, EntryListener { private fun createSeparator() = View(context).apply { - layoutParams = LinearLayout.LayoutParams( - 0, - LinearLayout.LayoutParams.MATCH_PARENT, - 1.0f, - ) + layoutParams = + LinearLayout.LayoutParams( + 0, + LinearLayout.LayoutParams.MATCH_PARENT, + 1.0f, + ) } private fun createMoreButton() = ImageButton(context).apply { - layoutParams = LinearLayout.LayoutParams( - LinearLayout.LayoutParams.WRAP_CONTENT, - LinearLayout.LayoutParams.WRAP_CONTENT, - ) + layoutParams = + LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT, + ) minimumWidth = resources.getDimension(R.dimen.action_button_min_touch_target_size).toInt() minimumHeight = minimumWidth contentDescription = getString(R.string.accessibility_text_more) diff --git a/actions/src/main/kotlin/com/alfresco/content/actions/ContextualActionsSheet.kt b/actions/src/main/kotlin/com/alfresco/content/actions/ContextualActionsSheet.kt index d16f3e383..cf0028fc2 100644 --- a/actions/src/main/kotlin/com/alfresco/content/actions/ContextualActionsSheet.kt +++ b/actions/src/main/kotlin/com/alfresco/content/actions/ContextualActionsSheet.kt @@ -49,47 +49,48 @@ class ContextualActionsSheet : BottomSheetDialogFragment(), MavericksView { } } - override fun invalidate() = withState(viewModel) { state -> + override fun invalidate() = + withState(viewModel) { state -> - setHeader(state) + setHeader(state) - binding.recyclerView.withModels { - if (state.actions == null) { - actionListLoading { id("loading") } - } - if (state.actions?.isEmpty() == true) { - val error = Pair(R.string.no_actions_available_title, R.string.no_actions_available_message) - viewEmptyMessage { - id("empty_message") - title(error.first) - message(error.second) + binding.recyclerView.withModels { + if (state.actions == null) { + actionListLoading { id("loading") } } - } - state.actions?.forEach { - val entry = it.entry as Entry - actionListRow { - id(it.title) - action(it) - clickListener { _ -> - AnalyticsManager().fileActionEvent( - entry.mimeType ?: "", - entry.name.substringAfterLast(".", ""), - it.eventName, - ) - withState(viewModel) { newState -> - if (!newState.isMultiSelection) { - viewModel.execute(it) + if (state.actions?.isEmpty() == true) { + val error = Pair(R.string.no_actions_available_title, R.string.no_actions_available_message) + viewEmptyMessage { + id("empty_message") + title(error.first) + message(error.second) + } + } + state.actions?.forEach { + val entry = it.entry as Entry + actionListRow { + id(it.title) + action(it) + clickListener { _ -> + AnalyticsManager().fileActionEvent( + entry.mimeType ?: "", + entry.name.substringAfterLast(".", ""), + it.eventName, + ) + withState(viewModel) { newState -> + if (!newState.isMultiSelection) { + viewModel.execute(it) - dismiss() - } else { - executeMultiAction(it) + dismiss() + } else { + executeMultiAction(it) + } } } } } } } - } private fun executeMultiAction(action: Action) { when (viewModel.canPerformActionOverNetwork()) { @@ -115,8 +116,9 @@ class ContextualActionsSheet : BottomSheetDialogFragment(), MavericksView { } companion object { - fun with(contextualActionData: ContextualActionData) = ContextualActionsSheet().apply { - arguments = bundleOf(Mavericks.KEY_ARG to contextualActionData) - } + fun with(contextualActionData: ContextualActionData) = + ContextualActionsSheet().apply { + arguments = bundleOf(Mavericks.KEY_ARG to contextualActionData) + } } } diff --git a/actions/src/main/kotlin/com/alfresco/content/actions/ContextualActionsSheetExtension.kt b/actions/src/main/kotlin/com/alfresco/content/actions/ContextualActionsSheetExtension.kt index 809a3f8c7..cc8a5a4b3 100644 --- a/actions/src/main/kotlin/com/alfresco/content/actions/ContextualActionsSheetExtension.kt +++ b/actions/src/main/kotlin/com/alfresco/content/actions/ContextualActionsSheetExtension.kt @@ -14,7 +14,12 @@ import com.alfresco.content.mimetype.MimeType fun ContextualActionsSheet.setHeader(state: ContextualActionsState) { if (state.isMultiSelection) { val titleHeader = SpannableString(getString(R.string.title_action_mode, state.entries.size)) - titleHeader.setSpan(ForegroundColorSpan(ContextCompat.getColor(requireContext(), R.color.colorActionMode)), 0, titleHeader.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + titleHeader.setSpan( + ForegroundColorSpan(ContextCompat.getColor(requireContext(), R.color.colorActionMode)), + 0, + titleHeader.length, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE, + ) binding.header.apply { parentTitle.contentDescription = titleHeader icon.visibility = View.GONE @@ -26,11 +31,12 @@ fun ContextualActionsSheet.setHeader(state: ContextualActionsState) { } else { val entryObj = state.entries.first() entryObj.let { entry -> - val type = when (entry.type) { - Entry.Type.SITE -> MimeType.LIBRARY - Entry.Type.FOLDER -> MimeType.FOLDER - else -> MimeType.with(entry.mimeType) - } + val type = + when (entry.type) { + Entry.Type.SITE -> MimeType.LIBRARY + Entry.Type.FOLDER -> MimeType.FOLDER + else -> MimeType.with(entry.mimeType) + } binding.header.apply { parentTitle.contentDescription = getString(R.string.accessibility_text_title_type, entry.name, type.name) diff --git a/actions/src/main/kotlin/com/alfresco/content/actions/ContextualActionsViewModel.kt b/actions/src/main/kotlin/com/alfresco/content/actions/ContextualActionsViewModel.kt index f6383584f..731d4fc6a 100644 --- a/actions/src/main/kotlin/com/alfresco/content/actions/ContextualActionsViewModel.kt +++ b/actions/src/main/kotlin/com/alfresco/content/actions/ContextualActionsViewModel.kt @@ -25,7 +25,6 @@ class ContextualActionsViewModel( val context: Context, private val settings: Settings, ) : MavericksViewModel(state) { - var listener: EntryListener? = null init { @@ -46,38 +45,50 @@ class ContextualActionsViewModel( viewModelScope.on(block = ::updateState) } - private fun buildModelSingleSelection() = withState { state -> - // If entry is partial and not in the offline tab - - if (state.entries.isNotEmpty()) { - state.entries.first().let { entry -> - - if (entry.isPartial && !entry.hasOfflineStatus) { - viewModelScope.launch { - fetchEntry(entry).execute { - when (it) { - is Success -> - ContextualActionsState(entries = listOf(it()), actions = makeActions(it()), topActions = makeTopActions(it()), fetch = it) - - is Fail -> - ContextualActionsState(entries = listOf(entry), actions = makeActions(entry), topActions = makeTopActions(entry), fetch = it) - - else -> - copy(fetch = it) + private fun buildModelSingleSelection() = + withState { state -> + // If entry is partial and not in the offline tab + + if (state.entries.isNotEmpty()) { + state.entries.first().let { entry -> + + if (entry.isPartial && !entry.hasOfflineStatus) { + viewModelScope.launch { + fetchEntry(entry).execute { + when (it) { + is Success -> + ContextualActionsState( + entries = listOf(it()), + actions = makeActions(it()), + topActions = makeTopActions(it()), + fetch = it, + ) + + is Fail -> + ContextualActionsState( + entries = listOf(entry), + actions = makeActions(entry), + topActions = makeTopActions(entry), + fetch = it, + ) + + else -> + copy(fetch = it) + } } } + } else { + setState { copy(actions = makeActions(entry), topActions = makeTopActions(entry), fetch = Success(entry)) } } - } else { - setState { copy(actions = makeActions(entry), topActions = makeTopActions(entry), fetch = Success(entry)) } } } } - } - private fun buildModelForMultiSelection() = withState { state -> - // If entry is partial and not in the offline tab - setState { copy(entries = state.entries, actions = makeMultiActions(state), topActions = emptyList()) } - } + private fun buildModelForMultiSelection() = + withState { state -> + // If entry is partial and not in the offline tab + setState { copy(entries = state.entries, actions = makeMultiActions(state), topActions = emptyList()) } + } private fun updateState(action: Action) { val entry = action.entry as Entry @@ -97,11 +108,12 @@ class ContextualActionsViewModel( this.listener = listener } - private fun onStartProcess(entries: List) = entries.run { - if (entries.all { it.isFile }) { - listener?.onProcessStart(entries) + private fun onStartProcess(entries: List) = + entries.run { + if (entries.all { it.isFile }) { + listener?.onProcessStart(entries) + } } - } private fun fetchEntry(entry: Entry): Flow = when (entry.type) { @@ -109,11 +121,9 @@ class ContextualActionsViewModel( else -> BrowseRepository()::fetchEntry.asFlow(entry.id) } - fun execute(action: Action) = - action.execute(context, GlobalScope) + fun execute(action: Action) = action.execute(context, GlobalScope) - fun executeMulti(action: Action) = - action.executeMulti(context, GlobalScope) + fun executeMulti(action: Action) = action.executeMulti(context, GlobalScope) private fun makeActions(entry: Entry): List = when { @@ -159,19 +169,23 @@ class ContextualActionsViewModel( return actions } - private fun sharedActions(entry: Entry, entries: List): List { + private fun sharedActions( + entry: Entry, + entries: List, + ): List { val actions = mutableListOf() // Added Favorite Action val version = SearchRepository().getPrefsServerVersion() val hasNonFavoriteEntries = entries.any { !it.isFavorite } - val favouriteActions = when { - version.toInt() < SERVER_VERSION_NUMBER -> null - hasNonFavoriteEntries && isMenuActionEnabled(MenuActions.AddFavourite) -> ActionAddFavorite(entry, entries) - !hasNonFavoriteEntries && isMenuActionEnabled(MenuActions.RemoveFavourite) -> ActionRemoveFavorite(entry, entries) - else -> null - } + val favouriteActions = + when { + version.toInt() < SERVER_VERSION_NUMBER -> null + hasNonFavoriteEntries && isMenuActionEnabled(MenuActions.AddFavourite) -> ActionAddFavorite(entry, entries) + !hasNonFavoriteEntries && isMenuActionEnabled(MenuActions.RemoveFavourite) -> ActionRemoveFavorite(entry, entries) + else -> null + } actions.addAll(listOfNotNull(favouriteActions)) @@ -229,7 +243,10 @@ class ContextualActionsViewModel( if (isMenuActionEnabled(MenuActions.StartProcess)) ActionStartProcess(entry) else null, ) - private fun processMultiActionFor(entry: Entry, entries: List): List { + private fun processMultiActionFor( + entry: Entry, + entries: List, + ): List { return if (settings.isProcessEnabled && (entries.isNotEmpty() && entries.all { it.isFile })) { listOfNotNull( if (isMenuActionEnabled(MenuActions.StartProcess)) ActionStartProcess(entry, entries) else null, @@ -253,7 +270,10 @@ class ContextualActionsViewModel( ) } - private fun offlineMultiActionFor(entry: Entry, entries: List): List { + private fun offlineMultiActionFor( + entry: Entry, + entries: List, + ): List { val filteredOffline = entries.filter { it.isFile || it.isFolder }.filter { !it.hasOfflineStatus || it.isOffline } return when { @@ -290,7 +310,14 @@ class ContextualActionsViewModel( emptyList() } - private fun deleteActionFor(entry: Entry) = if (entry.canDelete) listOfNotNull(if (isMenuActionEnabled(MenuActions.Trash)) ActionDelete(entry) else null) else listOf() + private fun deleteActionFor(entry: Entry) = + if (entry.canDelete) { + listOfNotNull( + if (isMenuActionEnabled(MenuActions.Trash)) ActionDelete(entry) else null, + ) + } else { + listOf() + } private fun renameMoveActionFor(entry: Entry): List { return if (entry.canDelete && (entry.isFile || entry.isFolder)) { @@ -307,11 +334,12 @@ class ContextualActionsViewModel( listOfNotNull( // Add or Remove favorite actions based on favorite status when { - !entry.hasOfflineStatus -> when { - entry.isFavorite && isMenuActionEnabled(MenuActions.RemoveFavourite) -> ActionRemoveFavorite(entry) - !entry.isFavorite && isMenuActionEnabled(MenuActions.AddFavourite) -> ActionAddFavorite(entry) - else -> null - } + !entry.hasOfflineStatus -> + when { + entry.isFavorite && isMenuActionEnabled(MenuActions.RemoveFavourite) -> ActionRemoveFavorite(entry) + !entry.isFavorite && isMenuActionEnabled(MenuActions.AddFavourite) -> ActionAddFavorite(entry) + else -> null + } else -> null }, @@ -331,8 +359,7 @@ class ContextualActionsViewModel( override fun create( viewModelContext: ViewModelContext, state: ContextualActionsState, - ) = - // Requires activity context in order to present other fragments + ) = // Requires activity context in order to present other fragments ContextualActionsViewModel(state, viewModelContext.activity, Settings(viewModelContext.activity)) } } diff --git a/actions/src/main/kotlin/com/alfresco/content/actions/ContextualActionsViewModelExtension.kt b/actions/src/main/kotlin/com/alfresco/content/actions/ContextualActionsViewModelExtension.kt index bc39664c8..19c03140c 100644 --- a/actions/src/main/kotlin/com/alfresco/content/actions/ContextualActionsViewModelExtension.kt +++ b/actions/src/main/kotlin/com/alfresco/content/actions/ContextualActionsViewModelExtension.kt @@ -4,4 +4,10 @@ import com.alfresco.content.data.Entry import com.alfresco.content.network.ConnectivityTracker internal fun ContextualActionsViewModel.canPerformActionOverNetwork() = ConnectivityTracker.isActiveNetwork(context) -fun isMoveDeleteAllowed(entries: List) = entries.isNotEmpty() && (entries.any { it.canDelete } && (entries.all { it.isFile || it.isFolder })) + +fun isMoveDeleteAllowed(entries: List) = + entries.isNotEmpty() && ( + entries.any { + it.canDelete + } && (entries.all { it.isFile || it.isFolder }) + ) diff --git a/actions/src/main/kotlin/com/alfresco/content/actions/CreateActionsSheet.kt b/actions/src/main/kotlin/com/alfresco/content/actions/CreateActionsSheet.kt index 2045e794e..df7532e0b 100644 --- a/actions/src/main/kotlin/com/alfresco/content/actions/CreateActionsSheet.kt +++ b/actions/src/main/kotlin/com/alfresco/content/actions/CreateActionsSheet.kt @@ -34,7 +34,6 @@ internal class ActionCreateViewModel( val context: Context, state: ActionCreateState, ) : MavericksViewModel(state) { - init { buildModel() } @@ -47,8 +46,7 @@ internal class ActionCreateViewModel( } } - fun execute(action: Action) = - action.execute(context, GlobalScope) + fun execute(action: Action) = action.execute(context, GlobalScope) private fun makeActions(parent: Entry): List { val actions = mutableListOf() @@ -94,26 +92,28 @@ class CreateActionsSheet : BottomSheetDialogFragment(), MavericksView { return binding.root } - override fun invalidate() = withState(viewModel) { state -> - - binding.recyclerView.withModels { - state.actions.forEach { - actionListRow { - id(it.title) - action(it) - clickListener { _ -> - AnalyticsManager().fileActionEvent(eventName = it.eventName) - viewModel.execute(it) - dismiss() + override fun invalidate() = + withState(viewModel) { state -> + + binding.recyclerView.withModels { + state.actions.forEach { + actionListRow { + id(it.title) + action(it) + clickListener { _ -> + AnalyticsManager().fileActionEvent(eventName = it.eventName) + viewModel.execute(it) + dismiss() + } } } } } - } companion object { - fun with(entry: Entry) = CreateActionsSheet().apply { - arguments = bundleOf(Mavericks.KEY_ARG to entry) - } + fun with(entry: Entry) = + CreateActionsSheet().apply { + arguments = bundleOf(Mavericks.KEY_ARG to entry) + } } } diff --git a/actions/src/main/kotlin/com/alfresco/content/actions/CreateFolderDialog.kt b/actions/src/main/kotlin/com/alfresco/content/actions/CreateFolderDialog.kt index 94827cd83..3cee18363 100644 --- a/actions/src/main/kotlin/com/alfresco/content/actions/CreateFolderDialog.kt +++ b/actions/src/main/kotlin/com/alfresco/content/actions/CreateFolderDialog.kt @@ -17,7 +17,6 @@ internal typealias CreateFolderSuccessCallback = (String, String) -> Unit internal typealias CreateFolderCancelCallback = () -> Unit class CreateFolderDialog : DialogFragment() { - private val binding: DialogCreateLayoutBinding by lazy { DialogCreateLayoutBinding.inflate(LayoutInflater.from(requireContext()), null, false) } @@ -56,19 +55,31 @@ class CreateFolderDialog : DialogFragment() { override fun onStart() { super.onStart() - binding.nameInputLayout.editText?.addTextChangedListener(object : TextWatcher { - override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { - // no-op - } - - override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { - // no-op - } - - override fun afterTextChanged(s: Editable?) { - validateInput(s.toString()) - } - }) + binding.nameInputLayout.editText?.addTextChangedListener( + object : TextWatcher { + override fun beforeTextChanged( + s: CharSequence?, + start: Int, + count: Int, + after: Int, + ) { + // no-op + } + + override fun onTextChanged( + s: CharSequence?, + start: Int, + before: Int, + count: Int, + ) { + // no-op + } + + override fun afterTextChanged(s: Editable?) { + validateInput(s.toString()) + } + }, + ) if (isUpdate && !name.isNullOrEmpty()) { binding.nameInput.setText(name) positiveButton.isEnabled = true @@ -82,11 +93,12 @@ class CreateFolderDialog : DialogFragment() { val isValid = isFolderNameValid(title) val isEmpty = title.isEmpty() - binding.nameInputLayout.error = when { - !isValid -> resources.getString(R.string.action_folder_name_invalid_chars) - isEmpty -> resources.getString(R.string.action_folder_name_empty) - else -> null - } + binding.nameInputLayout.error = + when { + !isValid -> resources.getString(R.string.action_folder_name_invalid_chars) + isEmpty -> resources.getString(R.string.action_folder_name_empty) + else -> null + } positiveButton.isEnabled = isValid && !isEmpty } @@ -105,19 +117,17 @@ class CreateFolderDialog : DialogFragment() { var onSuccess: CreateFolderSuccessCallback? = null, var onCancel: CreateFolderCancelCallback? = null, ) { + fun onSuccess(callback: CreateFolderSuccessCallback?) = apply { this.onSuccess = callback } - fun onSuccess(callback: CreateFolderSuccessCallback?) = - apply { this.onSuccess = callback } - - fun onCancel(callback: CreateFolderCancelCallback?) = - apply { this.onCancel = callback } + fun onCancel(callback: CreateFolderCancelCallback?) = apply { this.onCancel = callback } fun show() { - val fragmentManager = when (context) { - is AppCompatActivity -> context.supportFragmentManager - is Fragment -> context.childFragmentManager - else -> throw IllegalArgumentException() - } + val fragmentManager = + when (context) { + is AppCompatActivity -> context.supportFragmentManager + is Fragment -> context.childFragmentManager + else -> throw IllegalArgumentException() + } CreateFolderDialog().apply { onSuccess = this@Builder.onSuccess onCancel = this@Builder.onCancel diff --git a/actions/src/main/kotlin/com/alfresco/content/actions/CreateTaskDialog.kt b/actions/src/main/kotlin/com/alfresco/content/actions/CreateTaskDialog.kt index 54387cac9..316b1f655 100644 --- a/actions/src/main/kotlin/com/alfresco/content/actions/CreateTaskDialog.kt +++ b/actions/src/main/kotlin/com/alfresco/content/actions/CreateTaskDialog.kt @@ -23,7 +23,6 @@ internal typealias CreateTaskCancelCallback = () -> Unit * Mark as CreateTaskDialog class */ class CreateTaskDialog : DialogFragment() { - private val binding: DialogCreateLayoutBinding by lazy { DialogCreateLayoutBinding.inflate(LayoutInflater.from(requireContext()), null, false) } @@ -36,6 +35,7 @@ class CreateTaskDialog : DialogFragment() { var onCancel: CreateTaskCancelCallback? = null var isUpdate: Boolean = false var dataObj: CreateMetadata? = null + override fun onCreateDialog(savedInstanceState: Bundle?): AlertDialog = MaterialAlertDialogBuilder(requireContext()) .setCancelable(false) @@ -63,19 +63,31 @@ class CreateTaskDialog : DialogFragment() { super.onStart() binding.nameInput.filters = arrayOf(LengthFilter(255)) binding.descriptionInput.filters = arrayOf(LengthFilter(500)) - binding.nameInputLayout.editText?.addTextChangedListener(object : TextWatcher { - override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { - // no-op - } - - override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { - // no-op - } - - override fun afterTextChanged(s: Editable?) { - validateInput(s.toString()) - } - }) + binding.nameInputLayout.editText?.addTextChangedListener( + object : TextWatcher { + override fun beforeTextChanged( + s: CharSequence?, + start: Int, + count: Int, + after: Int, + ) { + // no-op + } + + override fun onTextChanged( + s: CharSequence?, + start: Int, + before: Int, + count: Int, + ) { + // no-op + } + + override fun afterTextChanged(s: Editable?) { + validateInput(s.toString()) + } + }, + ) if (isUpdate && dataObj?.name != null) { binding.nameInput.setText(dataObj?.name) @@ -92,10 +104,11 @@ class CreateTaskDialog : DialogFragment() { private fun validateInput(title: String) { val isEmpty = title.isEmpty() - binding.nameInputLayout.error = when { - isEmpty -> resources.getString(R.string.action_task_name_empty) - else -> null - } + binding.nameInputLayout.error = + when { + isEmpty -> resources.getString(R.string.action_task_name_empty) + else -> null + } positiveButton.isEnabled = !isEmpty } @@ -110,28 +123,26 @@ class CreateTaskDialog : DialogFragment() { var onSuccess: CreateTaskSuccessCallback? = null, var onCancel: CreateTaskCancelCallback? = null, ) { - /** * success callback if create the task */ - fun onSuccess(callback: CreateTaskSuccessCallback?) = - apply { this.onSuccess = callback } + fun onSuccess(callback: CreateTaskSuccessCallback?) = apply { this.onSuccess = callback } /** * cancel callback if dismiss the dialog */ - fun onCancel(callback: CreateTaskCancelCallback?) = - apply { this.onCancel = callback } + fun onCancel(callback: CreateTaskCancelCallback?) = apply { this.onCancel = callback } /** * It will show the create task dialog */ fun show() { - val fragmentManager = when (context) { - is AppCompatActivity -> context.supportFragmentManager - is Fragment -> context.childFragmentManager - else -> throw IllegalArgumentException() - } + val fragmentManager = + when (context) { + is AppCompatActivity -> context.supportFragmentManager + is Fragment -> context.childFragmentManager + else -> throw IllegalArgumentException() + } CreateTaskDialog().apply { onSuccess = this@Builder.onSuccess onCancel = this@Builder.onCancel diff --git a/actions/src/main/kotlin/com/alfresco/content/actions/ListRowProcessDefinitions.kt b/actions/src/main/kotlin/com/alfresco/content/actions/ListRowProcessDefinitions.kt index 106fd9d13..72eb5b507 100644 --- a/actions/src/main/kotlin/com/alfresco/content/actions/ListRowProcessDefinitions.kt +++ b/actions/src/main/kotlin/com/alfresco/content/actions/ListRowProcessDefinitions.kt @@ -11,23 +11,25 @@ import com.alfresco.content.actions.databinding.ViewProcessDefinitionsListRowBin import com.alfresco.content.data.RuntimeProcessDefinitionDataEntry @ModelView(autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT) -internal class ListRowProcessDefinitions @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0, -) : FrameLayout(context, attrs, defStyleAttr) { - private val binding = ViewProcessDefinitionsListRowBinding.inflate(LayoutInflater.from(context), this, true) +internal class ListRowProcessDefinitions + @JvmOverloads + constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + ) : FrameLayout(context, attrs, defStyleAttr) { + private val binding = ViewProcessDefinitionsListRowBinding.inflate(LayoutInflater.from(context), this, true) - @ModelProp(options = [ModelProp.Option.IgnoreRequireHashCode]) - fun setProcessDefinition(data: RuntimeProcessDefinitionDataEntry) { - binding.apply { - title.text = data.name - subtitle.text = data.description + @ModelProp(options = [ModelProp.Option.IgnoreRequireHashCode]) + fun setProcessDefinition(data: RuntimeProcessDefinitionDataEntry) { + binding.apply { + title.text = data.name + subtitle.text = data.description + } } - } - @CallbackProp - fun setClickListener(listener: OnClickListener?) { - setOnClickListener(listener) + @CallbackProp + fun setClickListener(listener: OnClickListener?) { + setOnClickListener(listener) + } } -} diff --git a/actions/src/main/kotlin/com/alfresco/content/actions/MoveResultContract.kt b/actions/src/main/kotlin/com/alfresco/content/actions/MoveResultContract.kt index 22e913f3d..7b02649cf 100644 --- a/actions/src/main/kotlin/com/alfresco/content/actions/MoveResultContract.kt +++ b/actions/src/main/kotlin/com/alfresco/content/actions/MoveResultContract.kt @@ -12,16 +12,23 @@ import com.alfresco.content.data.Entry */ class MoveResultContract(private val entryObj: Entry?) : ActivityResultContract() { @CallSuper - override fun createIntent(context: Context, input: Unit): Intent { - val intent = Intent( - context, - Class.forName("com.alfresco.content.app.activity.MoveActivity"), - ) + override fun createIntent( + context: Context, + input: Unit, + ): Intent { + val intent = + Intent( + context, + Class.forName("com.alfresco.content.app.activity.MoveActivity"), + ) intent.putExtra(ENTRY_OBJ_KEY, entryObj) return intent } - override fun parseResult(resultCode: Int, intent: Intent?): String? { + override fun parseResult( + resultCode: Int, + intent: Intent?, + ): String? { return if (intent == null || resultCode != Activity.RESULT_OK) { null } else { diff --git a/actions/src/main/kotlin/com/alfresco/content/actions/ViewEmptyMessage.kt b/actions/src/main/kotlin/com/alfresco/content/actions/ViewEmptyMessage.kt index be8c78c6b..0bf56d249 100644 --- a/actions/src/main/kotlin/com/alfresco/content/actions/ViewEmptyMessage.kt +++ b/actions/src/main/kotlin/com/alfresco/content/actions/ViewEmptyMessage.kt @@ -10,21 +10,26 @@ import com.airbnb.epoxy.ModelView import com.alfresco.content.actions.databinding.ViewEmptyMessageBinding @ModelView(autoLayout = ModelView.Size.MATCH_WIDTH_MATCH_HEIGHT) -class ViewEmptyMessage @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0, -) : FrameLayout(context, attrs, defStyleAttr) { +class ViewEmptyMessage + @JvmOverloads + constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + ) : FrameLayout(context, attrs, defStyleAttr) { + private val binding = ViewEmptyMessageBinding.inflate(LayoutInflater.from(context), this) - private val binding = ViewEmptyMessageBinding.inflate(LayoutInflater.from(context), this) + @ModelProp + fun setTitle( + @StringRes stringRes: Int, + ) { + binding.title.text = resources.getText(stringRes) + } - @ModelProp - fun setTitle(@StringRes stringRes: Int) { - binding.title.text = resources.getText(stringRes) + @ModelProp + fun setMessage( + @StringRes stringRes: Int, + ) { + binding.message.text = resources.getText(stringRes) + } } - - @ModelProp - fun setMessage(@StringRes stringRes: Int) { - binding.message.text = resources.getText(stringRes) - } -} diff --git a/actions/src/main/kotlin/com/alfresco/content/actions/sheet/ListViewErrorMessage.kt b/actions/src/main/kotlin/com/alfresco/content/actions/sheet/ListViewErrorMessage.kt index 323961dcd..0552a8c1d 100644 --- a/actions/src/main/kotlin/com/alfresco/content/actions/sheet/ListViewErrorMessage.kt +++ b/actions/src/main/kotlin/com/alfresco/content/actions/sheet/ListViewErrorMessage.kt @@ -11,26 +11,33 @@ import com.airbnb.epoxy.ModelView import com.alfresco.content.actions.databinding.ViewListProcessMessageBinding @ModelView(autoLayout = ModelView.Size.MATCH_WIDTH_MATCH_HEIGHT) -class ListViewErrorMessage @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0, -) : FrameLayout(context, attrs, defStyleAttr) { +class ListViewErrorMessage + @JvmOverloads + constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + ) : FrameLayout(context, attrs, defStyleAttr) { + private val binding = ViewListProcessMessageBinding.inflate(LayoutInflater.from(context), this) - private val binding = ViewListProcessMessageBinding.inflate(LayoutInflater.from(context), this) + @ModelProp + fun setIconRes( + @DrawableRes drawableRes: Int, + ) { + binding.icon.setImageResource(drawableRes) + } - @ModelProp - fun setIconRes(@DrawableRes drawableRes: Int) { - binding.icon.setImageResource(drawableRes) - } - - @ModelProp - fun setTitle(@StringRes stringRes: Int) { - binding.title.text = resources.getText(stringRes) - } + @ModelProp + fun setTitle( + @StringRes stringRes: Int, + ) { + binding.title.text = resources.getText(stringRes) + } - @ModelProp - fun setMessage(@StringRes stringRes: Int) { - binding.message.text = resources.getText(stringRes) + @ModelProp + fun setMessage( + @StringRes stringRes: Int, + ) { + binding.message.text = resources.getText(stringRes) + } } -} diff --git a/actions/src/main/kotlin/com/alfresco/content/actions/sheet/ProcessDefinitionsSheet.kt b/actions/src/main/kotlin/com/alfresco/content/actions/sheet/ProcessDefinitionsSheet.kt index 58de00016..cd8cb5e1c 100644 --- a/actions/src/main/kotlin/com/alfresco/content/actions/sheet/ProcessDefinitionsSheet.kt +++ b/actions/src/main/kotlin/com/alfresco/content/actions/sheet/ProcessDefinitionsSheet.kt @@ -25,7 +25,6 @@ import com.google.android.material.bottomsheet.BottomSheetDialog * Marked as ProcessDefinitionsSheet */ class ProcessDefinitionsSheet : BottomSheetDialogFragment(), MavericksView { - private val viewModel: ProcessDefinitionsViewModel by fragmentViewModel() private lateinit var binding: SheetActionListBinding @@ -54,53 +53,56 @@ class ProcessDefinitionsSheet : BottomSheetDialogFragment(), MavericksView { } } - override fun invalidate() = withState(viewModel) { state -> - binding.header.apply { - parentTitle.contentDescription = getString(R.string.title_select_workflow) - icon.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.ic_start_workflow, context?.theme)) - title.text = getString(R.string.title_select_workflow) - } - - binding.recyclerView.withModels { - if (state.listProcessDefinitions == null) { - actionListLoading { id("loading") } + override fun invalidate() = + withState(viewModel) { state -> + binding.header.apply { + parentTitle.contentDescription = getString(R.string.title_select_workflow) + icon.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.ic_start_workflow, context?.theme)) + title.text = getString(R.string.title_select_workflow) } - if (state.listProcessDefinitions?.isEmpty() == true) { - val args = viewModel.emptyMessageArgs() - listViewErrorMessage { - id("empty_message") - iconRes(args.first) - title(args.second) - message(args.third) + + binding.recyclerView.withModels { + if (state.listProcessDefinitions == null) { + actionListLoading { id("loading") } } - } else { - state.listProcessDefinitions?.forEach { - listRowProcessDefinitions { - id(it.id) - processDefinition(it) - clickListener { model, _, _, _ -> - val processEntry = ProcessEntry.with(model.processDefinition(), state.entries) + if (state.listProcessDefinitions?.isEmpty() == true) { + val args = viewModel.emptyMessageArgs() + listViewErrorMessage { + id("empty_message") + iconRes(args.first) + title(args.second) + message(args.third) + } + } else { + state.listProcessDefinitions?.forEach { + listRowProcessDefinitions { + id(it.id) + processDefinition(it) + clickListener { model, _, _, _ -> + val processEntry = ProcessEntry.with(model.processDefinition(), state.entries) - val intent = Intent( - requireActivity(), - Class.forName("com.alfresco.content.app.activity.ProcessActivity"), - ) - intent.putExtra(Mavericks.KEY_ARG, processEntry) - startActivity(intent) - dismiss() + val intent = + Intent( + requireActivity(), + Class.forName("com.alfresco.content.app.activity.ProcessActivity"), + ) + intent.putExtra(Mavericks.KEY_ARG, processEntry) + startActivity(intent) + dismiss() + } } } } } } - } companion object { /** * returns the instance of ProcessDefinitionsSheet with attached entry as bundle */ - fun with(entries: List = emptyList()) = ProcessDefinitionsSheet().apply { - arguments = bundleOf(Mavericks.KEY_ARG to entries) - } + fun with(entries: List = emptyList()) = + ProcessDefinitionsSheet().apply { + arguments = bundleOf(Mavericks.KEY_ARG to entries) + } } } diff --git a/actions/src/main/kotlin/com/alfresco/content/actions/sheet/ProcessDefinitionsViewModel.kt b/actions/src/main/kotlin/com/alfresco/content/actions/sheet/ProcessDefinitionsViewModel.kt index 93217c4b1..6494476f9 100644 --- a/actions/src/main/kotlin/com/alfresco/content/actions/sheet/ProcessDefinitionsViewModel.kt +++ b/actions/src/main/kotlin/com/alfresco/content/actions/sheet/ProcessDefinitionsViewModel.kt @@ -14,46 +14,50 @@ internal class ProcessDefinitionsViewModel( state: ProcessDefinitionsState, val context: Context, ) : MavericksViewModel(state) { - init { buildModel() } - private fun buildModel() = withState { state -> - viewModelScope.launch { - processDefinitions().execute { - when (it) { - is Success -> { - ProcessDefinitionsState( - entries = state.entries, - listProcessDefinitions = it().listRuntimeProcessDefinitions, - ) - } - - else -> { - ProcessDefinitionsState( - entries = state.entries, - listProcessDefinitions = null, - ) + private fun buildModel() = + withState { state -> + viewModelScope.launch { + processDefinitions().execute { + when (it) { + is Success -> { + ProcessDefinitionsState( + entries = state.entries, + listProcessDefinitions = it().listRuntimeProcessDefinitions, + ) + } + + else -> { + ProcessDefinitionsState( + entries = state.entries, + listProcessDefinitions = null, + ) + } } } } } - } private fun processDefinitions() = TaskRepository()::processDefinitions.asFlow() /** * returns the empty data if no workflows available */ - fun emptyMessageArgs() = Triple(R.drawable.ic_empty_workflow, R.string.workflows_unavailable_title, R.string.workflow_unavailable_message) + fun emptyMessageArgs() = + Triple( + R.drawable.ic_empty_workflow, + R.string.workflows_unavailable_title, + R.string.workflow_unavailable_message, + ) companion object : MavericksViewModelFactory { override fun create( viewModelContext: ViewModelContext, state: ProcessDefinitionsState, - ) = - // Requires activity context in order to present other fragments + ) = // Requires activity context in order to present other fragments ProcessDefinitionsViewModel(state, viewModelContext.activity()) } } diff --git a/actions/src/test/java/com/alfresco/content/actions/ContextualActionsViewModelTest.kt b/actions/src/test/java/com/alfresco/content/actions/ContextualActionsViewModelTest.kt index 0b3c4d033..3b3469ff9 100644 --- a/actions/src/test/java/com/alfresco/content/actions/ContextualActionsViewModelTest.kt +++ b/actions/src/test/java/com/alfresco/content/actions/ContextualActionsViewModelTest.kt @@ -17,11 +17,10 @@ import org.junit.ClassRule import org.junit.Test import org.mockito.Mock import org.mockito.MockitoAnnotations -import java.util.* +import java.util.UUID @ExperimentalCoroutinesApi class ContextualActionsViewModelTest { - @Mock private lateinit var mockContext: Context @@ -46,22 +45,58 @@ class ContextualActionsViewModelTest { @Test fun `test makeMultiActions and returns ActionAddFavorite`() { - val entries = createEntries( - Entry(id = UUID.randomUUID().toString(), isFavorite = false, type = Entry.Type.FILE, canDelete = false, offlineStatus = OfflineStatus.UNDEFINED, isOffline = false), - Entry(id = UUID.randomUUID().toString(), isFavorite = false, type = Entry.Type.FILE, canDelete = false, offlineStatus = OfflineStatus.UNDEFINED, isOffline = false), - Entry(id = UUID.randomUUID().toString(), isFavorite = false, type = Entry.Type.FILE, canDelete = false, offlineStatus = OfflineStatus.UNDEFINED, isOffline = false), - Entry(id = UUID.randomUUID().toString(), isFavorite = false, type = Entry.Type.FILE, canDelete = false, offlineStatus = OfflineStatus.UNDEFINED, isOffline = false), - Entry(id = UUID.randomUUID().toString(), isFavorite = false, type = Entry.Type.FILE, canDelete = false, offlineStatus = OfflineStatus.UNDEFINED, isOffline = false), - ) + val entries = + createEntries( + Entry( + id = UUID.randomUUID().toString(), + isFavorite = false, + type = Entry.Type.FILE, + canDelete = false, + offlineStatus = OfflineStatus.UNDEFINED, + isOffline = false, + ), + Entry( + id = UUID.randomUUID().toString(), + isFavorite = false, + type = Entry.Type.FILE, + canDelete = false, + offlineStatus = OfflineStatus.UNDEFINED, + isOffline = false, + ), + Entry( + id = UUID.randomUUID().toString(), + isFavorite = false, + type = Entry.Type.FILE, + canDelete = false, + offlineStatus = OfflineStatus.UNDEFINED, + isOffline = false, + ), + Entry( + id = UUID.randomUUID().toString(), + isFavorite = false, + type = Entry.Type.FILE, + canDelete = false, + offlineStatus = OfflineStatus.UNDEFINED, + isOffline = false, + ), + Entry( + id = UUID.randomUUID().toString(), + isFavorite = false, + type = Entry.Type.FILE, + canDelete = false, + offlineStatus = OfflineStatus.UNDEFINED, + isOffline = false, + ), + ) val initialState = ContextualActionsState(ContextualActionData(entries = entries, isMultiSelection = true)) val viewModel = ContextualActionsViewModel(initialState, mockContext, settings) withState(viewModel) { newState -> viewModel.makeMultiActions(newState) - assertEquals(1, newState.actions.size) + assertEquals(1, newState.actions?.size ?: 0) - newState.actions.forEach { + newState.actions?.forEach { assertEquals(true, it is ActionAddFavorite) } } @@ -69,22 +104,58 @@ class ContextualActionsViewModelTest { @Test fun `test makeMultiActions and returns ActionRemoveFavorite`() { - val entries = createEntries( - Entry(id = UUID.randomUUID().toString(), isFavorite = true, type = Entry.Type.FILE, canDelete = false, offlineStatus = OfflineStatus.UNDEFINED, isOffline = false), - Entry(id = UUID.randomUUID().toString(), isFavorite = true, type = Entry.Type.FILE, canDelete = false, offlineStatus = OfflineStatus.UNDEFINED, isOffline = false), - Entry(id = UUID.randomUUID().toString(), isFavorite = true, type = Entry.Type.FILE, canDelete = false, offlineStatus = OfflineStatus.UNDEFINED, isOffline = false), - Entry(id = UUID.randomUUID().toString(), isFavorite = true, type = Entry.Type.FILE, canDelete = false, offlineStatus = OfflineStatus.UNDEFINED, isOffline = false), - Entry(id = UUID.randomUUID().toString(), isFavorite = true, type = Entry.Type.FILE, canDelete = false, offlineStatus = OfflineStatus.UNDEFINED, isOffline = false), - ) + val entries = + createEntries( + Entry( + id = UUID.randomUUID().toString(), + isFavorite = true, + type = Entry.Type.FILE, + canDelete = false, + offlineStatus = OfflineStatus.UNDEFINED, + isOffline = false, + ), + Entry( + id = UUID.randomUUID().toString(), + isFavorite = true, + type = Entry.Type.FILE, + canDelete = false, + offlineStatus = OfflineStatus.UNDEFINED, + isOffline = false, + ), + Entry( + id = UUID.randomUUID().toString(), + isFavorite = true, + type = Entry.Type.FILE, + canDelete = false, + offlineStatus = OfflineStatus.UNDEFINED, + isOffline = false, + ), + Entry( + id = UUID.randomUUID().toString(), + isFavorite = true, + type = Entry.Type.FILE, + canDelete = false, + offlineStatus = OfflineStatus.UNDEFINED, + isOffline = false, + ), + Entry( + id = UUID.randomUUID().toString(), + isFavorite = true, + type = Entry.Type.FILE, + canDelete = false, + offlineStatus = OfflineStatus.UNDEFINED, + isOffline = false, + ), + ) val initialState = ContextualActionsState(ContextualActionData(entries = entries, isMultiSelection = true)) val viewModel = ContextualActionsViewModel(initialState, mockContext, settings) withState(viewModel) { newState -> viewModel.makeMultiActions(newState) - assertEquals(1, newState.actions.size) + assertEquals(1, newState.actions?.size) - newState.actions.forEach { + newState.actions?.forEach { assertEquals(true, it is ActionRemoveFavorite) } } diff --git a/app/build.gradle b/app/build.gradle index 7573d0f16..5334906d6 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -63,6 +63,9 @@ android { resources { pickFirsts += ['META-INF/auth_release.kotlin_module'] } + dex { + useLegacyPackaging false + } } // Temporary kotlin module duplication fix @@ -103,6 +106,7 @@ dependencies { implementation project(':process-app') implementation project(':data') implementation libs.alfresco.content +// implementation project(':content') implementation libs.kotlin.stdlib diff --git a/app/src/main/java/com/alfresco/content/app/AlfrescoApplication.kt b/app/src/main/java/com/alfresco/content/app/AlfrescoApplication.kt index 280e1f041..3494ed5f8 100644 --- a/app/src/main/java/com/alfresco/content/app/AlfrescoApplication.kt +++ b/app/src/main/java/com/alfresco/content/app/AlfrescoApplication.kt @@ -15,7 +15,6 @@ import kotlinx.coroutines.launch @Suppress("unused") class AlfrescoApplication : Application() { - private val applicationScope = CoroutineScope(SupervisorJob() + Dispatchers.Main) private lateinit var settings: Settings diff --git a/app/src/main/java/com/alfresco/content/app/CoilExt.kt b/app/src/main/java/com/alfresco/content/app/CoilExt.kt index 5366a9b10..ac3a7b03b 100644 --- a/app/src/main/java/com/alfresco/content/app/CoilExt.kt +++ b/app/src/main/java/com/alfresco/content/app/CoilExt.kt @@ -12,20 +12,21 @@ inline fun Preference.loadAny( imageLoader: ImageLoader = Coil.imageLoader(context), builder: ImageRequest.Builder.() -> Unit = {}, ): Disposable { - val request = ImageRequest.Builder(context) - .data(data) - .target( - onStart = { placeholder -> - icon = placeholder - }, - onSuccess = { result -> - icon = result - }, - onError = { error -> - icon = error - }, - ) - .apply(builder) - .build() + val request = + ImageRequest.Builder(context) + .data(data) + .target( + onStart = { placeholder -> + icon = placeholder + }, + onSuccess = { result -> + icon = result + }, + onError = { error -> + icon = error + }, + ) + .apply(builder) + .build() return imageLoader.enqueue(request) } diff --git a/app/src/main/java/com/alfresco/content/app/activity/ExtensionActivity.kt b/app/src/main/java/com/alfresco/content/app/activity/ExtensionActivity.kt index 30a139d13..10f72cfbc 100644 --- a/app/src/main/java/com/alfresco/content/app/activity/ExtensionActivity.kt +++ b/app/src/main/java/com/alfresco/content/app/activity/ExtensionActivity.kt @@ -33,7 +33,6 @@ import java.lang.ref.WeakReference * Marked as ExtensionActivity class */ class ExtensionActivity : AppCompatActivity(), MavericksView, ActionPermission { - @OptIn(InternalMavericksApi::class) private val viewModel: MainActivityViewModel by activityViewModel() private val navController by lazy { findNavController(R.id.nav_host_fragment) } @@ -91,23 +90,29 @@ class ExtensionActivity : AppCompatActivity(), MavericksView, ActionPermission { } } - private fun configure() = withState(viewModel) { - val graph = navController.navInflater.inflate(R.navigation.nav_share_extension) - graph.setStartDestination(R.id.nav_extension) - navController.graph = graph + private fun configure() = + withState(viewModel) { + val graph = navController.navInflater.inflate(R.navigation.nav_share_extension) + graph.setStartDestination(R.id.nav_extension) + navController.graph = graph - actionBarController = ActionBarController(findViewById(R.id.toolbar)) - actionBarController.setupActionBar(this, navController) - } + actionBarController = ActionBarController(findViewById(R.id.toolbar)) + actionBarController.setupActionBar(this, navController) + } override fun onSupportNavigateUp(): Boolean { return if (navController.currentDestination?.id == R.id.nav_browse_extension) { finish() false - } else navController.navigateUp() + } else { + navController.navigateUp() + } } - private fun handleFiles(intent: Intent, isMultipleFiles: Boolean) { + private fun handleFiles( + intent: Intent, + isMultipleFiles: Boolean, + ) { if (!isMultipleFiles) { (intent.getParcelableExtra(Intent.EXTRA_STREAM) as? Uri)?.let { saveShareData(arrayOf(it.toString())) @@ -136,65 +141,74 @@ class ExtensionActivity : AppCompatActivity(), MavericksView, ActionPermission { editor.apply() } - override fun invalidate() = withState(viewModel) { state -> - if (viewModel.readPermission) { - if (state.requiresReLogin && state.isOnline) { - showAlertPrompt( - resources.getString(R.string.auth_signed_out_title), - resources.getString(R.string.auth_signed_out_subtitle), - resources.getString(R.string.auth_basic_sign_in_button), - resources.getString(R.string.sign_out_confirmation_negative), - AlertType.TYPE_SIGN_OUT, - ) - } else if (!state.isOnline) { - showAlertPrompt( - resources.getString(R.string.auth_internet_unavailable_title), - resources.getString(R.string.auth_internet_unavailable_subtitle), - resources.getString(R.string.auth_internet_unavailable_ok_button), - null, - AlertType.TYPE_INTERNET_UNAVAILABLE, - ) + override fun invalidate() = + withState(viewModel) { state -> + if (viewModel.readPermission) { + if (state.requiresReLogin && state.isOnline) { + showAlertPrompt( + resources.getString(R.string.auth_signed_out_title), + resources.getString(R.string.auth_signed_out_subtitle), + resources.getString(R.string.auth_basic_sign_in_button), + resources.getString(R.string.sign_out_confirmation_negative), + AlertType.TYPE_SIGN_OUT, + ) + } else if (!state.isOnline) { + showAlertPrompt( + resources.getString(R.string.auth_internet_unavailable_title), + resources.getString(R.string.auth_internet_unavailable_subtitle), + resources.getString(R.string.auth_internet_unavailable_ok_button), + null, + AlertType.TYPE_INTERNET_UNAVAILABLE, + ) + } } } - } - private fun showAlertPrompt(title: String, message: String, positive: String, negative: String? = null, type: AlertType) { + private fun showAlertPrompt( + title: String, + message: String, + positive: String, + negative: String? = null, + type: AlertType, + ) { val oldDialog = alertDialog.get() if (oldDialog != null && oldDialog.isShowing) return val dialog: AlertDialog if (negative != null) { - dialog = MaterialAlertDialogBuilder(this) - .setTitle(title) - .setMessage(message) - .setCancelable(false) - .setNegativeButton(negative, null) - .setPositiveButton(positive) { _, _ -> - when (type) { - AlertType.TYPE_SIGN_OUT -> navigateToReLogin() - else -> { - Logger.d(getString(R.string.no_type)) + dialog = + MaterialAlertDialogBuilder(this) + .setTitle(title) + .setMessage(message) + .setCancelable(false) + .setNegativeButton(negative, null) + .setPositiveButton(positive) { _, _ -> + when (type) { + AlertType.TYPE_SIGN_OUT -> navigateToReLogin() + else -> { + Logger.d(getString(R.string.no_type)) + } } } - } - .show() + .show() } else { - dialog = MaterialAlertDialogBuilder(this) - .setTitle(title) - .setMessage(message) - .setCancelable(false) - .setPositiveButton(positive) { _, _ -> - when (type) { - AlertType.TYPE_NO_LOGIN -> { - startActivity(Intent(this, LoginActivity::class.java)) - finish() - } - AlertType.TYPE_INTERNET_UNAVAILABLE -> finish() - else -> { - Logger.d(getString(R.string.no_type)) + dialog = + MaterialAlertDialogBuilder(this) + .setTitle(title) + .setMessage(message) + .setCancelable(false) + .setPositiveButton(positive) { _, _ -> + when (type) { + AlertType.TYPE_NO_LOGIN -> { + startActivity(Intent(this, LoginActivity::class.java)) + finish() + } + AlertType.TYPE_INTERNET_UNAVAILABLE -> finish() + else -> { + Logger.d(getString(R.string.no_type)) + } } } - } - .show() + .show() } alertDialog = WeakReference(dialog) } diff --git a/app/src/main/java/com/alfresco/content/app/activity/LoginActivity.kt b/app/src/main/java/com/alfresco/content/app/activity/LoginActivity.kt index fe43b4496..134729653 100644 --- a/app/src/main/java/com/alfresco/content/app/activity/LoginActivity.kt +++ b/app/src/main/java/com/alfresco/content/app/activity/LoginActivity.kt @@ -18,8 +18,12 @@ import com.alfresco.content.session.Session import kotlinx.coroutines.launch class LoginActivity : com.alfresco.auth.activity.LoginActivity() { - - override fun onCredentials(credentials: Credentials, endpoint: String, authConfig: AuthConfig, isExtension: Boolean) { + override fun onCredentials( + credentials: Credentials, + endpoint: String, + authConfig: AuthConfig, + isExtension: Boolean, + ) { val account = Account(credentials.username, credentials.authState, credentials.authType, authConfig.jsonSerialize(), endpoint) val context = applicationContext @@ -42,7 +46,13 @@ class LoginActivity : com.alfresco.auth.activity.LoginActivity() { } } - private fun processAccountInformation(person: Person, myFiles: String, credentials: Credentials, authConfig: AuthConfig, endpoint: String) { + private fun processAccountInformation( + person: Person, + myFiles: String, + credentials: Credentials, + authConfig: AuthConfig, + endpoint: String, + ) { if (!viewModel.isReLogin) { Account.createAccount( this, diff --git a/app/src/main/java/com/alfresco/content/app/activity/MainActivity.kt b/app/src/main/java/com/alfresco/content/app/activity/MainActivity.kt index 6dd0c1558..9396cce4d 100644 --- a/app/src/main/java/com/alfresco/content/app/activity/MainActivity.kt +++ b/app/src/main/java/com/alfresco/content/app/activity/MainActivity.kt @@ -65,7 +65,6 @@ import java.lang.ref.WeakReference * Marked as MainActivity class */ class MainActivity : AppCompatActivity(), MavericksView, ActionMode.Callback { - @OptIn(InternalMavericksApi::class) private val viewModel: MainActivityViewModel by activityViewModel() private val navController by lazy { findNavController(R.id.nav_host_fragment) } @@ -128,11 +127,12 @@ class MainActivity : AppCompatActivity(), MavericksView, ActionMode.Callback { } private fun navigateTo(mode: MainActivityViewModel.NavigationMode) { - val data = Triple( - intent.extras?.getString(ID_KEY, "") ?: "", - intent.extras?.getString(MODE_KEY, "") ?: "", - "Preview", - ) + val data = + Triple( + intent.extras?.getString(ID_KEY, "") ?: "", + intent.extras?.getString(MODE_KEY, "") ?: "", + "Preview", + ) when (mode) { MainActivityViewModel.NavigationMode.FOLDER -> { @@ -150,13 +150,13 @@ class MainActivity : AppCompatActivity(), MavericksView, ActionMode.Callback { } } - override fun onNewIntent(intent: Intent?) { + override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) this.intent = intent isNewIntent = true viewModel.handleDataIntent( - intent?.extras?.getString(MODE_KEY, ""), - intent?.extras?.getBoolean(KEY_FOLDER, false) ?: false, + intent.extras?.getString(MODE_KEY, ""), + intent.extras?.getBoolean(KEY_FOLDER, false) ?: false, ) } @@ -189,53 +189,63 @@ class MainActivity : AppCompatActivity(), MavericksView, ActionMode.Callback { finish() } - private fun configure() = withState(viewModel) { state -> - navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment - val graph = navController.navInflater.inflate(R.navigation.nav_bottom) - graph.setStartDestination(if (state.isOnline) R.id.nav_recents else R.id.nav_offline) - navController.graph = graph + private fun configure() = + withState(viewModel) { state -> + navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment + val graph = navController.navInflater.inflate(R.navigation.nav_bottom) + graph.setStartDestination(if (state.isOnline) R.id.nav_recents else R.id.nav_offline) + navController.graph = graph - val appBarConfiguration = AppBarConfiguration(bottomNav.menu) - actionBarController = ActionBarController(findViewById(R.id.toolbar)) - actionBarController?.setupActionBar(this, navController, appBarConfiguration) + val appBarConfiguration = AppBarConfiguration(bottomNav.menu) + actionBarController = ActionBarController(findViewById(R.id.toolbar)) + actionBarController?.setupActionBar(this, navController, appBarConfiguration) - bottomNav.setupWithNavController(navController) + bottomNav.setupWithNavController(navController) - setupActionToasts() - setupDownloadNotifications() + setupActionToasts() + setupDownloadNotifications() - bottomNav.setOnItemSelectedListener { item -> - // In order to get the expected behavior, you have to call default Navigation method manually - NavigationUI.onNavDestinationSelected(item, navController) - true + bottomNav.setOnItemSelectedListener { item -> + // In order to get the expected behavior, you have to call default Navigation method manually + NavigationUI.onNavDestinationSelected(item, navController) + true + } } - } - override fun invalidate() = withState(viewModel) { state -> + override fun invalidate() = + withState(viewModel) { state -> - if (state.requiresReLogin) { - if (state.isOnline) { - showSignedOutPrompt() + if (state.requiresReLogin) { + if (state.isOnline) { + showSignedOutPrompt() + } + } else { + viewModel.checkIfAPSEnabled() + // Only when logged in otherwise triggers re-login prompts + actionBarController?.setProfileIcon(viewModel.profileIcon) + } + if (actionBarController != null) { + actionBarController?.setOnline(state.isOnline) } - } else { - viewModel.checkIfAPSEnabled() - // Only when logged in otherwise triggers re-login prompts - actionBarController?.setProfileIcon(viewModel.profileIcon) - } - if (actionBarController != null) { - actionBarController?.setOnline(state.isOnline) } - } override fun onSupportNavigateUp(): Boolean = navController.navigateUp() private fun showSignedOutPrompt() { val oldDialog = signedOutDialog.get() if (oldDialog != null && oldDialog.isShowing) return - val dialog = MaterialAlertDialogBuilder(this).setTitle(resources.getString(R.string.auth_signed_out_title)).setMessage(resources.getString(R.string.auth_signed_out_subtitle)) - .setNegativeButton(resources.getString(R.string.sign_out_confirmation_negative), null).setPositiveButton(resources.getString(R.string.auth_basic_sign_in_button)) { _, _ -> - navigateToReLogin() - }.show() + val dialog = + MaterialAlertDialogBuilder( + this, + ).setTitle( + resources.getString(R.string.auth_signed_out_title), + ).setMessage(resources.getString(R.string.auth_signed_out_subtitle)) + .setNegativeButton( + resources.getString(R.string.sign_out_confirmation_negative), + null, + ).setPositiveButton(resources.getString(R.string.auth_basic_sign_in_button)) { _, _ -> + navigateToReLogin() + }.show() signedOutDialog = WeakReference(dialog) } @@ -253,13 +263,17 @@ class MainActivity : AppCompatActivity(), MavericksView, ActionMode.Callback { startActivity(i) } - private fun setupActionToasts() = Action.showActionToasts( - lifecycleScope, - findViewById(android.R.id.content), - bottomNav, - ) + private fun setupActionToasts() = + Action.showActionToasts( + lifecycleScope, + findViewById(android.R.id.content), + bottomNav, + ) - private fun setupDownloadNotifications() = DownloadMonitor.smallIcon(R.drawable.ic_notification_small).tint(primaryColor(this)).observe(this) + private fun setupDownloadNotifications() = + DownloadMonitor.smallIcon( + R.drawable.ic_notification_small, + ).tint(primaryColor(this)).observe(this) private fun primaryColor(context: Context) = context.getColorForAttribute(R.attr.colorPrimary) @@ -269,14 +283,20 @@ class MainActivity : AppCompatActivity(), MavericksView, ActionMode.Callback { } viewModel.entriesMultiSelection = selectedEntries val title = SpannableString(getString(R.string.title_action_mode, selectedEntries.size)) - title.setSpan(ForegroundColorSpan(ContextCompat.getColor(this, R.color.colorActionMode)), 0, title.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + title.setSpan( + ForegroundColorSpan(ContextCompat.getColor(this, R.color.colorActionMode)), + 0, + title.length, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE, + ) actionMode?.title = title - val isMultiActionsEnabled = CommonRepository().isAllMultiActionsEnabled( - mobileConfigDataEntry?.featuresMobile?.menus, - viewModel.entriesMultiSelection, - ) + val isMultiActionsEnabled = + CommonRepository().isAllMultiActionsEnabled( + mobileConfigDataEntry?.featuresMobile?.menus, + viewModel.entriesMultiSelection, + ) actionMode?.menu?.findItem(R.id.move)?.isEnabled = isMultiActionsEnabled actionMode?.menu?.findItem(R.id.more_vert)?.isEnabled = isMultiActionsEnabled @@ -301,7 +321,10 @@ class MainActivity : AppCompatActivity(), MavericksView, ActionMode.Callback { actionMode = null } - override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean { + override fun onCreateActionMode( + mode: ActionMode?, + menu: Menu?, + ): Boolean { mode?.apply { val inflater: MenuInflater = menuInflater inflater.inflate(R.menu.menu_action_multi_selection, menu) @@ -310,19 +333,27 @@ class MainActivity : AppCompatActivity(), MavericksView, ActionMode.Callback { return false } - override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean { + override fun onPrepareActionMode( + mode: ActionMode?, + menu: Menu?, + ): Boolean { if ((viewModel.path.isNotEmpty() && viewModel.path == getString(com.alfresco.content.browse.R.string.nav_path_trash)) || navHostFragment?.navController?.currentDestination?.id == R.id.nav_offline ) { menu?.findItem(R.id.move)?.isVisible = false } - menu?.findItem(R.id.move)?.isEnabled = CommonRepository().isActionEnabled(MenuActions.Move, mobileConfigDataEntry?.featuresMobile?.menus) + menu?.findItem( + R.id.move, + )?.isEnabled = CommonRepository().isActionEnabled(MenuActions.Move, mobileConfigDataEntry?.featuresMobile?.menus) return true } - override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean { + override fun onActionItemClicked( + mode: ActionMode?, + item: MenuItem?, + ): Boolean { when (item?.itemId) { R.id.move -> { withState(viewModel) { state -> @@ -348,13 +379,14 @@ class MainActivity : AppCompatActivity(), MavericksView, ActionMode.Callback { disableMultiSelection() } - private fun showCreateSheet() = withState(viewModel) { - ContextualActionsSheet.with( - ContextualActionData.withEntries( - viewModel.entriesMultiSelection, - true, - mobileConfigData = mobileConfigDataEntry, - ), - ).show(supportFragmentManager, null) - } + private fun showCreateSheet() = + withState(viewModel) { + ContextualActionsSheet.with( + ContextualActionData.withEntries( + viewModel.entriesMultiSelection, + true, + mobileConfigData = mobileConfigDataEntry, + ), + ).show(supportFragmentManager, null) + } } diff --git a/app/src/main/java/com/alfresco/content/app/activity/MainActivityViewModel.kt b/app/src/main/java/com/alfresco/content/app/activity/MainActivityViewModel.kt index 008c558ac..71621144f 100644 --- a/app/src/main/java/com/alfresco/content/app/activity/MainActivityViewModel.kt +++ b/app/src/main/java/com/alfresco/content/app/activity/MainActivityViewModel.kt @@ -48,7 +48,8 @@ import kotlinx.coroutines.isActive import kotlinx.coroutines.launch data class MainActivityState( - val reLoginCount: Int = 0, // new state on each invalid auth + // new state on each invalid auth + val reLoginCount: Int = 0, val requiresReLogin: Boolean = false, val isOnline: Boolean = true, ) : MavericksState @@ -58,7 +59,6 @@ class MainActivityViewModel( private val appContext: Context, private val activityContext: Context, ) : MavericksViewModel(state), LifecycleObserver { - private val processLifecycleOwner = ProcessLifecycleOwner.get() private var refreshTicketJob: Job? = null private var syncService: SyncService? = null @@ -86,7 +86,10 @@ class MainActivityViewModel( } } - private fun init(context: Context, session: Session) { + private fun init( + context: Context, + session: Session, + ) { AnalyticsManager(session).appLaunch() session.onSignedOut { TaskRepository().clearAPSData() @@ -145,7 +148,10 @@ class MainActivityViewModel( OfflineRepository(session).removeCompletedUploads() } - private fun configureSync(context: Context, coroutineScope: CoroutineScope) = SyncService(context, coroutineScope).also { service -> + private fun configureSync( + context: Context, + coroutineScope: CoroutineScope, + ) = SyncService(context, coroutineScope).also { service -> coroutineScope.on { service.sync() } coroutineScope.on { service.sync() } coroutineScope.on { service.upload() } @@ -169,38 +175,47 @@ class MainActivityViewModel( private fun refreshTicket() { refreshTicketJob?.cancel() - refreshTicketJob = viewModelScope.launch { - var success = false - while (!success && isActive) { - try { - val session = SessionManager.currentSession ?: return@launch - session.ticket = AuthenticationRepository().fetchTicket() - success = true - if (!mode.isNullOrEmpty() && mode.equals(REMOTE)) { - if (!isFolder) { - _navigationMode.value = NavigationMode.FILE - } else _navigationMode.value = NavigationMode.FOLDER + refreshTicketJob = + viewModelScope.launch { + var success = false + while (!success && isActive) { + try { + val session = SessionManager.currentSession ?: return@launch + session.ticket = AuthenticationRepository().fetchTicket() + success = true + if (!mode.isNullOrEmpty() && mode.equals(REMOTE)) { + if (!isFolder) { + _navigationMode.value = NavigationMode.FILE + } else { + _navigationMode.value = NavigationMode.FOLDER + } + } + } catch (_: Exception) { + delay(60 * 1000L) } - } catch (_: Exception) { - delay(60 * 1000L) } + syncService?.uploadIfNeeded() + syncService?.syncIfNeeded() } - syncService?.uploadIfNeeded() - syncService?.syncIfNeeded() - } } /** * Mark as NavigationMode enum */ enum class NavigationMode { - FOLDER, FILE, LOGIN, DEFAULT + FOLDER, + FILE, + LOGIN, + DEFAULT, } /** * it will handle the intent which will come from the shareable link. */ - fun handleDataIntent(mode: String?, isFolder: Boolean) { + fun handleDataIntent( + mode: String?, + isFolder: Boolean, + ) { this.mode = mode this.isFolder = isFolder when (mode) { @@ -211,7 +226,9 @@ class MainActivityViewModel( VALUE_REMOTE -> { if (requiresLogin) { _navigationMode.value = NavigationMode.LOGIN - } else _navigationMode.value = NavigationMode.DEFAULT + } else { + _navigationMode.value = NavigationMode.DEFAULT + } } else -> _navigationMode.value = NavigationMode.DEFAULT @@ -224,11 +241,9 @@ class MainActivityViewModel( } } - private fun execute(action: Action) = - action.executeMulti(activityContext, GlobalScope) + private fun execute(action: Action) = action.executeMulti(activityContext, GlobalScope) companion object : MavericksViewModelFactory { - override fun create( viewModelContext: ViewModelContext, state: MainActivityState, diff --git a/app/src/main/java/com/alfresco/content/app/activity/MoveActivity.kt b/app/src/main/java/com/alfresco/content/app/activity/MoveActivity.kt index aac5dfb98..cdca91a49 100644 --- a/app/src/main/java/com/alfresco/content/app/activity/MoveActivity.kt +++ b/app/src/main/java/com/alfresco/content/app/activity/MoveActivity.kt @@ -26,7 +26,6 @@ import java.lang.ref.WeakReference * Marked as MoveActivity class */ class MoveActivity : AppCompatActivity(), MavericksView { - @OptIn(InternalMavericksApi::class) private val viewModel: MainActivityViewModel by activityViewModel() private val navController by lazy { findNavController(R.id.nav_host_fragment) } @@ -53,11 +52,12 @@ class MoveActivity : AppCompatActivity(), MavericksView { private fun configure() { val graph = navController.navInflater.inflate(R.navigation.nav_move_paths) graph.setStartDestination(R.id.nav_move) - val bundle = Bundle().apply { - if (entryObj != null) { - putParcelable(ENTRY_OBJ_KEY, entryObj) + val bundle = + Bundle().apply { + if (entryObj != null) { + putParcelable(ENTRY_OBJ_KEY, entryObj) + } } - } navController.setGraph(graph, bundle) setupActionToasts() actionBarController = ActionBarController(findViewById(R.id.toolbar)) @@ -68,33 +68,37 @@ class MoveActivity : AppCompatActivity(), MavericksView { return if (navController.currentDestination?.id == R.id.nav_browse_move) { finish() false - } else navController.navigateUp() + } else { + navController.navigateUp() + } } - override fun invalidate() = withState(viewModel) { state -> - if (state.requiresReLogin) { - if (state.isOnline) { - showSignedOutPrompt() + override fun invalidate() = + withState(viewModel) { state -> + if (state.requiresReLogin) { + if (state.isOnline) { + showSignedOutPrompt() + } + } else { + // Only when logged in otherwise triggers re-login prompts + actionBarController.setProfileIcon(viewModel.profileIcon) } - } else { - // Only when logged in otherwise triggers re-login prompts - actionBarController.setProfileIcon(viewModel.profileIcon) - } - actionBarController.setOnline(state.isOnline) - } + actionBarController.setOnline(state.isOnline) + } private fun showSignedOutPrompt() { val oldDialog = signedOutDialog.get() if (oldDialog != null && oldDialog.isShowing) return - val dialog = MaterialAlertDialogBuilder(this) - .setTitle(resources.getString(R.string.auth_signed_out_title)) - .setMessage(resources.getString(R.string.auth_signed_out_subtitle)) - .setNegativeButton(resources.getString(R.string.sign_out_confirmation_negative), null) - .setPositiveButton(resources.getString(R.string.auth_basic_sign_in_button)) { _, _ -> - navigateToReLogin() - } - .show() + val dialog = + MaterialAlertDialogBuilder(this) + .setTitle(resources.getString(R.string.auth_signed_out_title)) + .setMessage(resources.getString(R.string.auth_signed_out_subtitle)) + .setNegativeButton(resources.getString(R.string.sign_out_confirmation_negative), null) + .setPositiveButton(resources.getString(R.string.auth_basic_sign_in_button)) { _, _ -> + navigateToReLogin() + } + .show() signedOutDialog = WeakReference(dialog) } @@ -109,11 +113,12 @@ class MoveActivity : AppCompatActivity(), MavericksView { startActivity(i) } - private fun setupActionToasts() = Action.showActionToasts( - lifecycleScope, - findViewById(android.R.id.content), - bottomView, - ) + private fun setupActionToasts() = + Action.showActionToasts( + lifecycleScope, + findViewById(android.R.id.content), + bottomView, + ) override fun onBackPressed() { onSupportNavigateUp() diff --git a/app/src/main/java/com/alfresco/content/app/activity/ProcessActivity.kt b/app/src/main/java/com/alfresco/content/app/activity/ProcessActivity.kt index 640c5222f..3ad53de02 100644 --- a/app/src/main/java/com/alfresco/content/app/activity/ProcessActivity.kt +++ b/app/src/main/java/com/alfresco/content/app/activity/ProcessActivity.kt @@ -13,7 +13,6 @@ import com.alfresco.content.app.widget.ActionBarLayout import com.alfresco.content.common.BaseActivity class ProcessActivity : BaseActivity(), MavericksView { - private lateinit var binding: ActivityProcessBinding private lateinit var actionBarController: ActionBarController @@ -44,11 +43,12 @@ class ProcessActivity : BaseActivity(), MavericksView { actionBarLayout.toolbar.setNavigationOnClickListener { onBackPressed() } } - private fun setupActionToasts() = Action.showActionToasts( - lifecycleScope, - binding.parentView, - binding.bottomView, - ) + private fun setupActionToasts() = + Action.showActionToasts( + lifecycleScope, + binding.parentView, + binding.bottomView, + ) override fun invalidate() { } diff --git a/app/src/main/java/com/alfresco/content/app/activity/SplashActivity.kt b/app/src/main/java/com/alfresco/content/app/activity/SplashActivity.kt index 0c00b45c7..c134d45a6 100644 --- a/app/src/main/java/com/alfresco/content/app/activity/SplashActivity.kt +++ b/app/src/main/java/com/alfresco/content/app/activity/SplashActivity.kt @@ -4,7 +4,6 @@ import android.content.Intent import android.os.Bundle class SplashActivity : com.alfresco.ui.SplashActivity() { - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) } diff --git a/app/src/main/java/com/alfresco/content/app/activity/TaskViewerActivity.kt b/app/src/main/java/com/alfresco/content/app/activity/TaskViewerActivity.kt index 792b4712b..50fd67eda 100644 --- a/app/src/main/java/com/alfresco/content/app/activity/TaskViewerActivity.kt +++ b/app/src/main/java/com/alfresco/content/app/activity/TaskViewerActivity.kt @@ -23,7 +23,6 @@ import java.lang.ref.WeakReference * Marked as TaskViewerActivity class */ class TaskViewerActivity : BaseActivity(), MavericksView { - private lateinit var binding: ActivityTaskViewerBinding @OptIn(InternalMavericksApi::class) @@ -47,23 +46,25 @@ class TaskViewerActivity : BaseActivity(), MavericksView { navController.setGraph(graph, intent.extras) } - override fun invalidate() = withState(viewModel) { state -> - if (state.requiresReLogin) { - if (state.isOnline) { - showSignedOutPrompt() + override fun invalidate() = + withState(viewModel) { state -> + if (state.requiresReLogin) { + if (state.isOnline) { + showSignedOutPrompt() + } } } - } private fun showSignedOutPrompt() { val oldDialog = signedOutDialog.get() if (oldDialog != null && oldDialog.isShowing) return - val dialog = MaterialAlertDialogBuilder(this).setTitle(resources.getString(com.alfresco.content.app.R.string.auth_signed_out_title)) - .setMessage(resources.getString(com.alfresco.content.app.R.string.auth_signed_out_subtitle)) - .setNegativeButton(resources.getString(com.alfresco.content.app.R.string.sign_out_confirmation_negative), null) - .setPositiveButton(resources.getString(com.alfresco.content.app.R.string.auth_basic_sign_in_button)) { _, _ -> - navigateToReLogin() - }.show() + val dialog = + MaterialAlertDialogBuilder(this).setTitle(resources.getString(com.alfresco.content.app.R.string.auth_signed_out_title)) + .setMessage(resources.getString(com.alfresco.content.app.R.string.auth_signed_out_subtitle)) + .setNegativeButton(resources.getString(com.alfresco.content.app.R.string.sign_out_confirmation_negative), null) + .setPositiveButton(resources.getString(com.alfresco.content.app.R.string.auth_basic_sign_in_button)) { _, _ -> + navigateToReLogin() + }.show() signedOutDialog = WeakReference(dialog) } @@ -81,9 +82,10 @@ class TaskViewerActivity : BaseActivity(), MavericksView { startActivity(i) } - private fun setupActionToasts() = Action.showActionToasts( - lifecycleScope, - binding.root, - binding.bottomView, - ) + private fun setupActionToasts() = + Action.showActionToasts( + lifecycleScope, + binding.root, + binding.bottomView, + ) } diff --git a/app/src/main/java/com/alfresco/content/app/fragment/SettingsFragment.kt b/app/src/main/java/com/alfresco/content/app/fragment/SettingsFragment.kt index 627c78571..d2d3c63c9 100644 --- a/app/src/main/java/com/alfresco/content/app/fragment/SettingsFragment.kt +++ b/app/src/main/java/com/alfresco/content/app/fragment/SettingsFragment.kt @@ -1,8 +1,11 @@ package com.alfresco.content.app.fragment +import android.app.Activity import android.content.Intent import android.os.Bundle import android.view.View +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts import androidx.core.content.pm.PackageInfoCompat import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat @@ -24,13 +27,28 @@ import java.lang.ref.WeakReference @Suppress("unused") class SettingsFragment : PreferenceFragmentCompat() { + private lateinit var logoutActivityLauncher: ActivityResultLauncher override fun onStart() { super.onStart() requireActivity().title = resources.getString(R.string.nav_title_settings) } - override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + // Initialize the launcher in onCreate() + logoutActivityLauncher = + registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> + if (result.resultCode == Activity.RESULT_OK) { + deleteAccount() + } + } + } + + override fun onCreatePreferences( + savedInstanceState: Bundle?, + rootKey: String?, + ) { setPreferencesFromResource(R.xml.settings, rootKey) val acc = SessionManager.requireSession.account @@ -43,9 +61,10 @@ class SettingsFragment : PreferenceFragmentCompat() { error(R.drawable.ic_transparent) transformations(CircleCropTransformation()) } - onSignOutClickListener = View.OnClickListener { - showSignOutConfirmation() - } + onSignOutClickListener = + View.OnClickListener { + showSignOutConfirmation() + } } preferenceScreen.findPreference(resources.getString(R.string.pref_version_key))?.apply { @@ -74,16 +93,7 @@ class SettingsFragment : PreferenceFragmentCompat() { i.putExtra(LogoutViewModel.EXTRA_AUTH_TYPE, acc.authType) i.putExtra(LogoutViewModel.EXTRA_AUTH_CONFIG, acc.authConfig) i.putExtra(LogoutViewModel.EXTRA_AUTH_STATE, acc.authState) - startActivityForResult(i, REQUEST_CODE_LOGOUT) - } - - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - if (requestCode == REQUEST_CODE_LOGOUT) { - // no-op - deleteAccount() - } else { - super.onActivityResult(requestCode, resultCode, data) - } + logoutActivityLauncher.launch(i) } private fun deleteAccount() { @@ -102,8 +112,4 @@ class SettingsFragment : PreferenceFragmentCompat() { activity.finish() } } - - companion object { - const val REQUEST_CODE_LOGOUT = 0 - } } diff --git a/app/src/main/java/com/alfresco/content/app/widget/AccountPreference.kt b/app/src/main/java/com/alfresco/content/app/widget/AccountPreference.kt index 01df8d80a..b1e261fc9 100644 --- a/app/src/main/java/com/alfresco/content/app/widget/AccountPreference.kt +++ b/app/src/main/java/com/alfresco/content/app/widget/AccountPreference.kt @@ -10,7 +10,6 @@ import com.alfresco.content.app.R import com.google.android.material.button.MaterialButton class AccountPreference(context: Context, attrs: AttributeSet?) : Preference(context, attrs) { - private lateinit var signOutButton: MaterialButton private lateinit var parentUserInfo: LinearLayout var onSignOutClickListener: View.OnClickListener? = null diff --git a/app/src/main/java/com/alfresco/content/app/widget/ActionBarController.kt b/app/src/main/java/com/alfresco/content/app/widget/ActionBarController.kt index 61e66c0f6..6a478a080 100644 --- a/app/src/main/java/com/alfresco/content/app/widget/ActionBarController.kt +++ b/app/src/main/java/com/alfresco/content/app/widget/ActionBarController.kt @@ -11,13 +11,15 @@ import com.alfresco.content.app.R import com.alfresco.content.slideTop class ActionBarController(private val layout: ActionBarLayout) { - private lateinit var navController: NavController /** * setup the actionbar without appbar */ - fun setupActionBar(activity: AppCompatActivity, navController: NavController) { + fun setupActionBar( + activity: AppCompatActivity, + navController: NavController, + ) { this.navController = navController activity.setSupportActionBar(layout.toolbar) @@ -26,7 +28,11 @@ class ActionBarController(private val layout: ActionBarLayout) { layout.expand(false) } - fun setupActionBar(activity: AppCompatActivity, navController: NavController, appBarConfiguration: AppBarConfiguration) { + fun setupActionBar( + activity: AppCompatActivity, + navController: NavController, + appBarConfiguration: AppBarConfiguration, + ) { this.navController = navController activity.setSupportActionBar(layout.toolbar) @@ -41,10 +47,11 @@ class ActionBarController(private val layout: ActionBarLayout) { } navController.addOnDestinationChangedListener { _, destination, _ -> - val isTopLevelDestination = matchDestinations( - destination, - appBarConfiguration.topLevelDestinations, - ) + val isTopLevelDestination = + matchDestinations( + destination, + appBarConfiguration.topLevelDestinations, + ) if (isTopLevelDestination) { layout.collapse(false, destination.label == layout.context.getString(R.string.nav_title_tasks)) diff --git a/app/src/main/java/com/alfresco/content/app/widget/ActionBarLayout.kt b/app/src/main/java/com/alfresco/content/app/widget/ActionBarLayout.kt index 6f74ef57a..9a23dbe83 100644 --- a/app/src/main/java/com/alfresco/content/app/widget/ActionBarLayout.kt +++ b/app/src/main/java/com/alfresco/content/app/widget/ActionBarLayout.kt @@ -15,7 +15,6 @@ import com.alfresco.content.app.R import com.alfresco.ui.MaterialShapeView class ActionBarLayout(context: Context, attrs: AttributeSet?) : FrameLayout(context, attrs) { - lateinit var toolbar: Toolbar lateinit var taskToolbar: Toolbar lateinit var background: MaterialShapeView @@ -63,7 +62,10 @@ class ActionBarLayout(context: Context, attrs: AttributeSet?) : FrameLayout(cont expandedView.visibility = View.VISIBLE } - fun collapse(animated: Boolean, isTaskScreen: Boolean = false) { + fun collapse( + animated: Boolean, + isTaskScreen: Boolean = false, + ) { if (animated) { TransitionManager.beginDelayedTransition(this, makeTransition()) } diff --git a/app/src/main/java/com/alfresco/content/app/widget/ProfileIconView.kt b/app/src/main/java/com/alfresco/content/app/widget/ProfileIconView.kt index cb9b2c577..a25928355 100644 --- a/app/src/main/java/com/alfresco/content/app/widget/ProfileIconView.kt +++ b/app/src/main/java/com/alfresco/content/app/widget/ProfileIconView.kt @@ -19,7 +19,6 @@ class ProfileIconView( defStyleAttr: Int, defStyleRes: Int, ) : ConstraintLayout(context, attrs, defStyleAttr, defStyleRes) { - private val binding = ViewProfileIconBinding.inflate(LayoutInflater.from(context), this) init { diff --git a/app/src/test/java/com/alfresco/content/data/AppConfigUtilsKtTest.kt b/app/src/test/java/com/alfresco/content/data/AppConfigUtilsKtTest.kt index 9b3244a24..d158681d1 100644 --- a/app/src/test/java/com/alfresco/content/data/AppConfigUtilsKtTest.kt +++ b/app/src/test/java/com/alfresco/content/data/AppConfigUtilsKtTest.kt @@ -5,7 +5,6 @@ import org.junit.Test import java.util.concurrent.TimeUnit internal class AppConfigUtilsKtTest { - @Test fun previous_time_passed() { val previousFetchTime = TimeUnit.HOURS.toMillis(25) diff --git a/auth/build.gradle b/auth/build.gradle index b500d5a40..aa2caaccd 100644 --- a/auth/build.gradle +++ b/auth/build.gradle @@ -21,12 +21,11 @@ dependencies { implementation libs.kotlin.stdlib api libs.alfresco.auth - implementation libs.google.material implementation libs.androidx.appcompat implementation libs.androidx.core - implementation libs.androidx.constraintlayout + implementation libs.constraintlayout implementation libs.androidx.lifecycle.viewmodel implementation libs.androidx.lifecycle.runtime diff --git a/auth/src/main/kotlin/com/alfresco/auth/activity/LoginActivity.kt b/auth/src/main/kotlin/com/alfresco/auth/activity/LoginActivity.kt index b5fd9213b..d99218914 100644 --- a/auth/src/main/kotlin/com/alfresco/auth/activity/LoginActivity.kt +++ b/auth/src/main/kotlin/com/alfresco/auth/activity/LoginActivity.kt @@ -31,7 +31,6 @@ import com.google.android.material.snackbar.Snackbar import java.util.ArrayDeque abstract class LoginActivity : AuthenticationActivity() { - override val viewModel: LoginViewModel by lazy { getViewModel { LoginViewModel.with(applicationContext, intent) @@ -105,7 +104,12 @@ abstract class LoginActivity : AuthenticationActivity() { } } - abstract fun onCredentials(credentials: Credentials, endpoint: String, authConfig: AuthConfig, isExtension: Boolean) + abstract fun onCredentials( + credentials: Credentials, + endpoint: String, + authConfig: AuthConfig, + isExtension: Boolean, + ) override fun onCredentials(credentials: Credentials) { onCredentials(credentials, viewModel.canonicalApplicationUrl, viewModel.authConfig, viewModel.isExtension) @@ -128,15 +132,21 @@ abstract class LoginActivity : AuthenticationActivity() { } } - protected fun onError(@StringRes messageResId: Int) { + protected fun onError( + @StringRes messageResId: Int, + ) { onError(resources.getString(messageResId)) } - private fun showSettings(@Suppress("UNUSED_PARAMETER") value: Int) { + private fun showSettings( + @Suppress("UNUSED_PARAMETER") value: Int, + ) { AdvancedSettingsFragment.with(this).display() } - private fun showHelp(@StringRes msgResId: Int) { + private fun showHelp( + @StringRes msgResId: Int, + ) { HelpFragment.with(this).message(msgResId).show() } diff --git a/auth/src/main/kotlin/com/alfresco/auth/activity/LoginViewModel.kt b/auth/src/main/kotlin/com/alfresco/auth/activity/LoginViewModel.kt index 98179bfdb..bc0b4b4d1 100644 --- a/auth/src/main/kotlin/com/alfresco/auth/activity/LoginViewModel.kt +++ b/auth/src/main/kotlin/com/alfresco/auth/activity/LoginViewModel.kt @@ -27,7 +27,6 @@ class LoginViewModel( endpoint: String?, val isExtension: Boolean, ) : AuthenticationViewModel() { - lateinit var authConfig: AuthConfig override var context = applicationContext @@ -113,9 +112,10 @@ class LoginViewModel( when (authType) { AuthType.PKCE -> { viewModelScope.launch { - val isContentServicesInstalled = withContext(Dispatchers.IO) { - discoveryService.isContentServiceInstalled(identityUrl.value ?: "") - } + val isContentServicesInstalled = + withContext(Dispatchers.IO) { + discoveryService.isContentServiceInstalled(identityUrl.value ?: "") + } if (isContentServicesInstalled) { applicationUrl.value = identityUrl.value @@ -237,7 +237,9 @@ class LoginViewModel( _enabled.addSource(password, this::onFieldChange) } - private fun onFieldChange(@Suppress("UNUSED_PARAMETER") value: String) { + private fun onFieldChange( + @Suppress("UNUSED_PARAMETER") value: String, + ) { _enabled.value = !email.value.isNullOrEmpty() && !password.value.isNullOrEmpty() } @@ -258,7 +260,10 @@ class LoginViewModel( const val EXTRA_AUTH_STATE = "authState" const val EXTRA_AUTH_CONFIG = "authConfig" - fun with(context: Context, intent: Intent): LoginViewModel { + fun with( + context: Context, + intent: Intent, + ): LoginViewModel { var config: AuthConfig? = null var stateString: String? = null var authType: AuthType? = null @@ -267,11 +272,12 @@ class LoginViewModel( val extras = intent.extras if (extras != null) { - config = try { - AuthConfig.jsonDeserialize(extras.getString(EXTRA_AUTH_CONFIG)!!) - } catch (ex: Exception) { - null - } + config = + try { + AuthConfig.jsonDeserialize(extras.getString(EXTRA_AUTH_CONFIG)!!) + } catch (ex: Exception) { + null + } stateString = extras.getString(EXTRA_AUTH_STATE) endpoint = extras.getString(EXTRA_ENDPOINT) @@ -316,11 +322,15 @@ class LoginViewModel( port.value = if (https.value == true) DEFAULT_HTTPS_PORT else DEFAULT_HTTP_PORT } - private fun onChange(@Suppress("UNUSED_PARAMETER") value: Boolean) { + private fun onChange( + @Suppress("UNUSED_PARAMETER") value: Boolean, + ) { onChange() } - private fun onChange(@Suppress("UNUSED_PARAMETER") value: String) { + private fun onChange( + @Suppress("UNUSED_PARAMETER") value: String, + ) { onChange() } diff --git a/auth/src/main/kotlin/com/alfresco/auth/activity/LogoutActivity.kt b/auth/src/main/kotlin/com/alfresco/auth/activity/LogoutActivity.kt index 0a71a19f9..c1e7a0a22 100644 --- a/auth/src/main/kotlin/com/alfresco/auth/activity/LogoutActivity.kt +++ b/auth/src/main/kotlin/com/alfresco/auth/activity/LogoutActivity.kt @@ -11,28 +11,31 @@ import org.json.JSONException class LogoutViewModel(context: Context, authType: AuthType?, authState: String, authConfig: AuthConfig) : EndSessionViewModel(context, authType, authState, authConfig) { - companion object { const val EXTRA_AUTH_TYPE = "authType" const val EXTRA_AUTH_STATE = "authState" const val EXTRA_AUTH_CONFIG = "authConfig" - fun with(context: Context, bundle: Bundle?): LogoutViewModel { + fun with( + context: Context, + bundle: Bundle?, + ): LogoutViewModel { requireNotNull(bundle) val stateString = bundle.getString(EXTRA_AUTH_STATE) val configString = bundle.getString(EXTRA_AUTH_CONFIG) val authType = bundle.getString(EXTRA_AUTH_TYPE)?.let { AuthType.fromValue(it) } - val config = try { - if (configString != null) { - AuthConfig.jsonDeserialize(configString) - } else { + val config = + try { + if (configString != null) { + AuthConfig.jsonDeserialize(configString) + } else { + null + } + } catch (ex: JSONException) { null } - } catch (ex: JSONException) { - null - } requireNotNull(stateString) requireNotNull(config) @@ -43,7 +46,6 @@ class LogoutViewModel(context: Context, authType: AuthType?, authState: String, } class LogoutActivity : EndSessionActivity() { - override val viewModel: LogoutViewModel by lazy { getViewModel { LogoutViewModel.with(applicationContext, intent.extras) diff --git a/auth/src/main/kotlin/com/alfresco/auth/config/DefaultAuthConfig.kt b/auth/src/main/kotlin/com/alfresco/auth/config/DefaultAuthConfig.kt index 733e3d5c0..05d895615 100644 --- a/auth/src/main/kotlin/com/alfresco/auth/config/DefaultAuthConfig.kt +++ b/auth/src/main/kotlin/com/alfresco/auth/config/DefaultAuthConfig.kt @@ -3,11 +3,12 @@ package com.alfresco.auth.config import com.alfresco.auth.AuthConfig val AuthConfig.Companion.defaultConfig: AuthConfig - get() = AuthConfig( - https = true, - port = "443", - clientId = "alfresco-android-acs-app", - realm = "alfresco", - redirectUrl = "androidacsapp://aims/auth", - contentServicePath = "alfresco", - ) + get() = + AuthConfig( + https = true, + port = "443", + clientId = "alfresco-android-acs-app", + realm = "alfresco", + redirectUrl = "androidacsapp://aims/auth", + contentServicePath = "alfresco", + ) diff --git a/auth/src/main/kotlin/com/alfresco/auth/fragments/AdvancedSettingsFragment.kt b/auth/src/main/kotlin/com/alfresco/auth/fragments/AdvancedSettingsFragment.kt index 8d3e322a6..3dad136bf 100644 --- a/auth/src/main/kotlin/com/alfresco/auth/fragments/AdvancedSettingsFragment.kt +++ b/auth/src/main/kotlin/com/alfresco/auth/fragments/AdvancedSettingsFragment.kt @@ -20,7 +20,6 @@ import com.alfresco.common.FragmentBuilder import com.google.android.material.snackbar.Snackbar class AdvancedSettingsFragment : DialogFragment() { - private val viewModel: LoginViewModel by activityViewModels() private val rootView: View get() = requireView() @@ -51,7 +50,9 @@ class AdvancedSettingsFragment : DialogFragment() { observe(viewModel.onSaveSettings, ::onSave) } - private fun onSave(@Suppress("UNUSED_PARAMETER") value: Int) { + private fun onSave( + @Suppress("UNUSED_PARAMETER") value: Int, + ) { Snackbar.make( rootView, R.string.auth_settings_prompt_success, @@ -59,7 +60,10 @@ class AdvancedSettingsFragment : DialogFragment() { ).show() } - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + override fun onCreateOptionsMenu( + menu: Menu, + inflater: MenuInflater, + ) { inflater.inflate(R.menu.options_auth_settings, menu) } diff --git a/auth/src/main/kotlin/com/alfresco/auth/fragments/BasicAuthFragment.kt b/auth/src/main/kotlin/com/alfresco/auth/fragments/BasicAuthFragment.kt index b11c3db05..4a0314a13 100644 --- a/auth/src/main/kotlin/com/alfresco/auth/fragments/BasicAuthFragment.kt +++ b/auth/src/main/kotlin/com/alfresco/auth/fragments/BasicAuthFragment.kt @@ -15,7 +15,6 @@ import com.alfresco.auth.activity.LoginViewModel import com.alfresco.common.FragmentBuilder class BasicAuthFragment : DialogFragment() { - private val viewModel: LoginViewModel by activityViewModels() private var withCloud: Boolean = false diff --git a/auth/src/main/kotlin/com/alfresco/auth/fragments/HelpFragment.kt b/auth/src/main/kotlin/com/alfresco/auth/fragments/HelpFragment.kt index 5b48c7e19..b76985d9a 100644 --- a/auth/src/main/kotlin/com/alfresco/auth/fragments/HelpFragment.kt +++ b/auth/src/main/kotlin/com/alfresco/auth/fragments/HelpFragment.kt @@ -14,10 +14,13 @@ import com.alfresco.common.FragmentBuilder import com.alfresco.ui.BottomSheetDialogFragment class HelpFragment : BottomSheetDialogFragment() { - override val requiresFullscreen = true - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View? { return inflater.inflate(R.layout.fragment_auth_help, container, false) } @@ -47,7 +50,9 @@ class HelpFragment : BottomSheetDialogFragment() { return fragment } - fun message(@StringRes msgResId: Int): Builder { + fun message( + @StringRes msgResId: Int, + ): Builder { extraConfiguration.putInt(ARG_MESSAGE_RES_ID, msgResId) return this } diff --git a/auth/src/main/kotlin/com/alfresco/auth/fragments/InputIdentityFragment.kt b/auth/src/main/kotlin/com/alfresco/auth/fragments/InputIdentityFragment.kt index 0fe2a7722..dda42bf2a 100644 --- a/auth/src/main/kotlin/com/alfresco/auth/fragments/InputIdentityFragment.kt +++ b/auth/src/main/kotlin/com/alfresco/auth/fragments/InputIdentityFragment.kt @@ -15,7 +15,6 @@ import com.alfresco.auth.activity.LoginViewModel import com.alfresco.common.FragmentBuilder class InputIdentityFragment : DialogFragment() { - private val viewModel: LoginViewModel by activityViewModels() override fun onCreateView( @@ -23,7 +22,13 @@ class InputIdentityFragment : DialogFragment() { container: ViewGroup?, savedInstanceState: Bundle?, ): View { - val binding = DataBindingUtil.inflate(inflater, R.layout.container_auth_input_identity, container, false) + val binding = + DataBindingUtil.inflate( + inflater, + R.layout.container_auth_input_identity, + container, + false, + ) binding.viewModel = viewModel binding.lifecycleOwner = this return binding.root diff --git a/auth/src/main/kotlin/com/alfresco/auth/fragments/InputServerFragment.kt b/auth/src/main/kotlin/com/alfresco/auth/fragments/InputServerFragment.kt index 0efe59ed5..ff7dc1f75 100644 --- a/auth/src/main/kotlin/com/alfresco/auth/fragments/InputServerFragment.kt +++ b/auth/src/main/kotlin/com/alfresco/auth/fragments/InputServerFragment.kt @@ -15,7 +15,6 @@ import com.alfresco.auth.activity.LoginViewModel import com.alfresco.common.FragmentBuilder class InputServerFragment : DialogFragment() { - private val viewModel: LoginViewModel by activityViewModels() override fun onCreateView( @@ -23,7 +22,13 @@ class InputServerFragment : DialogFragment() { container: ViewGroup?, savedInstanceState: Bundle?, ): View { - val binding = DataBindingUtil.inflate(inflater, R.layout.container_auth_input_server, container, false) + val binding = + DataBindingUtil.inflate( + inflater, + R.layout.container_auth_input_server, + container, + false, + ) binding.viewModel = viewModel binding.lifecycleOwner = this return binding.root diff --git a/auth/src/main/kotlin/com/alfresco/auth/fragments/SignedOutFragment.kt b/auth/src/main/kotlin/com/alfresco/auth/fragments/SignedOutFragment.kt index db61efadf..d7b8ec4d1 100644 --- a/auth/src/main/kotlin/com/alfresco/auth/fragments/SignedOutFragment.kt +++ b/auth/src/main/kotlin/com/alfresco/auth/fragments/SignedOutFragment.kt @@ -28,7 +28,10 @@ class SignedOutFragment private constructor() { val TAG: String = SignedOutBottomSheet::class.java.name @JvmStatic - fun with(context: Context, adapter: SignedOutAdapter): DialogFragment { + fun with( + context: Context, + adapter: SignedOutAdapter, + ): DialogFragment { return if (context.resources.getBoolean(R.bool.isTablet)) { SignedOutDialog(adapter) } else { @@ -37,7 +40,12 @@ class SignedOutFragment private constructor() { } @JvmStatic - fun accountViewWith(fr: Fragment, container: ViewGroup, title: String, subtitle: String): View { + fun accountViewWith( + fr: Fragment, + container: ViewGroup, + title: String, + subtitle: String, + ): View { val view = fr.layoutInflater.inflate(R.layout.layout_auth_account_row, container, false) view.findViewById(R.id.title).text = title @@ -50,10 +58,23 @@ class SignedOutFragment private constructor() { } interface SignedOutAdapter { - fun onSignInButtonClicked(fragment: DialogFragment, view: View) - fun onAddAccountButtonClicked(fragment: DialogFragment, view: View) + fun onSignInButtonClicked( + fragment: DialogFragment, + view: View, + ) + + fun onAddAccountButtonClicked( + fragment: DialogFragment, + view: View, + ) + fun numberOfAccounts(): Int - fun viewForAccount(fragment: DialogFragment, container: ViewGroup, index: Int): View + + fun viewForAccount( + fragment: DialogFragment, + container: ViewGroup, + index: Int, + ): View } class SignedOutBottomSheet() : BottomSheetDialogFragment() { @@ -65,11 +86,18 @@ class SignedOutBottomSheet() : BottomSheetDialogFragment() { this.adapter = adapter } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View? { return inflater.inflate(R.layout.fragment_auth_signed_out, container, false) } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { super.onViewCreated(view, savedInstanceState) if (adapter != null) { @@ -111,11 +139,18 @@ class SignedOutDialog() : AppCompatDialogFragment() { this.adapter = adapter } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View? { return inflater.inflate(R.layout.fragment_auth_signed_out, container, false) } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { super.onViewCreated(view, savedInstanceState) if (adapter != null) { @@ -143,7 +178,11 @@ class SignedOutFragmentViewModel : ViewModel() { lateinit var adapter: SignedOutAdapter } -private fun onViewCreated(self: AppCompatDialogFragment, view: View, adapter: SignedOutAdapter) { +private fun onViewCreated( + self: AppCompatDialogFragment, + view: View, + adapter: SignedOutAdapter, +) { // Disable dismissing the dialog self.isCancelable = false diff --git a/auth/src/main/kotlin/com/alfresco/common/BaseViewModelFactory.kt b/auth/src/main/kotlin/com/alfresco/common/BaseViewModelFactory.kt index 4b4e6e31c..ce7cd4bee 100644 --- a/auth/src/main/kotlin/com/alfresco/common/BaseViewModelFactory.kt +++ b/auth/src/main/kotlin/com/alfresco/common/BaseViewModelFactory.kt @@ -15,13 +15,15 @@ class BaseViewModelFactory(val creator: () -> T) : ViewModelProvider.Factory inline fun Fragment.getViewModel(noinline creator: (() -> T)? = null): T { return if (creator == null) { ViewModelProvider(this).get(T::class.java) - } else + } else { ViewModelProvider(this, BaseViewModelFactory(creator)).get(T::class.java) + } } inline fun FragmentActivity.getViewModel(noinline creator: (() -> T)? = null): T { return if (creator == null) { ViewModelProvider(this).get(T::class.java) - } else + } else { ViewModelProvider(this, BaseViewModelFactory(creator)).get(T::class.java) + } } diff --git a/auth/src/main/kotlin/com/alfresco/ui/SplashActivity.kt b/auth/src/main/kotlin/com/alfresco/ui/SplashActivity.kt index c7afb2f5e..31c7c9fbd 100644 --- a/auth/src/main/kotlin/com/alfresco/ui/SplashActivity.kt +++ b/auth/src/main/kotlin/com/alfresco/ui/SplashActivity.kt @@ -17,7 +17,6 @@ import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch abstract class SplashActivity : AppCompatActivity() { - private val handler = Handler(Looper.getMainLooper()) private var entryId = "" private var isPreview = false diff --git a/auth/src/main/kotlin/com/alfresco/ui/components/Switch.kt b/auth/src/main/kotlin/com/alfresco/ui/components/Switch.kt index 3e99ca775..d1bfbe92c 100644 --- a/auth/src/main/kotlin/com/alfresco/ui/components/Switch.kt +++ b/auth/src/main/kotlin/com/alfresco/ui/components/Switch.kt @@ -11,7 +11,6 @@ import androidx.databinding.BindingAdapter * Subclass used only for data binding drag interaction */ class Switch(context: Context, attrs: AttributeSet?) : Switch(context, attrs) { - internal var touchAction: OnTouchEvent? = null @SuppressLint("ClickableViewAccessibility") @@ -23,10 +22,16 @@ class Switch(context: Context, attrs: AttributeSet?) : Switch(context, attrs) { } @BindingAdapter("android:onTouchEvent") -fun setOnTouchListener(switch: com.alfresco.ui.components.Switch, action: OnTouchEvent) { +fun setOnTouchListener( + switch: com.alfresco.ui.components.Switch, + action: OnTouchEvent, +) { switch.touchAction = action } interface OnTouchEvent { - fun onTouchEvent(switch: Switch, event: MotionEvent?) + fun onTouchEvent( + switch: Switch, + event: MotionEvent?, + ) } diff --git a/auth/src/main/kotlin/com/alfresco/ui/components/TextInputLayout.kt b/auth/src/main/kotlin/com/alfresco/ui/components/TextInputLayout.kt index 7d220169d..8a51ce7c1 100644 --- a/auth/src/main/kotlin/com/alfresco/ui/components/TextInputLayout.kt +++ b/auth/src/main/kotlin/com/alfresco/ui/components/TextInputLayout.kt @@ -12,38 +12,48 @@ import com.google.android.material.textfield.TextInputLayout class TextInputLayout(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : TextInputLayout(context, attrs, defStyleAttr) { - constructor(context: Context) : this(context, null) constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, R.attr.textInputStyle) - override fun addView(child: View, index: Int, params: ViewGroup.LayoutParams) { + override fun addView( + child: View, + index: Int, + params: ViewGroup.LayoutParams, + ) { if (child is EditText) { - child.addTextChangedListener(object : TextWatcher { - override fun afterTextChanged(s: Editable?) { - val editText = editText ?: return - - // Changing text programmatically incorrectly displays clear text icon - if (!editText.isFocused && endIconMode == END_ICON_CLEAR_TEXT) { - isEndIconVisible = false + child.addTextChangedListener( + object : TextWatcher { + override fun afterTextChanged(s: Editable?) { + val editText = editText ?: return + + // Changing text programmatically incorrectly displays clear text icon + if (!editText.isFocused && endIconMode == END_ICON_CLEAR_TEXT) { + isEndIconVisible = false + } } - } - override fun beforeTextChanged( - s: CharSequence?, - start: Int, - count: Int, - after: Int, - ) { - // no-op - } + override fun beforeTextChanged( + s: CharSequence?, + start: Int, + count: Int, + after: Int, + ) { + // no-op + } - override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { - // no-op - } - }) + override fun onTextChanged( + s: CharSequence?, + start: Int, + before: Int, + count: Int, + ) { + // no-op + } + }, + ) } super.addView(child, index, params) } diff --git a/base-ui/src/main/kotlin/com/alfresco/ui/BottomSheetDialogFragment.kt b/base-ui/src/main/kotlin/com/alfresco/ui/BottomSheetDialogFragment.kt index 50838582a..64c5dbcc1 100644 --- a/base-ui/src/main/kotlin/com/alfresco/ui/BottomSheetDialogFragment.kt +++ b/base-ui/src/main/kotlin/com/alfresco/ui/BottomSheetDialogFragment.kt @@ -7,7 +7,6 @@ import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialogFragment open class BottomSheetDialogFragment : BottomSheetDialogFragment() { - open val requiresFullscreen = false override fun onActivityCreated(savedInstanceState: Bundle?) { diff --git a/base-ui/src/main/kotlin/com/alfresco/ui/FabAwareScrollingBehavior.kt b/base-ui/src/main/kotlin/com/alfresco/ui/FabAwareScrollingBehavior.kt index 374ff600a..d074fcb47 100644 --- a/base-ui/src/main/kotlin/com/alfresco/ui/FabAwareScrollingBehavior.kt +++ b/base-ui/src/main/kotlin/com/alfresco/ui/FabAwareScrollingBehavior.kt @@ -12,10 +12,9 @@ class FabAwareScrollingBehavior( context: Context?, attrs: AttributeSet?, ) : CoordinatorLayout.Behavior( - context, - attrs, -) { - + context, + attrs, + ) { override fun layoutDependsOn( parent: CoordinatorLayout, child: View, diff --git a/base-ui/src/main/kotlin/com/alfresco/ui/MaterialShapeView.kt b/base-ui/src/main/kotlin/com/alfresco/ui/MaterialShapeView.kt index 86261db60..ce8379f04 100644 --- a/base-ui/src/main/kotlin/com/alfresco/ui/MaterialShapeView.kt +++ b/base-ui/src/main/kotlin/com/alfresco/ui/MaterialShapeView.kt @@ -17,7 +17,6 @@ class MaterialShapeView( defStyleAttr: Int, defStyleRes: Int, ) : FrameLayout(context, attrs, defStyleAttr, defStyleRes) { - private val background = MaterialShapeDrawable(context, attrs, defStyleAttr, defStyleRes) private var onElevation: ((Float) -> Unit)? = null @@ -61,12 +60,13 @@ class MaterialShapeView( ViewCompat.setBackground(this, background) // Load attributes - val attributes = context.obtainStyledAttributes( - attrs, - R.styleable.MaterialShapeView, - defStyleAttr, - defStyleRes, - ) + val attributes = + context.obtainStyledAttributes( + attrs, + R.styleable.MaterialShapeView, + defStyleAttr, + defStyleRes, + ) try { loadFromAttributes(attributes) diff --git a/base/src/main/kotlin/com/alfresco/Logger.kt b/base/src/main/kotlin/com/alfresco/Logger.kt index 0460e85f7..6858ac6ba 100644 --- a/base/src/main/kotlin/com/alfresco/Logger.kt +++ b/base/src/main/kotlin/com/alfresco/Logger.kt @@ -10,11 +10,18 @@ object Logger { } } - fun v(message: String, vararg args: Any?) { + fun v( + message: String, + vararg args: Any?, + ) { Timber.v(message, *args) } - fun v(t: Throwable, message: String, vararg args: Any?) { + fun v( + t: Throwable, + message: String, + vararg args: Any?, + ) { Timber.v(t, message, *args) } @@ -22,11 +29,18 @@ object Logger { Timber.v(t) } - fun d(message: String, vararg args: Any?) { + fun d( + message: String, + vararg args: Any?, + ) { Timber.d(message, *args) } - fun d(t: Throwable, message: String, vararg args: Any?) { + fun d( + t: Throwable, + message: String, + vararg args: Any?, + ) { Timber.d(t, message, *args) } @@ -34,11 +48,18 @@ object Logger { Timber.d(t) } - fun i(message: String, vararg args: Any?) { + fun i( + message: String, + vararg args: Any?, + ) { Timber.i(message, *args) } - fun i(t: Throwable, message: String, vararg args: Any?) { + fun i( + t: Throwable, + message: String, + vararg args: Any?, + ) { Timber.i(t, message, *args) } @@ -46,11 +67,18 @@ object Logger { Timber.i(t) } - fun w(message: String, vararg args: Any?) { + fun w( + message: String, + vararg args: Any?, + ) { Timber.w(message, *args) } - fun w(t: Throwable, message: String, vararg args: Any?) { + fun w( + t: Throwable, + message: String, + vararg args: Any?, + ) { Timber.w(t, message, *args) } @@ -58,11 +86,18 @@ object Logger { Timber.w(t) } - fun e(message: String, vararg args: Any?) { + fun e( + message: String, + vararg args: Any?, + ) { Timber.e(message, *args) } - fun e(t: Throwable, message: String, vararg args: Any?) { + fun e( + t: Throwable, + message: String, + vararg args: Any?, + ) { Timber.e(t, message, *args) } @@ -70,11 +105,18 @@ object Logger { Timber.e(t) } - fun wtf(message: String, vararg args: Any?) { + fun wtf( + message: String, + vararg args: Any?, + ) { Timber.wtf(message, *args) } - fun wtf(t: Throwable, message: String, vararg args: Any?) { + fun wtf( + t: Throwable, + message: String, + vararg args: Any?, + ) { Timber.wtf(t, message, *args) } @@ -87,7 +129,12 @@ object Logger { * DebugTree tailored for Timber being wrapped within another class. */ private class DebugTree : Timber.DebugTree() { - override fun log(priority: Int, tag: String?, message: String, t: Throwable?) { + override fun log( + priority: Int, + tag: String?, + message: String, + t: Throwable?, + ) { super.log(priority, createClassTag(), message, t) } diff --git a/base/src/main/kotlin/com/alfresco/coroutines/AsyncMap.kt b/base/src/main/kotlin/com/alfresco/coroutines/AsyncMap.kt index 0e45ce760..c4e156c5d 100644 --- a/base/src/main/kotlin/com/alfresco/coroutines/AsyncMap.kt +++ b/base/src/main/kotlin/com/alfresco/coroutines/AsyncMap.kt @@ -11,7 +11,10 @@ suspend fun Iterable.asyncMap(f: suspend (T) -> R): List = map { async { f(it) } }.awaitAll() } -suspend fun Iterable.asyncMap(limit: Int, f: suspend (T) -> R): List = +suspend fun Iterable.asyncMap( + limit: Int, + f: suspend (T) -> R, +): List = coroutineScope { Semaphore(limit).run { map { async { withPermit { f(it) } } }.awaitAll() @@ -23,7 +26,10 @@ suspend fun Iterable.asyncMapNotNull(f: suspend (T) -> R?): List = mapNotNull { async { f(it) } }.awaitAll().filterNotNull() } -suspend fun Iterable.asyncMapNotNull(limit: Int, f: suspend (T) -> R?): List = +suspend fun Iterable.asyncMapNotNull( + limit: Int, + f: suspend (T) -> R?, +): List = coroutineScope { Semaphore(limit).run { mapNotNull { async { withPermit { f(it) } } }.awaitAll().filterNotNull() diff --git a/base/src/main/kotlin/com/alfresco/coroutines/FlowExt.kt b/base/src/main/kotlin/com/alfresco/coroutines/FlowExt.kt index 139d81596..47ad6e0ea 100644 --- a/base/src/main/kotlin/com/alfresco/coroutines/FlowExt.kt +++ b/base/src/main/kotlin/com/alfresco/coroutines/FlowExt.kt @@ -3,14 +3,24 @@ package com.alfresco.coroutines import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow -fun (suspend (P) -> T).asFlow(p: P): Flow = flow { - emit(invoke(p)) -} +fun (suspend (P) -> T).asFlow(p: P): Flow = + flow { + emit(invoke(p)) + } -fun (suspend (P1, P2) -> T).asFlow(p1: P1, p2: P2): Flow = flow { - emit(invoke(p1, p2)) -} +fun (suspend (P1, P2) -> T).asFlow( + p1: P1, + p2: P2, +): Flow = + flow { + emit(invoke(p1, p2)) + } -fun (suspend (P1, P2, P3) -> T).asFlow(p1: P1, p2: P2, p3: P3): Flow = flow { - emit(invoke(p1, p2, p3)) -} +fun (suspend (P1, P2, P3) -> T).asFlow( + p1: P1, + p2: P2, + p3: P3, +): Flow = + flow { + emit(invoke(p1, p2, p3)) + } diff --git a/base/src/main/kotlin/com/alfresco/events/EventBus.kt b/base/src/main/kotlin/com/alfresco/events/EventBus.kt index f859eb57c..3648f71ba 100644 --- a/base/src/main/kotlin/com/alfresco/events/EventBus.kt +++ b/base/src/main/kotlin/com/alfresco/events/EventBus.kt @@ -11,7 +11,9 @@ import kotlin.coroutines.EmptyCoroutineContext class EventBus { val bus = MutableSharedFlow() - suspend fun send(obj: Any) { bus.emit(obj) } + suspend fun send(obj: Any) { + bus.emit(obj) + } inline fun on() = bus.filter { it is T }.map { it as T } diff --git a/base/src/main/kotlin/com/alfresco/kotlin/FilenameComparator.kt b/base/src/main/kotlin/com/alfresco/kotlin/FilenameComparator.kt index 5723d7620..4dcb49b49 100644 --- a/base/src/main/kotlin/com/alfresco/kotlin/FilenameComparator.kt +++ b/base/src/main/kotlin/com/alfresco/kotlin/FilenameComparator.kt @@ -1,16 +1,19 @@ package com.alfresco.kotlin object FilenameComparator : java.util.Comparator { - override fun compare(s1: String, s2: String): Int { + override fun compare( + s1: String, + s2: String, + ): Int { val r = base(s1).compareTo(base(s2), ignoreCase = true) return if (r != 0) { r - } else ext(s1).compareTo(ext(s2), ignoreCase = true) + } else { + ext(s1).compareTo(ext(s2), ignoreCase = true) + } } - private fun base(s: String) = - s.substringBeforeLast('.') + private fun base(s: String) = s.substringBeforeLast('.') - private fun ext(s: String) = - s.substringAfterLast('.', "") + private fun ext(s: String) = s.substringAfterLast('.', "") } diff --git a/base/src/main/kotlin/com/alfresco/kotlin/StringExt.kt b/base/src/main/kotlin/com/alfresco/kotlin/StringExt.kt index 38e29d09d..33d0d3104 100644 --- a/base/src/main/kotlin/com/alfresco/kotlin/StringExt.kt +++ b/base/src/main/kotlin/com/alfresco/kotlin/StringExt.kt @@ -7,30 +7,26 @@ import java.security.MessageDigest fun String.ellipsize(length: Int) = if (this.length <= length) { this - } else this.subSequence(0, length).toString().plus("\u2026") + } else { + this.subSequence(0, length).toString().plus("\u2026") + } fun String.isLocalPath() = with(Uri.parse(this).scheme) { this == null || this == "file" } -fun String.filename() = - Uri.parse(this).lastPathSegment +fun String.filename() = Uri.parse(this).lastPathSegment -fun String.parentFile(): File? = - File(Uri.parse(this).path ?: "").parentFile +fun String.parentFile(): File? = File(Uri.parse(this).path ?: "").parentFile -fun String.md5(): String = - hashString("MD5", this) +fun String.md5(): String = hashString("MD5", this) -fun String.sha1(): String = - hashString("SHA-1", this) +fun String.sha1(): String = hashString("SHA-1", this) -fun String.sha256(): String = - hashString("SHA-256", this) +fun String.sha256(): String = hashString("SHA-256", this) -fun String.sha512(): String = - hashString("SHA-512", this) +fun String.sha512(): String = hashString("SHA-512", this) /** * Supported algorithms on Android: @@ -43,11 +39,15 @@ fun String.sha512(): String = * SHA-384 1+ * SHA-512 1+ */ -private fun hashString(type: String, input: String): String { +private fun hashString( + type: String, + input: String, +): String { val hexChars = "0123456789ABCDEF" - val bytes = MessageDigest - .getInstance(type) - .digest(input.toByteArray()) + val bytes = + MessageDigest + .getInstance(type) + .digest(input.toByteArray()) val result = StringBuilder(bytes.size * 2) bytes.forEach { diff --git a/base/src/main/kotlin/com/alfresco/list/ListExt.kt b/base/src/main/kotlin/com/alfresco/list/ListExt.kt index ae46cd5d4..ef3bf8114 100644 --- a/base/src/main/kotlin/com/alfresco/list/ListExt.kt +++ b/base/src/main/kotlin/com/alfresco/list/ListExt.kt @@ -1,6 +1,9 @@ package com.alfresco.list -fun List.replace(newValue: T, block: (T) -> Boolean): List { +fun List.replace( + newValue: T, + block: (T) -> Boolean, +): List { return map { if (block(it)) newValue else it } diff --git a/browse/build.gradle b/browse/build.gradle index eee08ae6d..de9177beb 100644 --- a/browse/build.gradle +++ b/browse/build.gradle @@ -1,8 +1,9 @@ plugins{ - id('com.android.library') - id('kotlin-android') - id('kotlin-kapt') - id('kotlin-parcelize') + id 'com.android.library' + id 'kotlin-android' +// id('kotlin-kapt') + id 'kotlin-parcelize' + id 'com.google.devtools.ksp' } android { @@ -50,7 +51,7 @@ dependencies { implementation libs.mavericks implementation libs.epoxy.core - kapt libs.epoxy.processor + ksp libs.epoxy.processor // Testing testImplementation libs.junit @@ -59,7 +60,7 @@ dependencies { // Mockito // Coroutines test - testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.2' + testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.9.0' testImplementation libs.mavericks.testing diff --git a/browse/src/main/kotlin/com/alfresco/content/browse/BrowseFragment.kt b/browse/src/main/kotlin/com/alfresco/content/browse/BrowseFragment.kt index dd03897ba..332fdd798 100644 --- a/browse/src/main/kotlin/com/alfresco/content/browse/BrowseFragment.kt +++ b/browse/src/main/kotlin/com/alfresco/content/browse/BrowseFragment.kt @@ -72,7 +72,6 @@ data class BrowseArgs( } class BrowseFragment : ListFragment() { - private lateinit var args: BrowseArgs private var fab: FloatingActionButton? = null @@ -105,7 +104,10 @@ class BrowseFragment : ListFragment() { setViewRequiredMultiSelection(true) } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { super.onViewCreated(view, savedInstanceState) withState(viewModel) { state -> if (state.path == getString(R.string.nav_path_recents)) { @@ -114,35 +116,39 @@ class BrowseFragment : ListFragment() { bannerTransferData?.setOnClickListener { findNavController().navigateToUploadFilesPath(true, requireContext().getString(R.string.title_transfers)) } - } else bannerTransferData?.visibility = View.GONE + } else { + bannerTransferData?.visibility = View.GONE + } } } - override fun invalidate() = withState(viewModel) { state -> - super.invalidate() + override fun invalidate() = + withState(viewModel) { state -> + super.invalidate() - if (state.path == getString(R.string.nav_path_recents)) { - updateBanner(state.totalTransfersSize, state.uploadTransferList.size) - if (state.uploadTransferList.isEmpty()) { - viewModel.resetTransferData() + if (state.path == getString(R.string.nav_path_recents)) { + updateBanner(state.totalTransfersSize, state.uploadTransferList.size) + if (state.uploadTransferList.isEmpty()) { + viewModel.resetTransferData() + } } - } - state.title?.let { - (requireActivity() as AppCompatActivity).supportActionBar?.title = it - } + state.title?.let { + (requireActivity() as AppCompatActivity).supportActionBar?.title = it + } - if (viewModel.canAddItems(state) && fab == null) { - fab = makeFab(requireContext()) - (view as ViewGroup).addView(fab) - } + if (viewModel.canAddItems(state) && fab == null) { + fab = makeFab(requireContext()) + (view as ViewGroup).addView(fab) + } - fab?.visibility = if (state.selectedEntries.isNotEmpty()) { - View.GONE - } else { - View.VISIBLE + fab?.visibility = + if (state.selectedEntries.isNotEmpty()) { + View.GONE + } else { + View.VISIBLE + } } - } override fun onResume() { super.onResume() @@ -151,7 +157,10 @@ class BrowseFragment : ListFragment() { } } - private fun updateBanner(totalSize: Int, pendingFilesCount: Int) { + private fun updateBanner( + totalSize: Int, + pendingFilesCount: Int, + ) { if (totalSize != 0 && pendingFilesCount != 0) { bannerTransferData?.visibility = View.VISIBLE } @@ -161,10 +170,18 @@ class BrowseFragment : ListFragment() { if (pendingFilesCount != 0) { tvUploadingFiles?.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_upload, 0, 0, 0) - tvUploadingFiles?.text = String.format(getString(com.alfresco.content.listview.R.string.upload_file_text_multiple), pendingFilesCount) + tvUploadingFiles?.text = + String.format( + getString(com.alfresco.content.listview.R.string.upload_file_text_multiple), + pendingFilesCount, + ) } else { tvUploadingFiles?.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_upload_done, 0, 0, 0) - tvUploadingFiles?.text = String.format(getString(com.alfresco.content.listview.R.string.upload_complete_text_multiple), totalSize) + tvUploadingFiles?.text = + String.format( + getString(com.alfresco.content.listview.R.string.upload_complete_text_multiple), + totalSize, + ) } tvPercentage?.text = String.format(getString(com.alfresco.content.listview.R.string.upload_percentage_text), percentage) @@ -190,7 +207,10 @@ class BrowseFragment : ListFragment() { }, millis) } - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + override fun onCreateOptionsMenu( + menu: Menu, + inflater: MenuInflater, + ) { inflater.inflate(R.menu.menu_browse, menu) } @@ -217,8 +237,9 @@ class BrowseFragment : ListFragment() { entry.mimeType?.let { findNavController().navigateToLocalPreview(it, entry.path.toString(), entry.name) } - } else + } else { findNavController().navigateTo(entry) + } } override fun onItemLongClicked(entry: Entry) { @@ -230,17 +251,18 @@ class BrowseFragment : ListFragment() { private fun makeFab(context: Context) = FloatingActionButton(context).apply { - layoutParams = CoordinatorLayout.LayoutParams( - CoordinatorLayout.LayoutParams.WRAP_CONTENT, - CoordinatorLayout.LayoutParams.WRAP_CONTENT, - ).apply { - gravity = Gravity.BOTTOM or Gravity.END - // TODO: define margins - setMargins( - TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 16f, resources.displayMetrics) - .toInt(), - ) - } + layoutParams = + CoordinatorLayout.LayoutParams( + CoordinatorLayout.LayoutParams.WRAP_CONTENT, + CoordinatorLayout.LayoutParams.WRAP_CONTENT, + ).apply { + gravity = Gravity.BOTTOM or Gravity.END + // TODO: define margins + setMargins( + TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 16f, resources.displayMetrics) + .toInt(), + ) + } contentDescription = getString(R.string.accessibility_text_create_button) setImageResource(R.drawable.ic_add_fab) setOnClickListener { @@ -248,12 +270,12 @@ class BrowseFragment : ListFragment() { } } - private fun showCreateSheet() = withState(viewModel) { - CreateActionsSheet.with(requireNotNull(it.parent)).show(childFragmentManager, null) - } + private fun showCreateSheet() = + withState(viewModel) { + CreateActionsSheet.with(requireNotNull(it.parent)).show(childFragmentManager, null) + } companion object { - fun withArg(path: String): BrowseFragment { val fragment = BrowseFragment() fragment.arguments = BrowseArgs.bundle(path) diff --git a/browse/src/main/kotlin/com/alfresco/content/browse/BrowseViewModel.kt b/browse/src/main/kotlin/com/alfresco/content/browse/BrowseViewModel.kt index 5beefd752..35709b196 100644 --- a/browse/src/main/kotlin/com/alfresco/content/browse/BrowseViewModel.kt +++ b/browse/src/main/kotlin/com/alfresco/content/browse/BrowseViewModel.kt @@ -47,7 +47,6 @@ class BrowseViewModel( private val browseRepository: BrowseRepository, private val offlineRepository: OfflineRepository, ) : ListViewModel(state) { - private var observeUploadsJob: Job? = null private var observeTransferUploadsJob: Job? = null @@ -92,7 +91,10 @@ class BrowseViewModel( } } - private fun removeDataEntry(entry: Entry, entries: List) { + private fun removeDataEntry( + entry: Entry, + entries: List, + ) { if (entries.isNotEmpty()) { entries.forEach { entryObj -> removeEntry(entryObj) @@ -109,7 +111,10 @@ class BrowseViewModel( when (state.path) { context.getString(R.string.nav_path_recents) -> AnalyticsManager().screenViewEvent(PageView.Recent) context.getString(R.string.nav_path_favorites) -> AnalyticsManager().screenViewEvent(PageView.Favorites) - context.getString(R.string.nav_path_extension) -> AnalyticsManager().screenViewEvent(PageView.ShareExtension, noOfFiles = browseRepository.getExtensionDataList().size) + context.getString( + R.string.nav_path_extension, + ), + -> AnalyticsManager().screenViewEvent(PageView.ShareExtension, noOfFiles = browseRepository.getExtensionDataList().size) } } @@ -133,75 +138,86 @@ class BrowseViewModel( override fun refresh() = fetchInitial() - override fun fetchNextPage() = withState { state -> - val path = state.path - val nodeId = state.nodeId - val skipCount = state.baseEntries.count() - - viewModelScope.launch { - loadResults( - path, - nodeId, - skipCount, - ITEMS_PER_PAGE, - ).execute { - when (it) { - is Loading -> copy(request = Loading()) - is Fail -> copy(request = Fail(it.error)) - is Success -> { - update(it()).copy(request = Success(it())) - } - - else -> { - this + override fun fetchNextPage() = + withState { state -> + val path = state.path + val nodeId = state.nodeId + val skipCount = state.baseEntries.count() + + viewModelScope.launch { + loadResults( + path, + nodeId, + skipCount, + ITEMS_PER_PAGE, + ).execute { + when (it) { + is Loading -> copy(request = Loading()) + is Fail -> copy(request = Fail(it.error)) + is Success -> { + update(it()).copy(request = Success(it())) + } + + else -> { + this + } } } } } - } - private fun fetchInitial() = withState { state -> - viewModelScope.launch { - // Fetch children and folder information - loadResults( - state.path, - state.nodeId, - 0, - ITEMS_PER_PAGE, - ).zip( - fetchNode(state.path, state.nodeId), - ) { paging, parent -> - Pair(paging, parent) - }.execute { - when (it) { - is Loading -> copy(request = Loading()) - is Fail -> copy(request = Fail(it.error)) - is Success -> { - observeUploads(it().second?.id) - update(it().first).copy(parent = it().second, request = Success(it().first)) - } - - else -> { - this + private fun fetchInitial() = + withState { state -> + viewModelScope.launch { + // Fetch children and folder information + loadResults( + state.path, + state.nodeId, + 0, + ITEMS_PER_PAGE, + ).zip( + fetchNode(state.path, state.nodeId), + ) { paging, parent -> + Pair(paging, parent) + }.execute { + when (it) { + is Loading -> copy(request = Loading()) + is Fail -> copy(request = Fail(it.error)) + is Success -> { + observeUploads(it().second?.id) + update(it().first).copy(parent = it().second, request = Success(it().first)) + } + + else -> { + this + } } } } } - } - private suspend fun fetchNode(path: String, item: String?): Flow = if (item.isNullOrEmpty()) { - flowOf(null) - } else { - when (path) { - context.getString(R.string.nav_path_site) -> - BrowseRepository()::fetchLibraryDocumentsFolder.asFlow(item) + private suspend fun fetchNode( + path: String, + item: String?, + ): Flow = + if (item.isNullOrEmpty()) { + flowOf(null) + } else { + when (path) { + context.getString(R.string.nav_path_site) -> + BrowseRepository()::fetchLibraryDocumentsFolder.asFlow(item) - else -> - BrowseRepository()::fetchEntry.asFlow(item) + else -> + BrowseRepository()::fetchEntry.asFlow(item) + } } - } - private suspend fun loadResults(path: String, item: String?, skipCount: Int, maxItems: Int): Flow { + private suspend fun loadResults( + path: String, + item: String?, + skipCount: Int, + maxItems: Int, + ): Flow { return when (path) { context.getString(R.string.nav_path_recents) -> { SearchRepository()::getRecents.asFlow(skipCount, maxItems) @@ -244,14 +260,15 @@ class BrowseViewModel( repo.removeCompletedUploads(nodeId) observeUploadsJob?.cancel() - observeUploadsJob = repo.observeUploads(nodeId) - .execute { - if (it is Success) { - updateUploads(it()) - } else { - this + observeUploadsJob = + repo.observeUploads(nodeId) + .execute { + if (it is Success) { + updateUploads(it()) + } else { + this + } } - } } /** @@ -259,14 +276,15 @@ class BrowseViewModel( */ fun observeTransferUploads() { observeTransferUploadsJob?.cancel() - observeTransferUploadsJob = OfflineRepository().observeTransferUploads() - .execute { - if (it is Success) { - updateTransferUploads(it()) - } else { - this + observeTransferUploadsJob = + OfflineRepository().observeTransferUploads() + .execute { + if (it is Success) { + updateTransferUploads(it()) + } else { + this + } } - } } /** @@ -319,44 +337,49 @@ class BrowseViewModel( } } - private fun execute(action: ActionExtension, list: List) = - action.execute(context, GlobalScope, list) - - private fun execute(action: Action) = - action.execute(context, GlobalScope) + private fun execute( + action: ActionExtension, + list: List, + ) = action.execute(context, GlobalScope, list) + + private fun execute(action: Action) = action.execute(context, GlobalScope) + + fun toggleSelection(entry: Entry) = + setState { + val hasReachedLimit = selectedEntries.size == MULTI_SELECTION_LIMIT + if (!entry.isSelectedForMultiSelection && hasReachedLimit) { + copy(maxLimitReachedForMultiSelection = true) + } else { + val updatedEntries = + entries.map { + if (it.id == entry.id && it.type != Entry.Type.GROUP) { + it.copy(isSelectedForMultiSelection = !it.isSelectedForMultiSelection) + } else { + it + } + } + copy( + baseEntries = updatedEntries.filter { it.type != Entry.Type.GROUP }, + entries = updatedEntries, + selectedEntries = updatedEntries.filter { it.isSelectedForMultiSelection }, + maxLimitReachedForMultiSelection = false, + ) + } + } - fun toggleSelection(entry: Entry) = setState { - val hasReachedLimit = selectedEntries.size == MULTI_SELECTION_LIMIT - if (!entry.isSelectedForMultiSelection && hasReachedLimit) { - copy(maxLimitReachedForMultiSelection = true) - } else { - val updatedEntries = entries.map { - if (it.id == entry.id && it.type != Entry.Type.GROUP) { - it.copy(isSelectedForMultiSelection = !it.isSelectedForMultiSelection) - } else { - it + fun resetMultiSelection() = + setState { + val resetMultiEntries = + entries.map { + it.copy(isSelectedForMultiSelection = false) } - } copy( - baseEntries = updatedEntries.filter { it.type != Entry.Type.GROUP }, - entries = updatedEntries, - selectedEntries = updatedEntries.filter { it.isSelectedForMultiSelection }, + baseEntries = resetMultiEntries.filter { it.type != Entry.Type.GROUP }, + entries = resetMultiEntries, + selectedEntries = emptyList(), maxLimitReachedForMultiSelection = false, ) } - } - - fun resetMultiSelection() = setState { - val resetMultiEntries = entries.map { - it.copy(isSelectedForMultiSelection = false) - } - copy( - baseEntries = resetMultiEntries.filter { it.type != Entry.Type.GROUP }, - entries = resetMultiEntries, - selectedEntries = emptyList(), - maxLimitReachedForMultiSelection = false, - ) - } fun setSearchResult(entry: Entry) { CoroutineScope(Dispatchers.Main).launch { @@ -367,7 +390,6 @@ class BrowseViewModel( override fun resetMaxLimitError() = setState { copy(maxLimitReachedForMultiSelection = false) } companion object : MavericksViewModelFactory { - override fun create( viewModelContext: ViewModelContext, state: BrowseViewState, diff --git a/browse/src/main/kotlin/com/alfresco/content/browse/BrowseViewState.kt b/browse/src/main/kotlin/com/alfresco/content/browse/BrowseViewState.kt index 2d61b3977..d42ead980 100644 --- a/browse/src/main/kotlin/com/alfresco/content/browse/BrowseViewState.kt +++ b/browse/src/main/kotlin/com/alfresco/content/browse/BrowseViewState.kt @@ -29,35 +29,35 @@ data class BrowseViewState( val uploadTransferList: List = emptyList(), val totalTransfersSize: Int = 0, ) : ListViewState { - constructor(args: BrowseArgs) : this(args.path, args.id, args.moveId) override val isCompact: Boolean - get() = when (path) { - "site", "folder", "extension", "move", "my-libraries", "fav-libraries" -> true - else -> false - } + get() = + when (path) { + "site", "folder", "extension", "move", "my-libraries", "fav-libraries" -> true + else -> false + } - fun update( - response: ResponsePaging?, - ): BrowseViewState { + fun update(response: ResponsePaging?): BrowseViewState { if (response == null) return this val nextPage = response.pagination.skipCount > 0 - val listMoveIds = if (moveId.isNotEmpty() && moveId.contains(",")) { - moveId.split(",").toList() - } else { - listOf(moveId) - } + val listMoveIds = + if (moveId.isNotEmpty() && moveId.contains(",")) { + moveId.split(",").toList() + } else { + listOf(moveId) + } val pageEntries = response.entries.filterNot { listMoveIds.contains(it.id) } - val newEntries = if (nextPage) { - baseEntries + pageEntries - } else { - pageEntries - } + val newEntries = + if (nextPage) { + baseEntries + pageEntries + } else { + pageEntries + } return copyIncludingUploads(newEntries, uploads, response.pagination.hasMoreItems).copy(title = response.source?.name) } @@ -96,7 +96,11 @@ data class BrowseViewState( ) } - private fun mergeInUploads(base: List, uploads: List, includeRemaining: Boolean): List { + private fun mergeInUploads( + base: List, + uploads: List, + includeRemaining: Boolean, + ): List { return merge(base, uploads, includeRemainingRight = includeRemaining) { left: Entry, right: Entry -> if (left.isFolder || right.isFolder) { val cmp = right.isFolder.compareTo(left.isFolder) @@ -132,10 +136,11 @@ data class BrowseViewState( } val sortOrder: SortOrder - get() = when (path) { - "recents" -> SortOrder.ByModifiedDate // TODO: - else -> SortOrder.Default - } + get() = + when (path) { + "recents" -> SortOrder.ByModifiedDate // TODO: + else -> SortOrder.Default + } private fun defaultReducer(newEntries: List): BrowseViewState = copy( @@ -154,13 +159,14 @@ data class BrowseViewState( for (entry in newEntries) { val modified = entry.modified ?: startOfDay - val targetGroup = when { - modified >= startOfDay -> ModifiedGroup.Today - modified >= startOfYesterday -> ModifiedGroup.Yesterday - modified >= firstOfWeek -> ModifiedGroup.ThisWeek - modified >= firstOfLastWeek -> ModifiedGroup.LastWeek - else -> ModifiedGroup.Older - } + val targetGroup = + when { + modified >= startOfDay -> ModifiedGroup.Today + modified >= startOfYesterday -> ModifiedGroup.Yesterday + modified >= firstOfWeek -> ModifiedGroup.ThisWeek + modified >= firstOfLastWeek -> ModifiedGroup.LastWeek + else -> ModifiedGroup.Older + } if (currentGroup != targetGroup) { currentGroup = targetGroup diff --git a/browse/src/main/kotlin/com/alfresco/content/browse/FavoritesFragment.kt b/browse/src/main/kotlin/com/alfresco/content/browse/FavoritesFragment.kt index 9bd23cfe2..1eb8abf5d 100644 --- a/browse/src/main/kotlin/com/alfresco/content/browse/FavoritesFragment.kt +++ b/browse/src/main/kotlin/com/alfresco/content/browse/FavoritesFragment.kt @@ -17,7 +17,6 @@ import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch class FavoritesFragment : Fragment() { - private lateinit var pager: NonSwipeableViewPager private lateinit var pagerAdapter: PagerAdapter private var listFragments = mutableListOf() @@ -31,16 +30,20 @@ class FavoritesFragment : Fragment() { val view = inflater.inflate(R.layout.fragment_favorites, container, false) pager = view.findViewById(R.id.pager) tabs = view.findViewById(R.id.tabs) - listFragments = mutableListOf( - BrowseFragment.withArg(requireContext().getString(R.string.nav_path_favorites)), - BrowseFragment.withArg(requireContext().getString(R.string.nav_path_fav_libraries)), - ) + listFragments = + mutableListOf( + BrowseFragment.withArg(requireContext().getString(R.string.nav_path_favorites)), + BrowseFragment.withArg(requireContext().getString(R.string.nav_path_fav_libraries)), + ) pagerAdapter = PagerAdapter(requireContext(), childFragmentManager, listFragments) pager.adapter = pagerAdapter return view } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { super.onViewCreated(view, savedInstanceState) view.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_YES view.requestFocus() @@ -66,7 +69,6 @@ class FavoritesFragment : Fragment() { private class PagerAdapter(val context: Context, fragmentManager: FragmentManager, val listFragments: List) : FragmentPagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) { - override fun getItem(position: Int): Fragment { return when (position) { 0 -> listFragments[position] diff --git a/browse/src/main/kotlin/com/alfresco/content/browse/TaskProcessFragment.kt b/browse/src/main/kotlin/com/alfresco/content/browse/TaskProcessFragment.kt index d0ae8225f..f5c5e176c 100644 --- a/browse/src/main/kotlin/com/alfresco/content/browse/TaskProcessFragment.kt +++ b/browse/src/main/kotlin/com/alfresco/content/browse/TaskProcessFragment.kt @@ -16,7 +16,6 @@ import com.alfresco.content.browse.tasks.list.TasksFragment * Marked as TaskProcessFragment */ class TaskProcessFragment : Fragment() { - override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -28,7 +27,10 @@ class TaskProcessFragment : Fragment() { return view } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { super.onViewCreated(view, savedInstanceState) view.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_YES view.requestFocus() @@ -36,7 +38,6 @@ class TaskProcessFragment : Fragment() { private class PagerAdapter(val context: Context, fragmentManager: FragmentManager) : FragmentPagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) { - override fun getItem(position: Int): Fragment { return when (position) { 0 -> TasksFragment() diff --git a/browse/src/main/kotlin/com/alfresco/content/browse/menu/BrowseMenuFragment.kt b/browse/src/main/kotlin/com/alfresco/content/browse/menu/BrowseMenuFragment.kt index e33d35e11..e71aa201f 100644 --- a/browse/src/main/kotlin/com/alfresco/content/browse/menu/BrowseMenuFragment.kt +++ b/browse/src/main/kotlin/com/alfresco/content/browse/menu/BrowseMenuFragment.kt @@ -19,7 +19,6 @@ import com.alfresco.content.viewer.ViewerArgs.Companion.ID_KEY import com.alfresco.content.viewer.ViewerArgs.Companion.MODE_KEY class BrowseMenuFragment : Fragment(), MavericksView { - private val viewModel: BrowseMenuViewModel by fragmentViewModel() private lateinit var binding: FragmentBrowseMenuBinding @@ -32,7 +31,10 @@ class BrowseMenuFragment : Fragment(), MavericksView { return binding.root } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { super.onViewCreated(view, savedInstanceState) requireActivity().intent?.let { it.extras?.let { bundle -> @@ -50,30 +52,36 @@ class BrowseMenuFragment : Fragment(), MavericksView { } } - override fun invalidate() = withState(viewModel) { - binding.recyclerView.withModels { - it.entries.forEach { - if (it.path.isNotEmpty()) { - browseMenuRow { - id(it.title) - entry(it) - clickListener { _ -> navigateTo(it.path, it.title, it.pageView) } - } - } else { - browseMenuSeparator { - id(it.title) + override fun invalidate() = + withState(viewModel) { + binding.recyclerView.withModels { + it.entries.forEach { + if (it.path.isNotEmpty()) { + browseMenuRow { + id(it.title) + entry(it) + clickListener { _ -> navigateTo(it.path, it.title, it.pageView) } + } + } else { + browseMenuSeparator { + id(it.title) + } } } } } - } override fun onResume() { super.onResume() AnalyticsManager().screenViewEvent(PageView.Browse) } - private fun navigateTo(path: String, title: String, pageView: PageView, nodeId: String = viewModel.getMyFilesNodeId()) { + private fun navigateTo( + path: String, + title: String, + pageView: PageView, + nodeId: String = viewModel.getMyFilesNodeId(), + ) { AnalyticsManager().screenViewEvent(pageView) if (path == getString(R.string.nav_path_my_files)) { findNavController().navigateToFolder(nodeId, title) diff --git a/browse/src/main/kotlin/com/alfresco/content/browse/menu/BrowseMenuRow.kt b/browse/src/main/kotlin/com/alfresco/content/browse/menu/BrowseMenuRow.kt index 5d09cc2da..45946936d 100644 --- a/browse/src/main/kotlin/com/alfresco/content/browse/menu/BrowseMenuRow.kt +++ b/browse/src/main/kotlin/com/alfresco/content/browse/menu/BrowseMenuRow.kt @@ -11,24 +11,25 @@ import com.airbnb.epoxy.ModelView import com.alfresco.content.browse.databinding.ViewBrowseMenuRowBinding @ModelView(autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT) -class BrowseMenuRow @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0, -) : FrameLayout(context, attrs, defStyleAttr) { +class BrowseMenuRow + @JvmOverloads + constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + ) : FrameLayout(context, attrs, defStyleAttr) { + private val binding = ViewBrowseMenuRowBinding.inflate(LayoutInflater.from(context), this, true) - private val binding = ViewBrowseMenuRowBinding.inflate(LayoutInflater.from(context), this, true) + @ModelProp + fun setEntry(entry: MenuEntry) { + binding.title.text = entry.title + binding.icon.setImageDrawable( + ResourcesCompat.getDrawable(resources, entry.icon, context.theme), + ) + } - @ModelProp - fun setEntry(entry: MenuEntry) { - binding.title.text = entry.title - binding.icon.setImageDrawable( - ResourcesCompat.getDrawable(resources, entry.icon, context.theme), - ) + @CallbackProp + fun setClickListener(listener: OnClickListener?) { + setOnClickListener(listener) + } } - - @CallbackProp - fun setClickListener(listener: OnClickListener?) { - setOnClickListener(listener) - } -} diff --git a/browse/src/main/kotlin/com/alfresco/content/browse/menu/BrowseMenuSeparator.kt b/browse/src/main/kotlin/com/alfresco/content/browse/menu/BrowseMenuSeparator.kt index a01d8ebe6..4148d3641 100644 --- a/browse/src/main/kotlin/com/alfresco/content/browse/menu/BrowseMenuSeparator.kt +++ b/browse/src/main/kotlin/com/alfresco/content/browse/menu/BrowseMenuSeparator.kt @@ -8,13 +8,14 @@ import com.airbnb.epoxy.ModelView import com.alfresco.content.browse.R @ModelView(autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT) -class BrowseMenuSeparator @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0, -) : FrameLayout(context, attrs, defStyleAttr) { - - init { - LayoutInflater.from(context).inflate(R.layout.view_browse_menu_separator, this, true) +class BrowseMenuSeparator + @JvmOverloads + constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + ) : FrameLayout(context, attrs, defStyleAttr) { + init { + LayoutInflater.from(context).inflate(R.layout.view_browse_menu_separator, this, true) + } } -} diff --git a/browse/src/main/kotlin/com/alfresco/content/browse/menu/BrowseMenuViewModel.kt b/browse/src/main/kotlin/com/alfresco/content/browse/menu/BrowseMenuViewModel.kt index aa75453d1..12ad80277 100644 --- a/browse/src/main/kotlin/com/alfresco/content/browse/menu/BrowseMenuViewModel.kt +++ b/browse/src/main/kotlin/com/alfresco/content/browse/menu/BrowseMenuViewModel.kt @@ -15,7 +15,6 @@ class BrowseMenuViewModel( viewState: BrowseMenuViewState, val context: Context, ) : MavericksViewModel(viewState) { - init { val tiles = context.resources.getStringArray(R.array.browse_menu_titles) val icons = context.resources.getResourceList(R.array.browse_menu_icons) @@ -27,13 +26,14 @@ class BrowseMenuViewModel( fun getMyFilesNodeId() = BrowseRepository().myFilesNodeId - private fun getPageView(path: String): PageView = when (path) { - context.getString(R.string.browse_menu_personal) -> PageView.PersonalFiles - context.getString(R.string.browse_menu_my_libraries) -> PageView.MyLibraries - context.getString(R.string.browse_menu_shared) -> PageView.Shared - context.getString(R.string.browse_menu_trash) -> PageView.Trash - else -> PageView.None - } + private fun getPageView(path: String): PageView = + when (path) { + context.getString(R.string.browse_menu_personal) -> PageView.PersonalFiles + context.getString(R.string.browse_menu_my_libraries) -> PageView.MyLibraries + context.getString(R.string.browse_menu_shared) -> PageView.Shared + context.getString(R.string.browse_menu_trash) -> PageView.Trash + else -> PageView.None + } companion object : MavericksViewModelFactory { override fun create( @@ -43,7 +43,9 @@ class BrowseMenuViewModel( } } -private fun Resources.getResourceList(@ArrayRes id: Int): MutableList { +private fun Resources.getResourceList( + @ArrayRes id: Int, +): MutableList { val typedArray = this.obtainTypedArray(id) val list = typedArray.toResourceList() typedArray.recycle() diff --git a/browse/src/main/kotlin/com/alfresco/content/browse/offline/OfflineFragment.kt b/browse/src/main/kotlin/com/alfresco/content/browse/offline/OfflineFragment.kt index c9d6ff60b..b09c96f30 100644 --- a/browse/src/main/kotlin/com/alfresco/content/browse/offline/OfflineFragment.kt +++ b/browse/src/main/kotlin/com/alfresco/content/browse/offline/OfflineFragment.kt @@ -30,7 +30,6 @@ import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch class OfflineFragment : ListFragment() { - @OptIn(InternalMavericksApi::class) override val viewModel: OfflineViewModel by fragmentViewModelWithArgs { OfflineBrowseArgs.with(arguments) } private var fab: ExtendedFloatingActionButton? = null @@ -48,7 +47,10 @@ class OfflineFragment : ListFragment() { } } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { super.onViewCreated(view, savedInstanceState) setViewRequiredMultiSelection(true) } @@ -59,43 +61,47 @@ class OfflineFragment : ListFragment() { fab = null } - override fun invalidate() = withState(viewModel) { state -> - super.invalidate() + override fun invalidate() = + withState(viewModel) { state -> + super.invalidate() - // Add fab only to root folder - if (state.parentId == null && fab == null) { - fab = makeFab(requireContext()).apply { - visibility = View.INVISIBLE // required for animation + // Add fab only to root folder + if (state.parentId == null && fab == null) { + fab = + makeFab(requireContext()).apply { + visibility = View.INVISIBLE // required for animation + } + (view as ViewGroup).addView(fab) } - (view as ViewGroup).addView(fab) - } - fab?.apply { - if (state.entries.isNotEmpty()) { - show() - } else { - hide() - } + fab?.apply { + if (state.entries.isNotEmpty()) { + show() + } else { + hide() + } - isEnabled = state.syncNowEnabled - } + isEnabled = state.syncNowEnabled + } - fab?.visibility = if (state.selectedEntries.isNotEmpty()) { - View.GONE - } else { - View.VISIBLE + fab?.visibility = + if (state.selectedEntries.isNotEmpty()) { + View.GONE + } else { + View.VISIBLE + } } - } private fun makeFab(context: Context) = ExtendedFloatingActionButton(context).apply { - layoutParams = CoordinatorLayout.LayoutParams( - CoordinatorLayout.LayoutParams.WRAP_CONTENT, - CoordinatorLayout.LayoutParams.WRAP_CONTENT, - ).apply { - gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL - setMargins(0, 0, 0, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 16f, resources.displayMetrics).toInt()) - } + layoutParams = + CoordinatorLayout.LayoutParams( + CoordinatorLayout.LayoutParams.WRAP_CONTENT, + CoordinatorLayout.LayoutParams.WRAP_CONTENT, + ).apply { + gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL + setMargins(0, 0, 0, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 16f, resources.displayMetrics).toInt()) + } text = context.getText(R.string.offline_sync_button_title) gravity = Gravity.CENTER setOnClickListener { @@ -122,8 +128,7 @@ class OfflineFragment : ListFragment() { startSync(false) } - private fun startSync(overrideNetwork: Boolean) = - lifecycleScope.emit(ActionSyncNow(overrideNetwork)) + private fun startSync(overrideNetwork: Boolean) = lifecycleScope.emit(ActionSyncNow(overrideNetwork)) override fun onItemClicked(entry: Entry) { if (entry.isFolder || entry.isSynced) { diff --git a/browse/src/main/kotlin/com/alfresco/content/browse/offline/OfflineViewModel.kt b/browse/src/main/kotlin/com/alfresco/content/browse/offline/OfflineViewModel.kt index 9c442969f..b087bcf3c 100644 --- a/browse/src/main/kotlin/com/alfresco/content/browse/offline/OfflineViewModel.kt +++ b/browse/src/main/kotlin/com/alfresco/content/browse/offline/OfflineViewModel.kt @@ -22,7 +22,6 @@ class OfflineViewModel( state: OfflineViewState, val context: Context, ) : ListViewModel(state) { - init { AnalyticsManager().screenViewEvent(PageView.Offline) viewModelScope.launch { @@ -54,11 +53,12 @@ class OfflineViewModel( } } - override fun refresh() = withState { - // Faking a refresh since changes are updated via [observeDataChanges] - setState { copy(request = Loading()) } - setState { copy(request = it.request) } - } + override fun refresh() = + withState { + // Faking a refresh since changes are updated via [observeDataChanges] + setState { copy(request = Loading()) } + setState { copy(request = it.request) } + } override fun fetchNextPage() = Unit @@ -69,39 +69,44 @@ class OfflineViewModel( Settings(context).canSyncOverMeteredNetwork || !ConnectivityTracker.isActiveNetworkMetered(context) - fun toggleSelection(entry: Entry) = setState { - val hasReachedLimit = selectedEntries.size == MULTI_SELECTION_LIMIT - if (!entry.isSelectedForMultiSelection && hasReachedLimit) { - copy(maxLimitReachedForMultiSelection = true) - } else { - val updatedEntries = entries.map { - if (it.id == entry.id && it.type != Entry.Type.GROUP) { - it.copy(isSelectedForMultiSelection = !it.isSelectedForMultiSelection) - } else { - it - } + fun toggleSelection(entry: Entry) = + setState { + val hasReachedLimit = selectedEntries.size == MULTI_SELECTION_LIMIT + if (!entry.isSelectedForMultiSelection && hasReachedLimit) { + copy(maxLimitReachedForMultiSelection = true) + } else { + val updatedEntries = + entries.map { + if (it.id == entry.id && it.type != Entry.Type.GROUP) { + it.copy(isSelectedForMultiSelection = !it.isSelectedForMultiSelection) + } else { + it + } + } + copy( + entries = updatedEntries, + selectedEntries = updatedEntries.filter { it.isSelectedForMultiSelection }, + maxLimitReachedForMultiSelection = false, + ) } + } + + fun resetMultiSelection() = + setState { + val resetMultiEntries = + entries.map { + it.copy(isSelectedForMultiSelection = false) + } copy( - entries = updatedEntries, - selectedEntries = updatedEntries.filter { it.isSelectedForMultiSelection }, + entries = resetMultiEntries, + selectedEntries = emptyList(), maxLimitReachedForMultiSelection = false, ) } - } - fun resetMultiSelection() = setState { - val resetMultiEntries = entries.map { - it.copy(isSelectedForMultiSelection = false) - } - copy( - entries = resetMultiEntries, - selectedEntries = emptyList(), - maxLimitReachedForMultiSelection = false, - ) - } override fun resetMaxLimitError() = setState { copy(maxLimitReachedForMultiSelection = false) } - companion object : MavericksViewModelFactory { + companion object : MavericksViewModelFactory { override fun create( viewModelContext: ViewModelContext, state: OfflineViewState, diff --git a/browse/src/main/kotlin/com/alfresco/content/browse/offline/OfflineViewState.kt b/browse/src/main/kotlin/com/alfresco/content/browse/offline/OfflineViewState.kt index d9259e43c..ab482849c 100644 --- a/browse/src/main/kotlin/com/alfresco/content/browse/offline/OfflineViewState.kt +++ b/browse/src/main/kotlin/com/alfresco/content/browse/offline/OfflineViewState.kt @@ -15,7 +15,6 @@ data class OfflineViewState( override val maxLimitReachedForMultiSelection: Boolean = false, val syncNowEnabled: Boolean = false, ) : ListViewState { - constructor(args: OfflineBrowseArgs) : this(parentId = args.id) override val isCompact: Boolean @@ -28,16 +27,18 @@ data class OfflineViewState( val selectedEntriesMap = selectedEntries.associateBy { it.id } - val pageEntries = response.entries.map { entry -> - val isSelectedForMultiSelection = selectedEntriesMap[entry.id]?.isSelectedForMultiSelection ?: false - entry.copy(isSelectedForMultiSelection = isSelectedForMultiSelection) - }.toMutableList() - - val newEntries = if (nextPage) { - entries + pageEntries - } else { - pageEntries - } + val pageEntries = + response.entries.map { entry -> + val isSelectedForMultiSelection = selectedEntriesMap[entry.id]?.isSelectedForMultiSelection ?: false + entry.copy(isSelectedForMultiSelection = isSelectedForMultiSelection) + }.toMutableList() + + val newEntries = + if (nextPage) { + entries + pageEntries + } else { + pageEntries + } return copy(entries = newEntries, hasMoreItems = response.pagination.hasMoreItems) } diff --git a/browse/src/main/kotlin/com/alfresco/content/browse/preview/LocalPreviewActivity.kt b/browse/src/main/kotlin/com/alfresco/content/browse/preview/LocalPreviewActivity.kt index 9140abe80..0f022a0f6 100644 --- a/browse/src/main/kotlin/com/alfresco/content/browse/preview/LocalPreviewActivity.kt +++ b/browse/src/main/kotlin/com/alfresco/content/browse/preview/LocalPreviewActivity.kt @@ -25,11 +25,12 @@ class LocalPreviewActivity : AppCompatActivity() { supportActionBar?.setDisplayShowHomeEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true) - supportActionBar?.title = if (intent.extras?.containsKey(KEY_ENTRY_OBJ) == true) { - (intent.extras?.getParcelable(KEY_ENTRY_OBJ) as Entry?)?.name - } else { - intent.extras?.getString("title") - } + supportActionBar?.title = + if (intent.extras?.containsKey(KEY_ENTRY_OBJ) == true) { + (intent.extras?.getParcelable(KEY_ENTRY_OBJ) as Entry?)?.name + } else { + intent.extras?.getString("title") + } supportActionBar?.setHomeActionContentDescription(getString(R.string.label_navigation_back)) binding.toolbar.setNavigationOnClickListener { onBackPressed() } diff --git a/browse/src/main/kotlin/com/alfresco/content/browse/preview/LocalPreviewFragment.kt b/browse/src/main/kotlin/com/alfresco/content/browse/preview/LocalPreviewFragment.kt index bdb258ade..442921dd3 100644 --- a/browse/src/main/kotlin/com/alfresco/content/browse/preview/LocalPreviewFragment.kt +++ b/browse/src/main/kotlin/com/alfresco/content/browse/preview/LocalPreviewFragment.kt @@ -93,13 +93,19 @@ class LocalPreviewFragment : Fragment(), MavericksView { setHasOptionsMenu(true) } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { super.onViewCreated(view, savedInstanceState) configureViewer(args) } - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + override fun onCreateOptionsMenu( + menu: Menu, + inflater: MenuInflater, + ) { withState(viewModel) { state -> when (state.entry?.uploadServer) { UploadServerType.UPLOAD_TO_TASK -> { diff --git a/browse/src/main/kotlin/com/alfresco/content/browse/preview/LocalPreviewViewModel.kt b/browse/src/main/kotlin/com/alfresco/content/browse/preview/LocalPreviewViewModel.kt index c116d4c27..0ea5ac00f 100644 --- a/browse/src/main/kotlin/com/alfresco/content/browse/preview/LocalPreviewViewModel.kt +++ b/browse/src/main/kotlin/com/alfresco/content/browse/preview/LocalPreviewViewModel.kt @@ -25,23 +25,22 @@ class LocalPreviewViewModel( state: LocalPreviewState, val context: Context, ) : MavericksViewModel(state) { - /** * execute to download the files */ - fun execute() = withState { state -> - state.entry?.let { - val action = ActionOpenWith(it, hasChooser = true) - action.execute(context, GlobalScope) + fun execute() = + withState { state -> + state.entry?.let { + val action = ActionOpenWith(it, hasChooser = true) + action.execute(context, GlobalScope) + } } - } companion object : MavericksViewModelFactory { override fun create( viewModelContext: ViewModelContext, state: LocalPreviewState, - ) = - // Requires activity context in order to present other fragments + ) = // Requires activity context in order to present other fragments LocalPreviewViewModel(state, viewModelContext.activity()) } } diff --git a/browse/src/main/kotlin/com/alfresco/content/browse/processes/ProcessDetailActivity.kt b/browse/src/main/kotlin/com/alfresco/content/browse/processes/ProcessDetailActivity.kt index 21b717a12..f2e787599 100644 --- a/browse/src/main/kotlin/com/alfresco/content/browse/processes/ProcessDetailActivity.kt +++ b/browse/src/main/kotlin/com/alfresco/content/browse/processes/ProcessDetailActivity.kt @@ -12,7 +12,6 @@ import com.alfresco.content.browse.databinding.ActivityTaskViewerBinding * Marked as ProcessDetailActivity class */ class ProcessDetailActivity : AppCompatActivity() { - private lateinit var binding: ActivityTaskViewerBinding override fun onCreate(savedInstanceState: Bundle?) { @@ -33,9 +32,10 @@ class ProcessDetailActivity : AppCompatActivity() { navController.setGraph(graph, intent.extras) } - private fun setupActionToasts() = Action.showActionToasts( - lifecycleScope, - binding.parentView, - binding.bottomView, - ) + private fun setupActionToasts() = + Action.showActionToasts( + lifecycleScope, + binding.parentView, + binding.bottomView, + ) } diff --git a/browse/src/main/kotlin/com/alfresco/content/browse/processes/attachments/ProcessAttachedFilesFragment.kt b/browse/src/main/kotlin/com/alfresco/content/browse/processes/attachments/ProcessAttachedFilesFragment.kt index a64abe23d..f959f74c5 100644 --- a/browse/src/main/kotlin/com/alfresco/content/browse/processes/attachments/ProcessAttachedFilesFragment.kt +++ b/browse/src/main/kotlin/com/alfresco/content/browse/processes/attachments/ProcessAttachedFilesFragment.kt @@ -31,7 +31,6 @@ import com.alfresco.ui.getDrawableForAttribute * Marked as ProcessAttachedFilesFragment class */ class ProcessAttachedFilesFragment : BaseDetailFragment(), MavericksView, EntryListener { - val viewModel: ProcessDetailViewModel by activityViewModel() private lateinit var binding: FragmentAttachedFilesBinding private val epoxyController: AsyncEpoxyController by lazy { epoxyController() } @@ -45,7 +44,10 @@ class ProcessAttachedFilesFragment : BaseDetailFragment(), MavericksView, EntryL return binding.root } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { super.onViewCreated(view, savedInstanceState) AnalyticsManager().screenViewEvent(PageView.AttachedFiles) @@ -59,64 +61,72 @@ class ProcessAttachedFilesFragment : BaseDetailFragment(), MavericksView, EntryL binding.recyclerView.setController(epoxyController) - epoxyController.adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { - override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { - if (positionStart == 0) { - // @see: https://github.com/airbnb/epoxy/issues/224 - binding.recyclerView.layoutManager?.scrollToPosition(0) + epoxyController.adapter.registerAdapterDataObserver( + object : RecyclerView.AdapterDataObserver() { + override fun onItemRangeInserted( + positionStart: Int, + itemCount: Int, + ) { + if (positionStart == 0) { + // @see: https://github.com/airbnb/epoxy/issues/224 + binding.recyclerView.layoutManager?.scrollToPosition(0) + } } - } - }) + }, + ) } override fun onConfirmDelete(contentId: String) { viewModel.deleteAttachment(contentId) } - override fun invalidate() = withState(viewModel) { state -> - val handler = Handler(Looper.getMainLooper()) - binding.refreshLayout.isRefreshing = false - binding.loading.isVisible = false - handler.post { - if (state.listContents.size > 4) { - binding.tvNoOfAttachments.visibility = View.VISIBLE - binding.tvNoOfAttachments.text = getString(R.string.text_multiple_attachment, state.listContents.size) - } else { - binding.tvNoOfAttachments.visibility = View.GONE + override fun invalidate() = + withState(viewModel) { state -> + val handler = Handler(Looper.getMainLooper()) + binding.refreshLayout.isRefreshing = false + binding.loading.isVisible = false + handler.post { + if (state.listContents.size > 4) { + binding.tvNoOfAttachments.visibility = View.VISIBLE + binding.tvNoOfAttachments.text = getString(R.string.text_multiple_attachment, state.listContents.size) + } else { + binding.tvNoOfAttachments.visibility = View.GONE + } } - } - binding.fabAddAttachments.visibility = View.VISIBLE - binding.fabAddAttachments.setOnClickListener { - showCreateSheet(state, viewModel.observerID) - } + binding.fabAddAttachments.visibility = View.VISIBLE + binding.fabAddAttachments.setOnClickListener { + showCreateSheet(state, viewModel.observerID) + } - if (state.listContents.isEmpty()) requireActivity().onBackPressed() + if (state.listContents.isEmpty()) requireActivity().onBackPressed() - epoxyController.requestModelBuild() - } + epoxyController.requestModelBuild() + } - private fun epoxyController() = simpleController(viewModel) { state -> + private fun epoxyController() = + simpleController(viewModel) { state -> - if (state.listContents.isNotEmpty()) { - state.listContents.forEach { obj -> - listViewAttachmentRow { - id(stableId(obj)) - data(obj) - deleteContentClickListener { model, _, _, _ -> onConfirmDelete(model.data().id) } + if (state.listContents.isNotEmpty()) { + state.listContents.forEach { obj -> + listViewAttachmentRow { + id(stableId(obj)) + data(obj) + deleteContentClickListener { model, _, _, _ -> onConfirmDelete(model.data().id) } + } } } } - } private fun onItemClicked(contentEntry: Entry) { if (!contentEntry.isUpload) { if (!contentEntry.source.isNullOrEmpty()) { - val entry = Entry.convertContentEntryToEntry( - contentEntry, - MimeType.isDocFile(contentEntry.mimeType), - UploadServerType.UPLOAD_TO_PROCESS, - ) + val entry = + Entry.convertContentEntryToEntry( + contentEntry, + MimeType.isDocFile(contentEntry.mimeType), + UploadServerType.UPLOAD_TO_PROCESS, + ) remoteViewerIntent(entry) } } else { diff --git a/browse/src/main/kotlin/com/alfresco/content/browse/processes/details/ProcessDetailExtension.kt b/browse/src/main/kotlin/com/alfresco/content/browse/processes/details/ProcessDetailExtension.kt index ffdb03db3..24095fc65 100644 --- a/browse/src/main/kotlin/com/alfresco/content/browse/processes/details/ProcessDetailExtension.kt +++ b/browse/src/main/kotlin/com/alfresco/content/browse/processes/details/ProcessDetailExtension.kt @@ -68,14 +68,15 @@ internal fun ProcessDetailFragment.setListeners() { withState(viewModel) { state -> val dataObj = state.parent viewLifecycleOwner.lifecycleScope.launch { - val result = showComponentSheetDialog( - requireContext(), - ComponentData( - name = requireContext().getString(R.string.title_priority), - query = dataObj?.priority.toString(), - selector = ComponentType.TASK_PROCESS_PRIORITY.value, - ), - ) + val result = + showComponentSheetDialog( + requireContext(), + ComponentData( + name = requireContext().getString(R.string.title_priority), + query = dataObj?.priority.toString(), + selector = ComponentType.TASK_PROCESS_PRIORITY.value, + ), + ) if (result != null) { viewModel.updatePriority(result) @@ -87,10 +88,11 @@ internal fun ProcessDetailFragment.setListeners() { withState(viewModel) { state -> requireNotNull(state.parent) viewLifecycleOwner.lifecycleScope.launch { - val result = showSearchUserGroupComponentDialog( - requireContext(), - state.parent, - ) + val result = + showSearchUserGroupComponentDialog( + requireContext(), + state.parent, + ) if (result != null) { viewModel.updateAssignee(result) } @@ -132,18 +134,19 @@ internal fun ProcessDetailFragment.setListeners() { private fun ProcessDetailFragment.showCalendar(fromDate: String) { viewLifecycleOwner.lifecycleScope.launch { - val result = suspendCoroutine { - DatePickerBuilder( - context = requireContext(), - fromDate = fromDate, - isFrom = true, - isFutureDate = true, - dateFormat = DATE_FORMAT_4, - ) - .onSuccess { date -> it.resume(date) } - .onFailure { it.resume(null) } - .show() - } + val result = + suspendCoroutine { + DatePickerBuilder( + context = requireContext(), + fromDate = fromDate, + isFrom = true, + isFutureDate = true, + dateFormat = DATE_FORMAT_4, + ) + .onSuccess { date -> it.resume(date) } + .onFailure { it.resume(null) } + .show() + } result?.let { date -> viewModel.updateDate(date.getFormattedDate(DATE_FORMAT_4, DATE_FORMAT_5)) @@ -156,17 +159,28 @@ internal fun ProcessDetailFragment.setData(state: ProcessDetailViewState) { binding.tvTitle.text = dataEntry?.name binding.tvDescription.text = dataEntry?.description?.ifEmpty { requireContext().getString(R.string.empty_description) } binding.tvAssignedValue.apply { - text = if (dataEntry?.startedBy?.groupName?.isEmpty() == true && viewModel.getAPSUser().id == dataEntry.startedBy?.id) { - requireContext().getLocalizedName(dataEntry.startedBy?.let { UserGroupDetails.with(it).name } ?: getString(R.string.text_select_assignee)) - } else if (dataEntry?.startedBy?.groupName?.isNotEmpty() == true) { - requireContext().getLocalizedName(dataEntry.startedBy?.groupName ?: getString(R.string.text_select_assignee)) - } else requireContext().getLocalizedName(dataEntry?.startedBy?.name ?: getString(R.string.text_select_assignee)) + text = + if (dataEntry?.startedBy?.groupName?.isEmpty() == true && viewModel.getAPSUser().id == dataEntry.startedBy?.id) { + requireContext().getLocalizedName( + dataEntry.startedBy?.let { UserGroupDetails.with(it).name } ?: getString(R.string.text_select_assignee), + ) + } else if (dataEntry?.startedBy?.groupName?.isNotEmpty() == true) { + requireContext().getLocalizedName(dataEntry.startedBy?.groupName ?: getString(R.string.text_select_assignee)) + } else { + requireContext().getLocalizedName(dataEntry?.startedBy?.name ?: getString(R.string.text_select_assignee)) + } } if (dataEntry?.processDefinitionId.isNullOrEmpty()) { binding.tvAttachedTitle.text = getString(R.string.text_attached_files) binding.tvDueDateValue.text = - if (dataEntry?.formattedDueDate.isNullOrEmpty()) requireContext().getString(R.string.empty_no_due_date) else dataEntry?.formattedDueDate?.getFormattedDate(DATE_FORMAT_1, DATE_FORMAT_4) + if (dataEntry?.formattedDueDate.isNullOrEmpty()) { + requireContext().getString( + R.string.empty_no_due_date, + ) + } else { + dataEntry?.formattedDueDate?.getFormattedDate(DATE_FORMAT_1, DATE_FORMAT_4) + } binding.tvNoAttachedFilesError.text = getString(R.string.no_attached_files) binding.completeButton.text = getString(R.string.title_start_workflow) binding.tvPriorityValue.updatePriorityView(state.parent?.priority ?: -1) @@ -177,7 +191,14 @@ internal fun ProcessDetailFragment.setData(state: ProcessDetailViewState) { binding.tvDueDateTitle.text = getString(R.string.title_start_date) binding.tvAssignedTitle.text = getString(R.string.title_started_by) binding.tvDueDateValue.text = dataEntry?.started?.toLocalDate()?.toString()?.getFormattedDate(DATE_FORMAT_1, DATE_FORMAT_4) - binding.tvStatusValue.text = if (dataEntry?.ended != null) getString(R.string.status_completed) else getString(R.string.status_active) + binding.tvStatusValue.text = + if (dataEntry?.ended != null) { + getString( + R.string.status_completed, + ) + } else { + getString(R.string.status_active) + } if (state.listTask.isNotEmpty()) { binding.clTasks.visibility = View.VISIBLE binding.tvTasksValue.text = state.listTask.size.toString() @@ -215,20 +236,25 @@ private fun ProcessDetailFragment.formatDateAndShowCalendar() { } } -internal fun ProcessDetailFragment.showTitleDescriptionComponent() = withState(viewModel) { - viewLifecycleOwner.lifecycleScope.launch { - showComponentSheetDialog( - requireContext(), - ComponentData( - name = requireContext().getString(R.string.title_start_workflow), - query = it.parent?.name, - value = it.parent?.description, - selector = ComponentType.VIEW_TEXT.value, - ), - ) +internal fun ProcessDetailFragment.showTitleDescriptionComponent() = + withState(viewModel) { + viewLifecycleOwner.lifecycleScope.launch { + showComponentSheetDialog( + requireContext(), + ComponentData( + name = requireContext().getString(R.string.title_start_workflow), + query = it.parent?.name, + value = it.parent?.description, + selector = ComponentType.VIEW_TEXT.value, + ), + ) + } } -} -internal fun executeContinuation(continuation: Continuation, name: String, query: String) { +internal fun executeContinuation( + continuation: Continuation, + name: String, + query: String, +) { continuation.resume(ComponentMetaData(name = name, query = query)) } diff --git a/browse/src/main/kotlin/com/alfresco/content/browse/processes/details/ProcessDetailFragment.kt b/browse/src/main/kotlin/com/alfresco/content/browse/processes/details/ProcessDetailFragment.kt index a0744a43c..8b90205a8 100644 --- a/browse/src/main/kotlin/com/alfresco/content/browse/processes/details/ProcessDetailFragment.kt +++ b/browse/src/main/kotlin/com/alfresco/content/browse/processes/details/ProcessDetailFragment.kt @@ -45,7 +45,6 @@ import kotlin.coroutines.suspendCoroutine * Marked as ProcessDetailFragment */ class ProcessDetailFragment : BaseDetailFragment(), MavericksView, EntryListener { - lateinit var binding: FragmentTaskDetailBinding val viewModel: ProcessDetailViewModel by activityViewModel() private val epoxyAttachmentController: AsyncEpoxyController by lazy { epoxyAttachmentController() } @@ -53,7 +52,11 @@ class ProcessDetailFragment : BaseDetailFragment(), MavericksView, EntryListener private var confirmContentQueueDialog = WeakReference(null) private val dispatcher: CoroutineDispatcher = Dispatchers.Main - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View { if (viewLayout == null) { binding = FragmentTaskDetailBinding.inflate(inflater, container, false) viewLayout = binding.root @@ -61,7 +64,10 @@ class ProcessDetailFragment : BaseDetailFragment(), MavericksView, EntryListener return viewLayout as View } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { super.onViewCreated(view, savedInstanceState) AnalyticsManager().screenViewEvent(PageView.WorkflowView) (requireActivity() as ProcessDetailActivity).setSupportActionBar(binding.toolbar) @@ -84,24 +90,25 @@ class ProcessDetailFragment : BaseDetailFragment(), MavericksView, EntryListener viewModel.deleteAttachment(contentId) } - override fun invalidate() = withState(viewModel) { state -> - binding.loading.isVisible = state.requestContent is Loading || state.requestProcessDefinition is Loading || - state.requestStartWorkflow is Loading || state.requestTasks is Loading - setData(state) - if (state.parent?.processDefinitionId.isNullOrEmpty()) { - binding.toolbar.title = resources.getString(R.string.title_start_workflow) - updateUI(state) - when { - state.requestStartWorkflow is Success && state.requestStartWorkflow.complete -> { - viewModel.updateProcessList() - requireActivity().onBackPressed() + override fun invalidate() = + withState(viewModel) { state -> + binding.loading.isVisible = state.requestContent is Loading || state.requestProcessDefinition is Loading || + state.requestStartWorkflow is Loading || state.requestTasks is Loading + setData(state) + if (state.parent?.processDefinitionId.isNullOrEmpty()) { + binding.toolbar.title = resources.getString(R.string.title_start_workflow) + updateUI(state) + when { + state.requestStartWorkflow is Success && state.requestStartWorkflow.complete -> { + viewModel.updateProcessList() + requireActivity().onBackPressed() + } } + epoxyAttachmentController.requestModelBuild() + } else { + binding.toolbar.title = resources.getString(R.string.title_workflow) } - epoxyAttachmentController.requestModelBuild() - } else { - binding.toolbar.title = resources.getString(R.string.title_workflow) } - } internal suspend fun showComponentSheetDialog( context: Context, @@ -144,59 +151,64 @@ class ProcessDetailFragment : BaseDetailFragment(), MavericksView, EntryListener fun confirmContentQueuePrompt() { val oldDialog = confirmContentQueueDialog.get() if (oldDialog != null && oldDialog.isShowing) return - val dialog = MaterialAlertDialogBuilder(requireContext()) - .setCancelable(false) - .setTitle(getString(R.string.title_content_in_queue)) - .setMessage(getString(R.string.message_content_in_queue)) - .setNegativeButton(getString(R.string.dialog_negative_button_task), null) - .setPositiveButton(getString(R.string.dialog_positive_button_task)) { _, _ -> - viewModel.startWorkflow() - } - .show() + val dialog = + MaterialAlertDialogBuilder(requireContext()) + .setCancelable(false) + .setTitle(getString(R.string.title_content_in_queue)) + .setMessage(getString(R.string.message_content_in_queue)) + .setNegativeButton(getString(R.string.dialog_negative_button_task), null) + .setPositiveButton(getString(R.string.dialog_positive_button_task)) { _, _ -> + viewModel.startWorkflow() + } + .show() confirmContentQueueDialog = WeakReference(dialog) } - private fun epoxyAttachmentController() = simpleController(viewModel) { state -> - val handler = Handler(Looper.getMainLooper()) - if (state.listContents.isNotEmpty()) { - handler.post { - binding.clAddAttachment.visibility = View.VISIBLE - binding.tvNoAttachedFilesError.visibility = View.GONE - binding.tvAttachedTitle.text = getString(R.string.text_attached_files) - binding.recyclerViewAttachments.visibility = View.VISIBLE - - if (state.listContents.size > 1) { - binding.tvNoOfAttachments.visibility = View.VISIBLE - } else binding.tvNoOfAttachments.visibility = View.GONE - - if (state.listContents.size > 4) { - binding.tvAttachmentViewAll.visibility = View.VISIBLE - } else - binding.tvAttachmentViewAll.visibility = View.GONE + private fun epoxyAttachmentController() = + simpleController(viewModel) { state -> + val handler = Handler(Looper.getMainLooper()) + if (state.listContents.isNotEmpty()) { + handler.post { + binding.clAddAttachment.visibility = View.VISIBLE + binding.tvNoAttachedFilesError.visibility = View.GONE + binding.tvAttachedTitle.text = getString(R.string.text_attached_files) + binding.recyclerViewAttachments.visibility = View.VISIBLE + + if (state.listContents.size > 1) { + binding.tvNoOfAttachments.visibility = View.VISIBLE + } else { + binding.tvNoOfAttachments.visibility = View.GONE + } + + if (state.listContents.size > 4) { + binding.tvAttachmentViewAll.visibility = View.VISIBLE + } else { + binding.tvAttachmentViewAll.visibility = View.GONE + } + + binding.tvNoOfAttachments.text = getString(R.string.text_multiple_attachment, state.listContents.size) + } - binding.tvNoOfAttachments.text = getString(R.string.text_multiple_attachment, state.listContents.size) - } + state.listContents.take(4).forEach { obj -> + listViewAttachmentRow { + id(stableId(obj)) + data(obj) + deleteContentClickListener { model, _, _, _ -> onConfirmDelete(model.data().id) } + } + } + } else { + handler.post { + binding.recyclerViewAttachments.visibility = View.GONE + binding.tvAttachmentViewAll.visibility = View.GONE + binding.tvNoOfAttachments.visibility = View.GONE - state.listContents.take(4).forEach { obj -> - listViewAttachmentRow { - id(stableId(obj)) - data(obj) - deleteContentClickListener { model, _, _, _ -> onConfirmDelete(model.data().id) } + binding.clAddAttachment.visibility = View.VISIBLE + binding.tvAttachedTitle.text = getString(R.string.text_attached_files) + binding.tvNoAttachedFilesError.visibility = View.VISIBLE + binding.tvNoAttachedFilesError.text = getString(R.string.no_attached_files) } } - } else { - handler.post { - binding.recyclerViewAttachments.visibility = View.GONE - binding.tvAttachmentViewAll.visibility = View.GONE - binding.tvNoOfAttachments.visibility = View.GONE - - binding.clAddAttachment.visibility = View.VISIBLE - binding.tvAttachedTitle.text = getString(R.string.text_attached_files) - binding.tvNoAttachedFilesError.visibility = View.VISIBLE - binding.tvNoAttachedFilesError.text = getString(R.string.no_attached_files) - } } - } override fun onEntryCreated(entry: ParentEntry) { if (isAdded) { @@ -207,11 +219,12 @@ class ProcessDetailFragment : BaseDetailFragment(), MavericksView, EntryListener private fun onItemClicked(contentEntry: Entry) { if (!contentEntry.isUpload) { if (!contentEntry.source.isNullOrEmpty()) { - val entry = Entry.convertContentEntryToEntry( - contentEntry, - MimeType.isDocFile(contentEntry.mimeType), - UploadServerType.UPLOAD_TO_PROCESS, - ) + val entry = + Entry.convertContentEntryToEntry( + contentEntry, + MimeType.isDocFile(contentEntry.mimeType), + UploadServerType.UPLOAD_TO_PROCESS, + ) remoteViewerIntent(entry) } } else { diff --git a/browse/src/main/kotlin/com/alfresco/content/browse/processes/details/ProcessDetailViewModel.kt b/browse/src/main/kotlin/com/alfresco/content/browse/processes/details/ProcessDetailViewModel.kt index f4bee7324..1c5b34c5e 100644 --- a/browse/src/main/kotlin/com/alfresco/content/browse/processes/details/ProcessDetailViewModel.kt +++ b/browse/src/main/kotlin/com/alfresco/content/browse/processes/details/ProcessDetailViewModel.kt @@ -24,7 +24,7 @@ import com.alfresco.events.on import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.Job import kotlinx.coroutines.launch -import java.util.* +import java.util.UUID /** * Marked as ProcessDetailViewModel @@ -34,7 +34,6 @@ class ProcessDetailViewModel( val context: Context, private val repository: TaskRepository, ) : MavericksViewModel(state) { - private var observeUploadsJob: Job? = null var entryListener: EntryListener? = null var observerID: String = "" @@ -111,14 +110,15 @@ class ProcessDetailViewModel( repo.removeCompletedUploads() observeUploadsJob?.cancel() - observeUploadsJob = repo.observeUploads(observerID, UploadServerType.UPLOAD_TO_PROCESS) - .execute { - if (it is Success) { - updateUploads(it()) - } else { - this + observeUploadsJob = + repo.observeUploads(observerID, UploadServerType.UPLOAD_TO_PROCESS) + .execute { + if (it is Success) { + updateUploads(it()) + } else { + this + } } - } } private fun getStartForm(processEntry: ProcessEntry) { @@ -143,64 +143,69 @@ class ProcessDetailViewModel( /** * delete content locally */ - fun deleteAttachment(contentId: String) = stateFlow.execute { - deleteUploads(contentId) - } - - private fun linkContentToProcess(entry: Entry, sourceName: String) = - viewModelScope.launch { - repository::linkADWContentToProcess.asFlow(LinkContentPayload.with(entry, sourceName)).execute { - when (it) { - is Loading -> copy(requestContent = Loading()) - is Fail -> copy(requestContent = Fail(it.error)) - is Success -> { - updateContent(it()).copy(requestContent = Success(it())) - } + fun deleteAttachment(contentId: String) = + stateFlow.execute { + deleteUploads(contentId) + } - else -> this + private fun linkContentToProcess( + entry: Entry, + sourceName: String, + ) = viewModelScope.launch { + repository::linkADWContentToProcess.asFlow(LinkContentPayload.with(entry, sourceName)).execute { + when (it) { + is Loading -> copy(requestContent = Loading()) + is Fail -> copy(requestContent = Fail(it.error)) + is Success -> { + updateContent(it()).copy(requestContent = Success(it())) } + + else -> this } } + } - private fun singleProcessDefinition(appDefinitionId: String) = withState { state -> - viewModelScope.launch { - repository::singleProcessDefinition.asFlow(appDefinitionId).execute { - when (it) { - is Loading -> copy(requestProcessDefinition = Loading()) - is Fail -> copy(requestProcessDefinition = Fail(it.error)) - is Success -> { - val updatedState = updateSingleProcessDefinition(it()) - observeUploads(updatedState) - updatedState.parent?.let { processEntry -> - getStartForm(processEntry) + private fun singleProcessDefinition(appDefinitionId: String) = + withState { state -> + viewModelScope.launch { + repository::singleProcessDefinition.asFlow(appDefinitionId).execute { + when (it) { + is Loading -> copy(requestProcessDefinition = Loading()) + is Fail -> copy(requestProcessDefinition = Fail(it.error)) + is Success -> { + val updatedState = updateSingleProcessDefinition(it()) + observeUploads(updatedState) + updatedState.parent?.let { processEntry -> + getStartForm(processEntry) + } + copy(requestProcessDefinition = Success(it())) } - copy(requestProcessDefinition = Success(it())) - } - else -> { - this + else -> { + this + } } } } } - } /** * This method will execute the start flow api with required data */ - fun startWorkflow() = withState { state -> - val items = state.listContents.joinToString(separator = ",") { it.id } - viewModelScope.launch { - repository::startWorkflow.asFlow(state.parent, items, mapOf()).execute { - when (it) { - is Loading -> copy(requestStartWorkflow = Loading()) - is Fail -> copy(requestStartWorkflow = Fail(it.error)) - is Success -> copy(requestStartWorkflow = Success(it())) - else -> this + fun startWorkflow() = + withState { state -> + val items = state.listContents.joinToString(separator = ",") { it.id } + viewModelScope.launch { + repository::startWorkflow.asFlow(state.parent, items, mapOf()).execute { + when (it) { + is Loading -> copy(requestStartWorkflow = Loading()) + is Fail -> copy(requestStartWorkflow = Fail(it.error)) + is Success -> copy(requestStartWorkflow = Success(it())) + else -> this + } } } } - } /** * adding listener to update the View after downloading the content @@ -233,58 +238,59 @@ class ProcessDetailViewModel( } } - private fun fetchAccountInfo() = withState { state -> - viewModelScope.launch { - repository::getAccountInfo.execute { - when (it) { - is Loading -> copy(requestAccountInfo = Loading()) - is Fail -> copy(requestAccountInfo = Fail(it.error)) - is Success -> { - val response = it() - - repository.saveSourceName(response.listAccounts.first()) - val sourceName = response.listAccounts.first().sourceName - if (!isExecuted) { - isExecuted = true - state.parent?.defaultEntries?.map { entry -> - linkContentToProcess(entry, sourceName) + private fun fetchAccountInfo() = + withState { state -> + viewModelScope.launch { + repository::getAccountInfo.execute { + when (it) { + is Loading -> copy(requestAccountInfo = Loading()) + is Fail -> copy(requestAccountInfo = Fail(it.error)) + is Success -> { + val response = it() + + repository.saveSourceName(response.listAccounts.first()) + val sourceName = response.listAccounts.first().sourceName + if (!isExecuted) { + isExecuted = true + state.parent?.defaultEntries?.map { entry -> + linkContentToProcess(entry, sourceName) + } } + copy(requestAccountInfo = Success(response)) } - copy(requestAccountInfo = Success(response)) - } - else -> { - this + else -> { + this + } } } } } - } - private fun fetchTasks() = withState { state -> - viewModelScope.launch { - // Fetch tasks data - repository::getTasks.asFlow( - TaskProcessFiltersPayload.defaultTasksOfProcess(state.parent?.id), - ).execute { - when (it) { - is Loading -> copy(requestTasks = Loading()) - is Fail -> copy(requestTasks = Fail(it.error)) - is Success -> { - val response = it() - updateTasks(response).copy(requestTasks = Success(response)) - } + private fun fetchTasks() = + withState { state -> + viewModelScope.launch { + // Fetch tasks data + repository::getTasks.asFlow( + TaskProcessFiltersPayload.defaultTasksOfProcess(state.parent?.id), + ).execute { + when (it) { + is Loading -> copy(requestTasks = Loading()) + is Fail -> copy(requestTasks = Fail(it.error)) + is Success -> { + val response = it() + updateTasks(response).copy(requestTasks = Success(response)) + } - else -> { - this + else -> { + this + } } } } } - } companion object : MavericksViewModelFactory { - override fun create( viewModelContext: ViewModelContext, state: ProcessDetailViewState, diff --git a/browse/src/main/kotlin/com/alfresco/content/browse/processes/details/ProcessDetailViewState.kt b/browse/src/main/kotlin/com/alfresco/content/browse/processes/details/ProcessDetailViewState.kt index 591dbaed9..c67881088 100644 --- a/browse/src/main/kotlin/com/alfresco/content/browse/processes/details/ProcessDetailViewState.kt +++ b/browse/src/main/kotlin/com/alfresco/content/browse/processes/details/ProcessDetailViewState.kt @@ -32,7 +32,6 @@ data class ProcessDetailViewState( val requestStartWorkflow: Async = Uninitialized, val requestTasks: Async = Uninitialized, ) : MavericksState { - constructor(target: ProcessEntry) : this(parent = target) /** @@ -75,7 +74,10 @@ data class ProcessDetailViewState( /** * update form fields data */ - fun updateFormFields(response: ResponseListForm, processEntry: ProcessEntry): ProcessDetailViewState { + fun updateFormFields( + response: ResponseListForm, + processEntry: ProcessEntry, + ): ProcessDetailViewState { val formFields = response.fields.first().fields return copy(formFields = formFields, parent = ProcessEntry.updateReviewerType(processEntry, formFields)) } @@ -117,7 +119,10 @@ data class ProcessDetailViewState( ) } - private fun mergeInUploads(base: List, uploads: List): List { + private fun mergeInUploads( + base: List, + uploads: List, + ): List { return (uploads + base).distinctBy { it.id.ifEmpty { it.boxId } } } diff --git a/browse/src/main/kotlin/com/alfresco/content/browse/processes/list/ProcessesFragment.kt b/browse/src/main/kotlin/com/alfresco/content/browse/processes/list/ProcessesFragment.kt index d14f06a37..b471887bb 100644 --- a/browse/src/main/kotlin/com/alfresco/content/browse/processes/list/ProcessesFragment.kt +++ b/browse/src/main/kotlin/com/alfresco/content/browse/processes/list/ProcessesFragment.kt @@ -30,10 +30,12 @@ import com.google.android.material.floatingactionbutton.FloatingActionButton * Marked as ProcessesFragment */ class ProcessesFragment : ProcessListFragment() { - override val viewModel: ProcessesViewModel by fragmentViewModel() - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { super.onViewCreated(view, savedInstanceState) setupDropDown() } @@ -43,15 +45,16 @@ class ProcessesFragment : ProcessListFragment - super.invalidate() - filterTitle.text = requireContext().getLocalizedName(viewModel.filterName) - rlFilters.contentDescription = getString(R.string.text_filter_option, viewModel.filterName) - scrollToTop() - if (state.request is Success) { - clParent.addView(makeFab(requireContext())) + override fun invalidate() = + withState(viewModel) { state -> + super.invalidate() + filterTitle.text = requireContext().getLocalizedName(viewModel.filterName) + rlFilters.contentDescription = getString(R.string.text_filter_option, viewModel.filterName) + scrollToTop() + if (state.request is Success) { + clParent.addView(makeFab(requireContext())) + } } - } private fun scrollToTop() { if (isResumed && viewModel.scrollToTop) { @@ -86,17 +89,18 @@ class ProcessesFragment : ProcessListFragment(state) { - - val listProcesses = mapOf( - ProcessFilters.All.filter to ProcessFilters.All.name, - ProcessFilters.Active.filter to ProcessFilters.Running.name, - ProcessFilters.Completed.filter to ProcessFilters.Completed.name, - ) + val listProcesses = + mapOf( + ProcessFilters.All.filter to ProcessFilters.All.name, + ProcessFilters.Active.filter to ProcessFilters.Running.name, + ProcessFilters.Completed.filter to ProcessFilters.Completed.name, + ) var filterName: String = ProcessFilters.Active.filter var filterValue: String = "" @@ -49,7 +49,6 @@ class ProcessesViewModel( } companion object : MavericksViewModelFactory { - override fun create( viewModelContext: ViewModelContext, state: ProcessesViewState, @@ -58,26 +57,27 @@ class ProcessesViewModel( override fun refresh() = fetchInitial() - override fun fetchNextPage() = withState { state -> - val newPage = state.page.plus(1) - viewModelScope.launch { - // Fetch processes data - repository::getProcesses.asFlow( - TaskProcessFiltersPayload.updateFilters(state.filterParams, filterValue, newPage), - ).execute { - when (it) { - is Loading -> copy(request = Loading()) - is Fail -> copy(processEntries = emptyList(), request = Fail(it.error)) - is Success -> { - update(it()).copy(request = Success(it())) - } - else -> { - this + override fun fetchNextPage() = + withState { state -> + val newPage = state.page.plus(1) + viewModelScope.launch { + // Fetch processes data + repository::getProcesses.asFlow( + TaskProcessFiltersPayload.updateFilters(state.filterParams, filterValue, newPage), + ).execute { + when (it) { + is Loading -> copy(request = Loading()) + is Fail -> copy(processEntries = emptyList(), request = Fail(it.error)) + is Success -> { + update(it()).copy(request = Success(it())) + } + else -> { + this + } } } } } - } override fun emptyMessageArgs(state: ProcessListViewState): Triple { return when (state.request) { @@ -86,25 +86,26 @@ class ProcessesViewModel( } } - private fun fetchInitial() = withState { state -> - viewModelScope.launch { - // Fetch processes data - repository::getProcesses.asFlow( - TaskProcessFiltersPayload.updateFilters(state.filterParams, filterValue), - ).execute { - when (it) { - is Loading -> copy(request = Loading()) - is Fail -> copy(processEntries = emptyList(), request = Fail(it.error)) - is Success -> { - update(it()).copy(request = Success(it())) - } - else -> { - this + private fun fetchInitial() = + withState { state -> + viewModelScope.launch { + // Fetch processes data + repository::getProcesses.asFlow( + TaskProcessFiltersPayload.updateFilters(state.filterParams, filterValue), + ).execute { + when (it) { + is Loading -> copy(request = Loading()) + is Fail -> copy(processEntries = emptyList(), request = Fail(it.error)) + is Success -> { + update(it()).copy(request = Success(it())) + } + else -> { + this + } } } } } - } /** * Filter applied and execute api diff --git a/browse/src/main/kotlin/com/alfresco/content/browse/processes/list/ProcessesViewState.kt b/browse/src/main/kotlin/com/alfresco/content/browse/processes/list/ProcessesViewState.kt index cd960a0c0..32fb1f84d 100644 --- a/browse/src/main/kotlin/com/alfresco/content/browse/processes/list/ProcessesViewState.kt +++ b/browse/src/main/kotlin/com/alfresco/content/browse/processes/list/ProcessesViewState.kt @@ -20,7 +20,6 @@ data class ProcessesViewState( val loadItemsCount: Int = 0, val page: Int = 0, ) : ProcessListViewState { - override val isCompact = false override fun copy(_entries: List) = copy(processEntries = _entries) @@ -28,22 +27,21 @@ data class ProcessesViewState( /** * update the latest response */ - fun update( - response: ResponseList?, - ): ProcessesViewState { + fun update(response: ResponseList?): ProcessesViewState { if (response == null) return this val totalLoadCount: Int val taskPageEntries = response.listProcesses - val newTaskEntries = if (response.start != 0) { - totalLoadCount = loadItemsCount.plus(response.size) - baseTaskEntries + taskPageEntries - } else { - totalLoadCount = response.size - taskPageEntries - } + val newTaskEntries = + if (response.start != 0) { + totalLoadCount = loadItemsCount.plus(response.size) + baseTaskEntries + taskPageEntries + } else { + totalLoadCount = response.size + taskPageEntries + } return copy( processEntries = newTaskEntries, diff --git a/browse/src/main/kotlin/com/alfresco/content/browse/processes/status/TaskStatusFragment.kt b/browse/src/main/kotlin/com/alfresco/content/browse/processes/status/TaskStatusFragment.kt index 9f9e495e7..aba5da834 100644 --- a/browse/src/main/kotlin/com/alfresco/content/browse/processes/status/TaskStatusFragment.kt +++ b/browse/src/main/kotlin/com/alfresco/content/browse/processes/status/TaskStatusFragment.kt @@ -43,7 +43,6 @@ import kotlin.coroutines.suspendCoroutine * TaskStatusFragment */ class TaskStatusFragment : Fragment(), MavericksView { - val viewModel: TaskDetailViewModel by activityViewModel() lateinit var binding: FragmentTaskStatusBinding private var viewLayout: View? = null @@ -61,7 +60,10 @@ class TaskStatusFragment : Fragment(), MavericksView { return viewLayout as View } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { super.onViewCreated(view, savedInstanceState) AnalyticsManager().screenViewEvent(PageView.WorkflowTaskStatusView) @@ -102,7 +104,10 @@ class TaskStatusFragment : Fragment(), MavericksView { } } - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + override fun onCreateOptionsMenu( + menu: Menu, + inflater: MenuInflater, + ) { inflater.inflate(R.menu.menu_process, menu) } @@ -152,19 +157,24 @@ class TaskStatusFragment : Fragment(), MavericksView { } } - private fun executeContinuation(continuation: Continuation, name: String, query: String) { + private fun executeContinuation( + continuation: Continuation, + name: String, + query: String, + ) { continuation.resume(ComponentMetaData(name = name, query = query)) } - override fun invalidate() = withState(viewModel) { state -> + override fun invalidate() = + withState(viewModel) { state -> - binding.loading.isVisible = (state.requestSaveForm is Loading) + binding.loading.isVisible = (state.requestSaveForm is Loading) - if (state.requestSaveForm.invoke()?.code() == 200) { - viewModel.updateTaskStatusAndName(state.parent?.taskFormStatus, binding.commentInput.text.toString().trim()) - requireActivity().onBackPressed() - } + if (state.requestSaveForm.invoke()?.code() == 200) { + viewModel.updateTaskStatusAndName(state.parent?.taskFormStatus, binding.commentInput.text.toString().trim()) + requireActivity().onBackPressed() + } - binding.tvStatus.text = state.parent?.taskFormStatus - } + binding.tvStatus.text = state.parent?.taskFormStatus + } } diff --git a/browse/src/main/kotlin/com/alfresco/content/browse/tasks/BaseDetailFragment.kt b/browse/src/main/kotlin/com/alfresco/content/browse/tasks/BaseDetailFragment.kt index 67782d14a..41669c3d6 100644 --- a/browse/src/main/kotlin/com/alfresco/content/browse/tasks/BaseDetailFragment.kt +++ b/browse/src/main/kotlin/com/alfresco/content/browse/tasks/BaseDetailFragment.kt @@ -24,7 +24,6 @@ import java.lang.ref.WeakReference * Marked as BaseDetailFragment class */ abstract class BaseDetailFragment : Fragment(), DeleteContentListener { - private var deleteContentDialog = WeakReference(null) lateinit var listener: DeleteContentListener @@ -40,15 +39,16 @@ abstract class BaseDetailFragment : Fragment(), DeleteContentListener { AnalyticsManager().taskEvent(EventName.DeleteTaskAttachment) val oldDialog = deleteContentDialog.get() if (oldDialog != null && oldDialog.isShowing) return - val dialog = MaterialAlertDialogBuilder(requireContext()) - .setCancelable(false) - .setTitle(getString(R.string.dialog_title_delete_content)) - .setMessage(contentEntry.name) - .setNegativeButton(getString(R.string.dialog_negative_button_task), null) - .setPositiveButton(getString(R.string.dialog_positive_button_task)) { _, _ -> - listener.onConfirmDelete(contentEntry.id.toString()) - } - .show() + val dialog = + MaterialAlertDialogBuilder(requireContext()) + .setCancelable(false) + .setTitle(getString(R.string.dialog_title_delete_content)) + .setMessage(contentEntry.name) + .setNegativeButton(getString(R.string.dialog_negative_button_task), null) + .setPositiveButton(getString(R.string.dialog_positive_button_task)) { _, _ -> + listener.onConfirmDelete(contentEntry.id.toString()) + } + .show() deleteContentDialog = WeakReference(dialog) } @@ -57,7 +57,10 @@ abstract class BaseDetailFragment : Fragment(), DeleteContentListener { CreateActionsSheet.with(Entry.defaultAPSEntry(state.parent?.id)).show(childFragmentManager, null) } - internal fun showCreateSheet(state: ProcessDetailViewState, observerID: String) { + internal fun showCreateSheet( + state: ProcessDetailViewState, + observerID: String, + ) { AnalyticsManager().taskEvent(EventName.UploadProcessAttachment) CreateActionsSheet.with(Entry.defaultWorkflowEntry(observerID)).show(childFragmentManager, null) } @@ -68,30 +71,37 @@ abstract class BaseDetailFragment : Fragment(), DeleteContentListener { fun stableId(entry: Entry): String = if (entry.isUpload) { entry.boxId.toString() - } else entry.id + } else { + entry.id + } /** * This intent will open the remote file */ - fun remoteViewerIntent(entry: Entry) = startActivity( - Intent(requireActivity(), ViewerActivity::class.java) - .putExtra(ViewerActivity.KEY_ID, entry.id) - .putExtra(ViewerActivity.KEY_TITLE, entry.name) - .putExtra(ViewerActivity.KEY_MODE, REMOTE), - ) + fun remoteViewerIntent(entry: Entry) = + startActivity( + Intent(requireActivity(), ViewerActivity::class.java) + .putExtra(ViewerActivity.KEY_ID, entry.id) + .putExtra(ViewerActivity.KEY_TITLE, entry.name) + .putExtra(ViewerActivity.KEY_MODE, REMOTE), + ) /** * This intent will open the local file */ - fun localViewerIntent(contentEntry: Entry) = startActivity( - Intent(requireActivity(), LocalPreviewActivity::class.java) - .putExtra(KEY_ENTRY_OBJ, contentEntry), - ) + fun localViewerIntent(contentEntry: Entry) = + startActivity( + Intent(requireActivity(), LocalPreviewActivity::class.java) + .putExtra(KEY_ENTRY_OBJ, contentEntry), + ) /** * showing Snackbar */ - fun showSnackar(snackView: View, message: String) = Snackbar.make( + fun showSnackar( + snackView: View, + message: String, + ) = Snackbar.make( snackView, message, Snackbar.LENGTH_SHORT, @@ -102,7 +112,6 @@ abstract class BaseDetailFragment : Fragment(), DeleteContentListener { * Marked as DeleteContentListener interface */ interface DeleteContentListener { - /** * It will get call on confirm delete. */ diff --git a/browse/src/main/kotlin/com/alfresco/content/browse/tasks/attachments/AttachedFilesFragment.kt b/browse/src/main/kotlin/com/alfresco/content/browse/tasks/attachments/AttachedFilesFragment.kt index 4eab3ea9d..a4dfd5dac 100644 --- a/browse/src/main/kotlin/com/alfresco/content/browse/tasks/attachments/AttachedFilesFragment.kt +++ b/browse/src/main/kotlin/com/alfresco/content/browse/tasks/attachments/AttachedFilesFragment.kt @@ -38,7 +38,6 @@ import com.alfresco.ui.getDrawableForAttribute * Marked as AttachedFilesFragment class */ class AttachedFilesFragment : BaseDetailFragment(), MavericksView, EntryListener { - val viewModel: TaskDetailViewModel by activityViewModel() private lateinit var binding: FragmentAttachedFilesBinding private val epoxyController: AsyncEpoxyController by lazy { epoxyController() } @@ -52,7 +51,10 @@ class AttachedFilesFragment : BaseDetailFragment(), MavericksView, EntryListener return binding.root } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { super.onViewCreated(view, savedInstanceState) AnalyticsManager().screenViewEvent(PageView.AttachedFiles) @@ -66,14 +68,19 @@ class AttachedFilesFragment : BaseDetailFragment(), MavericksView, EntryListener binding.recyclerView.setController(epoxyController) - epoxyController.adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { - override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { - if (positionStart == 0) { - // @see: https://github.com/airbnb/epoxy/issues/224 - binding.recyclerView.layoutManager?.scrollToPosition(0) + epoxyController.adapter.registerAdapterDataObserver( + object : RecyclerView.AdapterDataObserver() { + override fun onItemRangeInserted( + positionStart: Int, + itemCount: Int, + ) { + if (positionStart == 0) { + // @see: https://github.com/airbnb/epoxy/issues/224 + binding.recyclerView.layoutManager?.scrollToPosition(0) + } } - } - }) + }, + ) binding.refreshLayout.setOnRefreshListener { viewModel.getContents() } @@ -85,61 +92,65 @@ class AttachedFilesFragment : BaseDetailFragment(), MavericksView, EntryListener viewModel.deleteAttachment(contentId) } - override fun invalidate() = withState(viewModel) { state -> - val handler = Handler(Looper.getMainLooper()) - binding.loading.isVisible = state.requestDeleteContent is Loading + override fun invalidate() = + withState(viewModel) { state -> + val handler = Handler(Looper.getMainLooper()) + binding.loading.isVisible = state.requestDeleteContent is Loading - if (state.requestContents.complete) { - binding.refreshLayout.isRefreshing = false - } - handler.post { - if (state.listContents.size > 4) { - binding.tvNoOfAttachments.visibility = View.VISIBLE - binding.tvNoOfAttachments.text = getString(R.string.text_multiple_attachment, state.listContents.size) - } else { - binding.tvNoOfAttachments.visibility = View.GONE + if (state.requestContents.complete) { + binding.refreshLayout.isRefreshing = false } - } - - if (state.requestContents is Success && !viewModel.isTaskCompleted(state)) { - binding.fabAddAttachments.visibility = View.VISIBLE - binding.fabAddAttachments.setOnClickListener { - showCreateSheet(state) + handler.post { + if (state.listContents.size > 4) { + binding.tvNoOfAttachments.visibility = View.VISIBLE + binding.tvNoOfAttachments.text = getString(R.string.text_multiple_attachment, state.listContents.size) + } else { + binding.tvNoOfAttachments.visibility = View.GONE + } } - } - binding.recyclerView.isNestedScrollingEnabled = !viewModel.isTaskCompleted(state) + if (state.requestContents is Success && !viewModel.isTaskCompleted(state)) { + binding.fabAddAttachments.visibility = View.VISIBLE + binding.fabAddAttachments.setOnClickListener { + showCreateSheet(state) + } + } - if (state.listContents.isEmpty()) requireActivity().onBackPressed() + binding.recyclerView.isNestedScrollingEnabled = !viewModel.isTaskCompleted(state) - epoxyController.requestModelBuild() - } + if (state.listContents.isEmpty()) requireActivity().onBackPressed() - private fun epoxyController() = simpleController(viewModel) { state -> + epoxyController.requestModelBuild() + } - if (state.listContents.isNotEmpty()) { - state.listContents.forEach { obj -> - listViewAttachmentRow { - id(stableId(obj)) - data(obj) - clickListener { model, _, _, _ -> onItemClicked(model.data()) } - deleteContentClickListener { model, _, _, _ -> deleteContentPrompt(model.data()) } + private fun epoxyController() = + simpleController(viewModel) { state -> + + if (state.listContents.isNotEmpty()) { + state.listContents.forEach { obj -> + listViewAttachmentRow { + id(stableId(obj)) + data(obj) + clickListener { model, _, _, _ -> onItemClicked(model.data()) } + deleteContentClickListener { model, _, _, _ -> deleteContentPrompt(model.data()) } + } } } } - } private fun onItemClicked(contentEntry: Entry) { if (!contentEntry.isUpload) { - val entry = Entry.convertContentEntryToEntry( - contentEntry, - MimeType.isDocFile(contentEntry.mimeType), - UploadServerType.UPLOAD_TO_TASK, - ) + val entry = + Entry.convertContentEntryToEntry( + contentEntry, + MimeType.isDocFile(contentEntry.mimeType), + UploadServerType.UPLOAD_TO_TASK, + ) if (!contentEntry.source.isNullOrEmpty()) { remoteViewerIntent(entry) - } else + } else { viewModel.executePreview(ActionOpenWith(entry)) + } } else { localViewerIntent(contentEntry) } diff --git a/browse/src/main/kotlin/com/alfresco/content/browse/tasks/attachments/ListViewAttachmentRow.kt b/browse/src/main/kotlin/com/alfresco/content/browse/tasks/attachments/ListViewAttachmentRow.kt index 6c2078b9f..452d9cb04 100644 --- a/browse/src/main/kotlin/com/alfresco/content/browse/tasks/attachments/ListViewAttachmentRow.kt +++ b/browse/src/main/kotlin/com/alfresco/content/browse/tasks/attachments/ListViewAttachmentRow.kt @@ -21,87 +21,88 @@ import com.alfresco.content.mimetype.MimeType * Marked as ListViewAttachmentRow class */ @ModelView(autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT) -class ListViewAttachmentRow @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0, -) : FrameLayout(context, attrs, defStyleAttr) { +class ListViewAttachmentRow + @JvmOverloads + constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + ) : FrameLayout(context, attrs, defStyleAttr) { + private val binding = ViewListAttachmentRowBinding.inflate(LayoutInflater.from(context), this) - private val binding = ViewListAttachmentRowBinding.inflate(LayoutInflater.from(context), this) + /** + * set the content data on list row + */ + @ModelProp + fun setData(data: Entry) { + binding.tvName.text = data.name + binding.iconFile.setImageDrawable(ResourcesCompat.getDrawable(resources, MimeType.with(data.mimeType).icon, context.theme)) - /** - * set the content data on list row - */ - @ModelProp - fun setData(data: Entry) { - binding.tvName.text = data.name - binding.iconFile.setImageDrawable(ResourcesCompat.getDrawable(resources, MimeType.with(data.mimeType).icon, context.theme)) + configureOfflineStatus(data) - configureOfflineStatus(data) - - binding.deleteContentButton.visibility = if (actionButtonVisibility(data)) View.VISIBLE else View.INVISIBLE - } + binding.deleteContentButton.visibility = if (actionButtonVisibility(data)) View.VISIBLE else View.INVISIBLE + } - private fun configureOfflineStatus(entry: Entry) { - // Offline screen items and uploads - if (entry.isFile && entry.hasOfflineStatus) { - val drawableRes = makeOfflineStatusConfig(entry) - if (drawableRes != null) { - val drawable = - ResourcesCompat.getDrawable(resources, drawableRes, context.theme) - if (drawable is AnimatedVectorDrawable) { - drawable.start() + private fun configureOfflineStatus(entry: Entry) { + // Offline screen items and uploads + if (entry.isFile && entry.hasOfflineStatus) { + val drawableRes = makeOfflineStatusConfig(entry) + if (drawableRes != null) { + val drawable = + ResourcesCompat.getDrawable(resources, drawableRes, context.theme) + if (drawable is AnimatedVectorDrawable) { + drawable.start() + } + binding.offlineIcon.setImageDrawable(drawable) + binding.offlineIcon.isVisible = true + } else { + binding.offlineIcon.isVisible = false } - binding.offlineIcon.setImageDrawable(drawable) - binding.offlineIcon.isVisible = true } else { binding.offlineIcon.isVisible = false } - } else { - binding.offlineIcon.isVisible = false } - } - private fun makeOfflineStatusConfig(entry: Entry): Int? = - when (entry.offlineStatus) { - OfflineStatus.PENDING -> - if (entry.isUpload) { - R.drawable.ic_offline_upload - } else { - R.drawable.ic_offline_status_pending - } + private fun makeOfflineStatusConfig(entry: Entry): Int? = + when (entry.offlineStatus) { + OfflineStatus.PENDING -> + if (entry.isUpload) { + R.drawable.ic_offline_upload + } else { + R.drawable.ic_offline_status_pending + } - OfflineStatus.SYNCING -> - R.drawable.ic_offline_status_in_progress_anim + OfflineStatus.SYNCING -> + R.drawable.ic_offline_status_in_progress_anim - OfflineStatus.SYNCED -> - R.drawable.ic_offline_status_synced + OfflineStatus.SYNCED -> + R.drawable.ic_offline_status_synced - OfflineStatus.ERROR -> - R.drawable.ic_offline_status_error + OfflineStatus.ERROR -> + R.drawable.ic_offline_status_error - else -> - R.drawable.ic_offline_status_synced - } + else -> + R.drawable.ic_offline_status_synced + } - private fun actionButtonVisibility(entry: Entry) = - !entry.isLink && !entry.isUpload && - // Child folder in offline tab - !(entry.isFolder && entry.hasOfflineStatus && !entry.isOffline) && !entry.isReadOnly + private fun actionButtonVisibility(entry: Entry) = + !entry.isLink && !entry.isUpload && + // Child folder in offline tab + !(entry.isFolder && entry.hasOfflineStatus && !entry.isOffline) && !entry.isReadOnly - /** - * list row click listener - */ - @CallbackProp - fun setClickListener(listener: OnClickListener?) { - setOnClickListener(listener) - } + /** + * list row click listener + */ + @CallbackProp + fun setClickListener(listener: OnClickListener?) { + setOnClickListener(listener) + } - /** - * delete icon click listener - */ - @CallbackProp - fun setDeleteContentClickListener(listener: OnClickListener?) { - binding.deleteContentButton.setOnClickListener(listener) + /** + * delete icon click listener + */ + @CallbackProp + fun setDeleteContentClickListener(listener: OnClickListener?) { + binding.deleteContentButton.setOnClickListener(listener) + } } -} diff --git a/browse/src/main/kotlin/com/alfresco/content/browse/tasks/comments/CommentsFragment.kt b/browse/src/main/kotlin/com/alfresco/content/browse/tasks/comments/CommentsFragment.kt index 00403714d..4272fda3b 100644 --- a/browse/src/main/kotlin/com/alfresco/content/browse/tasks/comments/CommentsFragment.kt +++ b/browse/src/main/kotlin/com/alfresco/content/browse/tasks/comments/CommentsFragment.kt @@ -29,7 +29,6 @@ import com.alfresco.ui.getDrawableForAttribute * Marked as CommentsFragment class */ class CommentsFragment : Fragment(), MavericksView { - val viewModel: TaskDetailViewModel by activityViewModel() private lateinit var binding: FragmentCommentsBinding private val epoxyController: AsyncEpoxyController by lazy { epoxyController() } @@ -44,7 +43,10 @@ class CommentsFragment : Fragment(), MavericksView { return binding.root } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { super.onViewCreated(view, savedInstanceState) AnalyticsManager().screenViewEvent(PageView.Comments) @@ -58,14 +60,19 @@ class CommentsFragment : Fragment(), MavericksView { binding.recyclerView.setController(epoxyController) - epoxyController.adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { - override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { - if (positionStart == 0) { - // @see: https://github.com/airbnb/epoxy/issues/224 - binding.recyclerView.layoutManager?.scrollToPosition(0) + epoxyController.adapter.registerAdapterDataObserver( + object : RecyclerView.AdapterDataObserver() { + override fun onItemRangeInserted( + positionStart: Int, + itemCount: Int, + ) { + if (positionStart == 0) { + // @see: https://github.com/airbnb/epoxy/issues/224 + binding.recyclerView.layoutManager?.scrollToPosition(0) + } } - } - }) + }, + ) updateSendIconView(binding.commentInput.text.toString()) withState(viewModel) { state -> @@ -88,19 +95,31 @@ class CommentsFragment : Fragment(), MavericksView { binding.commentInput.setText("") } - binding.commentInputLayout.editText?.addTextChangedListener(object : TextWatcher { - override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { - // no-op - } + binding.commentInputLayout.editText?.addTextChangedListener( + object : TextWatcher { + override fun beforeTextChanged( + s: CharSequence?, + start: Int, + count: Int, + after: Int, + ) { + // no-op + } - override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { - // no-op - } + override fun onTextChanged( + s: CharSequence?, + start: Int, + before: Int, + count: Int, + ) { + // no-op + } - override fun afterTextChanged(s: Editable?) { - updateSendIconView(s.toString()) - } - }) + override fun afterTextChanged(s: Editable?) { + updateSendIconView(s.toString()) + } + }, + ) requireActivity().window.decorView.viewTreeObserver.addOnGlobalLayoutListener { if (isAdded) { @@ -113,7 +132,9 @@ class CommentsFragment : Fragment(), MavericksView { binding.recyclerView.scrollToPosition(state.listComments.size - 1) } isScrolled = true - } else isScrolled = false + } else { + isScrolled = false + } } } @@ -133,38 +154,40 @@ class CommentsFragment : Fragment(), MavericksView { } } - override fun invalidate() = withState(viewModel) { state -> - if (state.requestComments.complete) { - binding.refreshLayout.isRefreshing = false - } + override fun invalidate() = + withState(viewModel) { state -> + if (state.requestComments.complete) { + binding.refreshLayout.isRefreshing = false + } - if (viewModel.isAddComment) { - requireContext().showKeyboard(binding.commentInput) - viewModel.isAddComment = false - } + if (viewModel.isAddComment) { + requireContext().showKeyboard(binding.commentInput) + viewModel.isAddComment = false + } - epoxyController.requestModelBuild() + epoxyController.requestModelBuild() - if (state.listComments.size > 1) { - binding.tvNoOfComments.visibility = View.VISIBLE - binding.tvNoOfComments.text = getString(R.string.text_multiple_comments, state.listComments.size) - } else { - binding.tvNoOfComments.visibility = View.GONE + if (state.listComments.size > 1) { + binding.tvNoOfComments.visibility = View.VISIBLE + binding.tvNoOfComments.text = getString(R.string.text_multiple_comments, state.listComments.size) + } else { + binding.tvNoOfComments.visibility = View.GONE + } } - } - private fun epoxyController() = simpleController(viewModel) { state -> + private fun epoxyController() = + simpleController(viewModel) { state -> - if (state.listComments.isNotEmpty()) { - state.listComments.forEach { obj -> - listViewCommentRow { - id(obj.id) - data(obj) + if (state.listComments.isNotEmpty()) { + state.listComments.forEach { obj -> + listViewCommentRow { + id(obj.id) + data(obj) + } + } + binding.recyclerView.post { + binding.recyclerView.scrollToPosition(state.listComments.size - 1) } - } - binding.recyclerView.post { - binding.recyclerView.scrollToPosition(state.listComments.size - 1) } } - } } diff --git a/browse/src/main/kotlin/com/alfresco/content/browse/tasks/comments/ListViewCommentRow.kt b/browse/src/main/kotlin/com/alfresco/content/browse/tasks/comments/ListViewCommentRow.kt index a8ba12fc5..734b73f63 100644 --- a/browse/src/main/kotlin/com/alfresco/content/browse/tasks/comments/ListViewCommentRow.kt +++ b/browse/src/main/kotlin/com/alfresco/content/browse/tasks/comments/ListViewCommentRow.kt @@ -17,22 +17,31 @@ import com.alfresco.content.getLocalizedName * Marked as ListViewCommentRow class */ @ModelView(autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT) -class ListViewCommentRow @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0, -) : FrameLayout(context, attrs, defStyleAttr) { +class ListViewCommentRow + @JvmOverloads + constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + ) : FrameLayout(context, attrs, defStyleAttr) { + private val binding = ViewListCommentRowBinding.inflate(LayoutInflater.from(context), this) - private val binding = ViewListCommentRowBinding.inflate(LayoutInflater.from(context), this) - - /** - * set the comment row on list row - */ - @ModelProp - fun setData(data: CommentEntry) { - binding.tvName.text = context.getLocalizedName(data.userGroupDetails?.name ?: "") - binding.tvUserInitial.text = context.getLocalizedName(data.userGroupDetails?.nameInitial ?: "") - binding.tvComment.text = data.message - binding.tvDate.text = if (data.created != null) data.created?.toLocalDate().toString().getFormattedDate(DATE_FORMAT_1, DATE_FORMAT_4) else "" + /** + * set the comment row on list row + */ + @ModelProp + fun setData(data: CommentEntry) { + binding.tvName.text = context.getLocalizedName(data.userGroupDetails?.name ?: "") + binding.tvUserInitial.text = context.getLocalizedName(data.userGroupDetails?.nameInitial ?: "") + binding.tvComment.text = data.message + binding.tvDate.text = + if (data.created != null) { + data.created?.toLocalDate().toString().getFormattedDate( + DATE_FORMAT_1, + DATE_FORMAT_4, + ) + } else { + "" + } + } } -} diff --git a/browse/src/main/kotlin/com/alfresco/content/browse/tasks/detail/TaskDetailExtension.kt b/browse/src/main/kotlin/com/alfresco/content/browse/tasks/detail/TaskDetailExtension.kt index fc2d9fa30..2e39dacf9 100644 --- a/browse/src/main/kotlin/com/alfresco/content/browse/tasks/detail/TaskDetailExtension.kt +++ b/browse/src/main/kotlin/com/alfresco/content/browse/tasks/detail/TaskDetailExtension.kt @@ -27,80 +27,90 @@ import kotlinx.coroutines.launch import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine -internal fun TaskDetailFragment.updateTaskDetailUI(isEdit: Boolean) = withState(viewModel) { state -> - viewModel.hasTaskEditMode = isEdit - menuDetail.findItem(R.id.action_edit).isVisible = !isEdit - menuDetail.findItem(R.id.action_done).isVisible = isEdit - if (isEdit) { - binding.iconTitleEdit.visibility = View.VISIBLE - if (state.parent?.localDueDate != null) { - binding.iconDueDateEdit.visibility = View.GONE - binding.iconDueDateClear.visibility = View.VISIBLE +internal fun TaskDetailFragment.updateTaskDetailUI(isEdit: Boolean) = + withState(viewModel) { state -> + viewModel.hasTaskEditMode = isEdit + menuDetail.findItem(R.id.action_edit).isVisible = !isEdit + menuDetail.findItem(R.id.action_done).isVisible = isEdit + if (isEdit) { + binding.iconTitleEdit.visibility = View.VISIBLE + if (state.parent?.localDueDate != null) { + binding.iconDueDateEdit.visibility = View.GONE + binding.iconDueDateClear.visibility = View.VISIBLE + } else { + binding.iconDueDateClear.visibility = View.GONE + binding.iconDueDateEdit.visibility = View.VISIBLE + } + binding.iconPriorityEdit.visibility = View.VISIBLE + binding.iconAssignedEdit.visibility = View.VISIBLE + binding.completeButton.isEnabled = false } else { - binding.iconDueDateClear.visibility = View.GONE - binding.iconDueDateEdit.visibility = View.VISIBLE + binding.iconTitleEdit.visibility = View.GONE + binding.iconDueDateEdit.visibility = View.INVISIBLE + binding.iconDueDateClear.visibility = View.INVISIBLE + binding.iconPriorityEdit.visibility = View.INVISIBLE + binding.iconAssignedEdit.visibility = View.INVISIBLE + binding.completeButton.isEnabled = true } - binding.iconPriorityEdit.visibility = View.VISIBLE - binding.iconAssignedEdit.visibility = View.VISIBLE - binding.completeButton.isEnabled = false - } else { - binding.iconTitleEdit.visibility = View.GONE - binding.iconDueDateEdit.visibility = View.INVISIBLE - binding.iconDueDateClear.visibility = View.INVISIBLE - binding.iconPriorityEdit.visibility = View.INVISIBLE - binding.iconAssignedEdit.visibility = View.INVISIBLE - binding.completeButton.isEnabled = true } -} -internal fun TaskDetailFragment.enableTaskFormUI() = withState(viewModel) { state -> - binding.clComment.visibility = View.GONE - binding.clIdentifier.visibility = View.VISIBLE +internal fun TaskDetailFragment.enableTaskFormUI() = + withState(viewModel) { state -> + binding.clComment.visibility = View.GONE + binding.clIdentifier.visibility = View.VISIBLE // binding.iconStatus.setImageResource(R.drawable.ic_task_status_star) /*binding.clStatus.setSafeOnClickListener { findNavController().navigate(R.id.action_nav_task_detail_to_nav_task_status) }*/ -} + } -internal fun TaskDetailFragment.setTaskDetailAfterResponse(dataObj: TaskEntry) = withState(viewModel) { state -> - if (viewModel.isTaskFormAndDetailRequestCompleted(state) || viewModel.isTaskDetailRequestCompleted(state)) { - if (dataObj.localDueDate != null) { - binding.tvDueDateValue.text = dataObj.localDueDate?.getFormattedDate(DATE_FORMAT_1, DATE_FORMAT_4) - } else { - binding.tvDueDateValue.text = requireContext().getString(R.string.empty_no_due_date) - } +internal fun TaskDetailFragment.setTaskDetailAfterResponse(dataObj: TaskEntry) = + withState(viewModel) { state -> + if (viewModel.isTaskFormAndDetailRequestCompleted(state) || viewModel.isTaskDetailRequestCompleted(state)) { + if (dataObj.localDueDate != null) { + binding.tvDueDateValue.text = dataObj.localDueDate?.getFormattedDate(DATE_FORMAT_1, DATE_FORMAT_4) + } else { + binding.tvDueDateValue.text = requireContext().getString(R.string.empty_no_due_date) + } - binding.tvPriorityValue.updatePriorityView(dataObj.priority) - binding.tvDescription.text = if (dataObj.description.isNullOrEmpty()) requireContext().getString(R.string.empty_description) else dataObj.description - binding.tvDescription.addTextViewPrefix(requireContext().getString(R.string.suffix_view_all)) { - showTitleDescriptionComponent() - } + binding.tvPriorityValue.updatePriorityView(dataObj.priority) + binding.tvDescription.text = + if (dataObj.description.isNullOrEmpty()) { + requireContext().getString( + R.string.empty_description, + ) + } else { + dataObj.description + } + binding.tvDescription.addTextViewPrefix(requireContext().getString(R.string.suffix_view_all)) { + showTitleDescriptionComponent() + } - binding.completeButton.visibility = if (viewModel.isCompleteButtonVisible(state)) View.VISIBLE else View.GONE + binding.completeButton.visibility = if (viewModel.isCompleteButtonVisible(state)) View.VISIBLE else View.GONE - if (viewModel.isTaskCompleted(state)) { - binding.tvAddComment.visibility = View.GONE - binding.iconAddCommentUser.visibility = View.GONE - binding.clCompleted.visibility = View.VISIBLE - if (state.listComments.isEmpty()) binding.viewComment2.visibility = View.GONE else View.VISIBLE - binding.tvCompletedValue.text = dataObj.endDate?.toLocalDate().toString().getFormattedDate(DATE_FORMAT_1, DATE_FORMAT_4) + if (viewModel.isTaskCompleted(state)) { + binding.tvAddComment.visibility = View.GONE + binding.iconAddCommentUser.visibility = View.GONE + binding.clCompleted.visibility = View.VISIBLE + if (state.listComments.isEmpty()) binding.viewComment2.visibility = View.GONE else View.VISIBLE + binding.tvCompletedValue.text = dataObj.endDate?.toLocalDate().toString().getFormattedDate(DATE_FORMAT_1, DATE_FORMAT_4) - (binding.clDueDate.layoutParams as ConstraintLayout.LayoutParams).apply { - topMargin = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 24f, resources.displayMetrics).toInt() - } - binding.clStatus.visibility = View.GONE - } else { - binding.clCompleted.visibility = View.GONE - (binding.clDueDate.layoutParams as ConstraintLayout.LayoutParams).apply { - topMargin = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 0f, resources.displayMetrics).toInt() - } - binding.clStatus.visibility = View.VISIBLE + (binding.clDueDate.layoutParams as ConstraintLayout.LayoutParams).apply { + topMargin = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 24f, resources.displayMetrics).toInt() + } + binding.clStatus.visibility = View.GONE + } else { + binding.clCompleted.visibility = View.GONE + (binding.clDueDate.layoutParams as ConstraintLayout.LayoutParams).apply { + topMargin = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 0f, resources.displayMetrics).toInt() + } + binding.clStatus.visibility = View.VISIBLE - binding.tvStatusValue.text = getString(R.string.status_active) + binding.tvStatusValue.text = getString(R.string.status_active) + } } } -} internal fun TaskDetailFragment.setListeners() { viewModel.setListener(this) @@ -152,14 +162,15 @@ internal fun TaskDetailFragment.setListeners() { withState(viewModel) { state -> val dataObj = state.parent viewLifecycleOwner.lifecycleScope.launch { - val result = showComponentSheetDialog( - requireContext(), - ComponentData( - name = requireContext().getString(R.string.title_priority), - query = dataObj?.priority.toString(), - selector = ComponentType.TASK_PROCESS_PRIORITY.value, - ), - ) + val result = + showComponentSheetDialog( + requireContext(), + ComponentData( + name = requireContext().getString(R.string.title_priority), + query = dataObj?.priority.toString(), + selector = ComponentType.TASK_PROCESS_PRIORITY.value, + ), + ) if (result != null) { viewModel.updatePriority(result) @@ -171,10 +182,11 @@ internal fun TaskDetailFragment.setListeners() { withState(viewModel) { state -> requireNotNull(state.parent) viewLifecycleOwner.lifecycleScope.launch { - val result = showSearchUserComponentDialog( - requireContext(), - state.parent, - ) + val result = + showSearchUserComponentDialog( + requireContext(), + state.parent, + ) if (result != null) { viewModel.updateAssignee(result) } @@ -196,18 +208,19 @@ private fun TaskDetailFragment.navigateToCommentScreen() { private fun TaskDetailFragment.showCalendar(fromDate: String) { viewLifecycleOwner.lifecycleScope.launch { - val result = suspendCoroutine { - DatePickerBuilder( - context = requireContext(), - fromDate = fromDate, - isFrom = true, - isFutureDate = true, - dateFormat = DATE_FORMAT_4, - ) - .onSuccess { date -> it.resume(date) } - .onFailure { it.resume(null) } - .show() - } + val result = + suspendCoroutine { + DatePickerBuilder( + context = requireContext(), + fromDate = fromDate, + isFrom = true, + isFutureDate = true, + dateFormat = DATE_FORMAT_4, + ) + .onSuccess { date -> it.resume(date) } + .onFailure { it.resume(null) } + .show() + } result?.let { date -> viewModel.updateDate(date.getFormattedDate(DATE_FORMAT_4, DATE_FORMAT_5)) @@ -215,27 +228,29 @@ private fun TaskDetailFragment.showCalendar(fromDate: String) { } } -internal fun TaskDetailFragment.showTitleDescriptionComponent() = withState(viewModel) { - viewLifecycleOwner.lifecycleScope.launch { - showComponentSheetDialog( - requireContext(), - ComponentData( - name = requireContext().getString(R.string.task_title), - query = it.parent?.name, - value = it.parent?.description, - selector = ComponentType.VIEW_TEXT.value, - ), - ) +internal fun TaskDetailFragment.showTitleDescriptionComponent() = + withState(viewModel) { + viewLifecycleOwner.lifecycleScope.launch { + showComponentSheetDialog( + requireContext(), + ComponentData( + name = requireContext().getString(R.string.task_title), + query = it.parent?.name, + value = it.parent?.description, + selector = ComponentType.VIEW_TEXT.value, + ), + ) + } } -} -internal fun TaskDetailFragment.makeClaimButton() = withState(viewModel) { state -> - if (binding.parentOutcomes.childCount == 0) { - val button = this.layoutInflater.inflate(R.layout.view_layout_positive_outcome, binding.parentOutcomes, false) as MaterialButton - button.text = getString(R.string.action_menu_claim) - button.setOnClickListener { - viewModel.claimTask() +internal fun TaskDetailFragment.makeClaimButton() = + withState(viewModel) { state -> + if (binding.parentOutcomes.childCount == 0) { + val button = this.layoutInflater.inflate(R.layout.view_layout_positive_outcome, binding.parentOutcomes, false) as MaterialButton + button.text = getString(R.string.action_menu_claim) + button.setOnClickListener { + viewModel.claimTask() + } + binding.parentOutcomes.addView(button) } - binding.parentOutcomes.addView(button) } -} diff --git a/browse/src/main/kotlin/com/alfresco/content/browse/tasks/detail/TaskDetailFragment.kt b/browse/src/main/kotlin/com/alfresco/content/browse/tasks/detail/TaskDetailFragment.kt index 81889c6c4..2ab749f95 100644 --- a/browse/src/main/kotlin/com/alfresco/content/browse/tasks/detail/TaskDetailFragment.kt +++ b/browse/src/main/kotlin/com/alfresco/content/browse/tasks/detail/TaskDetailFragment.kt @@ -60,7 +60,6 @@ import kotlin.coroutines.suspendCoroutine * Marked as TaskDetailFragment class */ class TaskDetailFragment : BaseDetailFragment(), MavericksView, EntryListener { - val viewModel: TaskDetailViewModel by activityViewModel() lateinit var binding: FragmentTaskDetailBinding lateinit var commentViewBinding: ViewListCommentRowBinding @@ -84,7 +83,10 @@ class TaskDetailFragment : BaseDetailFragment(), MavericksView, EntryListener { return viewLayout as View } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { super.onViewCreated(view, savedInstanceState) AnalyticsManager().screenViewEvent(if (viewModel.isWorkflowTask) PageView.WorkflowTaskView else PageView.TaskView) (requireActivity() as BaseActivity).setSupportActionBar(binding.toolbar) @@ -101,7 +103,9 @@ class TaskDetailFragment : BaseDetailFragment(), MavericksView, EntryListener { withState(viewModel) { state -> if (!viewModel.isWorkflowTask && (viewModel.isTaskAssigneeChanged(state) || viewModel.isTaskDetailsChanged(state))) { discardTaskPrompt() - } else requireActivity().onBackPressed() + } else { + requireActivity().onBackPressed() + } } } title = resources.getString(R.string.title_task_view) @@ -111,7 +115,10 @@ class TaskDetailFragment : BaseDetailFragment(), MavericksView, EntryListener { setListeners() } - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + override fun onCreateOptionsMenu( + menu: Menu, + inflater: MenuInflater, + ) { if (!viewModel.isWorkflowTask) { inflater.inflate(R.menu.menu_task_detail, menu) menuDetail = menu @@ -180,44 +187,45 @@ class TaskDetailFragment : BaseDetailFragment(), MavericksView, EntryListener { viewModel.deleteAttachment(contentId) } - override fun invalidate() = withState(viewModel) { state -> - - binding.loading.isVisible = (state.request is Loading && state.parent != null) || - (state.requestComments is Loading && state.listComments.isEmpty()) || - (state.requestContents is Loading && state.listContents.isEmpty()) || - (state.requestCompleteTask is Loading) || (state.requestUpdateTask is Loading) || - (state.requestDeleteContent is Loading) || (state.requestTaskForm is Loading) || - (state.requestOutcomes is Loading) || (state.requestClaimRelease is Loading) + override fun invalidate() = + withState(viewModel) { state -> - setData(state) + binding.loading.isVisible = (state.request is Loading && state.parent != null) || + (state.requestComments is Loading && state.listComments.isEmpty()) || + (state.requestContents is Loading && state.listContents.isEmpty()) || + (state.requestCompleteTask is Loading) || (state.requestUpdateTask is Loading) || + (state.requestDeleteContent is Loading) || (state.requestTaskForm is Loading) || + (state.requestOutcomes is Loading) || (state.requestClaimRelease is Loading) - setCommentData(state.listComments) + setData(state) - when { - (state.requestCompleteTask.invoke()?.code() == 200) || - (state.requestOutcomes.invoke()?.code() == 200) || - (state.requestClaimRelease.invoke()?.code() == 200) -> { - viewModel.updateTaskList() - requireActivity().onBackPressed() - } + setCommentData(state.listComments) - state.requestUpdateTask is Success -> { - if (!viewModel.isExecutingUpdateDetails && !viewModel.isExecutingAssignUser) { - AnalyticsManager().taskEvent(EventName.DoneTask) - menuDetail.findItem(R.id.action_done).isVisible = false - menuDetail.findItem(R.id.action_edit).isVisible = true - updateTaskDetailUI(false) - viewModel.copyEntry(state.parent) - viewModel.resetUpdateTaskRequest() + when { + (state.requestCompleteTask.invoke()?.code() == 200) || + (state.requestOutcomes.invoke()?.code() == 200) || + (state.requestClaimRelease.invoke()?.code() == 200) -> { viewModel.updateTaskList() + requireActivity().onBackPressed() + } + + state.requestUpdateTask is Success -> { + if (!viewModel.isExecutingUpdateDetails && !viewModel.isExecutingAssignUser) { + AnalyticsManager().taskEvent(EventName.DoneTask) + menuDetail.findItem(R.id.action_done).isVisible = false + menuDetail.findItem(R.id.action_edit).isVisible = true + updateTaskDetailUI(false) + viewModel.copyEntry(state.parent) + viewModel.resetUpdateTaskRequest() + viewModel.updateTaskList() + } } } - } - if (state.requestContents.complete || (viewModel.isWorkflowTask && state.requestTaskForm.complete)) { - epoxyAttachmentController.requestModelBuild() + if (state.requestContents.complete || (viewModel.isWorkflowTask && state.requestTaskForm.complete)) { + epoxyAttachmentController.requestModelBuild() + } } - } private fun setCommentData(listComments: List) { if (listComments.isNotEmpty()) { @@ -239,7 +247,15 @@ class TaskDetailFragment : BaseDetailFragment(), MavericksView, EntryListener { commentViewBinding.tvUserInitial.text = requireContext().getLocalizedName(commentObj.userGroupDetails?.nameInitial ?: "") commentViewBinding.tvName.text = requireContext().getLocalizedName(commentObj.userGroupDetails?.name ?: "") - commentViewBinding.tvDate.text = if (commentObj.created != null) commentObj.created?.toLocalDate().toString().getFormattedDate(DATE_FORMAT_1, DATE_FORMAT_4) else "" + commentViewBinding.tvDate.text = + if (commentObj.created != null) { + commentObj.created?.toLocalDate().toString().getFormattedDate( + DATE_FORMAT_1, + DATE_FORMAT_4, + ) + } else { + "" + } commentViewBinding.tvComment.text = commentObj.message } else { binding.clCommentHeader.visibility = View.GONE @@ -260,76 +276,85 @@ class TaskDetailFragment : BaseDetailFragment(), MavericksView, EntryListener { } binding.tvAssignedValue.apply { - text = if (viewModel.getAPSUser().id == dataObj.assignee?.id) { - requireContext().getLocalizedName(dataObj.assignee?.let { UserGroupDetails.with(it).name } ?: "") - } else requireContext().getLocalizedName(dataObj.assignee?.name ?: "") + text = + if (viewModel.getAPSUser().id == dataObj.assignee?.id) { + requireContext().getLocalizedName(dataObj.assignee?.let { UserGroupDetails.with(it).name } ?: "") + } else { + requireContext().getLocalizedName(dataObj.assignee?.name ?: "") + } } binding.tvIdentifierValue.text = dataObj.id setTaskDetailAfterResponse(dataObj) } } - private fun epoxyAttachmentController() = simpleController(viewModel) { state -> - val handler = Handler(Looper.getMainLooper()) - if (state.listContents.isNotEmpty()) { - handler.post { - binding.clAddAttachment.visibility = View.VISIBLE - binding.tvNoAttachedFilesError.visibility = View.GONE - binding.tvAttachedTitle.text = getString(R.string.text_attached_files) - binding.recyclerViewAttachments.visibility = View.VISIBLE - - if (state.listContents.size > 1) { - binding.tvNoOfAttachments.visibility = View.VISIBLE - } else binding.tvNoOfAttachments.visibility = View.GONE - - if (state.listContents.size > 4) { - binding.tvAttachmentViewAll.visibility = View.VISIBLE - } else - binding.tvAttachmentViewAll.visibility = View.GONE + private fun epoxyAttachmentController() = + simpleController(viewModel) { state -> + val handler = Handler(Looper.getMainLooper()) + if (state.listContents.isNotEmpty()) { + handler.post { + binding.clAddAttachment.visibility = View.VISIBLE + binding.tvNoAttachedFilesError.visibility = View.GONE + binding.tvAttachedTitle.text = getString(R.string.text_attached_files) + binding.recyclerViewAttachments.visibility = View.VISIBLE - binding.clAddAttachment.isVisible = !viewModel.isTaskCompleted(state) && !viewModel.isWorkflowTask + if (state.listContents.size > 1) { + binding.tvNoOfAttachments.visibility = View.VISIBLE + } else { + binding.tvNoOfAttachments.visibility = View.GONE + } - binding.tvNoOfAttachments.text = getString(R.string.text_multiple_attachment, state.listContents.size) - } + if (state.listContents.size > 4) { + binding.tvAttachmentViewAll.visibility = View.VISIBLE + } else { + binding.tvAttachmentViewAll.visibility = View.GONE + } + + binding.clAddAttachment.isVisible = !viewModel.isTaskCompleted(state) && !viewModel.isWorkflowTask - state.listContents.take(4).forEach { obj -> - listViewAttachmentRow { - id(stableId(obj)) - data(obj) - clickListener { model, _, _, _ -> onItemClicked(model.data()) } - deleteContentClickListener { model, _, _, _ -> deleteContentPrompt(model.data()) } + binding.tvNoOfAttachments.text = getString(R.string.text_multiple_attachment, state.listContents.size) } - } - } else { - handler.post { - binding.recyclerViewAttachments.visibility = View.GONE - binding.tvAttachmentViewAll.visibility = View.GONE - binding.tvNoOfAttachments.visibility = View.GONE - if (!viewModel.isTaskCompleted(state)) { - binding.clAddAttachment.visibility = if (!viewModel.isWorkflowTask) View.VISIBLE else View.GONE - binding.tvAttachedTitle.text = getString(R.string.text_attached_files) - binding.tvNoAttachedFilesError.visibility = View.VISIBLE - binding.tvNoAttachedFilesError.text = getString(R.string.no_attached_files) - } else { - binding.clAddAttachment.visibility = View.GONE - binding.tvAttachedTitle.text = "" - binding.tvNoAttachedFilesError.visibility = View.GONE + + state.listContents.take(4).forEach { obj -> + listViewAttachmentRow { + id(stableId(obj)) + data(obj) + clickListener { model, _, _, _ -> onItemClicked(model.data()) } + deleteContentClickListener { model, _, _, _ -> deleteContentPrompt(model.data()) } + } + } + } else { + handler.post { + binding.recyclerViewAttachments.visibility = View.GONE + binding.tvAttachmentViewAll.visibility = View.GONE + binding.tvNoOfAttachments.visibility = View.GONE + if (!viewModel.isTaskCompleted(state)) { + binding.clAddAttachment.visibility = if (!viewModel.isWorkflowTask) View.VISIBLE else View.GONE + binding.tvAttachedTitle.text = getString(R.string.text_attached_files) + binding.tvNoAttachedFilesError.visibility = View.VISIBLE + binding.tvNoAttachedFilesError.text = getString(R.string.no_attached_files) + } else { + binding.clAddAttachment.visibility = View.GONE + binding.tvAttachedTitle.text = "" + binding.tvNoAttachedFilesError.visibility = View.GONE + } } } } - } private fun onItemClicked(contentEntry: Entry) { if (!contentEntry.isUpload) { - val entry = Entry.convertContentEntryToEntry( - contentEntry, - MimeType.isDocFile(contentEntry.mimeType), - UploadServerType.UPLOAD_TO_TASK, - ) + val entry = + Entry.convertContentEntryToEntry( + contentEntry, + MimeType.isDocFile(contentEntry.mimeType), + UploadServerType.UPLOAD_TO_TASK, + ) if (!contentEntry.source.isNullOrEmpty()) { remoteViewerIntent(entry) - } else + } else { viewModel.executePreview(ActionOpenWith(entry)) + } } else { localViewerIntent(contentEntry) } @@ -338,35 +363,45 @@ class TaskDetailFragment : BaseDetailFragment(), MavericksView, EntryListener { internal fun taskCompletePrompt(filesInQueue: Boolean) { val oldDialog = taskCompleteConfirmationDialog.get() if (oldDialog != null && oldDialog.isShowing) return - val dialog = MaterialAlertDialogBuilder(requireContext()) - .setTitle(getString(R.string.dialog_title_complete_task)) - .setMessage(if (filesInQueue) getString(R.string.dialog_message_complete_task_files_queue) else getString(R.string.dialog_message_complete_task)) - .setNegativeButton(getString(R.string.dialog_negative_button_task), null) - .setPositiveButton(getString(R.string.dialog_positive_button_task)) { _, _ -> - if (filesInQueue) { - withState(viewModel) { state -> - viewModel.removeTaskEntries(state) + val dialog = + MaterialAlertDialogBuilder(requireContext()) + .setTitle(getString(R.string.dialog_title_complete_task)) + .setMessage( + if (filesInQueue) { + getString( + R.string.dialog_message_complete_task_files_queue, + ) + } else { + getString(R.string.dialog_message_complete_task) + }, + ) + .setNegativeButton(getString(R.string.dialog_negative_button_task), null) + .setPositiveButton(getString(R.string.dialog_positive_button_task)) { _, _ -> + if (filesInQueue) { + withState(viewModel) { state -> + viewModel.removeTaskEntries(state) + } } + AnalyticsManager().taskEvent(EventName.TaskComplete) + viewModel.completeTask() } - AnalyticsManager().taskEvent(EventName.TaskComplete) - viewModel.completeTask() - } - .show() + .show() taskCompleteConfirmationDialog = WeakReference(dialog) } private fun discardTaskPrompt() { val oldDialog = discardTaskDialog.get() if (oldDialog != null && oldDialog.isShowing) return - val dialog = MaterialAlertDialogBuilder(requireContext()) - .setCancelable(false) - .setTitle(getString(R.string.dialog_title_discard_task)) - .setMessage(getString(R.string.dialog_message_discard_task)) - .setNegativeButton(getString(R.string.dialog_negative_button_task), null) - .setPositiveButton(getString(R.string.dialog_positive_button_task)) { _, _ -> - requireActivity().onBackPressed() - } - .show() + val dialog = + MaterialAlertDialogBuilder(requireContext()) + .setCancelable(false) + .setTitle(getString(R.string.dialog_title_discard_task)) + .setMessage(getString(R.string.dialog_message_discard_task)) + .setNegativeButton(getString(R.string.dialog_negative_button_task), null) + .setPositiveButton(getString(R.string.dialog_positive_button_task)) { _, _ -> + requireActivity().onBackPressed() + } + .show() discardTaskDialog = WeakReference(dialog) } @@ -411,7 +446,11 @@ class TaskDetailFragment : BaseDetailFragment(), MavericksView, EntryListener { } } - private fun executeContinuation(continuation: Continuation, name: String, query: String) { + private fun executeContinuation( + continuation: Continuation, + name: String, + query: String, + ) { continuation.resume(ComponentMetaData(name = name, query = query)) } } diff --git a/browse/src/main/kotlin/com/alfresco/content/browse/tasks/detail/TaskDetailViewModel.kt b/browse/src/main/kotlin/com/alfresco/content/browse/tasks/detail/TaskDetailViewModel.kt index b22376a2f..c641d2647 100644 --- a/browse/src/main/kotlin/com/alfresco/content/browse/tasks/detail/TaskDetailViewModel.kt +++ b/browse/src/main/kotlin/com/alfresco/content/browse/tasks/detail/TaskDetailViewModel.kt @@ -37,7 +37,6 @@ class TaskDetailViewModel( val context: Context, val repository: TaskRepository, ) : MavericksViewModel(state) { - private var observeUploadsJob: Job? = null var isAddComment = false var hasTaskEditMode = false @@ -77,76 +76,79 @@ class TaskDetailViewModel( */ fun resetUpdateTaskRequest() = setState { copy(requestUpdateTask = Uninitialized) } - private fun getTaskDetails() = withState { state -> - viewModelScope.launch { - // Fetch tasks detail data - repository::getTaskDetails.asFlow( - state.parent?.id ?: "", - ).execute { - when (it) { - is Loading -> copy(request = Loading()) - is Fail -> copy(request = Fail(it.error)) - is Success -> { - val updateState = update(it()) - updateState.copy(request = Success(it())) - } - - else -> { - this + private fun getTaskDetails() = + withState { state -> + viewModelScope.launch { + // Fetch tasks detail data + repository::getTaskDetails.asFlow( + state.parent?.id ?: "", + ).execute { + when (it) { + is Loading -> copy(request = Loading()) + is Fail -> copy(request = Fail(it.error)) + is Success -> { + val updateState = update(it()) + updateState.copy(request = Success(it())) + } + + else -> { + this + } } } } } - } /** * gets all the comments from server by using given task Id. */ - fun getComments() = withState { state -> - viewModelScope.launch { - // Fetch tasks detail data - repository::getComments.asFlow( - state.parent?.id ?: "", - ).execute { - when (it) { - is Loading -> copy(requestComments = Loading()) - is Fail -> copy(requestComments = Fail(it.error)) - is Success -> { - update(it()).copy(requestComments = Success(it())) - } - - else -> { - this + fun getComments() = + withState { state -> + viewModelScope.launch { + // Fetch tasks detail data + repository::getComments.asFlow( + state.parent?.id ?: "", + ).execute { + when (it) { + is Loading -> copy(requestComments = Loading()) + is Fail -> copy(requestComments = Fail(it.error)) + is Success -> { + update(it()).copy(requestComments = Success(it())) + } + + else -> { + this + } } } } } - } /** * gets all the attachments from server by using given task Id. */ - fun getContents() = withState { state -> - viewModelScope.launch { - // Fetch tasks detail data - repository::getContents.asFlow( - state.parent?.id ?: "", - ).execute { - when (it) { - is Loading -> copy(requestContents = Loading()) - is Fail -> copy(requestContents = Fail(it.error)) - is Success -> { - if (!isTaskCompleted(state)) observeUploads(parent?.id) - update(it()).copy(requestContents = Success(it())) - } - - else -> { - this + fun getContents() = + withState { state -> + viewModelScope.launch { + // Fetch tasks detail data + repository::getContents.asFlow( + state.parent?.id ?: "", + ).execute { + when (it) { + is Loading -> copy(requestContents = Loading()) + is Fail -> copy(requestContents = Fail(it.error)) + is Success -> { + if (!isTaskCompleted(state)) observeUploads(parent?.id) + update(it()).copy(requestContents = Success(it())) + } + + else -> { + this + } } } } } - } private fun observeUploads(taskId: String?) { if (taskId == null) return @@ -157,65 +159,68 @@ class TaskDetailViewModel( repo.removeCompletedUploads(taskId) observeUploadsJob?.cancel() - observeUploadsJob = repo.observeUploads(taskId, UploadServerType.UPLOAD_TO_TASK) - .execute { - if (it is Success) { - updateUploads(it()) - } else { - this + observeUploadsJob = + repo.observeUploads(taskId, UploadServerType.UPLOAD_TO_TASK) + .execute { + if (it is Success) { + updateUploads(it()) + } else { + this + } } - } } /** * execute the add comment api */ - fun addComment(message: String) = withState { state -> - viewModelScope.launch { - // Fetch tasks detail data - repository::addComments.asFlow( - state.parent?.id ?: "", - CommentPayload.with(message), - ).execute { - when (it) { - is Loading -> copy(requestAddComment = Loading()) - is Fail -> copy(requestAddComment = Fail(it.error)) - is Success -> { - getComments() - copy(requestAddComment = Success(it())) - } - - else -> { - this + fun addComment(message: String) = + withState { state -> + viewModelScope.launch { + // Fetch tasks detail data + repository::addComments.asFlow( + state.parent?.id ?: "", + CommentPayload.with(message), + ).execute { + when (it) { + is Loading -> copy(requestAddComment = Loading()) + is Fail -> copy(requestAddComment = Fail(it.error)) + is Success -> { + getComments() + copy(requestAddComment = Success(it())) + } + + else -> { + this + } } } } } - } /** * execute the complete task api */ - fun completeTask() = withState { state -> - viewModelScope.launch { - // Fetch tasks detail data - repository::completeTask.asFlow( - state.parent?.id ?: "", - ).execute { - when (it) { - is Loading -> copy(requestCompleteTask = Loading()) - is Fail -> copy(requestCompleteTask = Fail(it.error)) - is Success -> { - copy(requestCompleteTask = Success(it())) - } - - else -> { - this + fun completeTask() = + withState { state -> + viewModelScope.launch { + // Fetch tasks detail data + repository::completeTask.asFlow( + state.parent?.id ?: "", + ).execute { + when (it) { + is Loading -> copy(requestCompleteTask = Loading()) + is Fail -> copy(requestCompleteTask = Fail(it.error)) + is Success -> { + copy(requestCompleteTask = Success(it())) + } + + else -> { + this + } } } } } - } /** * If there is any change in the task assignee then it will return true otherwise false @@ -236,7 +241,11 @@ class TaskDetailViewModel( return true } - if (state.parent?.localDueDate?.getFormattedDate(DATE_FORMAT_1, DATE_FORMAT_1) != state.taskEntry?.localDueDate?.getFormattedDate(DATE_FORMAT_1, DATE_FORMAT_1)) { + if (state.parent?.localDueDate?.getFormattedDate( + DATE_FORMAT_1, + DATE_FORMAT_1, + ) != state.taskEntry?.localDueDate?.getFormattedDate(DATE_FORMAT_1, DATE_FORMAT_1) + ) { return true } @@ -258,7 +267,10 @@ class TaskDetailViewModel( /** * update the formatted date and local date in the existing TaskEntry obj and update the UI. */ - fun updateDate(formattedDate: String?, isClearDueDate: Boolean = false) { + fun updateDate( + formattedDate: String?, + isClearDueDate: Boolean = false, + ) { setState { requireNotNull(this.parent) copy(parent = TaskEntry.updateTaskDueDate(this.parent, formattedDate, isClearDueDate)) @@ -293,88 +305,91 @@ class TaskDetailViewModel( /** * execute the update task detail api */ - fun updateTaskDetails() = withState { state -> - requireNotNull(state.parent) - viewModelScope.launch { - // Fetch tasks detail data - repository::updateTaskDetails.asFlow( - state.parent, - ).execute { - when (it) { - is Loading -> copy(requestUpdateTask = Loading()) - is Fail -> copy(requestUpdateTask = Fail(it.error)) - is Success -> { - isExecutingUpdateDetails = false - copy(requestUpdateTask = Success(it())) - } - - else -> { - this + fun updateTaskDetails() = + withState { state -> + requireNotNull(state.parent) + viewModelScope.launch { + // Fetch tasks detail data + repository::updateTaskDetails.asFlow( + state.parent, + ).execute { + when (it) { + is Loading -> copy(requestUpdateTask = Loading()) + is Fail -> copy(requestUpdateTask = Fail(it.error)) + is Success -> { + isExecutingUpdateDetails = false + copy(requestUpdateTask = Success(it())) + } + + else -> { + this + } } } } } - } /** * execute the assign user api */ - fun assignUser() = withState { state -> - requireNotNull(state.parent) - viewModelScope.launch { - // assign user to the task - repository::assignUser.asFlow( - state.parent.id, - state.parent.assignee?.id.toString(), - ).execute { - when (it) { - is Loading -> copy(requestUpdateTask = Loading()) - is Fail -> { - AnalyticsManager().apiTracker(APIEvent.AssignUser, false) - copy(requestUpdateTask = Fail(it.error)) - } - - is Success -> { - isExecutingAssignUser = false - AnalyticsManager().apiTracker(APIEvent.AssignUser, true) - copy(requestUpdateTask = Success(it())) - } - - else -> { - this + fun assignUser() = + withState { state -> + requireNotNull(state.parent) + viewModelScope.launch { + // assign user to the task + repository::assignUser.asFlow( + state.parent.id, + state.parent.assignee?.id.toString(), + ).execute { + when (it) { + is Loading -> copy(requestUpdateTask = Loading()) + is Fail -> { + AnalyticsManager().apiTracker(APIEvent.AssignUser, false) + copy(requestUpdateTask = Fail(it.error)) + } + + is Success -> { + isExecutingAssignUser = false + AnalyticsManager().apiTracker(APIEvent.AssignUser, true) + copy(requestUpdateTask = Success(it())) + } + + else -> { + this + } } } } } - } /** * execute the delete content api */ - fun deleteAttachment(contentId: String) = withState { state -> - requireNotNull(state.parent) - viewModelScope.launch { - // assign user to the task - repository::deleteContent.asFlow(contentId).execute { - when (it) { - is Loading -> copy(requestDeleteContent = Loading()) - is Fail -> { - AnalyticsManager().apiTracker(APIEvent.DeleteTaskAttachment, false) - copy(requestDeleteContent = Fail(it.error)) - } - - is Success -> { - AnalyticsManager().apiTracker(APIEvent.DeleteTaskAttachment, true) - updateDelete(contentId).copy(requestDeleteContent = Success(it())) - } - - else -> { - this + fun deleteAttachment(contentId: String) = + withState { state -> + requireNotNull(state.parent) + viewModelScope.launch { + // assign user to the task + repository::deleteContent.asFlow(contentId).execute { + when (it) { + is Loading -> copy(requestDeleteContent = Loading()) + is Fail -> { + AnalyticsManager().apiTracker(APIEvent.DeleteTaskAttachment, false) + copy(requestDeleteContent = Fail(it.error)) + } + + is Success -> { + AnalyticsManager().apiTracker(APIEvent.DeleteTaskAttachment, true) + updateDelete(contentId).copy(requestDeleteContent = Success(it())) + } + + else -> { + this + } } } } } - } /** * update the status of task related to workflow @@ -390,7 +405,10 @@ class TaskDetailViewModel( /** * update the status and name of task related to workflow */ - fun updateTaskStatusAndName(status: String?, comment: String?) { + fun updateTaskStatusAndName( + status: String?, + comment: String?, + ) { setState { requireNotNull(this.parent) copy(parent = TaskEntry.updateTaskStatusAndComment(this.parent, status, comment), requestSaveForm = Uninitialized) @@ -400,55 +418,56 @@ class TaskDetailViewModel( /** * execute API to claim the task */ - fun claimTask() = withState { state -> - requireNotNull(state.parent) - viewModelScope.launch { - repository::claimTask.asFlow(state.parent.id).execute { - when (it) { - is Loading -> copy(requestClaimRelease = Loading()) - is Fail -> { - copy(requestClaimRelease = Fail(it.error)) - } - - is Success -> { - copy(requestClaimRelease = Success(it())) - } - - else -> { - this + fun claimTask() = + withState { state -> + requireNotNull(state.parent) + viewModelScope.launch { + repository::claimTask.asFlow(state.parent.id).execute { + when (it) { + is Loading -> copy(requestClaimRelease = Loading()) + is Fail -> { + copy(requestClaimRelease = Fail(it.error)) + } + + is Success -> { + copy(requestClaimRelease = Success(it())) + } + + else -> { + this + } } } } } - } /** * execute API to release the task */ - fun releaseTask() = withState { state -> - requireNotNull(state.parent) - viewModelScope.launch { - repository::releaseTask.asFlow(state.parent.id).execute { - when (it) { - is Loading -> copy(requestClaimRelease = Loading()) - is Fail -> { - copy(requestClaimRelease = Fail(it.error)) - } - - is Success -> { - copy(requestClaimRelease = Success(it())) - } - - else -> { - this + fun releaseTask() = + withState { state -> + requireNotNull(state.parent) + viewModelScope.launch { + repository::releaseTask.asFlow(state.parent.id).execute { + when (it) { + is Loading -> copy(requestClaimRelease = Loading()) + is Fail -> { + copy(requestClaimRelease = Fail(it.error)) + } + + is Success -> { + copy(requestClaimRelease = Success(it())) + } + + else -> { + this + } } } } } - } companion object : MavericksViewModelFactory { - override fun create( viewModelContext: ViewModelContext, state: TaskDetailViewState, diff --git a/browse/src/main/kotlin/com/alfresco/content/browse/tasks/detail/TaskDetailViewModelExtension.kt b/browse/src/main/kotlin/com/alfresco/content/browse/tasks/detail/TaskDetailViewModelExtension.kt index 9abdc15b4..991010515 100644 --- a/browse/src/main/kotlin/com/alfresco/content/browse/tasks/detail/TaskDetailViewModelExtension.kt +++ b/browse/src/main/kotlin/com/alfresco/content/browse/tasks/detail/TaskDetailViewModelExtension.kt @@ -29,7 +29,9 @@ fun TaskDetailViewModel.executePreview(action: Action) { val file = File(repository.session.contentDir, entry.fileName) if (!entry.isDocFile && repository.session.isFileExists(file) && file.length() != 0L) { entryListener?.onEntryCreated(Entry.updateDownloadEntry(entry, file.path)) - } else action.execute(context, GlobalScope) + } else { + action.execute(context, GlobalScope) + } } /** @@ -37,7 +39,8 @@ fun TaskDetailViewModel.executePreview(action: Action) { */ internal fun TaskDetailViewModel.isTaskCompleted(state: TaskDetailViewState): Boolean = state.parent?.endDate != null -internal fun TaskDetailViewModel.hasTaskStatusEnabled(state: TaskDetailViewState): Boolean = state.parent?.statusOption?.isNotEmpty() == true +internal fun TaskDetailViewModel.hasTaskStatusEnabled(state: TaskDetailViewState): Boolean = + state.parent?.statusOption?.isNotEmpty() == true /** * returns true if the endDate is empty and the assignee user is same as loggedIn user otherwise false @@ -58,7 +61,8 @@ fun TaskDetailViewModel.isCompleteButtonVisible(state: TaskDetailViewState): Boo /** * returns true if taskFormStatus has value otherwise false */ -fun TaskDetailViewModel.hasTaskStatusValue(state: TaskDetailViewState) = state.parent?.taskFormStatus != state.parent?.statusOption?.find { option -> option.id == "empty" }?.name +fun TaskDetailViewModel.hasTaskStatusValue(state: TaskDetailViewState) = + state.parent?.taskFormStatus != state.parent?.statusOption?.find { option -> option.id == "empty" }?.name /** * return true if uploading files are in queue otherwise false @@ -73,6 +77,9 @@ internal fun TaskDetailViewModel.removeTaskEntries(state: TaskDetailViewState) { } internal fun TaskDetailViewModel.isAssigneeAndLoggedInSame(assignee: UserGroupDetails?) = getAPSUser().id == assignee?.id + internal fun TaskDetailViewModel.isStartedByAndLoggedInSame(initiatorId: String?) = getAPSUser().id.toString() == initiatorId + internal fun TaskDetailViewModel.isTaskFormAndDetailRequestCompleted(state: TaskDetailViewState) = isWorkflowTask && state.request.complete + internal fun TaskDetailViewModel.isTaskDetailRequestCompleted(state: TaskDetailViewState) = !isWorkflowTask && state.request.complete diff --git a/browse/src/main/kotlin/com/alfresco/content/browse/tasks/detail/TaskDetailViewState.kt b/browse/src/main/kotlin/com/alfresco/content/browse/tasks/detail/TaskDetailViewState.kt index 26f84a350..f8f8a383c 100644 --- a/browse/src/main/kotlin/com/alfresco/content/browse/tasks/detail/TaskDetailViewState.kt +++ b/browse/src/main/kotlin/com/alfresco/content/browse/tasks/detail/TaskDetailViewState.kt @@ -34,7 +34,6 @@ data class TaskDetailViewState( val requestSaveForm: Async> = Uninitialized, val requestClaimRelease: Async> = Uninitialized, ) : MavericksState { - constructor(target: TaskEntry) : this(parent = target) /** @@ -91,7 +90,10 @@ data class TaskDetailViewState( ) } - private fun mergeInUploads(base: List, uploads: List): List { + private fun mergeInUploads( + base: List, + uploads: List, + ): List { return (uploads + base).distinctBy { if (it.id.isEmpty()) it.boxId else it.id } } @@ -121,7 +123,10 @@ data class TaskDetailViewState( /** * update the taskDetailObj params after getting the response from server. */ - fun update(oldEntry: TaskEntry, response: ResponseListForm?): TaskDetailViewState { + fun update( + oldEntry: TaskEntry, + response: ResponseListForm?, + ): TaskDetailViewState { if (response == null) return this val taskEntry = TaskEntry.withTaskForm(response, oldEntry) diff --git a/browse/src/main/kotlin/com/alfresco/content/browse/tasks/list/TasksFragment.kt b/browse/src/main/kotlin/com/alfresco/content/browse/tasks/list/TasksFragment.kt index 6ae9b4b42..30d9ec37a 100644 --- a/browse/src/main/kotlin/com/alfresco/content/browse/tasks/list/TasksFragment.kt +++ b/browse/src/main/kotlin/com/alfresco/content/browse/tasks/list/TasksFragment.kt @@ -45,11 +45,13 @@ import kotlin.coroutines.suspendCoroutine * Marked as TasksFragment */ class TasksFragment : TaskListFragment() { - override val viewModel: TasksViewModel by fragmentViewModel() private val epoxyControllerFilters: AsyncEpoxyController by lazy { epoxyControllerFilters() } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { super.onViewCreated(view, savedInstanceState) actionReset.setOnClickListener { AnalyticsManager().taskFiltersEvent(EventName.TaskFilterReset.value) @@ -70,10 +72,11 @@ class TasksFragment : TaskListFragment() { recyclerViewFilters.setController(epoxyControllerFilters) } - private fun resetAllFilters() = withState(viewModel) { state -> - val listReset = viewModel.resetChips(state) - viewModel.applyFilters(listReset) - } + private fun resetAllFilters() = + withState(viewModel) { state -> + val listReset = viewModel.resetChips(state) + viewModel.applyFilters(listReset) + } override fun onResume() { super.onResume() @@ -94,29 +97,34 @@ class TasksFragment : TaskListFragment() { } } - override fun invalidate() = withState(viewModel) { state -> - super.invalidate() - epoxyControllerFilters.requestModelBuild() - scrollToTop() + override fun invalidate() = + withState(viewModel) { state -> + super.invalidate() + epoxyControllerFilters.requestModelBuild() + scrollToTop() - if (state.request is Success && !viewModel.isWorkflowTask) { - clParent.addView(makeFab(requireContext())) + if (state.request is Success && !viewModel.isWorkflowTask) { + clParent.addView(makeFab(requireContext())) + } } - } - private fun epoxyControllerFilters() = simpleController(viewModel) { state -> - state.listSortDataChips.forEach { sortDataObj -> - listViewSortChips { - id(sortDataObj.name) - data(sortDataObj) - clickListener { model, _, chipView, _ -> - onChipClicked(model.data(), chipView) + private fun epoxyControllerFilters() = + simpleController(viewModel) { state -> + state.listSortDataChips.forEach { sortDataObj -> + listViewSortChips { + id(sortDataObj.name) + data(sortDataObj) + clickListener { model, _, chipView, _ -> + onChipClicked(model.data(), chipView) + } } } } - } - private fun onChipClicked(data: TaskFilterData, chipView: View) { + private fun onChipClicked( + data: TaskFilterData, + chipView: View, + ) { hideSoftInput() if (recyclerViewFilters.isEnabled) { AnalyticsManager().taskFiltersEvent(data.name ?: "") @@ -138,7 +146,9 @@ class TasksFragment : TaskListFragment() { } } } - } else (chipView as FilterChip).isChecked = false + } else { + (chipView as FilterChip).isChecked = false + } } private suspend fun showFilterSheetDialog( @@ -162,17 +172,18 @@ class TasksFragment : TaskListFragment() { private fun makeFab(context: Context) = FloatingActionButton(context).apply { - layoutParams = CoordinatorLayout.LayoutParams( - CoordinatorLayout.LayoutParams.WRAP_CONTENT, - CoordinatorLayout.LayoutParams.WRAP_CONTENT, - ).apply { - gravity = Gravity.BOTTOM or Gravity.END - // TODO: define margins - setMargins( - TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 16f, resources.displayMetrics) - .toInt(), - ) - } + layoutParams = + CoordinatorLayout.LayoutParams( + CoordinatorLayout.LayoutParams.WRAP_CONTENT, + CoordinatorLayout.LayoutParams.WRAP_CONTENT, + ).apply { + gravity = Gravity.BOTTOM or Gravity.END + // TODO: define margins + setMargins( + TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 16f, resources.displayMetrics) + .toInt(), + ) + } id = R.id.fab_create_task contentDescription = context.getString(R.string.text_create_task) setImageResource(R.drawable.ic_add_fab) @@ -190,21 +201,22 @@ class TasksFragment : TaskListFragment() { ) = continuation.resume(ComponentMetaData(name = name, query = query, queryMap = queryMap)) override fun onItemClicked(entry: TaskEntry) { - val intent = if (entry.processInstanceId != null) { - Intent( - requireActivity(), - Class.forName("com.alfresco.content.app.activity.ProcessActivity"), - ).apply { - putExtra(Mavericks.KEY_ARG, ProcessEntry.with(entry)) - } - } else { - Intent( - requireActivity(), - Class.forName("com.alfresco.content.app.activity.TaskViewerActivity"), - ).apply { - putExtra(Mavericks.KEY_ARG, entry) + val intent = + if (entry.processInstanceId != null) { + Intent( + requireActivity(), + Class.forName("com.alfresco.content.app.activity.ProcessActivity"), + ).apply { + putExtra(Mavericks.KEY_ARG, ProcessEntry.with(entry)) + } + } else { + Intent( + requireActivity(), + Class.forName("com.alfresco.content.app.activity.TaskViewerActivity"), + ).apply { + putExtra(Mavericks.KEY_ARG, entry) + } } - } startActivity(intent) } } diff --git a/browse/src/main/kotlin/com/alfresco/content/browse/tasks/list/TasksViewModel.kt b/browse/src/main/kotlin/com/alfresco/content/browse/tasks/list/TasksViewModel.kt index 193c10622..f612e5848 100644 --- a/browse/src/main/kotlin/com/alfresco/content/browse/tasks/list/TasksViewModel.kt +++ b/browse/src/main/kotlin/com/alfresco/content/browse/tasks/list/TasksViewModel.kt @@ -32,7 +32,6 @@ class TasksViewModel( val context: Context, private val repository: TaskRepository, ) : TaskListViewModel(state) { - var scrollToTop = false val isWorkflowTask = state.processEntry != null @@ -52,27 +51,28 @@ class TasksViewModel( override fun refresh() = fetchInitial() - override fun fetchNextPage() = withState { state -> - val newPage = state.page.plus(1) - viewModelScope.launch { - // Fetch tasks data - repository::getTasks.asFlow( - TaskProcessFiltersPayload.updateFilters(state.filterParams, newPage), - ).execute { - when (it) { - is Loading -> copy(request = Loading()) - is Fail -> copy(taskEntries = emptyList(), request = Fail(it.error)) - is Success -> { - update(it()).copy(request = Success(it())) - } - - else -> { - this + override fun fetchNextPage() = + withState { state -> + val newPage = state.page.plus(1) + viewModelScope.launch { + // Fetch tasks data + repository::getTasks.asFlow( + TaskProcessFiltersPayload.updateFilters(state.filterParams, newPage), + ).execute { + when (it) { + is Loading -> copy(request = Loading()) + is Fail -> copy(taskEntries = emptyList(), request = Fail(it.error)) + is Success -> { + update(it()).copy(request = Success(it())) + } + + else -> { + this + } } } } } - } override fun emptyMessageArgs(state: TaskListViewState): Triple { return when (state.request) { @@ -81,28 +81,31 @@ class TasksViewModel( } } - private fun fetchInitial() = withState { state -> - viewModelScope.launch { - // Fetch tasks data - repository::getTasks.asFlow( - if (!isWorkflowTask) { - TaskProcessFiltersPayload.updateFilters(state.filterParams) - } else TaskProcessFiltersPayload.defaultTasksOfProcess(state.processEntry?.id), - ).execute { - when (it) { - is Loading -> copy(request = Loading()) - is Fail -> copy(taskEntries = emptyList(), request = Fail(it.error)) - is Success -> { - update(it()).copy(request = Success(it())) - } - - else -> { - this + private fun fetchInitial() = + withState { state -> + viewModelScope.launch { + // Fetch tasks data + repository::getTasks.asFlow( + if (!isWorkflowTask) { + TaskProcessFiltersPayload.updateFilters(state.filterParams) + } else { + TaskProcessFiltersPayload.defaultTasksOfProcess(state.processEntry?.id) + }, + ).execute { + when (it) { + is Loading -> copy(request = Loading()) + is Fail -> copy(taskEntries = emptyList(), request = Fail(it.error)) + is Success -> { + update(it()).copy(request = Success(it())) + } + + else -> { + this + } } } } } - } private fun fetchUserProfile() { if (repository.isAcsAndApsSameUser()) return @@ -162,7 +165,11 @@ class TasksViewModel( /** * update the isSelected state when user tap on filter chip. */ - fun updateSelected(state: TasksViewState, data: TaskFilterData, isSelected: Boolean) { + fun updateSelected( + state: TasksViewState, + data: TaskFilterData, + isSelected: Boolean, + ) { val list = mutableListOf() state.listSortDataChips.forEach { obj -> if (obj == data) { @@ -177,7 +184,11 @@ class TasksViewModel( /** * update the filter result */ - fun updateChipFilterResult(state: TasksViewState, model: TaskFilterData, metaData: ComponentMetaData): MutableList { + fun updateChipFilterResult( + state: TasksViewState, + model: TaskFilterData, + metaData: ComponentMetaData, + ): MutableList { val list = mutableListOf() state.listSortDataChips.forEach { obj -> @@ -191,7 +202,9 @@ class TasksViewModel( selectedQueryMap = metaData.queryMap ?: mapOf(), ), ) - } else list.add(obj) + } else { + list.add(obj) + } } setState { copy(listSortDataChips = list) } @@ -224,10 +237,12 @@ class TasksViewModel( /** * It will execute while showing the dialog to create task. */ - fun execute(requireContext: Context, action: Action) = action.execute(requireContext, GlobalScope) + fun execute( + requireContext: Context, + action: Action, + ) = action.execute(requireContext, GlobalScope) companion object : MavericksViewModelFactory { - override fun create( viewModelContext: ViewModelContext, state: TasksViewState, diff --git a/browse/src/main/kotlin/com/alfresco/content/browse/tasks/list/TasksViewState.kt b/browse/src/main/kotlin/com/alfresco/content/browse/tasks/list/TasksViewState.kt index 741db3dfd..a6447d877 100644 --- a/browse/src/main/kotlin/com/alfresco/content/browse/tasks/list/TasksViewState.kt +++ b/browse/src/main/kotlin/com/alfresco/content/browse/tasks/list/TasksViewState.kt @@ -25,7 +25,6 @@ data class TasksViewState( val loadItemsCount: Int = 0, val page: Int = 0, ) : TaskListViewState { - constructor(target: ProcessEntry) : this (processEntry = target) override fun copy(_entries: List) = copy(taskEntries = _entries) @@ -33,22 +32,21 @@ data class TasksViewState( /** * update the latest response */ - fun update( - response: ResponseList?, - ): TasksViewState { + fun update(response: ResponseList?): TasksViewState { if (response == null) return this val totalLoadCount: Int val taskPageEntries = response.listTask - val newTaskEntries = if (response.start != 0) { - totalLoadCount = loadItemsCount.plus(response.size) - baseTaskEntries + taskPageEntries - } else { - totalLoadCount = response.size - taskPageEntries - } + val newTaskEntries = + if (response.start != 0) { + totalLoadCount = loadItemsCount.plus(response.size) + baseTaskEntries + taskPageEntries + } else { + totalLoadCount = response.size + taskPageEntries + } return copy( taskEntries = newTaskEntries, diff --git a/browse/src/main/kotlin/com/alfresco/content/browse/transfer/TransferFilesFragment.kt b/browse/src/main/kotlin/com/alfresco/content/browse/transfer/TransferFilesFragment.kt index 4ae34617c..95fcd770d 100644 --- a/browse/src/main/kotlin/com/alfresco/content/browse/transfer/TransferFilesFragment.kt +++ b/browse/src/main/kotlin/com/alfresco/content/browse/transfer/TransferFilesFragment.kt @@ -54,7 +54,6 @@ data class TransferFilesArgs( * Mark as TransferFilesFragment */ class TransferFilesFragment : Fragment(), MavericksView { - private lateinit var args: TransferFilesArgs @OptIn(InternalMavericksApi::class) @@ -69,72 +68,85 @@ class TransferFilesFragment : Fragment(), MavericksView { args = TransferFilesArgs.with(requireArguments()) } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View { binding = FragmentTransferFilesListBinding.inflate(inflater, container, false) return binding.root } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { super.onViewCreated(view, savedInstanceState) binding.recyclerView.setController(epoxyController) } - override fun invalidate() = withState(viewModel) { state -> + override fun invalidate() = + withState(viewModel) { state -> - if (fab == null) { - fab = makeFab(requireContext()).apply { - visibility = View.INVISIBLE // required for animation + if (fab == null) { + fab = + makeFab(requireContext()).apply { + visibility = View.INVISIBLE // required for animation + } + (view as ViewGroup).addView(fab) } - (view as ViewGroup).addView(fab) - } - fab?.apply { - if (state.entries.isNotEmpty()) { - show() - } else { - hide() + fab?.apply { + if (state.entries.isNotEmpty()) { + show() + } else { + hide() + } + + isEnabled = state.syncNowEnabled } - isEnabled = state.syncNowEnabled + epoxyController.requestModelBuild() } - epoxyController.requestModelBuild() - } - - private fun epoxyController() = simpleController(viewModel) { state -> - if (state.entries.isEmpty()) { - val args = viewModel.emptyMessageArgs() - listViewMessage { - id("empty_message") - iconRes(args.first) - title(args.second) - message(args.third) - } - } else if (state.entries.isNotEmpty()) { - state.entries.forEach { - listViewRow { - id(stableId(it)) - data(it) + private fun epoxyController() = + simpleController(viewModel) { state -> + if (state.entries.isEmpty()) { + val args = viewModel.emptyMessageArgs() + listViewMessage { + id("empty_message") + iconRes(args.first) + title(args.second) + message(args.third) + } + } else if (state.entries.isNotEmpty()) { + state.entries.forEach { + listViewRow { + id(stableId(it)) + data(it) + } } } } - } private fun stableId(entry: Entry): String = if (entry.isUpload) { entry.boxId.toString() - } else entry.id + } else { + entry.id + } private fun makeFab(context: Context) = ExtendedFloatingActionButton(context).apply { - layoutParams = CoordinatorLayout.LayoutParams( - CoordinatorLayout.LayoutParams.WRAP_CONTENT, - CoordinatorLayout.LayoutParams.WRAP_CONTENT, - ).apply { - gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL - setMargins(0, 0, 0, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 16f, resources.displayMetrics).toInt()) - } + layoutParams = + CoordinatorLayout.LayoutParams( + CoordinatorLayout.LayoutParams.WRAP_CONTENT, + CoordinatorLayout.LayoutParams.WRAP_CONTENT, + ).apply { + gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL + setMargins(0, 0, 0, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 16f, resources.displayMetrics).toInt()) + } text = context.getText(R.string.offline_sync_button_title) gravity = Gravity.CENTER setOnClickListener { @@ -161,8 +173,7 @@ class TransferFilesFragment : Fragment(), MavericksView { startSync(false) } - private fun startSync(overrideNetwork: Boolean) = - lifecycleScope.emit(TransferSyncNow(overrideNetwork)) + private fun startSync(overrideNetwork: Boolean) = lifecycleScope.emit(TransferSyncNow(overrideNetwork)) } /** diff --git a/browse/src/main/kotlin/com/alfresco/content/browse/transfer/TransferFilesViewModel.kt b/browse/src/main/kotlin/com/alfresco/content/browse/transfer/TransferFilesViewModel.kt index 9f45384ce..d66216504 100644 --- a/browse/src/main/kotlin/com/alfresco/content/browse/transfer/TransferFilesViewModel.kt +++ b/browse/src/main/kotlin/com/alfresco/content/browse/transfer/TransferFilesViewModel.kt @@ -42,7 +42,6 @@ class TransferFilesViewModel( state: TransferFilesViewState, val context: Context, ) : MavericksViewModel(state) { - private var observeExtensionUploadsJob: Job? = null init { @@ -72,14 +71,15 @@ class TransferFilesViewModel( val repo = OfflineRepository() observeExtensionUploadsJob?.cancel() - observeExtensionUploadsJob = repo.observeTransferUploads() - .execute { - if (it is Success) { - updateTransferUploads(it()) - } else { - this + observeExtensionUploadsJob = + repo.observeTransferUploads() + .execute { + if (it is Success) { + updateTransferUploads(it()) + } else { + this + } } - } } /** @@ -90,7 +90,6 @@ class TransferFilesViewModel( !ConnectivityTracker.isActiveNetworkMetered(context) companion object : MavericksViewModelFactory { - override fun create( viewModelContext: ViewModelContext, state: TransferFilesViewState, diff --git a/browse/src/test/java/com/alfresco/content/browse/BrowseViewModelTest.kt b/browse/src/test/java/com/alfresco/content/browse/BrowseViewModelTest.kt index bf70cc0c8..7be1419c1 100644 --- a/browse/src/test/java/com/alfresco/content/browse/BrowseViewModelTest.kt +++ b/browse/src/test/java/com/alfresco/content/browse/BrowseViewModelTest.kt @@ -14,7 +14,6 @@ import org.junit.Test import org.mockito.MockitoAnnotations class BrowseViewModelTest { - private lateinit var viewModel: BrowseViewModel private val context: Context = mockk(relaxed = true) @@ -23,10 +22,11 @@ class BrowseViewModelTest { private var offlineRepository: OfflineRepository = mockk(relaxed = true) - private var testEntries = mutableListOf( - Entry(id = "1", name = "Entry 1", isSelectedForMultiSelection = false), - Entry(id = "2", name = "Entry 2", isSelectedForMultiSelection = false), - ) + private var testEntries = + mutableListOf( + Entry(id = "1", name = "Entry 1", isSelectedForMultiSelection = false), + Entry(id = "2", name = "Entry 2", isSelectedForMultiSelection = false), + ) @Before fun setup() { @@ -35,17 +35,18 @@ class BrowseViewModelTest { @Test fun `toggleSelection should update the state with entry selection`() { - viewModel = BrowseViewModel( - BrowseViewState( - entries = testEntries, - path = "", - nodeId = null, - moveId = "", - ), - context, - browseRepository, - offlineRepository, - ) + viewModel = + BrowseViewModel( + BrowseViewState( + entries = testEntries, + path = "", + nodeId = null, + moveId = "", + ), + context, + browseRepository, + offlineRepository, + ) testEntries.forEach { entry -> assertEquals(false, entry.isSelectedForMultiSelection) } @@ -65,18 +66,19 @@ class BrowseViewModelTest { testEntries.add(Entry(id = "$i", name = "Entry $i", isSelectedForMultiSelection = false)) } - viewModel = BrowseViewModel( - BrowseViewState( - entries = testEntries, - selectedEntries = emptyList(), - path = "", - nodeId = null, - moveId = "", - ), - context, - browseRepository, - offlineRepository, - ) + viewModel = + BrowseViewModel( + BrowseViewState( + entries = testEntries, + selectedEntries = emptyList(), + path = "", + nodeId = null, + moveId = "", + ), + context, + browseRepository, + offlineRepository, + ) withState(viewModel) { state -> assertEquals(50, state.entries.size) @@ -93,17 +95,18 @@ class BrowseViewModelTest { @Test fun `resetMultiSelection should update the state with entries deselection`() { - viewModel = BrowseViewModel( - BrowseViewState( - entries = testEntries, - path = "", - nodeId = null, - moveId = "", - ), - context, - browseRepository, - offlineRepository, - ) + viewModel = + BrowseViewModel( + BrowseViewState( + entries = testEntries, + path = "", + nodeId = null, + moveId = "", + ), + context, + browseRepository, + offlineRepository, + ) viewModel.resetMultiSelection() withState(viewModel) { state -> diff --git a/build.gradle b/build.gradle index 8b0580709..ce2a3a5a7 100644 --- a/build.gradle +++ b/build.gradle @@ -18,6 +18,10 @@ buildscript { } } +plugins { + id 'com.google.devtools.ksp' version '2.0.20-1.0.25' apply false +} + allprojects { repositories { maven { diff --git a/capture/build.gradle b/capture/build.gradle index 550510125..b956d9c8d 100644 --- a/capture/build.gradle +++ b/capture/build.gradle @@ -1,8 +1,9 @@ plugins{ - id('com.android.library') - id('kotlin-android') - id('kotlin-kapt') - id('kotlin-parcelize') + id 'com.android.library' + id 'kotlin-android' +// id 'kotlin-kapt' + id 'kotlin-parcelize' + id 'com.google.devtools.ksp' } android { @@ -49,10 +50,10 @@ dependencies { implementation libs.androidx.camera.lifecycle implementation libs.androidx.camera.view - implementation libs.androidx.constraintlayout + implementation libs.constraintlayout implementation libs.mavericks implementation libs.epoxy.core - kapt libs.epoxy.processor + ksp libs.epoxy.processor } diff --git a/capture/src/main/kotlin/androidx/camera/view/AlfrescoCameraController.kt b/capture/src/main/kotlin/androidx/camera/view/AlfrescoCameraController.kt index 5ae903b6c..26137533e 100644 --- a/capture/src/main/kotlin/androidx/camera/view/AlfrescoCameraController.kt +++ b/capture/src/main/kotlin/androidx/camera/view/AlfrescoCameraController.kt @@ -68,19 +68,22 @@ class AlfrescoCameraController(context: Context) : Logger.d("CameraProvider is not ready.") return null } - val useCaseGroup = createUseCaseGroup() // Use cases can't be created. - ?: return null + val useCaseGroup = + createUseCaseGroup() // Use cases can't be created. + ?: return null return mCameraProvider!!.bindToLifecycle(mLifecycleOwner!!, mCameraSelector, useCaseGroup) } - fun setCameraSelector(@LensFacing lensFacing: Int) { - this.cameraSelector = CameraSelector.Builder() - .requireLensFacing(lensFacing) - .build() + fun setCameraSelector( + @LensFacing lensFacing: Int, + ) { + this.cameraSelector = + CameraSelector.Builder() + .requireLensFacing(lensFacing) + .build() } - fun hasFlashUnit() = - mCamera?.cameraInfo?.hasFlashUnit() == true + fun hasFlashUnit() = mCamera?.cameraInfo?.hasFlashUnit() == true /** * @hide diff --git a/capture/src/main/kotlin/com/alfresco/capture/CameraFragment.kt b/capture/src/main/kotlin/com/alfresco/capture/CameraFragment.kt index 118e2d0a9..8cc0cd879 100644 --- a/capture/src/main/kotlin/com/alfresco/capture/CameraFragment.kt +++ b/capture/src/main/kotlin/com/alfresco/capture/CameraFragment.kt @@ -50,7 +50,6 @@ import java.util.concurrent.Executors @OptIn(ExperimentalVideo::class) class CameraFragment : Fragment(), KeyHandler, MavericksView { - private val viewModel: CaptureViewModel by activityViewModel() private lateinit var layout: CameraLayout @@ -118,10 +117,12 @@ class CameraFragment : Fragment(), KeyHandler, MavericksView { inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?, - ): View? = - inflater.inflate(R.layout.fragment_camera, container, false) + ): View? = inflater.inflate(R.layout.fragment_camera, container, false) - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { super.onViewCreated(view, savedInstanceState) layout = view as CameraLayout @@ -165,11 +166,12 @@ class CameraFragment : Fragment(), KeyHandler, MavericksView { // Select lensFacing depending on the available cameras if (viewModel.lensFacing == -1) { - viewModel.lensFacing = when { - hasBackCamera() -> CameraSelector.LENS_FACING_BACK - hasFrontCamera() -> CameraSelector.LENS_FACING_FRONT - else -> throw IllegalStateException("Back and front camera are unavailable") - } + viewModel.lensFacing = + when { + hasBackCamera() -> CameraSelector.LENS_FACING_BACK + hasFrontCamera() -> CameraSelector.LENS_FACING_FRONT + else -> throw IllegalStateException("Back and front camera are unavailable") + } } // Enable or disable switching between cameras @@ -185,17 +187,18 @@ class CameraFragment : Fragment(), KeyHandler, MavericksView { private fun configureCamera() { layout.aspectRatio = viewModel.mode.aspectRatio().toFloat() - cameraController = AlfrescoCameraController(requireContext()).apply { - setEnabledUseCases(useCaseFor(viewModel.mode)) - setCameraSelector(viewModel.lensFacing) - imageCaptureFlashMode = viewModel.flashMode - }.also { - it.bindToLifecycle(this) - it.initializationFuture.addListener({ - // Update flash button when ready - updateFlashControlState() - }, ContextCompat.getMainExecutor(requireContext())) - } + cameraController = + AlfrescoCameraController(requireContext()).apply { + setEnabledUseCases(useCaseFor(viewModel.mode)) + setCameraSelector(viewModel.lensFacing) + imageCaptureFlashMode = viewModel.flashMode + }.also { + it.bindToLifecycle(this) + it.initializationFuture.addListener({ + // Update flash button when ready + updateFlashControlState() + }, ContextCompat.getMainExecutor(requireContext())) + } layout.viewFinder.controller = cameraController @@ -211,7 +214,10 @@ class CameraFragment : Fragment(), KeyHandler, MavericksView { CaptureMode.Video -> CameraController.VIDEO_CAPTURE } - override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean { + override fun onKeyDown( + keyCode: Int, + event: KeyEvent, + ): Boolean { return when (keyCode) { KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_VOLUME_UP -> { // When the volume down button is pressed, simulate a shutter button click @@ -265,11 +271,12 @@ class CameraFragment : Fragment(), KeyHandler, MavericksView { // Setup for button used to switch cameras layout.cameraSwitchButton.setOnClickListener { loadDefaultSettings() - viewModel.lensFacing = if (CameraSelector.LENS_FACING_FRONT == viewModel.lensFacing) { - CameraSelector.LENS_FACING_BACK - } else { - CameraSelector.LENS_FACING_FRONT - } + viewModel.lensFacing = + if (CameraSelector.LENS_FACING_FRONT == viewModel.lensFacing) { + CameraSelector.LENS_FACING_BACK + } else { + CameraSelector.LENS_FACING_FRONT + } cameraController?.setCameraSelector(viewModel.lensFacing) cameraController?.imageCaptureFlashMode = viewModel.flashMode updateFlashControlState() @@ -302,16 +309,17 @@ class CameraFragment : Fragment(), KeyHandler, MavericksView { // Create output options object which contains file + metadata - val outputOptions = when { - LocationUtils.isLocationEnabled(requireActivity()) -> { - ImageCapture.OutputFileOptions.Builder(photoFile) - .setMetadata(viewModel.getMetaData()).build() - } + val outputOptions = + when { + LocationUtils.isLocationEnabled(requireActivity()) -> { + ImageCapture.OutputFileOptions.Builder(photoFile) + .setMetadata(viewModel.getMetaData()).build() + } - else -> { - ImageCapture.OutputFileOptions.Builder(photoFile).build() + else -> { + ImageCapture.OutputFileOptions.Builder(photoFile).build() + } } - } // Setup image capture listener which is triggered after photo has been taken controller.takePicture( @@ -366,27 +374,7 @@ class CameraFragment : Fragment(), KeyHandler, MavericksView { override fun onVideoSaved(output: OutputFileResults) { val savedUri = output.savedUri ?: Uri.fromFile(videoFile) Logger.d("Video capture succeeded: $savedUri") - savedUri.path?.let { - val file = File(it) - val length = file.length() - if (length > 0L) { - if (!GetMultipleContents.isFileSizeExceed(length, if (viewModel.isProcessUpload) GetMultipleContents.MAX_FILE_SIZE_10 else GetMultipleContents.MAX_FILE_SIZE_100)) { - viewModel.onCaptureVideo(savedUri) - navigateToSave() - } else { - file.delete() - Snackbar.make( - layout.captureDurationView, - if (viewModel.isProcessUpload) { - getString(R.string.error_file_size_exceed, GetMultipleContents.MAX_FILE_SIZE_10) - } else { - getString(R.string.error_file_size_exceed, GetMultipleContents.MAX_FILE_SIZE_100) - }, - Snackbar.LENGTH_SHORT, - ).show() - } - } - } + onSavedURI(savedUri) } override fun onError( @@ -402,10 +390,11 @@ class CameraFragment : Fragment(), KeyHandler, MavericksView { } private fun configureShutterButton(mode: CaptureMode) { - layout.shutterButton.state = when (mode) { - CaptureMode.Photo -> ShutterButton.State.Photo - CaptureMode.Video -> ShutterButton.State.Video - } + layout.shutterButton.state = + when (mode) { + CaptureMode.Photo -> ShutterButton.State.Photo + CaptureMode.Video -> ShutterButton.State.Video + } } private fun navigateToSave() { @@ -417,11 +406,12 @@ class CameraFragment : Fragment(), KeyHandler, MavericksView { private fun showFlashMenu() { layout.flashMenu.isVisible = true layout.flashMenu.onMenuItemClick = { mode -> - viewModel.flashMode = when (mode) { - FlashMenuItem.On -> ImageCapture.FLASH_MODE_ON - FlashMenuItem.Off -> ImageCapture.FLASH_MODE_OFF - FlashMenuItem.Auto -> ImageCapture.FLASH_MODE_AUTO - } + viewModel.flashMode = + when (mode) { + FlashMenuItem.On -> ImageCapture.FLASH_MODE_ON + FlashMenuItem.Off -> ImageCapture.FLASH_MODE_OFF + FlashMenuItem.Auto -> ImageCapture.FLASH_MODE_AUTO + } cameraController?.imageCaptureFlashMode = viewModel.flashMode layout.flashButton.setImageResource(flashModeIcon(viewModel.flashMode)) @@ -431,6 +421,36 @@ class CameraFragment : Fragment(), KeyHandler, MavericksView { } } + private fun onSavedURI(savedUri: Uri) { + savedUri.path?.let { + val file = File(it) + val length = file.length() + if (length > 0L) { + val maxFileSize = + if (viewModel.isProcessUpload) { + GetMultipleContents.MAX_FILE_SIZE_10 + } else { + GetMultipleContents.MAX_FILE_SIZE_100 + } + if (!GetMultipleContents.isFileSizeExceed(length, maxFileSize)) { + viewModel.onCaptureVideo(savedUri) + navigateToSave() + } else { + file.delete() + Snackbar.make( + layout.captureDurationView, + if (viewModel.isProcessUpload) { + getString(R.string.error_file_size_exceed, GetMultipleContents.MAX_FILE_SIZE_10) + } else { + getString(R.string.error_file_size_exceed, GetMultipleContents.MAX_FILE_SIZE_100) + }, + Snackbar.LENGTH_SHORT, + ).show() + } + } + } + } + private fun updateCameraSwitchButton() { layout.cameraSwitchButton.isVisible = try { @@ -449,24 +469,25 @@ class CameraFragment : Fragment(), KeyHandler, MavericksView { layout.flashButton.setImageResource(flashModeIcon(flashMode)) } - private fun flashControlEnabled(mode: CaptureMode, controller: AlfrescoCameraController?) = - when (mode) { - CaptureMode.Photo -> controller?.hasFlashUnit() ?: false - CaptureMode.Video -> false - } + private fun flashControlEnabled( + mode: CaptureMode, + controller: AlfrescoCameraController?, + ) = when (mode) { + CaptureMode.Photo -> controller?.hasFlashUnit() ?: false + CaptureMode.Video -> false + } - private fun flashModeIcon(@FlashMode flashMode: Int) = - when (flashMode) { - ImageCapture.FLASH_MODE_ON -> R.drawable.ic_flash_on - ImageCapture.FLASH_MODE_OFF -> R.drawable.ic_flash_off - else -> R.drawable.ic_flash_auto - } + private fun flashModeIcon( + @FlashMode flashMode: Int, + ) = when (flashMode) { + ImageCapture.FLASH_MODE_ON -> R.drawable.ic_flash_on + ImageCapture.FLASH_MODE_OFF -> R.drawable.ic_flash_off + else -> R.drawable.ic_flash_auto + } - private fun hasBackCamera(): Boolean = - cameraProvider?.hasCamera(CameraSelector.DEFAULT_BACK_CAMERA) ?: false + private fun hasBackCamera(): Boolean = cameraProvider?.hasCamera(CameraSelector.DEFAULT_BACK_CAMERA) ?: false - private fun hasFrontCamera(): Boolean = - cameraProvider?.hasCamera(CameraSelector.DEFAULT_FRONT_CAMERA) ?: false + private fun hasFrontCamera(): Boolean = cameraProvider?.hasCamera(CameraSelector.DEFAULT_FRONT_CAMERA) ?: false override fun invalidate() {} @@ -482,7 +503,11 @@ class CameraFragment : Fragment(), KeyHandler, MavericksView { } private fun invokeLocation() { - if (ActivityCompat.checkSelfPermission(requireContext(), Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED) { + if (ActivityCompat.checkSelfPermission( + requireContext(), + Manifest.permission.ACCESS_COARSE_LOCATION, + ) == PackageManager.PERMISSION_GRANTED + ) { when { LocationUtils.isLocationEnabled(requireActivity()) -> { locationData.observe(this) { diff --git a/capture/src/main/kotlin/com/alfresco/capture/CameraLayout.kt b/capture/src/main/kotlin/com/alfresco/capture/CameraLayout.kt index 6f11928f3..5d81ce5cf 100644 --- a/capture/src/main/kotlin/com/alfresco/capture/CameraLayout.kt +++ b/capture/src/main/kotlin/com/alfresco/capture/CameraLayout.kt @@ -19,7 +19,6 @@ class CameraLayout( defStyleAttr: Int, defStyleRes: Int, ) : ViewGroup(context, attrs, defStyleAttr, defStyleRes) { - private lateinit var topBar: ViewGroup private lateinit var previewHolder: ViewGroup private lateinit var onFrameControls: ViewGroup @@ -96,7 +95,10 @@ class CameraLayout( messageView.isVisible = false } - override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + override fun onMeasure( + widthMeasureSpec: Int, + heightMeasureSpec: Int, + ) { val parentWidth = MeasureSpec.getSize(widthMeasureSpec) val parentHeight = MeasureSpec.getSize(heightMeasureSpec) @@ -110,10 +112,11 @@ class CameraLayout( ) val width = MeasureSpec.getSize(widthMeasureSpec) - val height = topBar.measuredHeight + - previewHolder.measuredHeight + - shutterBar.measuredHeight + - modeBar.measuredHeight + val height = + topBar.measuredHeight + + previewHolder.measuredHeight + + shutterBar.measuredHeight + + modeBar.measuredHeight setMeasuredDimension( resolveSize(width, widthMeasureSpec), @@ -126,7 +129,13 @@ class CameraLayout( flashMenu.measure(flashMenuMeasureSpec, flashMenuMeasureSpec) } - override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { + override fun onLayout( + changed: Boolean, + left: Int, + top: Int, + right: Int, + bottom: Int, + ) { val width = right - left val height = bottom - top @@ -177,27 +186,29 @@ class CameraLayout( modeBar.layout(0, modeGuide, width, modeGuide + modeHeight) } - private val orientationEventListener = object : OrientationEventListener(context) { - override fun onOrientationChanged(orientation: Int) { - // Doesn't support upside down orientation - val rotation = when (orientation) { - in 45 until 135 -> -90 - in 135 until 225 -> controlRotation - in 225 until 315 -> 90 - else -> 0 - } - - if (controlRotation != rotation && - abs(deviceOrientation - orientation) > ORIENTATION_HYSTERESIS - ) { - controlRotation = rotation - deviceOrientation = orientation - orientationAwareControls.map { - it.animate().rotation(rotation.toFloat()).start() + private val orientationEventListener = + object : OrientationEventListener(context) { + override fun onOrientationChanged(orientation: Int) { + // Doesn't support upside down orientation + val rotation = + when (orientation) { + in 45 until 135 -> -90 + in 135 until 225 -> controlRotation + in 225 until 315 -> 90 + else -> 0 + } + + if (controlRotation != rotation && + abs(deviceOrientation - orientation) > ORIENTATION_HYSTERESIS + ) { + controlRotation = rotation + deviceOrientation = orientation + orientationAwareControls.map { + it.animate().rotation(rotation.toFloat()).start() + } } } } - } override fun onAttachedToWindow() { super.onAttachedToWindow() diff --git a/capture/src/main/kotlin/com/alfresco/capture/CaptureActivity.kt b/capture/src/main/kotlin/com/alfresco/capture/CaptureActivity.kt index 054076ebd..0224935dc 100644 --- a/capture/src/main/kotlin/com/alfresco/capture/CaptureActivity.kt +++ b/capture/src/main/kotlin/com/alfresco/capture/CaptureActivity.kt @@ -31,7 +31,10 @@ class CaptureActivity : AppCompatActivity() { navController.setGraph(graph, intent.extras) } - override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean { + override fun onKeyDown( + keyCode: Int, + event: KeyEvent, + ): Boolean { val fragment = supportFragmentManager .primaryNavigationFragment diff --git a/capture/src/main/kotlin/com/alfresco/capture/CaptureDurationView.kt b/capture/src/main/kotlin/com/alfresco/capture/CaptureDurationView.kt index 9a412f5f3..b6fee3bb6 100644 --- a/capture/src/main/kotlin/com/alfresco/capture/CaptureDurationView.kt +++ b/capture/src/main/kotlin/com/alfresco/capture/CaptureDurationView.kt @@ -13,11 +13,10 @@ class CaptureDurationView( attrs: AttributeSet?, defStyleAttr: Int, ) : Chronometer( - ContextThemeWrapper(context, R.style.Widget_Alfresco_Camera_Mode_Button), - attrs, - defStyleAttr, -) { - + ContextThemeWrapper(context, R.style.Widget_Alfresco_Camera_Mode_Button), + attrs, + defStyleAttr, + ) { constructor(context: Context) : this(context, null) @@ -30,7 +29,10 @@ class CaptureDurationView( setPadding(pad, 0, pad, 0) } - override fun onVisibilityChanged(changedView: View, visibility: Int) { + override fun onVisibilityChanged( + changedView: View, + visibility: Int, + ) { super.onVisibilityChanged(changedView, visibility) if (visibility == View.VISIBLE) { diff --git a/capture/src/main/kotlin/com/alfresco/capture/CaptureHelperFragment.kt b/capture/src/main/kotlin/com/alfresco/capture/CaptureHelperFragment.kt index aa7fb3f04..f67a24f4a 100644 --- a/capture/src/main/kotlin/com/alfresco/capture/CaptureHelperFragment.kt +++ b/capture/src/main/kotlin/com/alfresco/capture/CaptureHelperFragment.kt @@ -8,6 +8,8 @@ import androidx.fragment.app.Fragment import com.alfresco.content.withFragment import kotlinx.coroutines.CancellableContinuation import kotlinx.coroutines.suspendCancellableCoroutine +import kotlin.coroutines.cancellation.CancellationException +import kotlin.coroutines.resume class CaptureHelperFragment : Fragment() { private lateinit var requestLauncher: ActivityResultLauncher @@ -16,9 +18,18 @@ class CaptureHelperFragment : Fragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - requestLauncher = registerForActivityResult(CapturePhotoResultContract()) { - onResult?.resume(it, null) - } + requestLauncher = + registerForActivityResult(CapturePhotoResultContract()) { +// onResult?.resume(it, null) + onResult?.let { continuation -> + continuation.resume(it) { cause, _, _ -> + // Send null if cancelled + if (cause is CancellationException) { + continuation.resume(null) + } + } + } + } } private suspend fun capturePhoto(): List? = @@ -30,9 +41,7 @@ class CaptureHelperFragment : Fragment() { companion object { private val TAG = CaptureHelperFragment::class.java.simpleName - suspend fun capturePhoto( - context: Context, - ): List? = + suspend fun capturePhoto(context: Context): List? = withFragment( context, TAG, @@ -52,7 +61,6 @@ class CaptureHelperFragment : Fragment() { Manifest.permission.ACCESS_COARSE_LOCATION, ) - fun permissionRationale(context: Context) = - context.getString(R.string.capture_permissions_rationale) + fun permissionRationale(context: Context) = context.getString(R.string.capture_permissions_rationale) } } diff --git a/capture/src/main/kotlin/com/alfresco/capture/CaptureItem.kt b/capture/src/main/kotlin/com/alfresco/capture/CaptureItem.kt index 0a3bddcc4..706dd0c2c 100644 --- a/capture/src/main/kotlin/com/alfresco/capture/CaptureItem.kt +++ b/capture/src/main/kotlin/com/alfresco/capture/CaptureItem.kt @@ -19,17 +19,16 @@ data class CaptureItem( val filename: String get() = "$name$extension" val extension: String - get() = when (mimeType) { - PHOTO_MIMETYPE -> PHOTO_EXTENSION - VIDEO_MIMETYPE -> VIDEO_EXTENSION - else -> throw IllegalArgumentException() - } + get() = + when (mimeType) { + PHOTO_MIMETYPE -> PHOTO_EXTENSION + VIDEO_MIMETYPE -> VIDEO_EXTENSION + else -> throw IllegalArgumentException() + } - internal fun isPhoto() = - mimeType == PHOTO_MIMETYPE + internal fun isPhoto() = mimeType == PHOTO_MIMETYPE - internal fun isVideo() = - mimeType == VIDEO_MIMETYPE + internal fun isVideo() = mimeType == VIDEO_MIMETYPE internal companion object { const val PHOTO_EXTENSION = ".jpg" @@ -39,11 +38,9 @@ data class CaptureItem( private const val PHOTO_NAME_PREFIX = "IMG_" private const val VIDEO_NAME_PREFIX = "VID_" - fun photoCapture(uri: Uri) = - CaptureItem(uri, PHOTO_MIMETYPE, defaultFilename(PHOTO_NAME_PREFIX)) + fun photoCapture(uri: Uri) = CaptureItem(uri, PHOTO_MIMETYPE, defaultFilename(PHOTO_NAME_PREFIX)) - fun videoCapture(uri: Uri) = - CaptureItem(uri, VIDEO_MIMETYPE, defaultFilename(VIDEO_NAME_PREFIX)) + fun videoCapture(uri: Uri) = CaptureItem(uri, VIDEO_MIMETYPE, defaultFilename(VIDEO_NAME_PREFIX)) private fun defaultFilename(prefix: String): String { val formatter = SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.US) diff --git a/capture/src/main/kotlin/com/alfresco/capture/CaptureModeSelectorView.kt b/capture/src/main/kotlin/com/alfresco/capture/CaptureModeSelectorView.kt index faf1f8115..ad4e25077 100644 --- a/capture/src/main/kotlin/com/alfresco/capture/CaptureModeSelectorView.kt +++ b/capture/src/main/kotlin/com/alfresco/capture/CaptureModeSelectorView.kt @@ -25,7 +25,6 @@ class CaptureModeSelectorView( defStyleAttr: Int, defStyleRes: Int, ) : FrameLayout(context, attrs, defStyleAttr, defStyleRes) { - var modes: List = listOf(CaptureMode.Photo, CaptureMode.Video) set(value) { field = value @@ -52,16 +51,18 @@ class CaptureModeSelectorView( private fun createRecyclerView() = RecyclerView(context).apply { - layoutParams = LayoutParams( - LayoutParams.MATCH_PARENT, - LayoutParams.WRAP_CONTENT, - ) + layoutParams = + LayoutParams( + LayoutParams.MATCH_PARENT, + LayoutParams.WRAP_CONTENT, + ) adapter = Adapter(modes.map { it.title(context) }) - layoutManager = LinearLayoutManager( - context, - LinearLayoutManager.HORIZONTAL, - false, - ) + layoutManager = + LinearLayoutManager( + context, + LinearLayoutManager.HORIZONTAL, + false, + ) addItemDecoration( SpacingDecoration( resources.getDimension(R.dimen.capture_button_min_spacing).toInt(), @@ -100,20 +101,25 @@ class CaptureModeSelectorView( private inner class Adapter(private val dataSet: List) : RecyclerView.Adapter() { - inner class ViewHolder(val view: ModeView) : RecyclerView.ViewHolder(view) - override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int) = - ViewHolder( - ModeView(viewGroup.context).apply { - layoutParams = RecyclerView.LayoutParams( + override fun onCreateViewHolder( + viewGroup: ViewGroup, + viewType: Int, + ) = ViewHolder( + ModeView(viewGroup.context).apply { + layoutParams = + RecyclerView.LayoutParams( RecyclerView.LayoutParams.WRAP_CONTENT, resources.getDimension(R.dimen.capture_button_size).toInt(), ) - }, - ) + }, + ) - override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) { + override fun onBindViewHolder( + viewHolder: ViewHolder, + position: Int, + ) { viewHolder.view.apply { text = dataSet[position] setOnClickListener { @@ -129,10 +135,10 @@ class CaptureModeSelectorView( private class ModeView( context: Context, ) : AppCompatTextView( - ContextThemeWrapper(context, R.style.Widget_Alfresco_Camera_Mode_Button), - null, - 0, - ) { + ContextThemeWrapper(context, R.style.Widget_Alfresco_Camera_Mode_Button), + null, + 0, + ) { init { gravity = Gravity.CENTER val pad = resources.getDimension(R.dimen.capture_button_padding).toInt() @@ -140,9 +146,10 @@ class CaptureModeSelectorView( } } - private class SpacingDecoration(@Px private val innerSpacing: Int) : + private class SpacingDecoration( + @Px private val innerSpacing: Int, + ) : RecyclerView.ItemDecoration() { - override fun getItemOffsets( outRect: Rect, view: View, @@ -191,7 +198,10 @@ class CaptureModeSelectorView( ) : RecyclerView.OnScrollListener() { private var snapPosition = RecyclerView.NO_POSITION - override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { + override fun onScrollStateChanged( + recyclerView: RecyclerView, + newState: Int, + ) { if (newState == RecyclerView.SCROLL_STATE_IDLE) { val snapPosition = snapHelper.getSnapPosition(recyclerView) val snapPositionChanged = this.snapPosition != snapPosition @@ -210,20 +220,24 @@ class CaptureModeSelectorView( } private fun RecyclerView.smoothScrollToCenteredPosition(position: Int) { - val smoothScroller = object : LinearSmoothScroller(context) { - private val MILLISECONDS_PER_INCH = 65f - - override fun calculateDxToMakeVisible(view: View?, snapPreference: Int): Int { - val dxToStart = super.calculateDxToMakeVisible(view, SNAP_TO_START) - val dxToEnd = super.calculateDxToMakeVisible(view, SNAP_TO_END) - - return (dxToStart + dxToEnd) / 2 - } + val smoothScroller = + object : LinearSmoothScroller(context) { + private val MILLISECONDS_PER_INCH = 65f + + override fun calculateDxToMakeVisible( + view: View?, + snapPreference: Int, + ): Int { + val dxToStart = super.calculateDxToMakeVisible(view, SNAP_TO_START) + val dxToEnd = super.calculateDxToMakeVisible(view, SNAP_TO_END) + + return (dxToStart + dxToEnd) / 2 + } - override fun calculateSpeedPerPixel(displayMetrics: DisplayMetrics): Float { - return MILLISECONDS_PER_INCH / displayMetrics.densityDpi + override fun calculateSpeedPerPixel(displayMetrics: DisplayMetrics): Float { + return MILLISECONDS_PER_INCH / displayMetrics.densityDpi + } } - } smoothScroller.targetPosition = position layoutManager?.startSmoothScroll(smoothScroller) diff --git a/capture/src/main/kotlin/com/alfresco/capture/CapturePhotoResultContract.kt b/capture/src/main/kotlin/com/alfresco/capture/CapturePhotoResultContract.kt index d82ca0e76..5547d3491 100644 --- a/capture/src/main/kotlin/com/alfresco/capture/CapturePhotoResultContract.kt +++ b/capture/src/main/kotlin/com/alfresco/capture/CapturePhotoResultContract.kt @@ -11,11 +11,17 @@ import androidx.annotation.CallSuper */ class CapturePhotoResultContract : ActivityResultContract?>() { @CallSuper - override fun createIntent(context: Context, input: Unit): Intent { + override fun createIntent( + context: Context, + input: Unit, + ): Intent { return Intent(context, CaptureActivity::class.java) } - override fun parseResult(resultCode: Int, intent: Intent?): List? { + override fun parseResult( + resultCode: Int, + intent: Intent?, + ): List? { return if (intent == null || resultCode != Activity.RESULT_OK) { null } else { diff --git a/capture/src/main/kotlin/com/alfresco/capture/CaptureViewModel.kt b/capture/src/main/kotlin/com/alfresco/capture/CaptureViewModel.kt index 2b278e491..aeedc831a 100644 --- a/capture/src/main/kotlin/com/alfresco/capture/CaptureViewModel.kt +++ b/capture/src/main/kotlin/com/alfresco/capture/CaptureViewModel.kt @@ -46,9 +46,10 @@ class CaptureViewModel( /** * remove all the capture from the list */ - fun clearCaptureList() = setState { - copy(listCapture = listOf()) - } + fun clearCaptureList() = + setState { + copy(listCapture = listOf()) + } /** * remove capture object from the list and delete it's uri from capture directory @@ -65,14 +66,14 @@ class CaptureViewModel( private fun deleteCapture(captureItem: CaptureItem) = setState { copy( - listCapture = listCapture.filter { - it.uri != captureItem.uri - }, + listCapture = + listCapture.filter { + it.uri != captureItem.uri + }, ) } - fun prepareCaptureFile(mode: CaptureMode) = - File(captureDir, "${System.currentTimeMillis()}${extensionFor(mode)}") + fun prepareCaptureFile(mode: CaptureMode) = File(captureDir, "${System.currentTimeMillis()}${extensionFor(mode)}") private fun extensionFor(mode: CaptureMode) = when (mode) { @@ -83,21 +84,20 @@ class CaptureViewModel( /** * send capture list as result to the previous controller */ - fun save() = withState { - requireNotNull(it.listCapture) - - it.listCapture.let { capturedList -> - onSaveComplete?.invoke( - capturedList, - ) + fun save() = + withState { + requireNotNull(it.listCapture) + + it.listCapture.let { capturedList -> + onSaveComplete?.invoke( + capturedList, + ) + } } - } - fun onCapturePhoto(uri: Uri) = - onCaptureMedia(CaptureItem.photoCapture(uri)) + fun onCapturePhoto(uri: Uri) = onCaptureMedia(CaptureItem.photoCapture(uri)) - fun onCaptureVideo(uri: Uri) = - onCaptureMedia(CaptureItem.videoCapture(uri)) + fun onCaptureVideo(uri: Uri) = onCaptureMedia(CaptureItem.videoCapture(uri)) private fun onCaptureMedia(media: CaptureItem) = setState { @@ -133,34 +133,38 @@ class CaptureViewModel( /** * update the name for the current visible capture on carousel */ - fun updateName(newFileName: String) = withState { - val newList = it.listCapture.map { captureItem -> - if (captureItem == it.visibleItem) { - val updateCapture = captureItem.copy(name = newFileName) - setState { copy(visibleItem = updateCapture) } - updateCapture - } else { - captureItem - } + fun updateName(newFileName: String) = + withState { + val newList = + it.listCapture.map { captureItem -> + if (captureItem == it.visibleItem) { + val updateCapture = captureItem.copy(name = newFileName) + setState { copy(visibleItem = updateCapture) } + updateCapture + } else { + captureItem + } + } + setState { copy(listCapture = newList) } } - setState { copy(listCapture = newList) } - } /** * update the description for the current visible capture on carousel */ - fun updateDescription(newDescription: String) = withState { - val newList = it.listCapture.map { captureItem -> - if (captureItem == it.visibleItem) { - val updateCapture = captureItem.copy(description = newDescription) - setState { copy(visibleItem = updateCapture) } - updateCapture - } else { - captureItem - } + fun updateDescription(newDescription: String) = + withState { + val newList = + it.listCapture.map { captureItem -> + if (captureItem == it.visibleItem) { + val updateCapture = captureItem.copy(description = newDescription) + setState { copy(visibleItem = updateCapture) } + updateCapture + } else { + captureItem + } + } + setState { copy(listCapture = newList) } } - setState { copy(listCapture = newList) } - } /** * copy the visible item from the carousel as current item diff --git a/capture/src/main/kotlin/com/alfresco/capture/FlashMenu.kt b/capture/src/main/kotlin/com/alfresco/capture/FlashMenu.kt index 84113224b..e42743325 100644 --- a/capture/src/main/kotlin/com/alfresco/capture/FlashMenu.kt +++ b/capture/src/main/kotlin/com/alfresco/capture/FlashMenu.kt @@ -19,7 +19,6 @@ class FlashMenu( defStyleAttr: Int, defStyleRes: Int, ) : LinearLayout(context, attrs, defStyleAttr, defStyleRes) { - private val binding: ViewFlashMenuBinding var onMenuItemClick: ((FlashMenuItem) -> Unit)? = null @@ -39,11 +38,12 @@ class FlashMenu( arrayOf(binding.flashModeAuto, binding.flashModeOn, binding.flashModeOff).map { it.setOnClickListener { item -> - val mode = when (item) { - binding.flashModeOn -> FlashMenuItem.On - binding.flashModeOff -> FlashMenuItem.Off - else -> FlashMenuItem.Auto - } + val mode = + when (item) { + binding.flashModeOn -> FlashMenuItem.On + binding.flashModeOff -> FlashMenuItem.Off + else -> FlashMenuItem.Auto + } onMenuItemClick?.invoke(mode) } } diff --git a/capture/src/main/kotlin/com/alfresco/capture/ListViewPreview.kt b/capture/src/main/kotlin/com/alfresco/capture/ListViewPreview.kt index b25873cee..ffb02adc5 100644 --- a/capture/src/main/kotlin/com/alfresco/capture/ListViewPreview.kt +++ b/capture/src/main/kotlin/com/alfresco/capture/ListViewPreview.kt @@ -25,168 +25,178 @@ import java.util.concurrent.TimeUnit * Generated Model View for the Preview Screen */ @ModelView(autoLayout = ModelView.Size.WRAP_WIDTH_WRAP_HEIGHT) -class ListViewPreview @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0, -) : FrameLayout(context, attrs, defStyleAttr) { - - var captureItem: CaptureItem? = null - - private val binding = ViewListPreviewBinding.inflate(LayoutInflater.from(context), this) - private val imageLoader: ImageLoader by lazy { - ImageLoader.Builder(context) - .components { - add(VideoFrameDecoder.Factory()) - } - .eventListener(object : EventListener { - override fun onSuccess(request: ImageRequest, result: SuccessResult) { - super.onSuccess(request, result) - onSuccessMediaLoad() +class ListViewPreview + @JvmOverloads + constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + ) : FrameLayout(context, attrs, defStyleAttr) { + var captureItem: CaptureItem? = null + + private val binding = ViewListPreviewBinding.inflate(LayoutInflater.from(context), this) + private val imageLoader: ImageLoader by lazy { + ImageLoader.Builder(context) + .components { + add(VideoFrameDecoder.Factory()) } - }) - .build() - } - - /** - * Bind the capture item data to the view - */ - @ModelProp - fun setData(item: CaptureItem) { - captureItem = item - - if (item.isVideo()) { - setVideoScaleType(item) - } else { - setPhotoScaleType(item) + .eventListener( + object : EventListener { + override fun onSuccess( + request: ImageRequest, + result: SuccessResult, + ) { + super.onSuccess(request, result) + onSuccessMediaLoad() + } + }, + ) + .build() } - binding.preview.load(item.uri, imageLoader) - } + /** + * Bind the capture item data to the view + */ + @ModelProp + fun setData(item: CaptureItem) { + captureItem = item + + if (item.isVideo()) { + setVideoScaleType(item) + } else { + setPhotoScaleType(item) + } - private fun setPhotoScaleType(item: CaptureItem) { - val exif = item.uri.path?.let { ExifInterface(it) } - if (exif != null) { - val rotation = exif.getAttributeInt( - ExifInterface.TAG_ORIENTATION, - ExifInterface.ORIENTATION_NORMAL, - ) - binding.preview.scaleType = getScaleType(convertOrientationToDegree(rotation)) + binding.preview.load(item.uri, imageLoader) } - } - private fun setVideoScaleType(item: CaptureItem) { - val mediaMetadataRetriever = MediaMetadataRetriever() - mediaMetadataRetriever.setDataSource(item.uri.path) - val rotation = - mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION) - rotation?.let { - binding.preview.scaleType = getScaleType(it.toInt()) + private fun setPhotoScaleType(item: CaptureItem) { + val exif = item.uri.path?.let { ExifInterface(it) } + if (exif != null) { + val rotation = + exif.getAttributeInt( + ExifInterface.TAG_ORIENTATION, + ExifInterface.ORIENTATION_NORMAL, + ) + binding.preview.scaleType = getScaleType(convertOrientationToDegree(rotation)) + } } - } - private fun convertOrientationToDegree(orientation: Int): Int { - return when (orientation) { - ExifInterface.ORIENTATION_ROTATE_90 -> ORIENTATION_90 - ExifInterface.ORIENTATION_ROTATE_270 -> ORIENTATION_270 - ExifInterface.ORIENTATION_ROTATE_180 -> ORIENTATION_180 - else -> ORIENTATION_0 + private fun setVideoScaleType(item: CaptureItem) { + val mediaMetadataRetriever = MediaMetadataRetriever() + mediaMetadataRetriever.setDataSource(item.uri.path) + val rotation = + mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION) + rotation?.let { + binding.preview.scaleType = getScaleType(it.toInt()) + } } - } - private fun getScaleType(rotation: Int): ImageView.ScaleType { - val isTablet = context.resources.getBoolean(R.bool.isTablet) - return when { - isTablet && (rotation == ORIENTATION_0 || rotation == ORIENTATION_180) -> { - ImageView.ScaleType.FIT_CENTER + private fun convertOrientationToDegree(orientation: Int): Int { + return when (orientation) { + ExifInterface.ORIENTATION_ROTATE_90 -> ORIENTATION_90 + ExifInterface.ORIENTATION_ROTATE_270 -> ORIENTATION_270 + ExifInterface.ORIENTATION_ROTATE_180 -> ORIENTATION_180 + else -> ORIENTATION_0 } + } - !isTablet && (rotation == ORIENTATION_180 || rotation == ORIENTATION_0) -> { - ImageView.ScaleType.FIT_CENTER - } + private fun getScaleType(rotation: Int): ImageView.ScaleType { + val isTablet = context.resources.getBoolean(R.bool.isTablet) + return when { + isTablet && (rotation == ORIENTATION_0 || rotation == ORIENTATION_180) -> { + ImageView.ScaleType.FIT_CENTER + } - else -> { - ImageView.ScaleType.FIT_XY + !isTablet && (rotation == ORIENTATION_180 || rotation == ORIENTATION_0) -> { + ImageView.ScaleType.FIT_CENTER + } + + else -> { + ImageView.ScaleType.FIT_XY + } } } - } - /** - * set clickListener to the list item - */ - @CallbackProp - fun setClickListener(listener: OnClickListener?) { - setOnClickListener(listener) - } + /** + * set clickListener to the list item + */ + @CallbackProp + fun setClickListener(listener: OnClickListener?) { + setOnClickListener(listener) + } - /** - * set clickListener to the preview button to en-large the view - */ - @CallbackProp - fun setPreviewClickListener(listener: OnClickListener?) { - binding.preview.setOnClickListener(listener) - } + /** + * set clickListener to the preview button to en-large the view + */ + @CallbackProp + fun setPreviewClickListener(listener: OnClickListener?) { + binding.preview.setOnClickListener(listener) + } - /** - * set clickListener to the delete button to delete the view - */ - @CallbackProp - fun setDeletePhotoClickListener(listener: OnClickListener?) { - binding.deletePhotoButton.setOnClickListener(listener) - } + /** + * set clickListener to the delete button to delete the view + */ + @CallbackProp + fun setDeletePhotoClickListener(listener: OnClickListener?) { + binding.deletePhotoButton.setOnClickListener(listener) + } - companion object { - const val ORIENTATION_0 = 0 - const val ORIENTATION_90 = 90 - const val ORIENTATION_180 = 180 - const val ORIENTATION_270 = 270 - } + companion object { + const val ORIENTATION_0 = 0 + const val ORIENTATION_90 = 90 + const val ORIENTATION_180 = 180 + const val ORIENTATION_270 = 270 + } - private fun onSuccessMediaLoad() { - captureItem?.let { - if (it.isVideo()) { - val mediaMetadataRetriever = MediaMetadataRetriever() - mediaMetadataRetriever.setDataSource(it.uri.path) - val time: String? = - mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION) - val duration = time?.toLong() + private fun onSuccessMediaLoad() { + captureItem?.let { + if (it.isVideo()) { + val mediaMetadataRetriever = MediaMetadataRetriever() + mediaMetadataRetriever.setDataSource(it.uri.path) + val time: String? = + mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION) + val duration = time?.toLong() - binding.videoDuration.isVisible = it.isVideo() == true + binding.videoDuration.isVisible = it.isVideo() == true - duration?.let { millis -> - setVideoDuration(millis) + duration?.let { millis -> + setVideoDuration(millis) + } } } + binding.deletePhotoButton.isVisible = true } - binding.deletePhotoButton.isVisible = true - } - private fun setVideoDuration(millis: Long) { - val hour = TimeUnit.MILLISECONDS.toHours(millis) - val minutes = - TimeUnit.MILLISECONDS.toMinutes(millis) - TimeUnit.HOURS.toMinutes( - TimeUnit.MILLISECONDS.toHours(millis), - ) - val seconds = - TimeUnit.MILLISECONDS.toSeconds(millis) - TimeUnit.MINUTES.toSeconds( - TimeUnit.MILLISECONDS.toMinutes(millis), - ) - val hms = if (hour > 0L) { - java.lang.String.format( - ENGLISH, - context.getString(R.string.format_video_duration_hour), - hour, - minutes, - seconds, - ) - } else { - java.lang.String.format( - ENGLISH, - context.getString(R.string.format_video_duration_minute), - minutes, - seconds, - ) + private fun setVideoDuration(millis: Long) { + val hour = TimeUnit.MILLISECONDS.toHours(millis) + val minutes = + TimeUnit.MILLISECONDS.toMinutes(millis) - + TimeUnit.HOURS.toMinutes( + TimeUnit.MILLISECONDS.toHours(millis), + ) + val seconds = + TimeUnit.MILLISECONDS.toSeconds(millis) - + TimeUnit.MINUTES.toSeconds( + TimeUnit.MILLISECONDS.toMinutes(millis), + ) + val hms = + if (hour > 0L) { + java.lang.String.format( + ENGLISH, + context.getString(R.string.format_video_duration_hour), + hour, + minutes, + seconds, + ) + } else { + java.lang.String.format( + ENGLISH, + context.getString(R.string.format_video_duration_minute), + minutes, + seconds, + ) + } + binding.videoDuration.text = hms } - binding.videoDuration.text = hms } -} diff --git a/capture/src/main/kotlin/com/alfresco/capture/LocationUtils.kt b/capture/src/main/kotlin/com/alfresco/capture/LocationUtils.kt index ea7f78f53..5755324fc 100644 --- a/capture/src/main/kotlin/com/alfresco/capture/LocationUtils.kt +++ b/capture/src/main/kotlin/com/alfresco/capture/LocationUtils.kt @@ -4,7 +4,6 @@ import android.content.Context import android.location.LocationManager object LocationUtils { - /** * Function to check if location of the device is enabled or not */ diff --git a/capture/src/main/kotlin/com/alfresco/capture/PreviewFragment.kt b/capture/src/main/kotlin/com/alfresco/capture/PreviewFragment.kt index 6dd158466..9493530de 100644 --- a/capture/src/main/kotlin/com/alfresco/capture/PreviewFragment.kt +++ b/capture/src/main/kotlin/com/alfresco/capture/PreviewFragment.kt @@ -28,13 +28,14 @@ data class PreviewArgs( ) } - fun bundle(path: String, mimeType: String) = - bundleOf(PATH_KEY to path, MIME_TYPE_KEY to mimeType) + fun bundle( + path: String, + mimeType: String, + ) = bundleOf(PATH_KEY to path, MIME_TYPE_KEY to mimeType) } } class PreviewFragment : Fragment() { - private lateinit var binding: FragmentPreviewBinding override fun onCreateView( @@ -46,7 +47,10 @@ class PreviewFragment : Fragment() { return binding.root } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { super.onViewCreated(view, savedInstanceState) val args = PreviewArgs.with(requireArguments()) diff --git a/capture/src/main/kotlin/com/alfresco/capture/SaveFragment.kt b/capture/src/main/kotlin/com/alfresco/capture/SaveFragment.kt index 31cebd9a7..4d4e3ef82 100644 --- a/capture/src/main/kotlin/com/alfresco/capture/SaveFragment.kt +++ b/capture/src/main/kotlin/com/alfresco/capture/SaveFragment.kt @@ -28,7 +28,6 @@ import com.alfresco.ui.getDrawableForAttribute import com.alfresco.ui.text class SaveFragment : Fragment(), MavericksView { - private val viewModel: CaptureViewModel by activityViewModel() private lateinit var binding: FragmentSaveBinding private val epoxyController: AsyncEpoxyController by lazy { epoxyController() } @@ -42,7 +41,10 @@ class SaveFragment : Fragment(), MavericksView { return binding.root } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { super.onViewCreated(view, savedInstanceState) binding.toolbar.apply { @@ -53,7 +55,7 @@ class SaveFragment : Fragment(), MavericksView { if (savedInstanceState == null) { withState(viewModel) { - binding.fileNameInputLayout.text = it.listCapture.first()?.name ?: "" + binding.fileNameInputLayout.text = it.listCapture.first().name } } @@ -62,11 +64,13 @@ class SaveFragment : Fragment(), MavericksView { binding.recyclerView.setController(epoxyController) EpoxyVisibilityTracker().attach(binding.recyclerView) - Carousel.setDefaultGlobalSnapHelperFactory(object : Carousel.SnapHelperFactory() { - override fun buildSnapHelper(context: Context?): SnapHelper { - return PagerSnapHelper() - } - }) + Carousel.setDefaultGlobalSnapHelperFactory( + object : Carousel.SnapHelperFactory() { + override fun buildSnapHelper(context: Context?): SnapHelper { + return PagerSnapHelper() + } + }, + ) binding.saveButton.setOnClickListener { viewModel.save() @@ -82,83 +86,110 @@ class SaveFragment : Fragment(), MavericksView { } private fun setListeners() { - binding.fileNameInputLayout.editText?.addTextChangedListener(object : TextWatcher { - override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { - // no-op - } + binding.fileNameInputLayout.editText?.addTextChangedListener( + object : TextWatcher { + override fun beforeTextChanged( + s: CharSequence?, + start: Int, + count: Int, + after: Int, + ) { + // no-op + } - override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { - // no-op - } + override fun onTextChanged( + s: CharSequence?, + start: Int, + before: Int, + count: Int, + ) { + // no-op + } - override fun afterTextChanged(s: Editable?) { - validateName(s.toString()) - viewModel.updateName(s.toString()) - } - }) + override fun afterTextChanged(s: Editable?) { + validateName(s.toString()) + viewModel.updateName(s.toString()) + } + }, + ) - binding.descriptionInputLayout.editText?.addTextChangedListener(object : TextWatcher { - override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { - // no-op - } + binding.descriptionInputLayout.editText?.addTextChangedListener( + object : TextWatcher { + override fun beforeTextChanged( + s: CharSequence?, + start: Int, + count: Int, + after: Int, + ) { + // no-op + } - override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { - // no-op - } + override fun onTextChanged( + s: CharSequence?, + start: Int, + before: Int, + count: Int, + ) { + // no-op + } - override fun afterTextChanged(s: Editable?) { - viewModel.updateDescription(s.toString()) - } - }) + override fun afterTextChanged(s: Editable?) { + viewModel.updateDescription(s.toString()) + } + }, + ) } - private fun epoxyController() = simpleController(viewModel) { state -> - if (state.listCapture.isNotEmpty()) { - val viewsOnScreen = if (state.listCapture.size > 1) 1.5f else 1f - carouselBuilder { - id("carousel") - numViewsToShowOnScreen(viewsOnScreen) - padding(Carousel.Padding.dp(20, 8, 20, 8, 8)) - hasFixedSize(true) - - for (item in state.listCapture) { - this.listViewPreview { - id(item.uri.toString()) - data(item) - previewClickListener { model, _, _, _ -> showPreview(model.data()) } - deletePhotoClickListener { model, _, _, _ -> delete(model.data()) } - onVisibilityStateChanged { _, _, visibilityState -> - if (visibilityState == VisibilityState.FOCUSED_VISIBLE) { - viewModel.copyVisibleItem(item) - binding.fileNameInputLayout.text = item.name - binding.descriptionInputLayout.text = item.description + private fun epoxyController() = + simpleController(viewModel) { state -> + if (state.listCapture.isNotEmpty()) { + val viewsOnScreen = if (state.listCapture.size > 1) 1.5f else 1f + carouselBuilder { + id("carousel") + numViewsToShowOnScreen(viewsOnScreen) + padding(Carousel.Padding.dp(20, 8, 20, 8, 8)) + hasFixedSize(true) + + for (item in state.listCapture) { + this.listViewPreview { + id(item.uri.toString()) + data(item) + previewClickListener { model, _, _, _ -> showPreview(model.data()) } + deletePhotoClickListener { model, _, _, _ -> delete(model.data()) } + onVisibilityStateChanged { _, _, visibilityState -> + if (visibilityState == VisibilityState.FOCUSED_VISIBLE) { + viewModel.copyVisibleItem(item) + binding.fileNameInputLayout.text = item.name + binding.descriptionInputLayout.text = item.description + } } } } } - } - } else { - requireActivity().runOnUiThread { - goBack() + } else { + requireActivity().runOnUiThread { + goBack() + } } } - } private fun validateName(name: String) { val valid = viewModel.isFilenameValid(name) val empty = name.isEmpty() - binding.fileNameInputLayout.error = when { - !valid -> resources.getString(R.string.capture_file_name_invalid_chars) - empty -> resources.getString(R.string.capture_file_name_empty) - else -> null - } + binding.fileNameInputLayout.error = + when { + !valid -> resources.getString(R.string.capture_file_name_invalid_chars) + empty -> resources.getString(R.string.capture_file_name_empty) + else -> null + } setSaveButtonState(valid && !empty) } - private fun setSaveButtonState(isVisibleFieldValid: Boolean) = withState(viewModel) { - binding.saveButton.isEnabled = isVisibleFieldValid && viewModel.isAllFileNameValid(it.listCapture) - } + private fun setSaveButtonState(isVisibleFieldValid: Boolean) = + withState(viewModel) { + binding.saveButton.isEnabled = isVisibleFieldValid && viewModel.isAllFileNameValid(it.listCapture) + } private fun goBack() { viewModel.clearCaptureList() @@ -170,16 +201,18 @@ class SaveFragment : Fragment(), MavericksView { viewModel.clearSingleCaptures(captureItem) } - override fun invalidate(): Unit = withState(viewModel) { - epoxyController.requestModelBuild() - } + override fun invalidate(): Unit = + withState(viewModel) { + epoxyController.requestModelBuild() + } - private fun showPreview(captureItem: CaptureItem?) = withState(viewModel) { - requireNotNull(captureItem) - findNavController().navigateToPreview( - captureItem.mimeType, - captureItem.uri.toString(), - captureItem.name, - ) - } + private fun showPreview(captureItem: CaptureItem?) = + withState(viewModel) { + requireNotNull(captureItem) + findNavController().navigateToPreview( + captureItem.mimeType, + captureItem.uri.toString(), + captureItem.name, + ) + } } diff --git a/capture/src/main/kotlin/com/alfresco/capture/ShutterButton.kt b/capture/src/main/kotlin/com/alfresco/capture/ShutterButton.kt index f5401a190..038afc052 100644 --- a/capture/src/main/kotlin/com/alfresco/capture/ShutterButton.kt +++ b/capture/src/main/kotlin/com/alfresco/capture/ShutterButton.kt @@ -13,7 +13,6 @@ class ShutterButton( attrs: AttributeSet?, defStyleAttr: Int, ) : FrameLayout(context, attrs, defStyleAttr) { - private val background = createBackground() private val imageView = createImageView() @@ -36,10 +35,11 @@ class ShutterButton( private fun createImageView() = ImageView(context).apply { - layoutParams = LayoutParams( - LayoutParams.WRAP_CONTENT, - LayoutParams.WRAP_CONTENT, - ) + layoutParams = + LayoutParams( + LayoutParams.WRAP_CONTENT, + LayoutParams.WRAP_CONTENT, + ) setImageResource(R.drawable.ic_shutter_photo_to_video) // Seems that the drawable is sometimes loaded in the end state, so reset it. (drawable as? AnimatedVectorDrawable)?.reset() @@ -47,23 +47,28 @@ class ShutterButton( private fun createBackground() = ImageView(context).apply { - layoutParams = LayoutParams( - LayoutParams.WRAP_CONTENT, - LayoutParams.WRAP_CONTENT, - ) + layoutParams = + LayoutParams( + LayoutParams.WRAP_CONTENT, + LayoutParams.WRAP_CONTENT, + ) setImageResource(R.drawable.bg_shutter_btn) } - private fun updateImage(old: State, new: State) { + private fun updateImage( + old: State, + new: State, + ) { if (old == new) return - val resId = when { - old == State.Photo && new == State.Video -> R.drawable.ic_shutter_photo_to_video - old == State.Video && new == State.Photo -> R.drawable.ic_shutter_video_to_photo - old == State.Video && new == State.Recording -> R.drawable.ic_shutter_video_to_record - old == State.Recording && new == State.Video -> R.drawable.ic_shutter_record_to_video - else -> throw IllegalStateException() - } + val resId = + when { + old == State.Photo && new == State.Video -> R.drawable.ic_shutter_photo_to_video + old == State.Video && new == State.Photo -> R.drawable.ic_shutter_video_to_photo + old == State.Video && new == State.Recording -> R.drawable.ic_shutter_video_to_record + old == State.Recording && new == State.Video -> R.drawable.ic_shutter_record_to_video + else -> throw IllegalStateException() + } imageView.setImageResource(resId) (imageView.drawable as? AnimatedVectorDrawable)?.start() diff --git a/capture/src/main/kotlin/com/alfresco/ui/KeyHandler.kt b/capture/src/main/kotlin/com/alfresco/ui/KeyHandler.kt index 303f0e60e..1d9f05ba6 100644 --- a/capture/src/main/kotlin/com/alfresco/ui/KeyHandler.kt +++ b/capture/src/main/kotlin/com/alfresco/ui/KeyHandler.kt @@ -3,5 +3,8 @@ package com.alfresco.ui import android.view.KeyEvent interface KeyHandler { - fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean + fun onKeyDown( + keyCode: Int, + event: KeyEvent, + ): Boolean } diff --git a/capture/src/main/kotlin/com/alfresco/ui/WindowCompat.kt b/capture/src/main/kotlin/com/alfresco/ui/WindowCompat.kt index 05a1b5d7d..b17c55631 100644 --- a/capture/src/main/kotlin/com/alfresco/ui/WindowCompat.kt +++ b/capture/src/main/kotlin/com/alfresco/ui/WindowCompat.kt @@ -30,7 +30,7 @@ object WindowCompat { or View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION - ) + ) } } diff --git a/common/src/main/kotlin/com/alfresco/content/ContentPickerFragment.kt b/common/src/main/kotlin/com/alfresco/content/ContentPickerFragment.kt index 0875ee27c..02ab490d9 100644 --- a/common/src/main/kotlin/com/alfresco/content/ContentPickerFragment.kt +++ b/common/src/main/kotlin/com/alfresco/content/ContentPickerFragment.kt @@ -16,16 +16,21 @@ class ContentPickerFragment : Fragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - requestLauncher = registerForActivityResult(GetMultipleContents()) { - onResult?.resume(it, null) - } + requestLauncher = + registerForActivityResult(GetMultipleContents()) { + onResult?.resume(it, null) + } - requestLauncherSingle = registerForActivityResult(GetSingleContent()) { - onResult?.resume(it, null) - } + requestLauncherSingle = + registerForActivityResult(GetSingleContent()) { + onResult?.resume(it, null) + } } - private suspend fun pickItems(mimeTypes: Array, isMultiple: Boolean): List = + private suspend fun pickItems( + mimeTypes: Array, + isMultiple: Boolean, + ): List = suspendCancellableCoroutine { continuation -> onResult = continuation if (!isMultiple) { diff --git a/common/src/main/kotlin/com/alfresco/content/ContextExt.kt b/common/src/main/kotlin/com/alfresco/content/ContextExt.kt index e759989f4..454bba8ea 100644 --- a/common/src/main/kotlin/com/alfresco/content/ContextExt.kt +++ b/common/src/main/kotlin/com/alfresco/content/ContextExt.kt @@ -35,8 +35,7 @@ fun FragmentActivity.hideSoftInput() { /** * hiding the keyboard */ -fun View.hideSoftInput() = - context.getSystemService()?.hideSoftInputFromWindow(this.windowToken, 0) +fun View.hideSoftInput() = context.getSystemService()?.hideSoftInputFromWindow(this.windowToken, 0) fun Fragment.hideSoftInput() = requireActivity().hideSoftInput() @@ -69,8 +68,9 @@ fun Context.getLocalizedName(name: String): String { * avoiding the multiple click listener on view */ fun View.setSafeOnClickListener(onSafeClick: (View) -> Unit) { - val safeClickListener = SafeClickListener { - onSafeClick(it) - } + val safeClickListener = + SafeClickListener { + onSafeClick(it) + } setOnClickListener(safeClickListener) } diff --git a/common/src/main/kotlin/com/alfresco/content/FragmentExt.kt b/common/src/main/kotlin/com/alfresco/content/FragmentExt.kt index 9c136d21e..387084bdd 100644 --- a/common/src/main/kotlin/com/alfresco/content/FragmentExt.kt +++ b/common/src/main/kotlin/com/alfresco/content/FragmentExt.kt @@ -44,11 +44,12 @@ private fun findFragmentAndResume( continuation: CancellableContinuation, factory: () -> F, ) { - val fragmentManager = when (context) { - is AppCompatActivity -> context.supportFragmentManager - is Fragment -> context.childFragmentManager - else -> null - } + val fragmentManager = + when (context) { + is AppCompatActivity -> context.supportFragmentManager + is Fragment -> context.childFragmentManager + else -> null + } if (fragmentManager == null) { continuation.cancel(ClassCastException()) @@ -79,11 +80,12 @@ private fun findNewInstance( continuation: CancellableContinuation, factory: () -> F, ) { - val fragmentManager = when (context) { - is AppCompatActivity -> context.supportFragmentManager - is Fragment -> context.childFragmentManager - else -> null - } + val fragmentManager = + when (context) { + is AppCompatActivity -> context.supportFragmentManager + is Fragment -> context.childFragmentManager + else -> null + } if (fragmentManager == null) { continuation.cancel(ClassCastException()) diff --git a/common/src/main/kotlin/com/alfresco/content/GetMultipleContents.kt b/common/src/main/kotlin/com/alfresco/content/GetMultipleContents.kt index 76bbc39ff..913f4e029 100644 --- a/common/src/main/kotlin/com/alfresco/content/GetMultipleContents.kt +++ b/common/src/main/kotlin/com/alfresco/content/GetMultipleContents.kt @@ -13,9 +13,11 @@ import androidx.annotation.CallSuper * that allows specifying multiple mimeTypes. */ class GetMultipleContents : ActivityResultContract, List>() { - @CallSuper - override fun createIntent(context: Context, input: Array): Intent { + override fun createIntent( + context: Context, + input: Array, + ): Intent { return Intent(Intent.ACTION_GET_CONTENT) .addCategory(Intent.CATEGORY_OPENABLE) .setType("*/*") @@ -23,7 +25,10 @@ class GetMultipleContents : ActivityResultContract, List>() { .putExtra(Intent.EXTRA_MIME_TYPES, input) } - override fun parseResult(resultCode: Int, intent: Intent?): List { + override fun parseResult( + resultCode: Int, + intent: Intent?, + ): List { return if (intent == null || resultCode != Activity.RESULT_OK) { emptyList() } else { @@ -38,7 +43,10 @@ class GetMultipleContents : ActivityResultContract, List>() { /** * returns true if file exceed the 100mb length otherwise false */ - fun isFileSizeExceed(length: Long, maxSize: Int): Boolean { + fun isFileSizeExceed( + length: Long, + maxSize: Int, + ): Boolean { val fileLength = length.div(1024L).div(1024L) return fileLength > maxSize.minus(1).toLong() } diff --git a/common/src/main/kotlin/com/alfresco/content/GetSingleContent.kt b/common/src/main/kotlin/com/alfresco/content/GetSingleContent.kt index d5333625f..83977305f 100644 --- a/common/src/main/kotlin/com/alfresco/content/GetSingleContent.kt +++ b/common/src/main/kotlin/com/alfresco/content/GetSingleContent.kt @@ -13,9 +13,11 @@ import androidx.annotation.CallSuper * that allows specifying multiple mimeTypes. */ class GetSingleContent : ActivityResultContract, List>() { - @CallSuper - override fun createIntent(context: Context, input: Array): Intent { + override fun createIntent( + context: Context, + input: Array, + ): Intent { return Intent(Intent.ACTION_GET_CONTENT) .addCategory(Intent.CATEGORY_OPENABLE) .setType("*/*") @@ -23,7 +25,10 @@ class GetSingleContent : ActivityResultContract, List>() { .putExtra(Intent.EXTRA_MIME_TYPES, input) } - override fun parseResult(resultCode: Int, intent: Intent?): List { + override fun parseResult( + resultCode: Int, + intent: Intent?, + ): List { return if (intent == null || resultCode != Activity.RESULT_OK) { emptyList() } else { diff --git a/common/src/main/kotlin/com/alfresco/content/HideSoftInputOnScrollListener.kt b/common/src/main/kotlin/com/alfresco/content/HideSoftInputOnScrollListener.kt index b8dfb809a..d25ebcfe2 100644 --- a/common/src/main/kotlin/com/alfresco/content/HideSoftInputOnScrollListener.kt +++ b/common/src/main/kotlin/com/alfresco/content/HideSoftInputOnScrollListener.kt @@ -8,7 +8,10 @@ import androidx.recyclerview.widget.RecyclerView class HideSoftInputOnScrollListener : RecyclerView.OnScrollListener() { private var imm: InputMethodManager? = null - override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { + override fun onScrollStateChanged( + recyclerView: RecyclerView, + newState: Int, + ) { if (newState == RecyclerView.SCROLL_STATE_DRAGGING && recyclerView.childCount > 0) { if (imm == null) { imm = recyclerView.context.getSystemService() diff --git a/common/src/main/kotlin/com/alfresco/content/MvRxEpoxy.kt b/common/src/main/kotlin/com/alfresco/content/MvRxEpoxy.kt index 86f42da94..8f3c425b5 100644 --- a/common/src/main/kotlin/com/alfresco/content/MvRxEpoxy.kt +++ b/common/src/main/kotlin/com/alfresco/content/MvRxEpoxy.kt @@ -13,7 +13,6 @@ import com.airbnb.mvrx.withState open class MvRxEpoxyController( val buildModelsCallback: EpoxyController.() -> Unit = {}, ) : AsyncEpoxyController() { - override fun buildModels() { buildModelsCallback() } @@ -22,14 +21,13 @@ open class MvRxEpoxyController( /** * Create a [MvRxEpoxyController] that builds models with the given callback. */ -fun Fragment.simpleController( - buildModels: EpoxyController.() -> Unit, -) = MvRxEpoxyController { - // Models are built asynchronously, so it is possible that this is called after the fragment - // is detached under certain race conditions. - if (view == null || isRemoving) return@MvRxEpoxyController - buildModels() -} +fun Fragment.simpleController(buildModels: EpoxyController.() -> Unit) = + MvRxEpoxyController { + // Models are built asynchronously, so it is possible that this is called after the fragment + // is detached under certain race conditions. + if (view == null || isRemoving) return@MvRxEpoxyController + buildModels() + } /** * Create a [MvRxEpoxyController] that builds models with the given callback. diff --git a/common/src/main/kotlin/com/alfresco/content/MvRxExt.kt b/common/src/main/kotlin/com/alfresco/content/MvRxExt.kt index 7e02d8bfd..ac414d12f 100644 --- a/common/src/main/kotlin/com/alfresco/content/MvRxExt.kt +++ b/common/src/main/kotlin/com/alfresco/content/MvRxExt.kt @@ -26,19 +26,21 @@ inline fun , reified S : MavericksState> T viewModelClass: KClass = VM::class, crossinline keyFactory: () -> String = { viewModelClass.java.name }, crossinline argsProvider: () -> Any?, -) where T : Fragment, T : MavericksView = lifecycleAwareLazy(this) { - MavericksViewModelProvider.get( - viewModelClass = viewModelClass.java, - stateClass = S::class.java, - viewModelContext = FragmentViewModelContext( - activity = requireActivity(), - args = argsProvider(), - fragment = this, - ), - key = keyFactory(), - initialStateFactory = RealMavericksStateFactory(), - ).apply { _internal(this@fragmentViewModelWithArgs, action = { postInvalidate() }) } -} +) where T : Fragment, T : MavericksView = + lifecycleAwareLazy(this) { + MavericksViewModelProvider.get( + viewModelClass = viewModelClass.java, + stateClass = S::class.java, + viewModelContext = + FragmentViewModelContext( + activity = requireActivity(), + args = argsProvider(), + fragment = this, + ), + key = keyFactory(), + initialStateFactory = RealMavericksStateFactory(), + ).apply { _internal(this@fragmentViewModelWithArgs, action = { postInvalidate() }) } + } /** * Gets or creates a ViewModel scoped to this activity. You will get the same instance every time @@ -50,15 +52,17 @@ inline fun , reified S : MavericksState> T inline fun , reified S : MavericksState> T.activityViewModel( viewModelClass: KClass = VM::class, crossinline keyFactory: () -> String = { viewModelClass.java.name }, -) where T : AppCompatActivity, T : MavericksView = lifecycleAwareLazy(this) { - MavericksViewModelProvider.get( - viewModelClass = viewModelClass.java, - stateClass = S::class.java, - viewModelContext = ActivityViewModelContext( - activity = this, - args = intent.extras?.get(Mavericks.KEY_ARG), - ), - key = keyFactory(), - initialStateFactory = RealMavericksStateFactory(), - ).apply { _internal(this@activityViewModel, action = { postInvalidate() }) } -} +) where T : AppCompatActivity, T : MavericksView = + lifecycleAwareLazy(this) { + MavericksViewModelProvider.get( + viewModelClass = viewModelClass.java, + stateClass = S::class.java, + viewModelContext = + ActivityViewModelContext( + activity = this, + args = intent.extras?.get(Mavericks.KEY_ARG), + ), + key = keyFactory(), + initialStateFactory = RealMavericksStateFactory(), + ).apply { _internal(this@activityViewModel, action = { postInvalidate() }) } + } diff --git a/common/src/main/kotlin/com/alfresco/content/NavControllerExt.kt b/common/src/main/kotlin/com/alfresco/content/NavControllerExt.kt index 0222e2638..7abb4c375 100644 --- a/common/src/main/kotlin/com/alfresco/content/NavControllerExt.kt +++ b/common/src/main/kotlin/com/alfresco/content/NavControllerExt.kt @@ -16,20 +16,21 @@ fun NavController.navigateTo(entry: Entry) { } } -private fun NavController.navigateToFolder(entry: Entry) = - navigateToFolder(entry.id, entry.name, modeFor(entry)) +private fun NavController.navigateToFolder(entry: Entry) = navigateToFolder(entry.id, entry.name, modeFor(entry)) /** * navigate to move screen */ -fun NavController.navigateToFolder(entry: Entry, moveId: String = "", isProcess: Boolean = false) = - navigateToChildFolder(entry.id, entry.name, moveId, modeFor(entry), isProcess = isProcess) +fun NavController.navigateToFolder( + entry: Entry, + moveId: String = "", + isProcess: Boolean = false, +) = navigateToChildFolder(entry.id, entry.name, moveId, modeFor(entry), isProcess = isProcess) /** * navigate to extension child folders */ -fun NavController.navigateToExtensionFolder(entry: Entry) = - navigateToChildFolder(entry.id, entry.name, mode = modeFor(entry)) +fun NavController.navigateToExtensionFolder(entry: Entry) = navigateToChildFolder(entry.id, entry.name, mode = modeFor(entry)) private fun modeFor(entry: Entry) = if (entry.hasOfflineStatus) { @@ -38,12 +39,18 @@ private fun modeFor(entry: Entry) = REMOTE } -fun NavController.navigateToFolder(id: String, title: String, mode: String = REMOTE) { +fun NavController.navigateToFolder( + id: String, + title: String, + mode: String = REMOTE, +) { navigate(Uri.parse("$BASE_URI/browse/folder/$mode/$id?title=${Uri.encode(title)}")) } -fun NavController.navigateToKnownPath(path: String, title: String) = - navigate(Uri.parse("$BASE_URI/browse/$path/$REMOTE?title=${Uri.encode(title)}")) +fun NavController.navigateToKnownPath( + path: String, + title: String, +) = navigate(Uri.parse("$BASE_URI/browse/$path/$REMOTE?title=${Uri.encode(title)}")) private fun NavController.navigateToSite(entry: Entry) = navigate(Uri.parse("$BASE_URI/browse/site/$REMOTE/${entry.id}?title=${Uri.encode(entry.name)}")) @@ -60,7 +67,12 @@ private fun NavController.navigateFolderLink(entry: Entry) = /** * navigate to contextual search */ -fun NavController.navigateToContextualSearch(id: String, title: String, isExtension: Boolean, moveId: String = "") { +fun NavController.navigateToContextualSearch( + id: String, + title: String, + isExtension: Boolean, + moveId: String = "", +) { if (moveId.isNotEmpty()) { navigate(Uri.parse("$BASE_URI/search/folder/$id?title=${Uri.encode(title)},extension=$isExtension,moveId=$moveId")) } else { @@ -71,21 +83,34 @@ fun NavController.navigateToContextualSearch(id: String, title: String, isExtens /** * navigate to contextual search from process app */ -fun NavController.navigateToContextualSearch(id: String, title: String, isProcess: Boolean) { +fun NavController.navigateToContextualSearch( + id: String, + title: String, + isProcess: Boolean, +) { navigate(Uri.parse("$BASE_URI/search/folder/$id?title=${Uri.encode(title)},isProcess=$isProcess")) } /** * navigate to browse parent folder */ -fun NavController.navigateToParent(id: String, title: String, mode: String = REMOTE) { +fun NavController.navigateToParent( + id: String, + title: String, + mode: String = REMOTE, +) { navigate(Uri.parse("$BASE_URI/browse_parent/extension/$mode/$id?title=${Uri.encode(title)}")) } /** * navigate to browse move parent folder */ -fun NavController.navigateToMoveParent(id: String, moveId: String, title: String, isProcess: Boolean = false) { +fun NavController.navigateToMoveParent( + id: String, + moveId: String, + title: String, + isProcess: Boolean = false, +) { val path = "move" navigate(Uri.parse("$BASE_URI/browse_move_parent/$id?title=${Uri.encode(title)},moveId=$moveId,path=$path,isProcess=$isProcess")) } @@ -93,7 +118,13 @@ fun NavController.navigateToMoveParent(id: String, moveId: String, title: String /** * navigate to browse child folder */ -fun NavController.navigateToChildFolder(id: String, title: String, moveId: String = "", mode: String = REMOTE, isProcess: Boolean = false) { +fun NavController.navigateToChildFolder( + id: String, + title: String, + moveId: String = "", + mode: String = REMOTE, + isProcess: Boolean = false, +) { when { moveId.isNotEmpty() -> { navigate(Uri.parse("$BASE_URI/browse_move_child/$mode/$id?title=${Uri.encode(title)},moveId=$moveId,path=extension")) @@ -112,20 +143,28 @@ fun NavController.navigateToChildFolder(id: String, title: String, moveId: Strin /** * navigate to transfer files */ -fun NavController.navigateToUploadFilesPath(extension: Boolean, title: String) = - navigate(Uri.parse("$BASE_URI/transfer_files/$extension?title=${Uri.encode(title)}")) +fun NavController.navigateToUploadFilesPath( + extension: Boolean, + title: String, +) = navigate(Uri.parse("$BASE_URI/transfer_files/$extension?title=${Uri.encode(title)}")) /** * navigate to preview activity using deep linking */ -fun NavController.navigateToPreview(mimeType: String, path: String, title: String) = - navigate(Uri.parse("$BASE_URI/view/preview?title=${Uri.encode(title)},mimeType=$mimeType,path=$path")) +fun NavController.navigateToPreview( + mimeType: String, + path: String, + title: String, +) = navigate(Uri.parse("$BASE_URI/view/preview?title=${Uri.encode(title)},mimeType=$mimeType,path=$path")) /** * navigate to local preview activity using deep linking */ -fun NavController.navigateToLocalPreview(mimeType: String, path: String, title: String) = - navigate(Uri.parse("$BASE_URI/view/local/preview?title=${Uri.encode(title)},mimeType=$mimeType,path=$path")) +fun NavController.navigateToLocalPreview( + mimeType: String, + path: String, + title: String, +) = navigate(Uri.parse("$BASE_URI/view/local/preview?title=${Uri.encode(title)},mimeType=$mimeType,path=$path")) private const val BASE_URI = "alfresco://content" const val REMOTE = "remote" diff --git a/common/src/main/kotlin/com/alfresco/content/PermissionFragment.kt b/common/src/main/kotlin/com/alfresco/content/PermissionFragment.kt index 46e8652ec..17c23ae94 100644 --- a/common/src/main/kotlin/com/alfresco/content/PermissionFragment.kt +++ b/common/src/main/kotlin/com/alfresco/content/PermissionFragment.kt @@ -13,7 +13,6 @@ import kotlinx.coroutines.CancellableContinuation import kotlinx.coroutines.suspendCancellableCoroutine open class PermissionFragment : Fragment() { - private lateinit var requestLauncher: ActivityResultLauncher> private var requestCallback: ((Map) -> Unit)? = null @@ -21,11 +20,12 @@ open class PermissionFragment : Fragment() { super.onCreate(savedInstanceState) // Register for result is not allowed after onCreate - requestLauncher = registerForActivityResult( - ActivityResultContracts.RequestMultiplePermissions(), - ) { - requestCallback?.invoke(it) - } + requestLauncher = + registerForActivityResult( + ActivityResultContracts.RequestMultiplePermissions(), + ) { + requestCallback?.invoke(it) + } } private fun requestPermissions( @@ -66,8 +66,7 @@ open class PermissionFragment : Fragment() { } } - private fun shouldShowRequestRationale(permissions: List) = - permissions.any { shouldShowRequestPermissionRationale(it) } + private fun shouldShowRequestRationale(permissions: List) = permissions.any { shouldShowRequestPermissionRationale(it) } private fun showRationaleDialog( rationale: String, @@ -101,9 +100,8 @@ open class PermissionFragment : Fragment() { rationale: String, ): Boolean = suspendCancellableCoroutine { requestPermissions(permission, rationale, it) } - private suspend fun requestOptionalPermissions( - permission: List, - ): Boolean = suspendCancellableCoroutine { requestOptionalPermissions(permission, it) } + private suspend fun requestOptionalPermissions(permission: List): Boolean = + suspendCancellableCoroutine { requestOptionalPermissions(permission, it) } companion object { private val TAG = PermissionFragment::class.java.simpleName @@ -137,9 +135,11 @@ open class PermissionFragment : Fragment() { { PermissionFragment() }, ) - fun deniedPermissions(context: Context, permissions: List) = - permissions.filter { - ContextCompat.checkSelfPermission(context, it) == PackageManager.PERMISSION_DENIED - } + fun deniedPermissions( + context: Context, + permissions: List, + ) = permissions.filter { + ContextCompat.checkSelfPermission(context, it) == PackageManager.PERMISSION_DENIED + } } } diff --git a/common/src/main/kotlin/com/alfresco/content/SafeClickListener.kt b/common/src/main/kotlin/com/alfresco/content/SafeClickListener.kt index bed4e913b..254c2bea0 100644 --- a/common/src/main/kotlin/com/alfresco/content/SafeClickListener.kt +++ b/common/src/main/kotlin/com/alfresco/content/SafeClickListener.kt @@ -11,6 +11,7 @@ class SafeClickListener( private val onSafeCLick: (View) -> Unit, ) : View.OnClickListener { private var lastTimeClicked: Long = 0 + override fun onClick(v: View) { if (SystemClock.elapsedRealtime() - lastTimeClicked < defaultInterval) { return diff --git a/common/src/main/kotlin/com/alfresco/content/ZoneDateTimeExt.kt b/common/src/main/kotlin/com/alfresco/content/ZoneDateTimeExt.kt index c3591cce7..bfb2613dc 100644 --- a/common/src/main/kotlin/com/alfresco/content/ZoneDateTimeExt.kt +++ b/common/src/main/kotlin/com/alfresco/content/ZoneDateTimeExt.kt @@ -30,7 +30,10 @@ fun String.parseDate(format: String): Date? { * @param format * @param date */ -fun Date.formatDate(format: String, date: Date?): String? { +fun Date.formatDate( + format: String, + date: Date?, +): String? { val formatter = SimpleDateFormat(format, Locale.ENGLISH) if (date != null) { return formatter.format(date) @@ -43,7 +46,10 @@ fun Date.formatDate(format: String, date: Date?): String? { * @param currentFormat * @param convertFormat */ -fun String.getFormattedDate(currentFormat: String, convertFormat: String): String { +fun String.getFormattedDate( + currentFormat: String, + convertFormat: String, +): String { val date = SimpleDateFormat(currentFormat, Locale.ENGLISH).parse(this) val formatter = SimpleDateFormat(convertFormat, Locale.getDefault()) if (date != null) { @@ -57,7 +63,10 @@ fun String.getFormattedDate(currentFormat: String, convertFormat: String): Strin * @param currentFormat * @param convertFormat */ -fun String.getLocalFormattedDate(currentFormat: String, convertFormat: String): String { +fun String.getLocalFormattedDate( + currentFormat: String, + convertFormat: String, +): String { val parserFormat = SimpleDateFormat(currentFormat, Locale.getDefault()) parserFormat.timeZone = TimeZone.getTimeZone("UTC") val date = parserFormat.parse(this) @@ -73,7 +82,10 @@ fun String.getLocalFormattedDate(currentFormat: String, convertFormat: String): * @param currentFormat * @param convertFormat */ -fun String.getLocalFormattedDate1(currentFormat: String, convertFormat: String): String { +fun String.getLocalFormattedDate1( + currentFormat: String, + convertFormat: String, +): String { val parserFormat = SimpleDateFormat(currentFormat, Locale.getDefault()) parserFormat.timeZone = TimeZone.getTimeZone("UTC") val date = parserFormat.parse(this) @@ -90,11 +102,14 @@ fun updateDateFormat(originalDateString: String?): String? { originalDateString ?: return null val (datePart, timePart) = originalDateString.split(" ", limit = 2) - val updatedDatePart = datePart.split("-").joinToString("-") { part -> - if (part.equals("MM", ignoreCase = true)) { - part // Keep "MM" as is - } else part.lowercase() // Convert "DD" and "YYYY" to lowercase - } + val updatedDatePart = + datePart.split("-").joinToString("-") { part -> + if (part.equals("MM", ignoreCase = true)) { + part // Keep "MM" as is + } else { + part.lowercase() // Convert "DD" and "YYYY" to lowercase + } + } return if (timePart.isNotEmpty()) "$updatedDatePart $timePart" else updatedDatePart } diff --git a/common/src/main/kotlin/com/alfresco/content/common/EntryListener.kt b/common/src/main/kotlin/com/alfresco/content/common/EntryListener.kt index 43b4c27a5..39a9af008 100644 --- a/common/src/main/kotlin/com/alfresco/content/common/EntryListener.kt +++ b/common/src/main/kotlin/com/alfresco/content/common/EntryListener.kt @@ -7,7 +7,6 @@ import com.alfresco.content.data.payloads.FieldsData * Mark as EntryListener interface */ interface EntryListener { - /** * It will get called once new entry created. */ @@ -19,5 +18,6 @@ interface EntryListener { fun onProcessStart(entries: List) {} fun onAttachFolder(entry: ParentEntry) {} + fun onAttachFiles(field: FieldsData) {} } diff --git a/common/src/main/kotlin/com/alfresco/content/common/SharedURLParser.kt b/common/src/main/kotlin/com/alfresco/content/common/SharedURLParser.kt index f5d0c090f..acd822c72 100644 --- a/common/src/main/kotlin/com/alfresco/content/common/SharedURLParser.kt +++ b/common/src/main/kotlin/com/alfresco/content/common/SharedURLParser.kt @@ -7,7 +7,6 @@ import java.net.URL import java.net.URLDecoder class SharedURLParser { - lateinit var session: Session init { @@ -23,7 +22,10 @@ class SharedURLParser { * String * isRemoteFolder (Boolean) */ - fun getEntryIdFromShareURL(url: String, isHyperLink: Boolean = false): Triple { + fun getEntryIdFromShareURL( + url: String, + isHyperLink: Boolean = false, + ): Triple { val extData = URLDecoder.decode(url, "UTF-8") val hostname = URL(session.baseUrl).host diff --git a/common/src/main/kotlin/com/alfresco/content/common/StringExt.kt b/common/src/main/kotlin/com/alfresco/content/common/StringExt.kt index 8d77f88cf..cfa1f1282 100644 --- a/common/src/main/kotlin/com/alfresco/content/common/StringExt.kt +++ b/common/src/main/kotlin/com/alfresco/content/common/StringExt.kt @@ -6,14 +6,15 @@ import java.util.regex.Pattern * It will return true if the string matches the email pattern otherwise false. */ fun String.isValidEmail(): Boolean { - val emailAddressPattern = Pattern.compile( - "[a-zA-Z0-9\\+\\.\\_\\%\\-\\+]{1,256}" + - "\\@" + - "[a-zA-Z0-9][a-zA-Z0-9\\-]{0,64}" + - "(" + - "\\." + - "[a-zA-Z0-9][a-zA-Z0-9\\-]{0,25}" + - ")+", - ) + val emailAddressPattern = + Pattern.compile( + "[a-zA-Z0-9\\+\\.\\_\\%\\-\\+]{1,256}" + + "\\@" + + "[a-zA-Z0-9][a-zA-Z0-9\\-]{0,64}" + + "(" + + "\\." + + "[a-zA-Z0-9][a-zA-Z0-9\\-]{0,25}" + + ")+", + ) return emailAddressPattern.matcher(this).matches() } diff --git a/common/src/main/kotlin/com/alfresco/content/common/TextViewExt.kt b/common/src/main/kotlin/com/alfresco/content/common/TextViewExt.kt index 1384860d2..291803a97 100644 --- a/common/src/main/kotlin/com/alfresco/content/common/TextViewExt.kt +++ b/common/src/main/kotlin/com/alfresco/content/common/TextViewExt.kt @@ -58,7 +58,10 @@ fun TextView.isEllipsized() = layout.text.toString() != text.toString() /** * It adds the read more text if line exceeds more than 4 lines */ -fun TextView.addTextViewPrefix(prefix: String, callback: TextViewCallback) { +fun TextView.addTextViewPrefix( + prefix: String, + callback: TextViewCallback, +) { if (layout == null) { return } @@ -89,7 +92,12 @@ fun TextView.addTextViewPrefix(prefix: String, callback: TextViewCallback) { } } -private fun getTruncatedText(textView: TextView, maxLineCount: Int, count: Int, prefix: String): String { +private fun getTruncatedText( + textView: TextView, + maxLineCount: Int, + count: Int, + prefix: String, +): String { var startCount = count val newString: StringBuilder = StringBuilder("") for (i in 0 until maxLineCount) { @@ -100,27 +108,33 @@ private fun getTruncatedText(textView: TextView, maxLineCount: Int, count: Int, if (lineEnd.minus(startCount) > prefix.length + 1) { newString.append(textView.text.subSequence(startCount, lineEnd - (prefix.length + 1)).toString().replace("\n", "")) newString.append(" $prefix") - } else newString.append("${textView.text.subSequence(startCount, lineEnd).toString().replace("\n", "")} $prefix") + } else { + newString.append("${textView.text.subSequence(startCount, lineEnd).toString().replace("\n", "")} $prefix") + } } startCount = lineEnd } return newString.toString() } -private fun addClickableSpan(context: Context, callback: TextViewCallback): ClickableSpan { +private fun addClickableSpan( + context: Context, + callback: TextViewCallback, +): ClickableSpan { val typedValue = TypedValue() context.theme.resolveAttribute(R.attr.colorControlNormal, typedValue, true) // Clickable Span will help us to make clickable a text - val clickableSpan = object : ClickableSpan() { - override fun onClick(textView: View) { - callback?.invoke(true) - } + val clickableSpan = + object : ClickableSpan() { + override fun onClick(textView: View) { + callback?.invoke(true) + } - override fun updateDrawState(ds: TextPaint) { - super.updateDrawState(ds) - ds.isUnderlineText = false - ds.color = typedValue.data + override fun updateDrawState(ds: TextPaint) { + super.updateDrawState(ds) + ds.isUnderlineText = false + ds.color = typedValue.data + } } - } return clickableSpan } diff --git a/common/src/main/kotlin/com/alfresco/content/network/ConnectivityTracker.kt b/common/src/main/kotlin/com/alfresco/content/network/ConnectivityTracker.kt index e36a7400d..b6fed305d 100644 --- a/common/src/main/kotlin/com/alfresco/content/network/ConnectivityTracker.kt +++ b/common/src/main/kotlin/com/alfresco/content/network/ConnectivityTracker.kt @@ -19,15 +19,16 @@ object ConnectivityTracker { if (isTracking) return val cm = context.getSystemService() - val networkCallback = object : ConnectivityManager.NetworkCallback() { - override fun onAvailable(network: Network) { - _networkAvailable.value = true - } + val networkCallback = + object : ConnectivityManager.NetworkCallback() { + override fun onAvailable(network: Network) { + _networkAvailable.value = true + } - override fun onLost(network: Network) { - _networkAvailable.value = false + override fun onLost(network: Network) { + _networkAvailable.value = false + } } - } cm?.registerDefaultNetworkCallback(networkCallback) isTracking = true } @@ -35,8 +36,7 @@ object ConnectivityTracker { /** * returns true if network is metered otherwise false */ - fun isActiveNetworkMetered(context: Context): Boolean = - context.getSystemService()?.isActiveNetworkMetered == true + fun isActiveNetworkMetered(context: Context): Boolean = context.getSystemService()?.isActiveNetworkMetered == true /** * returns true if network is active otherwise false diff --git a/common/src/main/res/values-de/strings.xml b/common/src/main/res/values-de/strings.xml index d5f8e504f..2b981be6d 100755 --- a/common/src/main/res/values-de/strings.xml +++ b/common/src/main/res/values-de/strings.xml @@ -44,4 +44,5 @@ Das Hochladen von Dateien ist in Bearbeitung. Tippen Sie auf Bestätigen, um ohne laufende Dateien fortzufahren. Keine angehängten Dateien Kein Name + Zurücksetzen diff --git a/common/src/main/res/values-es/strings.xml b/common/src/main/res/values-es/strings.xml index 00c42b100..d5b12f61d 100755 --- a/common/src/main/res/values-es/strings.xml +++ b/common/src/main/res/values-es/strings.xml @@ -44,4 +44,5 @@ La carga de ficheros está en curso. Toque Confirmar para continuar sin los ficheros en curso. No hay ficheros adjuntos No tiene ningún nombre + Reiniciar diff --git a/common/src/main/res/values-fr/strings.xml b/common/src/main/res/values-fr/strings.xml index fb5581c1f..943a9e570 100755 --- a/common/src/main/res/values-fr/strings.xml +++ b/common/src/main/res/values-fr/strings.xml @@ -44,4 +44,5 @@ Le téléchargement des fichiers est en cours. Tapez sur Confirmer pour continuer sans fichiers en cours. Aucun fichier joint Sans nom + Réinitialiser diff --git a/common/src/main/res/values-it/strings.xml b/common/src/main/res/values-it/strings.xml index c7b85aab3..19fb5c3f4 100755 --- a/common/src/main/res/values-it/strings.xml +++ b/common/src/main/res/values-it/strings.xml @@ -44,4 +44,5 @@ Il caricamento dei file è in corso. Toccare su Conferma per continuare senza questi file. Nessun file allegato Nessun nome + Reimposta diff --git a/common/src/main/res/values-night/colors.xml b/common/src/main/res/values-night/colors.xml index c4eaa7830..ca25be35a 100644 --- a/common/src/main/res/values-night/colors.xml +++ b/common/src/main/res/values-night/colors.xml @@ -9,4 +9,5 @@ #1FFFFFFF @android:color/white @android:color/white + #3D2A7DE1 diff --git a/common/src/main/res/values-nl/strings.xml b/common/src/main/res/values-nl/strings.xml index d9f0ce8a5..14a3f0a7b 100755 --- a/common/src/main/res/values-nl/strings.xml +++ b/common/src/main/res/values-nl/strings.xml @@ -44,4 +44,5 @@ Er worden bestanden geüpload. Tik om te bevestigen dat u wilt doorgaan zonder de bestanden in uitvoering. Geen bijgevoegde bestanden Geen naam + Opnieuw instellen diff --git a/common/src/main/res/values/colors.xml b/common/src/main/res/values/colors.xml index 76dffde4b..1976400e6 100644 --- a/common/src/main/res/values/colors.xml +++ b/common/src/main/res/values/colors.xml @@ -11,4 +11,5 @@ #212121 #1F212121 #262626 + #3D2A7DE1 diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml index 5e559f2c6..b17f959ba 100644 --- a/common/src/main/res/values/strings.xml +++ b/common/src/main/res/values/strings.xml @@ -44,4 +44,5 @@ Files uploading is in progress. Tap on Confirm to continue without in-progress files. No Attached Files No Name + Reset diff --git a/component/build.gradle b/component/build.gradle index fe94e69a8..6259410f7 100644 --- a/component/build.gradle +++ b/component/build.gradle @@ -3,7 +3,9 @@ plugins { id 'org.jetbrains.kotlin.android' id 'kotlin-android' id 'kotlin-parcelize' - id 'kotlin-kapt' +// id 'kotlin-kapt' + id 'com.google.devtools.ksp' + id 'org.jetbrains.kotlin.plugin.compose' version '2.0.20' } android { @@ -21,10 +23,6 @@ android { } } -kapt { - correctErrorTypes = true -} - dependencies { implementation project(':base') @@ -44,7 +42,7 @@ dependencies { implementation libs.mavericks implementation libs.epoxy.core - kapt libs.epoxy.processor + ksp libs.epoxy.processor implementation libs.ui implementation libs.activity.compose diff --git a/component/src/main/java/com/alfresco/content/component/ComponentBuilder.kt b/component/src/main/java/com/alfresco/content/component/ComponentBuilder.kt index bc6e46ee7..870dca6b3 100644 --- a/component/src/main/java/com/alfresco/content/component/ComponentBuilder.kt +++ b/component/src/main/java/com/alfresco/content/component/ComponentBuilder.kt @@ -20,34 +20,31 @@ data class ComponentBuilder( var onReset: ComponentResetCallback? = null, var onCancel: ComponentCancelCallback? = null, ) { - /** * Filter sheet apply callback */ - fun onApply(callback: ComponentApplyCallback?) = - apply { this.onApply = callback } + fun onApply(callback: ComponentApplyCallback?) = apply { this.onApply = callback } /** * Filter sheet reset callback */ - fun onReset(callback: ComponentResetCallback?) = - apply { this.onReset = callback } + fun onReset(callback: ComponentResetCallback?) = apply { this.onReset = callback } /** * Filter sheet cancel callback */ - fun onCancel(callback: ComponentCancelCallback?) = - apply { this.onCancel = callback } + fun onCancel(callback: ComponentCancelCallback?) = apply { this.onCancel = callback } /** * Filter sheet show method */ fun show() { - val fragmentManager = when (context) { - is AppCompatActivity -> context.supportFragmentManager - is Fragment -> context.childFragmentManager - else -> throw IllegalArgumentException() - } + val fragmentManager = + when (context) { + is AppCompatActivity -> context.supportFragmentManager + is Fragment -> context.childFragmentManager + else -> throw IllegalArgumentException() + } ComponentSheet().apply { arguments = bundleOf(Mavericks.KEY_ARG to componentData) diff --git a/component/src/main/java/com/alfresco/content/component/ComponentData.kt b/component/src/main/java/com/alfresco/content/component/ComponentData.kt index 11e132f3b..b8a1b59b0 100644 --- a/component/src/main/java/com/alfresco/content/component/ComponentData.kt +++ b/component/src/main/java/com/alfresco/content/component/ComponentData.kt @@ -26,14 +26,17 @@ data class ComponentData( val selectedQueryMap: Map = mapOf(), ) : Parcelable { companion object { - /** * update the ComponentData obj after getting the result (name and query) from filters * @param category * @param name * @param query */ - fun with(category: CategoriesItem?, name: String, query: String): ComponentData { + fun with( + category: CategoriesItem?, + name: String, + query: String, + ): ComponentData { return ComponentData( id = category?.id ?: "", name = category?.name ?: "", @@ -51,13 +54,18 @@ data class ComponentData( * @param name * @param query */ - fun with(fieldsData: FieldsData, name: String, query: String): ComponentData { + fun with( + fieldsData: FieldsData, + name: String, + query: String, + ): ComponentData { return ComponentData( id = fieldsData.id, name = fieldsData.name, selector = ComponentType.DROPDOWN_RADIO.value, - options = fieldsData.options.filter { it.id != "empty" } - .map { ComponentOptions.withProcess(it) }, + options = + fieldsData.options.filter { it.id != "empty" } + .map { ComponentOptions.withProcess(it) }, selectedName = name, selectedQuery = query, ) @@ -69,7 +77,11 @@ data class ComponentData( * @param name * @param query */ - fun with(outcomes: List, name: String, query: String): ComponentData { + fun with( + outcomes: List, + name: String, + query: String, + ): ComponentData { return ComponentData( selector = ComponentType.PROCESS_ACTION.value, options = outcomes.map { ComponentOptions.withProcess(it) }, @@ -84,7 +96,11 @@ data class ComponentData( * @param name * @param query */ - fun with(facets: Facets?, name: String, query: String): ComponentData { + fun with( + facets: Facets?, + name: String, + query: String, + ): ComponentData { return ComponentData( id = facets?.hashCode().toString(), name = facets?.label ?: "", @@ -119,7 +135,11 @@ data class ComponentData( * @param name * @param query */ - fun with(componentData: ComponentData?, name: String, query: String): ComponentData { + fun with( + componentData: ComponentData?, + name: String, + query: String, + ): ComponentData { return ComponentData( id = componentData?.id, name = componentData?.name, @@ -165,8 +185,9 @@ data class ComponentData( return ComponentData( name = "title_status", selector = ComponentType.RADIO.value, - options = taskEntry.statusOption.filter { it.id != "empty" } - .map { ComponentOptions.withTaskStatus(it) }, + options = + taskEntry.statusOption.filter { it.id != "empty" } + .map { ComponentOptions.withTaskStatus(it) }, selectedName = taskEntry.taskFormStatus ?: "", selectedQuery = taskEntry.taskFormStatus ?: "", ) diff --git a/component/src/main/java/com/alfresco/content/component/ComponentOptions.kt b/component/src/main/java/com/alfresco/content/component/ComponentOptions.kt index 4d9f9a27b..1e6376aa8 100644 --- a/component/src/main/java/com/alfresco/content/component/ComponentOptions.kt +++ b/component/src/main/java/com/alfresco/content/component/ComponentOptions.kt @@ -18,7 +18,6 @@ data class ComponentOptions( val count: Int = 0, ) : Parcelable { companion object { - /** * return the updated ComponentOptions obj by using OptionsModel obj * @param optionsModel @@ -65,7 +64,6 @@ data class ComponentOptions( label = options.name ?: "", query = options.value ?: "", default = options.default ?: false, - ) } @@ -78,7 +76,6 @@ data class ComponentOptions( label = options.name, query = options.id, default = options.default, - ) } } diff --git a/component/src/main/java/com/alfresco/content/component/ComponentSheet.kt b/component/src/main/java/com/alfresco/content/component/ComponentSheet.kt index 8741d6361..ca9e899cb 100644 --- a/component/src/main/java/com/alfresco/content/component/ComponentSheet.kt +++ b/component/src/main/java/com/alfresco/content/component/ComponentSheet.kt @@ -27,7 +27,6 @@ import kotlin.coroutines.suspendCoroutine * Marked as ComponentSheet class */ class ComponentSheet : BottomSheetDialogFragment(), MavericksView { - internal val viewModel: ComponentViewModel by fragmentViewModel() lateinit var binding: SheetComponentFilterBinding @@ -44,120 +43,191 @@ class ComponentSheet : BottomSheetDialogFragment(), MavericksView { val minVisibleItem = 10 private val textFileSize = "search.facet_fields.size" - val nameInputTextWatcher = object : TextWatcher { - override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { - // no-op - } + val nameInputTextWatcher = + object : TextWatcher { + override fun beforeTextChanged( + s: CharSequence?, + start: Int, + count: Int, + after: Int, + ) { + // no-op + } - override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { - // no-op - } + override fun onTextChanged( + s: CharSequence?, + start: Int, + before: Int, + count: Int, + ) { + // no-op + } - override fun afterTextChanged(s: Editable?) { - viewModel.updateSingleComponentData(s.toString()) + override fun afterTextChanged(s: Editable?) { + viewModel.updateSingleComponentData(s.toString()) + } } - } - val fromInputTextWatcher = object : TextWatcher { - override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { - // no-op - } + val fromInputTextWatcher = + object : TextWatcher { + override fun beforeTextChanged( + s: CharSequence?, + start: Int, + count: Int, + after: Int, + ) { + // no-op + } - override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { - // no-op - } + override fun onTextChanged( + s: CharSequence?, + start: Int, + before: Int, + count: Int, + ) { + // no-op + } - override fun afterTextChanged(s: Editable?) { - viewModel.fromDate = s.toString() - if (s.toString().isNotEmpty()) { - binding.dateRangeComponent.fromInputLayout.error = null - binding.dateRangeComponent.fromInputLayout.isErrorEnabled = false + override fun afterTextChanged(s: Editable?) { + viewModel.fromDate = s.toString() + if (s.toString().isNotEmpty()) { + binding.dateRangeComponent.fromInputLayout.error = null + binding.dateRangeComponent.fromInputLayout.isErrorEnabled = false + } } } - } - val toInputTextWatcher = object : TextWatcher { - override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { - // no-op - } + val toInputTextWatcher = + object : TextWatcher { + override fun beforeTextChanged( + s: CharSequence?, + start: Int, + count: Int, + after: Int, + ) { + // no-op + } - override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { - // no-op - } + override fun onTextChanged( + s: CharSequence?, + start: Int, + before: Int, + count: Int, + ) { + // no-op + } - override fun afterTextChanged(s: Editable?) { - viewModel.toDate = s.toString() - if (s.toString().isNotEmpty()) { - binding.dateRangeComponent.toInputLayout.error = null - binding.dateRangeComponent.toInputLayout.isErrorEnabled = false + override fun afterTextChanged(s: Editable?) { + viewModel.toDate = s.toString() + if (s.toString().isNotEmpty()) { + binding.dateRangeComponent.toInputLayout.error = null + binding.dateRangeComponent.toInputLayout.isErrorEnabled = false + } } } - } - val searchInputTextWatcher = object : TextWatcher { - override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { - // no-op - } + val searchInputTextWatcher = + object : TextWatcher { + override fun beforeTextChanged( + s: CharSequence?, + start: Int, + count: Int, + after: Int, + ) { + // no-op + } - override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { - // no-op - } + override fun onTextChanged( + s: CharSequence?, + start: Int, + before: Int, + count: Int, + ) { + // no-op + } - override fun afterTextChanged(s: Editable?) { - viewModel.searchQuery = s.toString().trim().replace("\\s+".toRegex(), " ").trim() - viewModel.searchBucket(searchText = viewModel.searchQuery) + override fun afterTextChanged(s: Editable?) { + viewModel.searchQuery = s.toString().trim().replace("\\s+".toRegex(), " ").trim() + viewModel.searchBucket(searchText = viewModel.searchQuery) + } } - } - val numberFromInputTextWatcher = object : TextWatcher { - override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { - // no-op - } + val numberFromInputTextWatcher = + object : TextWatcher { + override fun beforeTextChanged( + s: CharSequence?, + start: Int, + count: Int, + after: Int, + ) { + // no-op + } - override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { - // no-op - } + override fun onTextChanged( + s: CharSequence?, + start: Int, + before: Int, + count: Int, + ) { + // no-op + } - override fun afterTextChanged(s: Editable?) { - val min = s.toString().trim() - val valid = viewModel.isFromValueValid(min) - binding.numberRangeComponent.numberRangeError.visibility = when { - !valid -> View.VISIBLE - else -> View.GONE + override fun afterTextChanged(s: Editable?) { + val min = s.toString().trim() + val valid = viewModel.isFromValueValid(min) + binding.numberRangeComponent.numberRangeError.visibility = + when { + !valid -> View.VISIBLE + else -> View.GONE + } + viewModel.fromValue = min + viewModel.updateFormatNumberRange(false) } - viewModel.fromValue = min - viewModel.updateFormatNumberRange(false) } - } - val numberToInputTextWatcher = object : TextWatcher { - override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { - // no-op - } + val numberToInputTextWatcher = + object : TextWatcher { + override fun beforeTextChanged( + s: CharSequence?, + start: Int, + count: Int, + after: Int, + ) { + // no-op + } - override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { - // no-op - } + override fun onTextChanged( + s: CharSequence?, + start: Int, + before: Int, + count: Int, + ) { + // no-op + } - override fun afterTextChanged(s: Editable?) { - val max = s.toString().trim() - val valid = viewModel.isToValueValid(max) - binding.numberRangeComponent.numberRangeError.visibility = when { - !valid -> View.VISIBLE - else -> View.GONE + override fun afterTextChanged(s: Editable?) { + val max = s.toString().trim() + val valid = viewModel.isToValueValid(max) + binding.numberRangeComponent.numberRangeError.visibility = + when { + !valid -> View.VISIBLE + else -> View.GONE + } + viewModel.toValue = max + viewModel.updateFormatNumberRange(false) } - viewModel.toValue = max - viewModel.updateFormatNumberRange(false) } - } - val sliderChangeListener = Slider.OnChangeListener { _, value, _ -> - val sliderValue = value.toInt().toString() - if (viewModel.isToValueValid(sliderValue)) { - viewModel.toValue = sliderValue - } else viewModel.toValue = "" - viewModel.updateFormatNumberRange(true) - } + val sliderChangeListener = + Slider.OnChangeListener { _, value, _ -> + val sliderValue = value.toInt().toString() + if (viewModel.isToValueValid(sliderValue)) { + viewModel.toValue = sliderValue + } else { + viewModel.toValue = "" + } + viewModel.updateFormatNumberRange(true) + } override fun onCreateView( inflater: LayoutInflater, @@ -168,9 +238,14 @@ class ComponentSheet : BottomSheetDialogFragment(), MavericksView { return binding.root } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { super.onViewCreated(view, savedInstanceState) - dialog?.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE or WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE) + dialog?.window?.setSoftInputMode( + WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE or WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE, + ) dialog?.setOnCancelListener { onCancel?.invoke() } @@ -193,67 +268,69 @@ class ComponentSheet : BottomSheetDialogFragment(), MavericksView { } } - private fun setupComponents() = withState(viewModel) { state -> - binding.parentView.removeAllViews() + private fun setupComponents() = + withState(viewModel) { state -> + binding.parentView.removeAllViews() - if (state.parent?.selector != ComponentType.PROCESS_ACTION.value) { - binding.parentView.addView(binding.topView) - binding.parentView.addView(binding.separator) - } + if (state.parent?.selector != ComponentType.PROCESS_ACTION.value) { + binding.parentView.addView(binding.topView) + binding.parentView.addView(binding.separator) + } - when { - (state.parent?.selector == ComponentType.DROPDOWN_RADIO.value) || - (state.parent?.selector == ComponentType.PROCESS_ACTION.value) -> { + when { + (state.parent?.selector == ComponentType.DROPDOWN_RADIO.value) || + (state.parent?.selector == ComponentType.PROCESS_ACTION.value) -> { + } + + else -> { + binding.bottomSeparator.visibility = View.VISIBLE + binding.bottomView.visibility = View.VISIBLE + } } - else -> { - binding.bottomSeparator.visibility = View.VISIBLE - binding.bottomView.visibility = View.VISIBLE + val replacedString = state.parent?.name?.replace(" ", ".") ?: "" + val localizedName = requireContext().getLocalizedName(replacedString) + + if (localizedName == replacedString) { + binding.title.text = state.parent?.name ?: "" + } else if (state.parent?.name?.lowercase().equals(textFileSize)) { + binding.title.text = requireContext().getString(R.string.size_end_kb, localizedName) + } else { + binding.title.text = localizedName } - } - val replacedString = state.parent?.name?.replace(" ", ".") ?: "" - val localizedName = requireContext().getLocalizedName(replacedString) - - if (localizedName == replacedString) { - binding.title.text = state.parent?.name ?: "" - } else if (state.parent?.name?.lowercase().equals(textFileSize)) { - binding.title.text = requireContext().getString(R.string.size_end_kb, localizedName) - } else - binding.title.text = localizedName - - when (val selector = state.parent?.selector) { - ComponentType.TEXT.value -> setupSingleInputTextComponent(state) - ComponentType.TASK_PROCESS_PRIORITY.value -> setupTaskPriorityComponent(state) - ComponentType.VIEW_TEXT.value -> setupTextComponent(state) - ComponentType.CHECK_LIST.value -> setupCheckListComponent(viewModel) - ComponentType.RADIO.value, ComponentType.DROPDOWN_RADIO.value -> setupRadioListComponent(state, viewModel) - ComponentType.NUMBER_RANGE.value -> setupNumberRangeComponent(state, viewModel) - ComponentType.SLIDER.value -> setupSliderComponent(state, viewModel) - ComponentType.DATE_RANGE.value, ComponentType.DATE_RANGE_FUTURE.value -> { - setupDateRangeComponent(state, viewModel) - - binding.dateRangeComponent.fromInput.setOnClickListener { - if (!executedPicker) { - binding.dateRangeComponent.componentParent.isEnabled = false - executedPicker = true - showCalendar(true, (selector == ComponentType.DATE_RANGE_FUTURE.value)) + when (val selector = state.parent?.selector) { + ComponentType.TEXT.value -> setupSingleInputTextComponent(state) + ComponentType.TASK_PROCESS_PRIORITY.value -> setupTaskPriorityComponent(state) + ComponentType.VIEW_TEXT.value -> setupTextComponent(state) + ComponentType.CHECK_LIST.value -> setupCheckListComponent(viewModel) + ComponentType.RADIO.value, ComponentType.DROPDOWN_RADIO.value -> setupRadioListComponent(state, viewModel) + ComponentType.NUMBER_RANGE.value -> setupNumberRangeComponent(state, viewModel) + ComponentType.SLIDER.value -> setupSliderComponent(state, viewModel) + ComponentType.DATE_RANGE.value, ComponentType.DATE_RANGE_FUTURE.value -> { + setupDateRangeComponent(state, viewModel) + + binding.dateRangeComponent.fromInput.setOnClickListener { + if (!executedPicker) { + binding.dateRangeComponent.componentParent.isEnabled = false + executedPicker = true + showCalendar(true, (selector == ComponentType.DATE_RANGE_FUTURE.value)) + } } - } - binding.dateRangeComponent.toInput.setOnClickListener { - if (!executedPicker) { - binding.dateRangeComponent.componentParent.isEnabled = false - executedPicker = true - showCalendar(false, (selector == ComponentType.DATE_RANGE_FUTURE.value)) + binding.dateRangeComponent.toInput.setOnClickListener { + if (!executedPicker) { + binding.dateRangeComponent.componentParent.isEnabled = false + executedPicker = true + showCalendar(false, (selector == ComponentType.DATE_RANGE_FUTURE.value)) + } } } - } - ComponentType.FACETS.value -> setupFacetComponent(state, viewModel) - ComponentType.PROCESS_ACTION.value -> setupProcessActionsComponent(state, viewModel) + ComponentType.FACETS.value -> setupFacetComponent(state, viewModel) + ComponentType.PROCESS_ACTION.value -> setupProcessActionsComponent(state, viewModel) + } } - } private fun setListeners() { binding.applyButton.setOnClickListener { @@ -315,129 +392,140 @@ class ComponentSheet : BottomSheetDialogFragment(), MavericksView { } } - override fun invalidate() = withState(viewModel) { state -> - when (state.parent?.selector) { - ComponentType.CHECK_LIST.value -> { - epoxyCheckListController.requestModelBuild() - } + override fun invalidate() = + withState(viewModel) { state -> + when (state.parent?.selector) { + ComponentType.CHECK_LIST.value -> { + epoxyCheckListController.requestModelBuild() + } - ComponentType.RADIO.value, ComponentType.DROPDOWN_RADIO.value -> { - epoxyRadioListController.requestModelBuild() - } + ComponentType.RADIO.value, ComponentType.DROPDOWN_RADIO.value -> { + epoxyRadioListController.requestModelBuild() + } - ComponentType.FACETS.value -> { - epoxyCheckFacetListController.requestModelBuild() - } + ComponentType.FACETS.value -> { + epoxyCheckFacetListController.requestModelBuild() + } - ComponentType.PROCESS_ACTION.value -> { - epoxyActionListController.requestModelBuild() + ComponentType.PROCESS_ACTION.value -> { + epoxyActionListController.requestModelBuild() + } } } - } - private fun epoxyCheckListController() = simpleController(viewModel) { state -> - if (state.parent?.options?.isNotEmpty() == true) { - state.parent.options.forEach { option -> - listViewCheckRow { - id(option.hashCode()) - data(option) - optionSelected(viewModel.isOptionSelected(state, option)) - clickListener { model, _, _, _ -> - viewModel.updateMultipleComponentData( - model.data().label, - model.data().value, - ) + private fun epoxyCheckListController() = + simpleController(viewModel) { state -> + if (state.parent?.options?.isNotEmpty() == true) { + state.parent.options.forEach { option -> + listViewCheckRow { + id(option.hashCode()) + data(option) + optionSelected(viewModel.isOptionSelected(state, option)) + clickListener { model, _, _, _ -> + viewModel.updateMultipleComponentData( + model.data().label, + model.data().value, + ) + } } } } } - } - private fun epoxyRadioListController() = simpleController(viewModel) { state -> - if (state.parent?.options?.isNotEmpty() == true) { - state.parent.options.forEach { option -> - listViewRadioRow { - id(option.hashCode()) - data(option) - optionSelected(viewModel.isOptionSelected(state, option)) - clickListener { model, _, _, _ -> - if (state.parent.selector == ComponentType.DROPDOWN_RADIO.value) { - onApply?.invoke( - requireContext().getLocalizedName(model.data().label), - model.data().query, - mapOf(), - ) - dismiss() - } else { - viewModel.updateSingleComponentData( - requireContext().getLocalizedName(model.data().label), - model.data().query, - ) + private fun epoxyRadioListController() = + simpleController(viewModel) { state -> + if (state.parent?.options?.isNotEmpty() == true) { + state.parent.options.forEach { option -> + listViewRadioRow { + id(option.hashCode()) + data(option) + optionSelected(viewModel.isOptionSelected(state, option)) + clickListener { model, _, _, _ -> + if (state.parent.selector == ComponentType.DROPDOWN_RADIO.value) { + onApply?.invoke( + requireContext().getLocalizedName(model.data().label), + model.data().query, + mapOf(), + ) + dismiss() + } else { + viewModel.updateSingleComponentData( + requireContext().getLocalizedName(model.data().label), + model.data().query, + ) + } } } } } } - } - private fun epoxyCheckFacetListController() = simpleController(viewModel) { state -> - var listBucket: List? = null - when (state.parent?.selector) { - ComponentType.FACETS.value -> { - listBucket = if (viewModel.searchQuery.isNotEmpty()) { - viewModel.searchComponentList - } else - state.parent.options + private fun epoxyCheckFacetListController() = + simpleController(viewModel) { state -> + var listBucket: List? = null + when (state.parent?.selector) { + ComponentType.FACETS.value -> { + listBucket = + if (viewModel.searchQuery.isNotEmpty()) { + viewModel.searchComponentList + } else { + state.parent.options + } + } } - } - if (listBucket?.isNotEmpty() == true) { - listBucket.forEach { bucket -> - listViewFacetCheckRow { - id(bucket.hashCode()) - data(bucket) - optionSelected(viewModel.isOptionSelected(state, bucket)) - clickListener { model, _, _, _ -> - viewModel.updateMultipleComponentData( - requireContext().getLocalizedName(model.data().label), - model.data().query, - ) + if (listBucket?.isNotEmpty() == true) { + listBucket.forEach { bucket -> + listViewFacetCheckRow { + id(bucket.hashCode()) + data(bucket) + optionSelected(viewModel.isOptionSelected(state, bucket)) + clickListener { model, _, _, _ -> + viewModel.updateMultipleComponentData( + requireContext().getLocalizedName(model.data().label), + model.data().query, + ) + } } } } } - } - private fun epoxyActionListController() = simpleController(viewModel) { state -> + private fun epoxyActionListController() = + simpleController(viewModel) { state -> - if (state.parent?.options?.isNotEmpty() == true) { - state.parent.options.forEach { option -> - listViewActionsRow { - id(option.hashCode()) - data(option) - clickListener { model, _, _, _ -> - onApply?.invoke(model.data().label, model.data().query, mapOf()) - dismiss() + if (state.parent?.options?.isNotEmpty() == true) { + state.parent.options.forEach { option -> + listViewActionsRow { + id(option.hashCode()) + data(option) + clickListener { model, _, _, _ -> + onApply?.invoke(model.data().label, model.data().query, mapOf()) + dismiss() + } } } } } - } - private fun showCalendar(isFrom: Boolean, isFutureDate: Boolean) { + private fun showCalendar( + isFrom: Boolean, + isFutureDate: Boolean, + ) { viewLifecycleOwner.lifecycleScope.launch { - val result = suspendCoroutine { - DatePickerBuilder( - context = requireContext(), - fromDate = viewModel.fromDate, - toDate = viewModel.toDate, - isFrom = isFrom, - isFutureDate = isFutureDate, - dateFormat = viewModel.dateFormat, - ) - .onSuccess { date -> it.resume(date) } - .onFailure { it.resume(null) } - .show() - } + val result = + suspendCoroutine { + DatePickerBuilder( + context = requireContext(), + fromDate = viewModel.fromDate, + toDate = viewModel.toDate, + isFrom = isFrom, + isFutureDate = isFutureDate, + dateFormat = viewModel.dateFormat, + ) + .onSuccess { date -> it.resume(date) } + .onFailure { it.resume(null) } + .show() + } executedPicker = false binding.dateRangeComponent.componentParent.isEnabled = true result?.let { date -> diff --git a/component/src/main/java/com/alfresco/content/component/ComponentSheetExtension.kt b/component/src/main/java/com/alfresco/content/component/ComponentSheetExtension.kt index 4d625a060..e493f0f80 100644 --- a/component/src/main/java/com/alfresco/content/component/ComponentSheetExtension.kt +++ b/component/src/main/java/com/alfresco/content/component/ComponentSheetExtension.kt @@ -45,7 +45,10 @@ fun ComponentSheet.setupCheckListComponent(viewModel: ComponentViewModel) { * @param state * @param viewModel */ -fun ComponentSheet.setupRadioListComponent(state: ComponentState, viewModel: ComponentViewModel) { +fun ComponentSheet.setupRadioListComponent( + state: ComponentState, + viewModel: ComponentViewModel, +) { binding.parentView.addView(binding.frameRadio) viewModel.buildSingleDataModel() if (state.parent?.selectedName?.isEmpty() == true) { @@ -60,7 +63,10 @@ fun ComponentSheet.setupRadioListComponent(state: ComponentState, viewModel: Com * @param state * @param viewModel */ -fun ComponentSheet.setupNumberRangeComponent(state: ComponentState, viewModel: ComponentViewModel) { +fun ComponentSheet.setupNumberRangeComponent( + state: ComponentState, + viewModel: ComponentViewModel, +) { binding.parentView.addView(binding.frameNumberRange) binding.title.text = requireContext().getString(R.string.size_end_kb, binding.title.text.toString()) binding.numberRangeComponent.componentParent.visibility = View.VISIBLE @@ -83,7 +89,10 @@ fun ComponentSheet.setupNumberRangeComponent(state: ComponentState, viewModel: C * @param state * @param viewModel */ -fun ComponentSheet.setupSliderComponent(state: ComponentState, viewModel: ComponentViewModel) { +fun ComponentSheet.setupSliderComponent( + state: ComponentState, + viewModel: ComponentViewModel, +) { binding.parentView.addView(binding.frameSlider) binding.sliderComponent.componentParent.visibility = View.VISIBLE @@ -113,7 +122,10 @@ fun ComponentSheet.setupSliderComponent(state: ComponentState, viewModel: Compon * @param state * @param viewModel */ -fun ComponentSheet.setupDateRangeComponent(state: ComponentState, viewModel: ComponentViewModel) { +fun ComponentSheet.setupDateRangeComponent( + state: ComponentState, + viewModel: ComponentViewModel, +) { binding.parentView.addView(binding.frameDateRange) binding.dateRangeComponent.componentParent.visibility = View.VISIBLE @@ -140,13 +152,25 @@ fun ComponentSheet.setupDateRangeComponent(state: ComponentState, viewModel: Com if (state.parent?.selector == ComponentType.DATE_RANGE_FUTURE.value) { if (state.parent.selectedQueryMap.containsKey(DUE_BEFORE)) { val dueBeforeArray = state.parent.selectedQueryMap[DUE_BEFORE]!!.split("-") - viewModel.toDate = getString(R.string.date_format_new, dueBeforeArray[0].trim(), dueBeforeArray[1].trim(), dueBeforeArray[2].trim()) + viewModel.toDate = + getString( + R.string.date_format_new, + dueBeforeArray[0].trim(), + dueBeforeArray[1].trim(), + dueBeforeArray[2].trim(), + ) binding.dateRangeComponent.toInput.setText(viewModel.toDate) } if (state.parent.selectedQueryMap.containsKey(DUE_AFTER)) { val dueAfterArray = state.parent.selectedQueryMap[DUE_AFTER]!!.split("-") - viewModel.fromDate = getString(R.string.date_format_new, dueAfterArray[0].trim(), dueAfterArray[1].trim(), dueAfterArray[2].trim()) + viewModel.fromDate = + getString( + R.string.date_format_new, + dueAfterArray[0].trim(), + dueAfterArray[1].trim(), + dueAfterArray[2].trim(), + ) binding.dateRangeComponent.fromInput.setText(viewModel.fromDate) } } @@ -168,7 +192,10 @@ fun ComponentSheet.setupDateRangeComponent(state: ComponentState, viewModel: Com * @param viewModel */ @SuppressLint("ClickableViewAccessibility") -fun ComponentSheet.setupFacetComponent(state: ComponentState, viewModel: ComponentViewModel) { +fun ComponentSheet.setupFacetComponent( + state: ComponentState, + viewModel: ComponentViewModel, +) { viewModel.buildCheckListModel() state.parent?.options?.let { if (it.size > minVisibleItem) { @@ -189,7 +216,10 @@ fun ComponentSheet.setupFacetComponent(state: ComponentState, viewModel: Compone * @param viewModel */ @SuppressLint("ClickableViewAccessibility") -fun ComponentSheet.setupProcessActionsComponent(state: ComponentState, viewModel: ComponentViewModel) { +fun ComponentSheet.setupProcessActionsComponent( + state: ComponentState, + viewModel: ComponentViewModel, +) { viewModel.buildCheckListModel() binding.parentView.addView(binding.frameActions) @@ -265,8 +295,16 @@ fun ComponentSheet.setupTaskPriorityComponent(state: ComponentState) { binding.bottomSeparator.visibility = View.GONE } -private fun getRecyclerviewLayoutParams(context: Context, minVisibleItem: Int): LinearLayout.LayoutParams { - val calculatedHeight = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, minVisibleItem * 48f, context.resources.displayMetrics).toInt() +private fun getRecyclerviewLayoutParams( + context: Context, + minVisibleItem: Int, +): LinearLayout.LayoutParams { + val calculatedHeight = + TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, + minVisibleItem * 48f, + context.resources.displayMetrics, + ).toInt() return LinearLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, calculatedHeight) } diff --git a/component/src/main/java/com/alfresco/content/component/ComponentType.kt b/component/src/main/java/com/alfresco/content/component/ComponentType.kt index dcceec73c..c93c459a0 100644 --- a/component/src/main/java/com/alfresco/content/component/ComponentType.kt +++ b/component/src/main/java/com/alfresco/content/component/ComponentType.kt @@ -4,7 +4,6 @@ package com.alfresco.content.component * Marked as ComponentType class */ enum class ComponentType(val value: String) { - TEXT("text"), VIEW_TEXT("view-text"), CHECK_LIST("check-list"), diff --git a/component/src/main/java/com/alfresco/content/component/ComponentViewModel.kt b/component/src/main/java/com/alfresco/content/component/ComponentViewModel.kt index 085028c57..d1a48ba3a 100644 --- a/component/src/main/java/com/alfresco/content/component/ComponentViewModel.kt +++ b/component/src/main/java/com/alfresco/content/component/ComponentViewModel.kt @@ -25,7 +25,6 @@ class ComponentViewModel( val context: Context, stateChipCreate: ComponentState, ) : MavericksViewModel(stateChipCreate) { - var listOptionsData: MutableList = mutableListOf() private var isFacetComponent: Boolean = false var onSearchComplete: ((List) -> Unit)? = null @@ -43,80 +42,89 @@ class ComponentViewModel( updateComponentType() } - private fun updateComponentType() = withState { - if (it.parent?.selector == ComponentType.FACETS.value - ) { - delimiters = " OR " - isFacetComponent = true - } else { - delimiters = " ${it.parent?.properties?.operator} " - isFacetComponent = false + private fun updateComponentType() = + withState { + if (it.parent?.selector == ComponentType.FACETS.value + ) { + delimiters = " OR " + isFacetComponent = true + } else { + delimiters = " ${it.parent?.properties?.operator} " + isFacetComponent = false + } } - } /** * update the value for number range */ - fun updateFormatNumberRange(isSlider: Boolean) = withState { - if ((fromValue.isNotEmpty() && toValue.isNotEmpty()) && fromValue.toInt() < toValue.toInt()) { - val nameFormat = if (isSlider) { - toValue - } else - context.getLocalizedName("$fromValue - $toValue") - val queryFormat = "${it.parent?.properties?.field}:[${fromValue.kBToByte()} TO ${toValue.kBToByte()}]" - updateSingleComponentData(nameFormat, queryFormat) - } else { - updateSingleComponentData("", "") + fun updateFormatNumberRange(isSlider: Boolean) = + withState { + if ((fromValue.isNotEmpty() && toValue.isNotEmpty()) && fromValue.toInt() < toValue.toInt()) { + val nameFormat = + if (isSlider) { + toValue + } else { + context.getLocalizedName("$fromValue - $toValue") + } + val queryFormat = "${it.parent?.properties?.field}:[${fromValue.kBToByte()} TO ${toValue.kBToByte()}]" + updateSingleComponentData(nameFormat, queryFormat) + } else { + updateSingleComponentData("", "") + } } - } /** * update the value for date range */ - fun updateFormatDateRange() = withState { state -> - - when (state.parent?.selector) { - ComponentType.DATE_RANGE.value -> { - if ((fromDate.isNotEmpty() && toDate.isNotEmpty())) { - val dateFormat = "$fromDate - $toDate" - - val queryFormat = "${state.parent.properties?.field}:['${fromDate.getFormattedDate(DATE_FORMAT_2, DATE_FORMAT_1)}' TO '${toDate.getFormattedDate(DATE_FORMAT_2, DATE_FORMAT_1)}']" - updateSingleComponentData(dateFormat, queryFormat) - } else { - updateSingleComponentData("", "") - } - } - - ComponentType.DATE_RANGE_FUTURE.value -> { - var dates: Map = mapOf() - var selectedDateName = "" - - if (fromDate.isNotEmpty() && toDate.isNotEmpty()) { - selectedDateName = "$fromDate - $toDate" - dates = mapOf(DUE_AFTER to fromDate, DUE_BEFORE to toDate) - } else if (fromDate.isNotEmpty() && toDate.isEmpty()) { - selectedDateName = fromDate - dates = mapOf(DUE_AFTER to fromDate) - } else if (fromDate.isEmpty() && toDate.isNotEmpty()) { - selectedDateName = toDate - dates = mapOf(DUE_BEFORE to toDate) + fun updateFormatDateRange() = + withState { state -> + + when (state.parent?.selector) { + ComponentType.DATE_RANGE.value -> { + if ((fromDate.isNotEmpty() && toDate.isNotEmpty())) { + val dateFormat = "$fromDate - $toDate" + + val queryFormat = "${state.parent.properties?.field}:['${fromDate.getFormattedDate( + DATE_FORMAT_2, + DATE_FORMAT_1, + )}' TO '${toDate.getFormattedDate(DATE_FORMAT_2, DATE_FORMAT_1)}']" + updateSingleComponentData(dateFormat, queryFormat) + } else { + updateSingleComponentData("", "") + } } - setState { - copy(parent = ComponentData.with(parent, selectedDateName, dates)) + ComponentType.DATE_RANGE_FUTURE.value -> { + var dates: Map = mapOf() + var selectedDateName = "" + + if (fromDate.isNotEmpty() && toDate.isNotEmpty()) { + selectedDateName = "$fromDate - $toDate" + dates = mapOf(DUE_AFTER to fromDate, DUE_BEFORE to toDate) + } else if (fromDate.isNotEmpty() && toDate.isEmpty()) { + selectedDateName = fromDate + dates = mapOf(DUE_AFTER to fromDate) + } else if (fromDate.isEmpty() && toDate.isNotEmpty()) { + selectedDateName = toDate + dates = mapOf(DUE_BEFORE to toDate) + } + + setState { + copy(parent = ComponentData.with(parent, selectedDateName, dates)) + } } } } - } /** * build single value component data */ - fun buildSingleDataModel() = withState { state -> - if (state.parent?.selectedQuery?.isNotEmpty() == true) { - listOptionsData.add(ComponentMetaData(state.parent.selectedName, state.parent.selectedQuery)) + fun buildSingleDataModel() = + withState { state -> + if (state.parent?.selectedQuery?.isNotEmpty() == true) { + listOptionsData.add(ComponentMetaData(state.parent.selectedName, state.parent.selectedQuery)) + } } - } /** * update single selected component option (text) @@ -130,7 +138,10 @@ class ComponentViewModel( /** * update single selected component option(radio) */ - fun updateSingleComponentData(name: String, query: String) { + fun updateSingleComponentData( + name: String, + query: String, + ) { setState { copy(parent = ComponentData.with(parent, name, query)) } } @@ -147,25 +158,29 @@ class ComponentViewModel( /** * build check list model for query and name */ - fun buildCheckListModel() = withState { state -> - if (state.parent?.selectedQuery?.isNotEmpty() == true) { - if (state.parent.selectedQuery.contains(delimiters)) { - val arrayQuery = state.parent.selectedQuery.split(delimiters) - val arrayName = state.parent.selectedName.split(",") - - arrayQuery.forEachIndexed { index, query -> - listOptionsData.add(ComponentMetaData(arrayName[index], query)) + fun buildCheckListModel() = + withState { state -> + if (state.parent?.selectedQuery?.isNotEmpty() == true) { + if (state.parent.selectedQuery.contains(delimiters)) { + val arrayQuery = state.parent.selectedQuery.split(delimiters) + val arrayName = state.parent.selectedName.split(",") + + arrayQuery.forEachIndexed { index, query -> + listOptionsData.add(ComponentMetaData(arrayName[index], query)) + } + } else { + listOptionsData.add(ComponentMetaData(state.parent.selectedName, state.parent.selectedQuery)) } - } else { - listOptionsData.add(ComponentMetaData(state.parent.selectedName, state.parent.selectedQuery)) } } - } /** * update multiple component option (check list) */ - fun updateMultipleComponentData(name: String, query: String) { + fun updateMultipleComponentData( + name: String, + query: String, + ) { if (listOptionsData.find { it.query == query } == null) { listOptionsData.add(ComponentMetaData(name, query)) } else { @@ -182,18 +197,18 @@ class ComponentViewModel( /** * returns the search result from bucket list on the basis of searchText */ - fun searchBucket(searchText: String) = withState { state -> - when (state.parent?.selector) { - ComponentType.FACETS.value -> { - requireNotNull(state.parent.options) - searchComponentList = state.parent.options.filter { it.label.contains(searchText) } - onSearchComplete?.invoke(searchComponentList) + fun searchBucket(searchText: String) = + withState { state -> + when (state.parent?.selector) { + ComponentType.FACETS.value -> { + requireNotNull(state.parent.options) + searchComponentList = state.parent.options.filter { it.label.contains(searchText) } + onSearchComplete?.invoke(searchComponentList) + } } } - } companion object : MavericksViewModelFactory { - const val DUE_BEFORE = "dueBefore" const val DUE_AFTER = "dueAfter" diff --git a/component/src/main/java/com/alfresco/content/component/ComponentViewModelExt.kt b/component/src/main/java/com/alfresco/content/component/ComponentViewModelExt.kt index efbf7831e..cb904f80c 100644 --- a/component/src/main/java/com/alfresco/content/component/ComponentViewModelExt.kt +++ b/component/src/main/java/com/alfresco/content/component/ComponentViewModelExt.kt @@ -3,7 +3,10 @@ package com.alfresco.content.component /** * return true if the component is selected,otherwise false */ -fun ComponentViewModel.isOptionSelected(state: ComponentState, options: ComponentOptions): Boolean { +fun ComponentViewModel.isOptionSelected( + state: ComponentState, + options: ComponentOptions, +): Boolean { if (state.parent?.selectedQuery?.isEmpty() == true) { return options.default } @@ -31,8 +34,9 @@ fun ComponentViewModel.isToValueValid(to: String): Boolean { return if (fromValue.isEmpty()) { true - } else + } else { to.toLong() > fromValue.toLong() + } } /** @@ -45,6 +49,7 @@ fun ComponentViewModel.isFromValueValid(from: String): Boolean { return if (toValue.isEmpty()) { true - } else + } else { from.toLong() < toValue.toLong() + } } diff --git a/component/src/main/java/com/alfresco/content/component/DatePickerBuilder.kt b/component/src/main/java/com/alfresco/content/component/DatePickerBuilder.kt index 9b855f96b..aeed12243 100644 --- a/component/src/main/java/com/alfresco/content/component/DatePickerBuilder.kt +++ b/component/src/main/java/com/alfresco/content/component/DatePickerBuilder.kt @@ -36,7 +36,6 @@ data class DatePickerBuilder( var onFailure: DatePickerOnFailure? = null, var fieldsData: FieldsData? = null, ) { - private val dateFormatddMMMyy = "dd-MMM-yy" private val dateFormatddMMyyyy = "dd-MM-yyyy" private val addOneDay = 1000 * 60 * 60 * 24 @@ -50,49 +49,50 @@ data class DatePickerBuilder( /** * success callback */ - fun onSuccess(callback: DatePickerOnSuccess?) = - apply { this.onSuccess = callback } + fun onSuccess(callback: DatePickerOnSuccess?) = apply { this.onSuccess = callback } /** * failure callback */ - fun onFailure(callback: DatePickerOnFailure?) = - apply { this.onFailure = callback } + fun onFailure(callback: DatePickerOnFailure?) = apply { this.onFailure = callback } /** * show material date picker */ fun show() { - val fragmentManager = when (context) { - is AppCompatActivity -> context.supportFragmentManager - is Fragment -> context.childFragmentManager - else -> throw IllegalArgumentException() - } + val fragmentManager = + when (context) { + is AppCompatActivity -> context.supportFragmentManager + is Fragment -> context.childFragmentManager + else -> throw IllegalArgumentException() + } val constraintsBuilder = CalendarConstraints.Builder() constraintsBuilder.setValidator(CompositeDateValidator.allOf(getValidators(fieldsData))) - val datePicker = MaterialDatePicker.Builder.datePicker().apply { - if (fieldsData != null) { - setTitleText(fieldsData?.name) - } else { - if (isFrom) { - setTitleText(context.getString(R.string.hint_range_from_date)) + val datePicker = + MaterialDatePicker.Builder.datePicker().apply { + if (fieldsData != null) { + setTitleText(fieldsData?.name) } else { - setTitleText(context.getString(R.string.hint_range_to_date)) + if (isFrom) { + setTitleText(context.getString(R.string.hint_range_from_date)) + } else { + setTitleText(context.getString(R.string.hint_range_to_date)) + } } - } - setSelection(getSelectionDate()) - setCalendarConstraints(constraintsBuilder.build()) - }.build() + setSelection(getSelectionDate()) + setCalendarConstraints(constraintsBuilder.build()) + }.build() datePicker.show(fragmentManager, DatePickerBuilder::class.java.simpleName) - val timePicker = MaterialTimePicker - .Builder() - .setTitleText(fieldsData?.name) - .build() + val timePicker = + MaterialTimePicker + .Builder() + .setTitleText(fieldsData?.name) + .build() var stringDateTime = "" datePicker.addOnPositiveButtonClickListener { diff --git a/component/src/main/java/com/alfresco/content/component/FilterChip.kt b/component/src/main/java/com/alfresco/content/component/FilterChip.kt index 6ddd5ee80..c0c4cf3be 100644 --- a/component/src/main/java/com/alfresco/content/component/FilterChip.kt +++ b/component/src/main/java/com/alfresco/content/component/FilterChip.kt @@ -8,7 +8,6 @@ import com.google.android.material.chip.Chip * Convenience subclass which enables toggling checked status without notifying the listener. */ class FilterChip(context: Context, attrs: AttributeSet?) : Chip(context, attrs) { - private var onCheckedChangeListener: OnCheckedChangeListener? = null override fun setOnCheckedChangeListener(listener: OnCheckedChangeListener?) { @@ -19,7 +18,10 @@ class FilterChip(context: Context, attrs: AttributeSet?) : Chip(context, attrs) /** * Changes [checked] state and will [notify] the listener if required. */ - fun setChecked(checked: Boolean, notify: Boolean) { + fun setChecked( + checked: Boolean, + notify: Boolean, + ) { if (!notify) { super.setOnCheckedChangeListener(null) super.setChecked(checked) diff --git a/component/src/main/java/com/alfresco/content/component/ListViewActionsRow.kt b/component/src/main/java/com/alfresco/content/component/ListViewActionsRow.kt index 57627bf3e..c233390e4 100644 --- a/component/src/main/java/com/alfresco/content/component/ListViewActionsRow.kt +++ b/component/src/main/java/com/alfresco/content/component/ListViewActionsRow.kt @@ -11,21 +11,23 @@ import com.alfresco.content.component.databinding.ViewActionsListRowBinding import com.alfresco.content.getLocalizedName @ModelView(autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT) -internal class ListViewActionsRow @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0, -) : FrameLayout(context, attrs, defStyleAttr) { - private val binding = - ViewActionsListRowBinding.inflate(LayoutInflater.from(context), this, true) +internal class ListViewActionsRow + @JvmOverloads + constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + ) : FrameLayout(context, attrs, defStyleAttr) { + private val binding = + ViewActionsListRowBinding.inflate(LayoutInflater.from(context), this, true) - @ModelProp - fun setData(options: ComponentOptions) { - binding.actionButton.text = context.getLocalizedName(options.label ?: "") - } + @ModelProp + fun setData(options: ComponentOptions) { + binding.actionButton.text = context.getLocalizedName(options.label) + } - @CallbackProp - fun setClickListener(listener: OnClickListener?) { - binding.actionButton.setOnClickListener(listener) + @CallbackProp + fun setClickListener(listener: OnClickListener?) { + binding.actionButton.setOnClickListener(listener) + } } -} diff --git a/component/src/main/java/com/alfresco/content/component/ListViewCheckRow.kt b/component/src/main/java/com/alfresco/content/component/ListViewCheckRow.kt index 1151140e4..cd5f665e6 100644 --- a/component/src/main/java/com/alfresco/content/component/ListViewCheckRow.kt +++ b/component/src/main/java/com/alfresco/content/component/ListViewCheckRow.kt @@ -11,25 +11,27 @@ import com.alfresco.content.component.databinding.ViewCheckListRowBinding import com.alfresco.content.getLocalizedName @ModelView(autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT) -internal class ListViewCheckRow @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0, -) : FrameLayout(context, attrs, defStyleAttr) { - private val binding = ViewCheckListRowBinding.inflate(LayoutInflater.from(context), this, true) +internal class ListViewCheckRow + @JvmOverloads + constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + ) : FrameLayout(context, attrs, defStyleAttr) { + private val binding = ViewCheckListRowBinding.inflate(LayoutInflater.from(context), this, true) - @ModelProp - fun setData(options: ComponentOptions) { - binding.title.text = context.getLocalizedName(options.label ?: "") - } + @ModelProp + fun setData(options: ComponentOptions) { + binding.title.text = context.getLocalizedName(options.label) + } - @ModelProp - fun setOptionSelected(isSelected: Boolean) { - binding.checkBox.isChecked = isSelected - } + @ModelProp + fun setOptionSelected(isSelected: Boolean) { + binding.checkBox.isChecked = isSelected + } - @CallbackProp - fun setClickListener(listener: OnClickListener?) { - binding.parentListRow.setOnClickListener(listener) + @CallbackProp + fun setClickListener(listener: OnClickListener?) { + binding.parentListRow.setOnClickListener(listener) + } } -} diff --git a/component/src/main/java/com/alfresco/content/component/ListViewFacetCheckRow.kt b/component/src/main/java/com/alfresco/content/component/ListViewFacetCheckRow.kt index 9d0ef80b8..cd1092690 100644 --- a/component/src/main/java/com/alfresco/content/component/ListViewFacetCheckRow.kt +++ b/component/src/main/java/com/alfresco/content/component/ListViewFacetCheckRow.kt @@ -11,25 +11,32 @@ import com.alfresco.content.component.databinding.ViewCheckListRowBinding import com.alfresco.content.getLocalizedName @ModelView(autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT) -internal class ListViewFacetCheckRow @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0, -) : FrameLayout(context, attrs, defStyleAttr) { - private val binding = ViewCheckListRowBinding.inflate(LayoutInflater.from(context), this, true) +internal class ListViewFacetCheckRow + @JvmOverloads + constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + ) : FrameLayout(context, attrs, defStyleAttr) { + private val binding = ViewCheckListRowBinding.inflate(LayoutInflater.from(context), this, true) - @ModelProp - fun setData(options: ComponentOptions) { - binding.title.text = String.format(context.getString(R.string.label_count_format_integer), context.getLocalizedName(options.label), options.count) - } + @ModelProp + fun setData(options: ComponentOptions) { + binding.title.text = + String.format( + context.getString(R.string.label_count_format_integer), + context.getLocalizedName(options.label), + options.count, + ) + } - @ModelProp - fun setOptionSelected(isSelected: Boolean) { - binding.checkBox.isChecked = isSelected - } + @ModelProp + fun setOptionSelected(isSelected: Boolean) { + binding.checkBox.isChecked = isSelected + } - @CallbackProp - fun setClickListener(listener: OnClickListener?) { - binding.parentListRow.setOnClickListener(listener) + @CallbackProp + fun setClickListener(listener: OnClickListener?) { + binding.parentListRow.setOnClickListener(listener) + } } -} diff --git a/component/src/main/java/com/alfresco/content/component/ListViewFilterChips.kt b/component/src/main/java/com/alfresco/content/component/ListViewFilterChips.kt index 243fc8059..c4d5cee8c 100644 --- a/component/src/main/java/com/alfresco/content/component/ListViewFilterChips.kt +++ b/component/src/main/java/com/alfresco/content/component/ListViewFilterChips.kt @@ -11,8 +11,8 @@ import com.airbnb.epoxy.ModelProp import com.airbnb.epoxy.ModelView import com.alfresco.content.component.databinding.ViewListFilterChipsBinding import com.alfresco.content.component.models.SearchChipCategory +import com.alfresco.content.data.CHIP_TEXT_DISPLAY_LIMIT import com.alfresco.content.data.SearchFilter -import com.alfresco.content.data.chipTextDisplayLimit import com.alfresco.content.data.wrapWithLimit import com.alfresco.content.getLocalizedName @@ -20,62 +20,70 @@ import com.alfresco.content.getLocalizedName * Generated Model View for the Advance Filter Chips */ @ModelView(autoLayout = ModelView.Size.WRAP_WIDTH_WRAP_HEIGHT) -class ListViewFilterChips @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0, -) : FrameLayout(context, attrs, defStyleAttr) { +class ListViewFilterChips + @JvmOverloads + constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + ) : FrameLayout(context, attrs, defStyleAttr) { + private val binding = ViewListFilterChipsBinding.inflate(LayoutInflater.from(context), this) - private val binding = ViewListFilterChipsBinding.inflate(LayoutInflater.from(context), this) + /** + * Bind the capture item data to the view + */ + @ModelProp + fun setData(dataObj: SearchChipCategory) { + binding.chip.uncheck(false) - /** - * Bind the capture item data to the view - */ - @ModelProp - fun setData(dataObj: SearchChipCategory) { - binding.chip.uncheck(false) - - if (dataObj.category?.id == SearchFilter.Contextual.toString()) { - binding.chip.isChecked = true - } + if (dataObj.category?.id == SearchFilter.Contextual.toString()) { + binding.chip.isChecked = true + } - when (dataObj.category?.component?.selector) { - ComponentType.TEXT.value -> { - if (dataObj.selectedName.length > chipTextDisplayLimit) { - binding.chip.filters = arrayOf(InputFilter.LengthFilter(chipTextDisplayLimit.plus(3))) - binding.chip.ellipsize = TextUtils.TruncateAt.END + when (dataObj.category?.component?.selector) { + ComponentType.TEXT.value -> { + if (dataObj.selectedName.length > CHIP_TEXT_DISPLAY_LIMIT) { + binding.chip.filters = arrayOf(InputFilter.LengthFilter(CHIP_TEXT_DISPLAY_LIMIT.plus(3))) + binding.chip.ellipsize = TextUtils.TruncateAt.END + } + binding.chip.text = + if (dataObj.selectedName.isNotEmpty()) { + dataObj.selectedName.wrapWithLimit(context, CHIP_TEXT_DISPLAY_LIMIT) + } else { + context.getLocalizedName(dataObj.category?.name?.wrapWithLimit(context, CHIP_TEXT_DISPLAY_LIMIT) ?: "") + } } - binding.chip.text = if (dataObj.selectedName.isNotEmpty()) { - dataObj.selectedName.wrapWithLimit(context, chipTextDisplayLimit) - } else context.getLocalizedName(dataObj.category?.name?.wrapWithLimit(context, chipTextDisplayLimit) ?: "") - } - ComponentType.FACETS.value -> { - if (dataObj.selectedName.isNotEmpty()) { - binding.chip.text = dataObj.selectedName.wrapWithLimit(context, chipTextDisplayLimit, ",") - } else { - val replacedString = dataObj.facets?.label?.replace(" ", ".") ?: "" - val localizedName = context.getLocalizedName(replacedString) - if (localizedName == replacedString) { - binding.chip.text = dataObj.facets?.label?.wrapWithLimit(context, chipTextDisplayLimit) ?: "" - } else - binding.chip.text = localizedName.wrapWithLimit(context, chipTextDisplayLimit) + ComponentType.FACETS.value -> { + if (dataObj.selectedName.isNotEmpty()) { + binding.chip.text = dataObj.selectedName.wrapWithLimit(context, CHIP_TEXT_DISPLAY_LIMIT, ",") + } else { + val replacedString = dataObj.facets?.label?.replace(" ", ".") ?: "" + val localizedName = context.getLocalizedName(replacedString) + if (localizedName == replacedString) { + binding.chip.text = dataObj.facets?.label?.wrapWithLimit(context, CHIP_TEXT_DISPLAY_LIMIT) ?: "" + } else { + binding.chip.text = localizedName.wrapWithLimit(context, CHIP_TEXT_DISPLAY_LIMIT) + } + } + } + else -> { + binding.chip.text = + if (dataObj.selectedName.isNotEmpty()) { + dataObj.selectedName.wrapWithLimit(context, CHIP_TEXT_DISPLAY_LIMIT, ",") + } else { + context.getLocalizedName(dataObj.category?.name?.wrapWithLimit(context, CHIP_TEXT_DISPLAY_LIMIT) ?: "") + } } } - else -> { - binding.chip.text = if (dataObj.selectedName.isNotEmpty()) { - dataObj.selectedName.wrapWithLimit(context, chipTextDisplayLimit, ",") - } else context.getLocalizedName(dataObj.category?.name?.wrapWithLimit(context, chipTextDisplayLimit) ?: "") - } - } - binding.chip.isChecked = dataObj.isSelected - } + binding.chip.isChecked = dataObj.isSelected + } - /** - * set clickListener to the list item - */ - @CallbackProp - fun setClickListener(listener: OnClickListener?) { - binding.chip.setOnClickListener(listener) + /** + * set clickListener to the list item + */ + @CallbackProp + fun setClickListener(listener: OnClickListener?) { + binding.chip.setOnClickListener(listener) + } } -} diff --git a/component/src/main/java/com/alfresco/content/component/ListViewRadioRow.kt b/component/src/main/java/com/alfresco/content/component/ListViewRadioRow.kt index e32982018..8963d4765 100644 --- a/component/src/main/java/com/alfresco/content/component/ListViewRadioRow.kt +++ b/component/src/main/java/com/alfresco/content/component/ListViewRadioRow.kt @@ -11,25 +11,27 @@ import com.alfresco.content.component.databinding.ViewRadioListRowBinding import com.alfresco.content.getLocalizedName @ModelView(autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT) -internal class ListViewRadioRow @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0, -) : FrameLayout(context, attrs, defStyleAttr) { - private val binding = ViewRadioListRowBinding.inflate(LayoutInflater.from(context), this, true) +internal class ListViewRadioRow + @JvmOverloads + constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + ) : FrameLayout(context, attrs, defStyleAttr) { + private val binding = ViewRadioListRowBinding.inflate(LayoutInflater.from(context), this, true) - @ModelProp - fun setData(options: ComponentOptions) { - binding.title.text = context.getLocalizedName(options.label) - } + @ModelProp + fun setData(options: ComponentOptions) { + binding.title.text = context.getLocalizedName(options.label) + } - @ModelProp - fun setOptionSelected(isSelected: Boolean) { - binding.radioButton.isChecked = isSelected - } + @ModelProp + fun setOptionSelected(isSelected: Boolean) { + binding.radioButton.isChecked = isSelected + } - @CallbackProp - fun setClickListener(listener: OnClickListener?) { - binding.parentListRow.setOnClickListener(listener) + @CallbackProp + fun setClickListener(listener: OnClickListener?) { + binding.parentListRow.setOnClickListener(listener) + } } -} diff --git a/component/src/main/java/com/alfresco/content/component/ListViewSortChips.kt b/component/src/main/java/com/alfresco/content/component/ListViewSortChips.kt index 1ff1092d6..b67e3ba91 100644 --- a/component/src/main/java/com/alfresco/content/component/ListViewSortChips.kt +++ b/component/src/main/java/com/alfresco/content/component/ListViewSortChips.kt @@ -10,8 +10,8 @@ import com.airbnb.epoxy.CallbackProp import com.airbnb.epoxy.ModelProp import com.airbnb.epoxy.ModelView import com.alfresco.content.component.databinding.ViewListSortChipsBinding +import com.alfresco.content.data.CHIP_TEXT_DISPLAY_LIMIT import com.alfresco.content.data.TaskFilterData -import com.alfresco.content.data.chipTextDisplayLimit import com.alfresco.content.data.wrapWithLimit import com.alfresco.content.getLocalizedName @@ -19,47 +19,54 @@ import com.alfresco.content.getLocalizedName * Generated Model View for the Task sort chips */ @ModelView(autoLayout = ModelView.Size.WRAP_WIDTH_WRAP_HEIGHT) -class ListViewSortChips @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0, -) : FrameLayout(context, attrs, defStyleAttr) { +class ListViewSortChips + @JvmOverloads + constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + ) : FrameLayout(context, attrs, defStyleAttr) { + private val binding = ViewListSortChipsBinding.inflate(LayoutInflater.from(context), this) - private val binding = ViewListSortChipsBinding.inflate(LayoutInflater.from(context), this) + /** + * Binding the TaskSortData type data to chip + */ + @ModelProp + fun setData(dataObj: TaskFilterData) { + binding.chip.uncheck(false) + binding.chip.text = dataObj.selectedName.ifEmpty { dataObj.name } - /** - * Binding the TaskSortData type data to chip - */ - @ModelProp - fun setData(dataObj: TaskFilterData) { - binding.chip.uncheck(false) - binding.chip.text = dataObj.selectedName.ifEmpty { dataObj.name } - - when (dataObj.selector) { - ComponentType.TEXT.value -> { - if (dataObj.selectedName.length > chipTextDisplayLimit) { - binding.chip.filters = arrayOf(InputFilter.LengthFilter(chipTextDisplayLimit.plus(3))) - binding.chip.ellipsize = TextUtils.TruncateAt.END + when (dataObj.selector) { + ComponentType.TEXT.value -> { + if (dataObj.selectedName.length > CHIP_TEXT_DISPLAY_LIMIT) { + binding.chip.filters = arrayOf(InputFilter.LengthFilter(CHIP_TEXT_DISPLAY_LIMIT.plus(3))) + binding.chip.ellipsize = TextUtils.TruncateAt.END + } + binding.chip.text = + if (dataObj.selectedName.isNotEmpty()) { + dataObj.selectedName.wrapWithLimit(context, CHIP_TEXT_DISPLAY_LIMIT) + } else { + context.getLocalizedName(dataObj.name?.wrapWithLimit(context, CHIP_TEXT_DISPLAY_LIMIT) ?: "") + } + } + else -> { + binding.chip.text = + if (dataObj.selectedName.isNotEmpty()) { + dataObj.selectedName.wrapWithLimit(context, CHIP_TEXT_DISPLAY_LIMIT, ",") + } else { + context.getLocalizedName(dataObj.name?.wrapWithLimit(context, CHIP_TEXT_DISPLAY_LIMIT) ?: "") + } } - binding.chip.text = if (dataObj.selectedName.isNotEmpty()) { - dataObj.selectedName.wrapWithLimit(context, chipTextDisplayLimit) - } else context.getLocalizedName(dataObj.name?.wrapWithLimit(context, chipTextDisplayLimit) ?: "") - } - else -> { - binding.chip.text = if (dataObj.selectedName.isNotEmpty()) { - dataObj.selectedName.wrapWithLimit(context, chipTextDisplayLimit, ",") - } else context.getLocalizedName(dataObj.name?.wrapWithLimit(context, chipTextDisplayLimit) ?: "") } - } - binding.chip.isChecked = dataObj.isSelected - } + binding.chip.isChecked = dataObj.isSelected + } - /** - * set clickListener to the list item - */ - @CallbackProp - fun setClickListener(listener: OnClickListener?) { - binding.chip.setOnClickListener(listener) + /** + * set clickListener to the list item + */ + @CallbackProp + fun setClickListener(listener: OnClickListener?) { + binding.chip.setOnClickListener(listener) + } } -} diff --git a/component/src/main/java/com/alfresco/content/component/ListViewUserRow.kt b/component/src/main/java/com/alfresco/content/component/ListViewUserRow.kt index d2b8473ad..08f0302d3 100644 --- a/component/src/main/java/com/alfresco/content/component/ListViewUserRow.kt +++ b/component/src/main/java/com/alfresco/content/component/ListViewUserRow.kt @@ -12,24 +12,26 @@ import com.alfresco.content.data.UserGroupDetails import com.alfresco.content.getLocalizedName @ModelView(autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT) -internal class ListViewUserRow @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0, -) : FrameLayout(context, attrs, defStyleAttr) { - private val binding = ViewListUserRowBinding.inflate(LayoutInflater.from(context), this) +internal class ListViewUserRow + @JvmOverloads + constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + ) : FrameLayout(context, attrs, defStyleAttr) { + private val binding = ViewListUserRowBinding.inflate(LayoutInflater.from(context), this) - @ModelProp - fun setData(dataObj: UserGroupDetails) { - binding.tvUserInitial.text = context.getLocalizedName(dataObj.nameInitial) - binding.tvName.text = dataObj.groupName.ifEmpty { context.getLocalizedName(dataObj.name ?: "") } - } + @ModelProp + fun setData(dataObj: UserGroupDetails) { + binding.tvUserInitial.text = context.getLocalizedName(dataObj.nameInitial) + binding.tvName.text = dataObj.groupName.ifEmpty { context.getLocalizedName(dataObj.name) } + } - /** - * set clickListener to the list item - */ - @CallbackProp - fun setClickListener(listener: OnClickListener?) { - binding.clUserParent.setOnClickListener(listener) + /** + * set clickListener to the list item + */ + @CallbackProp + fun setClickListener(listener: OnClickListener?) { + binding.clUserParent.setOnClickListener(listener) + } } -} diff --git a/component/src/main/java/com/alfresco/content/component/models/SearchChipCategory.kt b/component/src/main/java/com/alfresco/content/component/models/SearchChipCategory.kt index 9e64726e1..6971b5e10 100644 --- a/component/src/main/java/com/alfresco/content/component/models/SearchChipCategory.kt +++ b/component/src/main/java/com/alfresco/content/component/models/SearchChipCategory.kt @@ -19,13 +19,15 @@ data class SearchChipCategory( var selectedName: String = "", var selectedQuery: String = "", ) : Parcelable { - companion object { - /** * update and returns the searchChipCategory */ - fun with(searchChipCategory: SearchChipCategory?, name: String, query: String): SearchChipCategory { + fun with( + searchChipCategory: SearchChipCategory?, + name: String, + query: String, + ): SearchChipCategory { return SearchChipCategory( category = searchChipCategory?.category, isSelected = searchChipCategory?.isSelected == true, @@ -38,15 +40,19 @@ data class SearchChipCategory( /** * returns the contextual searchChipCategory object */ - fun withContextual(name: String, contextual: SearchFilter): SearchChipCategory { + fun withContextual( + name: String, + contextual: SearchFilter, + ): SearchChipCategory { return SearchChipCategory( - category = CategoriesItem( - name = name, - expanded = null, - component = null, - enabled = null, - id = contextual.toString(), - ), + category = + CategoriesItem( + name = name, + expanded = null, + component = null, + enabled = null, + id = contextual.toString(), + ), isSelected = true, selectedName = name, selectedQuery = contextual.name, @@ -71,13 +77,14 @@ data class SearchChipCategory( */ fun withDefaultFacet(data: Facets): SearchChipCategory { return SearchChipCategory( - category = CategoriesItem( - null, - Component(null, ComponentType.FACETS.value), - data.label, - data.label, - null, - ), + category = + CategoriesItem( + null, + Component(null, ComponentType.FACETS.value), + data.label, + data.label, + null, + ), facets = data, selectedName = "", selectedQuery = "", @@ -87,7 +94,10 @@ data class SearchChipCategory( /** * return the update SearchChipCategory obj using FacetIntervals data obj */ - fun updateFacet(oldDataObj: SearchChipCategory, data: Facets): SearchChipCategory { + fun updateFacet( + oldDataObj: SearchChipCategory, + data: Facets, + ): SearchChipCategory { return SearchChipCategory( category = oldDataObj.category, facets = data, @@ -102,13 +112,14 @@ data class SearchChipCategory( */ fun withFilterCountZero(data: Facets): SearchChipCategory { return SearchChipCategory( - category = CategoriesItem( - null, - Component(null, ComponentType.FACETS.value), - data.label, - data.label, - null, - ), + category = + CategoriesItem( + null, + Component(null, ComponentType.FACETS.value), + data.label, + data.label, + null, + ), facets = Facets.filterZeroCount(data), selectedName = "", selectedQuery = "", diff --git a/component/src/main/java/com/alfresco/content/component/searchusergroup/SearchUserGroupComponentBuilder.kt b/component/src/main/java/com/alfresco/content/component/searchusergroup/SearchUserGroupComponentBuilder.kt index dc8798d7c..104afd49b 100644 --- a/component/src/main/java/com/alfresco/content/component/searchusergroup/SearchUserGroupComponentBuilder.kt +++ b/component/src/main/java/com/alfresco/content/component/searchusergroup/SearchUserGroupComponentBuilder.kt @@ -20,28 +20,26 @@ data class SearchUserGroupComponentBuilder( var onApply: SearchUserComponentApplyCallback? = null, var onCancel: SearchUserComponentCancelCallback? = null, ) { - /** * Filter sheet apply callback */ - fun onApply(callback: SearchUserComponentApplyCallback?) = - apply { this.onApply = callback } + fun onApply(callback: SearchUserComponentApplyCallback?) = apply { this.onApply = callback } /** * Filter sheet cancel callback */ - fun onCancel(callback: SearchUserComponentCancelCallback?) = - apply { this.onCancel = callback } + fun onCancel(callback: SearchUserComponentCancelCallback?) = apply { this.onCancel = callback } /** * Filter sheet show method */ fun show() { - val fragmentManager = when (context) { - is AppCompatActivity -> context.supportFragmentManager - is Fragment -> context.childFragmentManager - else -> throw IllegalArgumentException() - } + val fragmentManager = + when (context) { + is AppCompatActivity -> context.supportFragmentManager + is Fragment -> context.childFragmentManager + else -> throw IllegalArgumentException() + } SearchUserGroupComponentSheet().apply { arguments = bundleOf(Mavericks.KEY_ARG to parentEntry) diff --git a/component/src/main/java/com/alfresco/content/component/searchusergroup/SearchUserGroupComponentSheet.kt b/component/src/main/java/com/alfresco/content/component/searchusergroup/SearchUserGroupComponentSheet.kt index 4cfac6afc..3b6d3825e 100644 --- a/component/src/main/java/com/alfresco/content/component/searchusergroup/SearchUserGroupComponentSheet.kt +++ b/component/src/main/java/com/alfresco/content/component/searchusergroup/SearchUserGroupComponentSheet.kt @@ -33,7 +33,6 @@ import com.google.android.material.bottomsheet.BottomSheetDialog * Marked as SearchUserGroupComponentSheet class */ class SearchUserGroupComponentSheet : BottomSheetDialogFragment(), MavericksView { - internal val viewModel: SearchUserGroupComponentViewModel by fragmentViewModel() lateinit var binding: SheetComponentSearchUserBinding @@ -51,9 +50,14 @@ class SearchUserGroupComponentSheet : BottomSheetDialogFragment(), MavericksView return binding.root } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { super.onViewCreated(view, savedInstanceState) - dialog?.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE or WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE) + dialog?.window?.setSoftInputMode( + WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE or WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE, + ) binding.toolbar.apply { navigationIcon = requireContext().getDrawableForAttribute(R.attr.homeAsUpIndicator) setNavigationOnClickListener { dismiss() } @@ -66,19 +70,21 @@ class SearchUserGroupComponentSheet : BottomSheetDialogFragment(), MavericksView @SuppressLint("ClickableViewAccessibility") private fun setListeners() { - binding.searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener { - override fun onQueryTextChange(newText: String?): Boolean { - if (isResumed) { - setSearchQuery(newText ?: "") + binding.searchView.setOnQueryTextListener( + object : SearchView.OnQueryTextListener { + override fun onQueryTextChange(newText: String?): Boolean { + if (isResumed) { + setSearchQuery(newText ?: "") + } + return true } - return true - } - override fun onQueryTextSubmit(query: String?): Boolean { - binding.searchView.hideSoftInput() - return true - } - }) + override fun onQueryTextSubmit(query: String?): Boolean { + binding.searchView.hideSoftInput() + return true + } + }, + ) binding.searchByNameOrIndividual.setOnCheckedChangeListener { button, isChecked -> if (isChecked) { @@ -103,7 +109,10 @@ class SearchUserGroupComponentSheet : BottomSheetDialogFragment(), MavericksView } } - private fun changeTab(selected: CompoundButton, notSelected: CompoundButton) { + private fun changeTab( + selected: CompoundButton, + notSelected: CompoundButton, + ) { selected.setTextColor(ContextCompat.getColor(requireContext(), R.color.alfresco_gray_radio_text_color)) notSelected.setTextColor(ContextCompat.getColor(requireContext(), R.color.alfresco_gray_radio_text_color_60)) setSearchQuery(binding.searchView.query.toString()) @@ -152,29 +161,31 @@ class SearchUserGroupComponentSheet : BottomSheetDialogFragment(), MavericksView binding.componentParent.requestLayout() } - override fun invalidate() = withState(viewModel) { state -> - binding.loading.isVisible = state.requestUser is Loading - - if (viewModel.canSearchGroups) { - binding.rgSelector.visibility = View.GONE - binding.searchView.queryHint = when ((state.parent as ProcessEntry).reviewerType) { - ReviewerType.FUNCTIONAL_GROUP -> { - viewModel.searchByNameOrIndividual = false - getString(R.string.search_group) - } - else -> { - viewModel.searchByNameOrIndividual = true - getString(R.string.search_user) - } + override fun invalidate() = + withState(viewModel) { state -> + binding.loading.isVisible = state.requestUser is Loading + + if (viewModel.canSearchGroups) { + binding.rgSelector.visibility = View.GONE + binding.searchView.queryHint = + when ((state.parent as ProcessEntry).reviewerType) { + ReviewerType.FUNCTIONAL_GROUP -> { + viewModel.searchByNameOrIndividual = false + getString(R.string.search_group) + } + else -> { + viewModel.searchByNameOrIndividual = true + getString(R.string.search_user) + } + } + binding.searchByNameOrIndividual.text = getString(R.string.individual_title) + binding.searchByEmailOrGroups.text = getString(R.string.group_title) + } else { + binding.rgSelector.visibility = View.VISIBLE + binding.searchByNameOrIndividual.text = getString(R.string.text_by_name) + binding.searchByEmailOrGroups.text = getString(R.string.text_by_email) } - binding.searchByNameOrIndividual.text = getString(R.string.individual_title) - binding.searchByEmailOrGroups.text = getString(R.string.group_title) - } else { - binding.rgSelector.visibility = View.VISIBLE - binding.searchByNameOrIndividual.text = getString(R.string.text_by_name) - binding.searchByEmailOrGroups.text = getString(R.string.text_by_email) - } - epoxyController.requestModelBuild() - } + epoxyController.requestModelBuild() + } } diff --git a/component/src/main/java/com/alfresco/content/component/searchusergroup/SearchUserGroupComponentSheetExtension.kt b/component/src/main/java/com/alfresco/content/component/searchusergroup/SearchUserGroupComponentSheetExtension.kt index 7399344f8..3ebc0881d 100644 --- a/component/src/main/java/com/alfresco/content/component/searchusergroup/SearchUserGroupComponentSheetExtension.kt +++ b/component/src/main/java/com/alfresco/content/component/searchusergroup/SearchUserGroupComponentSheetExtension.kt @@ -3,18 +3,19 @@ package com.alfresco.content.component.searchusergroup import com.alfresco.content.component.listViewUserRow import com.alfresco.content.simpleController -internal fun SearchUserGroupComponentSheet.epoxyController() = simpleController(viewModel) { state -> - state.listUserGroup.forEach { item -> - listViewUserRow { - id(item.id) - data(item) - clickListener { model, _, _, _ -> - onApply?.invoke(model.data()) - dismiss() +internal fun SearchUserGroupComponentSheet.epoxyController() = + simpleController(viewModel) { state -> + state.listUserGroup.forEach { item -> + listViewUserRow { + id(item.id) + data(item) + clickListener { model, _, _, _ -> + onApply?.invoke(model.data()) + dismiss() + } } } } -} internal fun SearchUserGroupComponentSheet.executeSearch(term: String) { scrollToTop() diff --git a/component/src/main/java/com/alfresco/content/component/searchusergroup/SearchUserGroupComponentState.kt b/component/src/main/java/com/alfresco/content/component/searchusergroup/SearchUserGroupComponentState.kt index 1848db52d..3c87f668b 100644 --- a/component/src/main/java/com/alfresco/content/component/searchusergroup/SearchUserGroupComponentState.kt +++ b/component/src/main/java/com/alfresco/content/component/searchusergroup/SearchUserGroupComponentState.kt @@ -23,13 +23,17 @@ data class SearchUserGroupComponentState( /** * update search user entries after fetch the result from server. */ - fun updateUserGroupEntries(response: ResponseUserGroupList?, userGroupDetails: UserGroupDetails): SearchUserGroupComponentState { + fun updateUserGroupEntries( + response: ResponseUserGroupList?, + userGroupDetails: UserGroupDetails, + ): SearchUserGroupComponentState { if (response == null) return this requireNotNull(parent) - val filterList = when (parent) { - is ProcessEntry -> response.listUserGroup.filter { it.id != parent.startedBy?.id } - else -> response.listUserGroup.filter { it.id != (parent as TaskEntry).assignee?.id } - } + val filterList = + when (parent) { + is ProcessEntry -> response.listUserGroup.filter { it.id != parent.startedBy?.id } + else -> response.listUserGroup.filter { it.id != (parent as TaskEntry).assignee?.id } + } var filterUser = filterList.toMutableList() if (!response.isGroupSearch) { filterUser = filterList.filter { it.id != userGroupDetails.id }.toMutableList() diff --git a/component/src/main/java/com/alfresco/content/component/searchusergroup/SearchUserGroupComponentViewModel.kt b/component/src/main/java/com/alfresco/content/component/searchusergroup/SearchUserGroupComponentViewModel.kt index 6146913f6..b9af2c417 100644 --- a/component/src/main/java/com/alfresco/content/component/searchusergroup/SearchUserGroupComponentViewModel.kt +++ b/component/src/main/java/com/alfresco/content/component/searchusergroup/SearchUserGroupComponentViewModel.kt @@ -54,15 +54,16 @@ class SearchUserGroupComponentViewModel( setState { canSearchGroups = parent is ProcessEntry var listUserGroup: List = listOf() - listUserGroup = when (parent) { - is ProcessEntry -> { - if (parent.reviewerType != ReviewerType.FUNCTIONAL_GROUP) { - listOf(getLoggedInUser()) - } else { - listOf() - } - } else -> listOf(getLoggedInUser()) - } + listUserGroup = + when (parent) { + is ProcessEntry -> { + if (parent.reviewerType != ReviewerType.FUNCTIONAL_GROUP) { + listOf(getLoggedInUser()) + } else { + listOf() + } + } else -> listOf(getLoggedInUser()) + } copy(listUserGroup = listUserGroup) } @@ -75,8 +76,9 @@ class SearchUserGroupComponentViewModel( }.executeOnLatest({ if (canSearchGroups && it.emailOrGroups.isNotEmpty()) { repository.searchGroups(it.emailOrGroups) - } else + } else { repository.searchUser(it.nameOrIndividual, it.emailOrGroups) + } }) { if (it is Loading) { copy(requestUser = it) @@ -116,13 +118,14 @@ class SearchUserGroupComponentViewModel( * update the params on the basis of name or email to search the user. */ fun setSearchQuery(term: String) { - params = if (searchByNameOrIndividual) { - if (term.isEmpty()) updateDefaultUserGroup(listOf(getLoggedInUser())) - params.copy(nameOrIndividual = term, emailOrGroups = "") - } else { - if (canSearchGroups && term.isEmpty()) updateDefaultUserGroup(emptyList()) - params.copy(nameOrIndividual = "", emailOrGroups = term) - } + params = + if (searchByNameOrIndividual) { + if (term.isEmpty()) updateDefaultUserGroup(listOf(getLoggedInUser())) + params.copy(nameOrIndividual = term, emailOrGroups = "") + } else { + if (canSearchGroups && term.isEmpty()) updateDefaultUserGroup(emptyList()) + params.copy(nameOrIndividual = "", emailOrGroups = term) + } liveSearchUserEvents.value = params } @@ -134,6 +137,7 @@ class SearchUserGroupComponentViewModel( companion object : MavericksViewModelFactory { const val MIN_QUERY_LENGTH = 1 const val DEFAULT_DEBOUNCE_TIME = 300L + override fun create( viewModelContext: ViewModelContext, state: SearchUserGroupComponentState, diff --git a/component/src/main/res/values-de/strings.xml b/component/src/main/res/values-de/strings.xml index 1828a1fde..57518679e 100644 --- a/component/src/main/res/values-de/strings.xml +++ b/component/src/main/res/values-de/strings.xml @@ -7,7 +7,6 @@ Startdatum Enddatum Der \„Von\“ Wert muss kleiner als der \„Bis\“ Wert sein - Zurücksetzen Anwenden Abbrechen Erforderlicher Wert diff --git a/component/src/main/res/values-es/strings.xml b/component/src/main/res/values-es/strings.xml index e80bdf161..488fc153a 100644 --- a/component/src/main/res/values-es/strings.xml +++ b/component/src/main/res/values-es/strings.xml @@ -7,7 +7,6 @@ Desde la fecha Hasta la fecha El valor \'De\' debe ser menor que el valor \'A\' - Reiniciar Aplicar Cancelar Valor requerido diff --git a/component/src/main/res/values-fr/strings.xml b/component/src/main/res/values-fr/strings.xml index 6691cd52a..5fe14fe8d 100644 --- a/component/src/main/res/values-fr/strings.xml +++ b/component/src/main/res/values-fr/strings.xml @@ -7,7 +7,6 @@ Date de début Date de fin La valeur \'De\' doit être inférieure à la valeur \'A\' - Réinitialiser Appliquer Annuler Valeur requise diff --git a/component/src/main/res/values-it/strings.xml b/component/src/main/res/values-it/strings.xml index 34ebdd166..56ec4ef7d 100644 --- a/component/src/main/res/values-it/strings.xml +++ b/component/src/main/res/values-it/strings.xml @@ -7,7 +7,6 @@ Data di inizio Data di fine Il valore \'Da\' deve essere inferiore al valore \'A\' - Reimposta Applica Annulla Valore obbligatorio diff --git a/component/src/main/res/values-night/colors.xml b/component/src/main/res/values-night/colors.xml index 6050a5eae..2187df355 100644 --- a/component/src/main/res/values-night/colors.xml +++ b/component/src/main/res/values-night/colors.xml @@ -1,7 +1,6 @@ - #3D2A7DE1 @color/white @color/white_60 diff --git a/component/src/main/res/values-nl/strings.xml b/component/src/main/res/values-nl/strings.xml index c9ba0bbf0..96f5579d7 100644 --- a/component/src/main/res/values-nl/strings.xml +++ b/component/src/main/res/values-nl/strings.xml @@ -7,7 +7,6 @@ Begindatum Einddatum Van-waarde moet kleiner zijn dan Tot-waarde - Opnieuw instellen Toepassen Annuleren Vereiste waarde diff --git a/component/src/main/res/values/colors.xml b/component/src/main/res/values/colors.xml index b4f52db9c..f53a788b3 100644 --- a/component/src/main/res/values/colors.xml +++ b/component/src/main/res/values/colors.xml @@ -1,6 +1,5 @@ - #3D2A7DE1 #212121 #1F212121 #0D212328 diff --git a/component/src/main/res/values/strings.xml b/component/src/main/res/values/strings.xml index 068d1f306..7a0135616 100644 --- a/component/src/main/res/values/strings.xml +++ b/component/src/main/res/values/strings.xml @@ -7,7 +7,6 @@ From Date To Date From value should be less than To value - Reset Apply Cancel Required value diff --git a/component/src/test/java/com/alfresco/content/app/component/ComponentViewModelTest.kt b/component/src/test/java/com/alfresco/content/app/component/ComponentViewModelTest.kt index f5cc9b7b8..62d13854f 100644 --- a/component/src/test/java/com/alfresco/content/app/component/ComponentViewModelTest.kt +++ b/component/src/test/java/com/alfresco/content/app/component/ComponentViewModelTest.kt @@ -20,7 +20,6 @@ import org.junit.ClassRule import org.junit.Test class ComponentViewModelTest { - private lateinit var viewModel: ComponentViewModel private lateinit var state: ComponentState @@ -32,19 +31,21 @@ class ComponentViewModelTest { @Test fun dateNumberRange_success() { // Creating the actual data for ComponentData. - componentData = ComponentData( - id = "createdDateRange", - name = "Date Range", - selector = "date-range", - options = emptyList(), - properties = ComponentProperties( - field = "cm:created", - maxDate = "today", - dateFormat = DATE_FORMAT, - ), - selectedName = "", - selectedQuery = "", - ) + componentData = + ComponentData( + id = "createdDateRange", + name = "Date Range", + selector = "date-range", + options = emptyList(), + properties = + ComponentProperties( + field = "cm:created", + maxDate = "today", + dateFormat = DATE_FORMAT, + ), + selectedName = "", + selectedQuery = "", + ) // Initializing the state state = ComponentState(componentData) @@ -63,26 +64,31 @@ class ComponentViewModelTest { withState(viewModel) { assertEquals(it.parent?.selector, ComponentType.DATE_RANGE.value) assertEquals(it.parent?.selectedName, "${viewModel.fromValue} - ${viewModel.toValue}") - assertEquals(it.parent?.selectedQuery, "${componentData.properties?.field}:[${viewModel.fromValue.kBToByte()} TO ${viewModel.toValue.kBToByte()}]") + assertEquals( + it.parent?.selectedQuery, + "${componentData.properties?.field}:[${viewModel.fromValue.kBToByte()} TO ${viewModel.toValue.kBToByte()}]", + ) } } @Test fun test_dateNumberRange_empty() { // Creating the actual data for ComponentData. - componentData = ComponentData( - id = "createdDateRange", - name = "Date Range", - selector = "date-range", - options = emptyList(), - properties = ComponentProperties( - field = "cm:created", - maxDate = "today", - dateFormat = DATE_FORMAT, - ), - selectedName = "", - selectedQuery = "", - ) + componentData = + ComponentData( + id = "createdDateRange", + name = "Date Range", + selector = "date-range", + options = emptyList(), + properties = + ComponentProperties( + field = "cm:created", + maxDate = "today", + dateFormat = DATE_FORMAT, + ), + selectedName = "", + selectedQuery = "", + ) // Initializing the state state = ComponentState(componentData) @@ -107,19 +113,21 @@ class ComponentViewModelTest { @Test fun sliderNumberRange_success() { // Creating the actual data for ComponentData. - componentData = ComponentData( - id = "createdDateRange", - name = "Date Range", - selector = "date-range", - options = emptyList(), - properties = ComponentProperties( - field = "cm:created", - maxDate = "today", - dateFormat = DATE_FORMAT, - ), - selectedName = "", - selectedQuery = "", - ) + componentData = + ComponentData( + id = "createdDateRange", + name = "Date Range", + selector = "date-range", + options = emptyList(), + properties = + ComponentProperties( + field = "cm:created", + maxDate = "today", + dateFormat = DATE_FORMAT, + ), + selectedName = "", + selectedQuery = "", + ) // Initializing the state state = ComponentState(componentData) @@ -136,26 +144,31 @@ class ComponentViewModelTest { withState(viewModel) { assertEquals(it.parent?.selector, ComponentType.DATE_RANGE.value) assertEquals(it.parent?.selectedName, viewModel.toValue) - assertEquals(it.parent?.selectedQuery, "${componentData.properties?.field}:[${viewModel.fromValue.kBToByte()} TO ${viewModel.toValue.kBToByte()}]") + assertEquals( + it.parent?.selectedQuery, + "${componentData.properties?.field}:[${viewModel.fromValue.kBToByte()} TO ${viewModel.toValue.kBToByte()}]", + ) } } @Test fun test_sliderNumberRange_empty() { // Creating the actual data for ComponentData. - componentData = ComponentData( - id = "createdDateRange", - name = "Date Range", - selector = "date-range", - options = emptyList(), - properties = ComponentProperties( - field = "cm:created", - maxDate = "today", - dateFormat = DATE_FORMAT, - ), - selectedName = "", - selectedQuery = "", - ) + componentData = + ComponentData( + id = "createdDateRange", + name = "Date Range", + selector = "date-range", + options = emptyList(), + properties = + ComponentProperties( + field = "cm:created", + maxDate = "today", + dateFormat = DATE_FORMAT, + ), + selectedName = "", + selectedQuery = "", + ) // Initializing the state state = ComponentState(componentData) @@ -177,19 +190,21 @@ class ComponentViewModelTest { @Test fun test_updateFormatDateRange_empty() { // Creating the actual data for ComponentData. - componentData = ComponentData( - id = "createdDateRange", - name = "Date Range", - selector = "date-range", - options = emptyList(), - properties = ComponentProperties( - field = "cm:created", - maxDate = "today", - dateFormat = DATE_FORMAT, - ), - selectedName = "", - selectedQuery = "", - ) + componentData = + ComponentData( + id = "createdDateRange", + name = "Date Range", + selector = "date-range", + options = emptyList(), + properties = + ComponentProperties( + field = "cm:created", + maxDate = "today", + dateFormat = DATE_FORMAT, + ), + selectedName = "", + selectedQuery = "", + ) // Initializing the state state = ComponentState(componentData) @@ -210,19 +225,21 @@ class ComponentViewModelTest { @Test fun test_updateFormatDateRange() { // Creating the actual data for ComponentData. - componentData = ComponentData( - id = "createdDateRange", - name = "Date Range", - selector = "date-range", - options = emptyList(), - properties = ComponentProperties( - field = "cm:created", - maxDate = "today", - dateFormat = DATE_FORMAT, - ), - selectedName = "", - selectedQuery = "", - ) + componentData = + ComponentData( + id = "createdDateRange", + name = "Date Range", + selector = "date-range", + options = emptyList(), + properties = + ComponentProperties( + field = "cm:created", + maxDate = "today", + dateFormat = DATE_FORMAT, + ), + selectedName = "", + selectedQuery = "", + ) // Initializing the state state = ComponentState(componentData) @@ -241,7 +258,10 @@ class ComponentViewModelTest { assertNotEquals(viewModel.fromDate, "") assertNotEquals(viewModel.toDate, "") val query = - "${componentData.properties?.field}:['${viewModel.fromDate.getFormattedDate(DATE_FORMAT_2, DATE_FORMAT_1)}' TO '${viewModel.toDate.getFormattedDate(DATE_FORMAT_2, DATE_FORMAT_1)}']" + "${componentData.properties?.field}:['${viewModel.fromDate.getFormattedDate( + DATE_FORMAT_2, + DATE_FORMAT_1, + )}' TO '${viewModel.toDate.getFormattedDate(DATE_FORMAT_2, DATE_FORMAT_1)}']" assertEquals(it.parent?.selectedQuery, query) assertEquals(it.parent?.selectedName, "${viewModel.fromDate} - ${viewModel.toDate}") } @@ -250,19 +270,21 @@ class ComponentViewModelTest { @Test fun test_buildSingleDataModel() { // Creating the actual data for ComponentData. - componentData = ComponentData( - id = "createdDateRange", - name = "Date Range", - selector = "date-range", - options = emptyList(), - properties = ComponentProperties( - field = "cm:created", - maxDate = "today", - dateFormat = DATE_FORMAT, - ), - selectedName = TEST_NAME, - selectedQuery = TEST_QUERY, - ) + componentData = + ComponentData( + id = "createdDateRange", + name = "Date Range", + selector = "date-range", + options = emptyList(), + properties = + ComponentProperties( + field = "cm:created", + maxDate = "today", + dateFormat = DATE_FORMAT, + ), + selectedName = TEST_NAME, + selectedQuery = TEST_QUERY, + ) // Initializing the state state = ComponentState(componentData) @@ -282,23 +304,25 @@ class ComponentViewModelTest { @Test fun test_searchBucket() { // Creating the actual data for ComponentData. - componentData = ComponentData( - id = "FacetsID", - name = "Facets", - selector = "facets", - options = listOf( - ComponentOptions( - label = "test label 1", - query = "test query 1", - ), - ComponentOptions( - label = "test label 2", - query = TEST_QUERY_2, - ), - ), - selectedName = TEST_NAME, - selectedQuery = TEST_QUERY, - ) + componentData = + ComponentData( + id = "FacetsID", + name = "Facets", + selector = "facets", + options = + listOf( + ComponentOptions( + label = "test label 1", + query = "test query 1", + ), + ComponentOptions( + label = "test label 2", + query = TEST_QUERY_2, + ), + ), + selectedName = TEST_NAME, + selectedQuery = TEST_QUERY, + ) // Initializing the state state = ComponentState(componentData) @@ -318,23 +342,25 @@ class ComponentViewModelTest { @Test fun test_searchBucket_empty() { // Creating the actual data for ComponentData. - componentData = ComponentData( - id = "FacetsID", - name = "Facets", - selector = "facets", - options = listOf( - ComponentOptions( - label = "test label 1", - query = "test query 1", - ), - ComponentOptions( - label = "test label 2", - query = TEST_QUERY_2, - ), - ), - selectedName = TEST_NAME, - selectedQuery = TEST_QUERY, - ) + componentData = + ComponentData( + id = "FacetsID", + name = "Facets", + selector = "facets", + options = + listOf( + ComponentOptions( + label = "test label 1", + query = "test query 1", + ), + ComponentOptions( + label = "test label 2", + query = TEST_QUERY_2, + ), + ), + selectedName = TEST_NAME, + selectedQuery = TEST_QUERY, + ) // Initializing the state state = ComponentState(componentData) @@ -354,24 +380,26 @@ class ComponentViewModelTest { @Test fun test_copyDefaultComponentData_radioType_selected() { // Creating the actual data for ComponentData. - componentData = ComponentData( - id = "queryType", - name = "Radio", - selector = "radio", - options = listOf( - ComponentOptions( - label = "None", - query = "", - default = true, - ), - ComponentOptions( - label = "All", - query = TEST_QUERY_2, - ), - ), - selectedName = "", - selectedQuery = "", - ) + componentData = + ComponentData( + id = "queryType", + name = "Radio", + selector = "radio", + options = + listOf( + ComponentOptions( + label = "None", + query = "", + default = true, + ), + ComponentOptions( + label = "All", + query = TEST_QUERY_2, + ), + ), + selectedName = "", + selectedQuery = "", + ) // Initializing the state state = ComponentState(componentData) @@ -390,23 +418,25 @@ class ComponentViewModelTest { @Test fun test_copyDefaultComponentData_radioType_notSelected() { // Creating the actual data for ComponentData. - componentData = ComponentData( - id = "queryType", - name = "Radio", - selector = "radio", - options = listOf( - ComponentOptions( - label = "None", - query = "", - ), - ComponentOptions( - label = "All", - query = TEST_QUERY_2, - ), - ), - selectedName = "", - selectedQuery = "", - ) + componentData = + ComponentData( + id = "queryType", + name = "Radio", + selector = "radio", + options = + listOf( + ComponentOptions( + label = "None", + query = "", + ), + ComponentOptions( + label = "All", + query = TEST_QUERY_2, + ), + ), + selectedName = "", + selectedQuery = "", + ) // Initializing the state state = ComponentState(componentData) @@ -425,17 +455,19 @@ class ComponentViewModelTest { @Test fun test_updateSingleComponentData_name_only() { // Creating the actual data for ComponentData. - componentData = ComponentData( - id = "queryName", - name = "File Name", - selector = "text", - options = emptyList(), - properties = ComponentProperties( - field = "cm:name", - ), - selectedName = "", - selectedQuery = "", - ) + componentData = + ComponentData( + id = "queryName", + name = "File Name", + selector = "text", + options = emptyList(), + properties = + ComponentProperties( + field = "cm:name", + ), + selectedName = "", + selectedQuery = "", + ) // Initializing the state state = ComponentState(componentData) @@ -484,17 +516,19 @@ class ComponentViewModelTest { @Test fun test_buildCheckListModel() { // Creating the actual data for ComponentData. - componentData = ComponentData( - id = "queryType", - name = "check-list", - selector = "Check List", - options = emptyList(), - properties = ComponentProperties( - operator = "OR", - ), - selectedName = "data 1,data 2,data 3,data 4", - selectedQuery = "query 1 OR query 2 OR query 3 OR query4", - ) + componentData = + ComponentData( + id = "queryType", + name = "check-list", + selector = "Check List", + options = emptyList(), + properties = + ComponentProperties( + operator = "OR", + ), + selectedName = "data 1,data 2,data 3,data 4", + selectedQuery = "query 1 OR query 2 OR query 3 OR query4", + ) val nameListSize = componentData.selectedName.split(",").size val queryListSize = componentData.selectedName.split(componentData.properties?.operator ?: "").size diff --git a/component/src/test/java/com/alfresco/content/app/component/SearchUserGroupComponentViewModelTest.kt b/component/src/test/java/com/alfresco/content/app/component/SearchUserGroupComponentViewModelTest.kt index e45af8eb7..298dc68d7 100644 --- a/component/src/test/java/com/alfresco/content/app/component/SearchUserGroupComponentViewModelTest.kt +++ b/component/src/test/java/com/alfresco/content/app/component/SearchUserGroupComponentViewModelTest.kt @@ -16,7 +16,6 @@ import org.junit.ClassRule import org.junit.Test class SearchUserGroupComponentViewModelTest { - private lateinit var viewModel: SearchUserGroupComponentViewModel private lateinit var state: SearchUserGroupComponentState diff --git a/config/spotless.gradle b/config/spotless.gradle index 0c1d65226..03033e6a5 100644 --- a/config/spotless.gradle +++ b/config/spotless.gradle @@ -3,7 +3,10 @@ apply plugin: 'com.diffplug.spotless' spotless { kotlin { target '**/*.kt' + targetExclude("**src/main/kotlin/com/alfresco/content/process/ui/**/**.kt") + ktlint() + trimTrailingWhitespace() indentWithSpaces() endWithNewline() diff --git a/data/src/main/kotlin/com/alfresco/content/data/AnalyticsManager.kt b/data/src/main/kotlin/com/alfresco/content/data/AnalyticsManager.kt index b1f8ace23..a3e35b23b 100644 --- a/data/src/main/kotlin/com/alfresco/content/data/AnalyticsManager.kt +++ b/data/src/main/kotlin/com/alfresco/content/data/AnalyticsManager.kt @@ -13,7 +13,6 @@ import kotlinx.coroutines.launch * Marked as AnalyticsManager class */ class AnalyticsManager(otherSession: Session? = null) { - lateinit var session: Session private val coroutineScope = CoroutineScope(Dispatchers.Main) @@ -33,7 +32,11 @@ class AnalyticsManager(otherSession: Session? = null) { /** * analytics event for preview file */ - fun previewFile(fileMimeType: String, fileExtension: String, success: Boolean) { + fun previewFile( + fileMimeType: String, + fileExtension: String, + success: Boolean, + ) { val params = repository.defaultParams() params.putString(Parameters.FileMimeType.value, fileMimeType) params.putString(Parameters.FileExtension.value, fileExtension) @@ -45,7 +48,11 @@ class AnalyticsManager(otherSession: Session? = null) { /** * analytics for multiple actions */ - fun fileActionEvent(fileMimeType: String = "", fileExtension: String = "", eventName: EventName) { + fun fileActionEvent( + fileMimeType: String = "", + fileExtension: String = "", + eventName: EventName, + ) { val params = repository.defaultParams() params.putString(Parameters.FileMimeType.value, fileMimeType) params.putString(Parameters.FileExtension.value, fileExtension) @@ -104,7 +111,10 @@ class AnalyticsManager(otherSession: Session? = null) { /** * analytics for multiple screen view */ - fun screenViewEvent(pageViewName: PageView, noOfFiles: Int = -1) { + fun screenViewEvent( + pageViewName: PageView, + noOfFiles: Int = -1, + ) { val params = repository.defaultParams() if (noOfFiles > -1) { @@ -118,7 +128,12 @@ class AnalyticsManager(otherSession: Session? = null) { /** * analytics for API tracker */ - fun apiTracker(apiName: APIEvent, status: Boolean = false, size: String = "", outcome: String = "") { + fun apiTracker( + apiName: APIEvent, + status: Boolean = false, + size: String = "", + outcome: String = "", + ) { val apiTrackName = if (status) "${apiName.value}_success".lowercase() else "${apiName.value}_fail".lowercase() val params = repository.defaultParams() diff --git a/data/src/main/kotlin/com/alfresco/content/data/AnalyticsRepository.kt b/data/src/main/kotlin/com/alfresco/content/data/AnalyticsRepository.kt index b5ffd0810..1f711aaa2 100644 --- a/data/src/main/kotlin/com/alfresco/content/data/AnalyticsRepository.kt +++ b/data/src/main/kotlin/com/alfresco/content/data/AnalyticsRepository.kt @@ -16,7 +16,6 @@ import com.google.firebase.ktx.Firebase * Marked as AnalyticsRepository class */ class AnalyticsRepository(val session: Session) { - private val context get() = session.context private val firebaseAnalytics: FirebaseAnalytics = Firebase.analytics @@ -33,11 +32,12 @@ class AnalyticsRepository(val session: Session) { return "${manufacturer.uppercase()} $model" } - private fun deviceOS() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - "android: ${Build.VERSION.RELEASE_OR_CODENAME}" - } else { - "android: ${Build.VERSION.RELEASE}" - } + private fun deviceOS() = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + "android: ${Build.VERSION.RELEASE_OR_CODENAME}" + } else { + "android: ${Build.VERSION.RELEASE}" + } private fun deviceNetwork(): String { val cm = context.getSystemService() ?: return NetworkStatus.NOT_CONNECTED.name @@ -60,21 +60,25 @@ class AnalyticsRepository(val session: Session) { * default parameters added in bundle for analytics */ fun defaultParams(): Bundle { - val bundle = Bundle().apply { - putString(DefaultParameters.ServerURL.value, serverURL()) - putString(DefaultParameters.DeviceName.value, deviceName()) - putString(DefaultParameters.DeviceOS.value, deviceOS()) - putString(DefaultParameters.DeviceNetwork.value, deviceNetwork()) - putString(DefaultParameters.AppVersion.value, appVersion()) - putString(DefaultParameters.DeviceID.value, deviceID()) - } + val bundle = + Bundle().apply { + putString(DefaultParameters.ServerURL.value, serverURL()) + putString(DefaultParameters.DeviceName.value, deviceName()) + putString(DefaultParameters.DeviceOS.value, deviceOS()) + putString(DefaultParameters.DeviceNetwork.value, deviceNetwork()) + putString(DefaultParameters.AppVersion.value, appVersion()) + putString(DefaultParameters.DeviceID.value, deviceID()) + } return bundle } /** * It will get triggered to log analytics on firebase console */ - fun logEvent(type: String, params: Bundle) { + fun logEvent( + type: String, + params: Bundle, + ) { firebaseAnalytics.logEvent(type, params) } } diff --git a/data/src/main/kotlin/com/alfresco/content/data/AppConfigUtils.kt b/data/src/main/kotlin/com/alfresco/content/data/AppConfigUtils.kt index f6146f9ec..697ef464a 100644 --- a/data/src/main/kotlin/com/alfresco/content/data/AppConfigUtils.kt +++ b/data/src/main/kotlin/com/alfresco/content/data/AppConfigUtils.kt @@ -42,7 +42,10 @@ fun isTimeToFetchConfig(previousFetchTime: Long): Boolean { * @property fileName * get the Json file from the asset folder */ -fun getJsonDataFromAsset(context: Context, fileName: String): String? { +fun getJsonDataFromAsset( + context: Context, + fileName: String, +): String? { val jsonString: String try { val inputStream = context.assets.open(fileName) @@ -74,7 +77,10 @@ inline fun getModelFromStringJSON(jsonFileString: String): T { * @property jsonFileString * Save AppConfigJSON to the internal storage */ -fun saveJSONToInternalDirectory(context: Context, jsonFileString: String) { +fun saveJSONToInternalDirectory( + context: Context, + jsonFileString: String, +) { val fileDirectory = getAppConfigParentDirectory(context) if (fileDirectory != null && !fileDirectory.exists()) { fileDirectory.mkdirs() @@ -131,31 +137,40 @@ fun retrieveJSONFromInternalDirectory(context: Context): String { */ inline fun getJSONFromModel(model: T): String = Gson().toJson(model) -val gson: Gson = GsonBuilder() - .registerTypeAdapter( - ZonedDateTime::class.java, - object : TypeAdapter() { - override fun write(out: JsonWriter, value: ZonedDateTime?) { - out.value(value.toString()) - } - - @RequiresApi(Build.VERSION_CODES.O) - override fun read(inType: JsonReader): ZonedDateTime? { - return ZonedDateTime.parse(inType.nextString(), formatter) - } - }, - ) - .enableComplexMapKeySerialization() - .create() +val gson: Gson = + GsonBuilder() + .registerTypeAdapter( + ZonedDateTime::class.java, + object : TypeAdapter() { + override fun write( + out: JsonWriter, + value: ZonedDateTime?, + ) { + out.value(value.toString()) + } + + @RequiresApi(Build.VERSION_CODES.O) + override fun read(inType: JsonReader): ZonedDateTime? { + return ZonedDateTime.parse(inType.nextString(), formatter) + } + }, + ) + .enableComplexMapKeySerialization() + .create() @RequiresApi(Build.VERSION_CODES.O) -private val formatter = DateTimeFormatterBuilder() - .append(DateTimeFormatter.ISO_LOCAL_DATE_TIME) - .optionalStart().appendOffset("+HHMM", "Z").optionalEnd() - .toFormatter() +private val formatter = + DateTimeFormatterBuilder() + .append(DateTimeFormatter.ISO_LOCAL_DATE_TIME) + .optionalStart().appendOffset("+HHMM", "Z").optionalEnd() + .toFormatter() // Function to store a JSON object -fun saveJsonToSharedPrefs(context: Context, key: String, obj: Any) { +fun saveJsonToSharedPrefs( + context: Context, + key: String, + obj: Any, +) { val sharedPreferences: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) val editor = sharedPreferences.edit() val gson = Gson() @@ -169,7 +184,10 @@ fun saveJsonToSharedPrefs(context: Context, key: String, obj: Any) { } // Function to retrieve a JSON object -inline fun getJsonFromSharedPrefs(context: Context, key: String): T? { +inline fun getJsonFromSharedPrefs( + context: Context, + key: String, +): T? { val sharedPreferences: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) val gson = Gson() diff --git a/data/src/main/kotlin/com/alfresco/content/data/AttachFolderSearchData.kt b/data/src/main/kotlin/com/alfresco/content/data/AttachFolderSearchData.kt index 026e638a8..5a10db0a6 100644 --- a/data/src/main/kotlin/com/alfresco/content/data/AttachFolderSearchData.kt +++ b/data/src/main/kotlin/com/alfresco/content/data/AttachFolderSearchData.kt @@ -3,4 +3,5 @@ package com.alfresco.content.data import com.alfresco.content.data.payloads.FieldsData data class AttachFolderSearchData(val entry: Entry? = null) + data class AttachFilesData(val field: FieldsData? = null) diff --git a/data/src/main/kotlin/com/alfresco/content/data/AuthenticationRepository.kt b/data/src/main/kotlin/com/alfresco/content/data/AuthenticationRepository.kt index 0d88bca4c..11c3471d7 100644 --- a/data/src/main/kotlin/com/alfresco/content/data/AuthenticationRepository.kt +++ b/data/src/main/kotlin/com/alfresco/content/data/AuthenticationRepository.kt @@ -14,7 +14,6 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch class AuthenticationRepository { - lateinit var session: Session private val coroutineScope = CoroutineScope(Dispatchers.Main) diff --git a/data/src/main/kotlin/com/alfresco/content/data/BrowseRepository.kt b/data/src/main/kotlin/com/alfresco/content/data/BrowseRepository.kt index cb3b9435d..0bcffe010 100644 --- a/data/src/main/kotlin/com/alfresco/content/data/BrowseRepository.kt +++ b/data/src/main/kotlin/com/alfresco/content/data/BrowseRepository.kt @@ -29,7 +29,6 @@ import java.io.File * Mark as BrowseRepository */ class BrowseRepository(otherSession: Session? = null) { - lateinit var session: Session private val coroutineScope = CoroutineScope(Dispatchers.Main) @@ -60,33 +59,38 @@ class BrowseRepository(otherSession: Session? = null) { val myFilesNodeId: String get() = session.account.myFiles ?: "" - suspend fun myFilesNodeId() = - service.getMyNode().entry.id - - suspend fun fetchFolderItems(folderId: String, skipCount: Int, maxItems: Int) = - ResponsePaging.with( - service.listNodeChildren( - folderId, - skipCount, - maxItems, - include = extraFields(), - includeSource = true, - ), - ) + suspend fun myFilesNodeId() = service.getMyNode().entry.id + + suspend fun fetchFolderItems( + folderId: String, + skipCount: Int, + maxItems: Int, + ) = ResponsePaging.with( + service.listNodeChildren( + folderId, + skipCount, + maxItems, + include = extraFields(), + includeSource = true, + ), + ) /** * fetching the folder items */ - suspend fun fetchExtensionFolderItems(folderId: String, skipCount: Int, maxItems: Int) = - ResponsePaging.withExtension( - service.listNodeChildren( - folderId, - skipCount, - maxItems, - include = extraFields(), - includeSource = true, - ), - ) + suspend fun fetchExtensionFolderItems( + folderId: String, + skipCount: Int, + maxItems: Int, + ) = ResponsePaging.withExtension( + service.listNodeChildren( + folderId, + skipCount, + maxItems, + include = extraFields(), + includeSource = true, + ), + ) suspend fun fetchLibraryDocumentsFolder(siteId: String) = Entry.with( @@ -97,19 +101,21 @@ class BrowseRepository(otherSession: Session? = null) { ).entry, ) - suspend fun fetchLibraryItems(siteId: String, skipCount: Int, maxItems: Int) = - ResponsePaging.with( - service.listNodeChildren( - siteId, - skipCount, - maxItems, - include = extraFields(), - relativePath = LIB_DOCUMENTS_PATH, - ), - ) - - private fun extraFields() = - AlfrescoApi.csvQueryParam("path", "isFavorite", "allowableOperations", "properties") + suspend fun fetchLibraryItems( + siteId: String, + skipCount: Int, + maxItems: Int, + ) = ResponsePaging.with( + service.listNodeChildren( + siteId, + skipCount, + maxItems, + include = extraFields(), + relativePath = LIB_DOCUMENTS_PATH, + ), + ) + + private fun extraFields() = AlfrescoApi.csvQueryParam("path", "isFavorite", "allowableOperations", "properties") suspend fun fetchEntry(entryId: String) = Entry.with( @@ -123,7 +129,10 @@ class BrowseRepository(otherSession: Session? = null) { service.deleteNode(entry.id, null) } - suspend fun createEntry(local: Entry, file: File): Entry { + suspend fun createEntry( + local: Entry, + file: File, + ): Entry { // TODO: Support creating empty entries and folders requireNotNull(local.parentId) requireNotNull(local.mimeType) @@ -149,18 +158,25 @@ class BrowseRepository(otherSession: Session? = null) { ) } - suspend fun createFolder(name: String, description: String, parentId: String?, autoRename: Boolean = true): Entry { - val nodeBodyCreate = NodeBodyCreate( - name = name, - nodeType = "cm:folder", - properties = mapOf("cm:title" to name, "cm:description" to description), - ) - - val response = service.createNode( - nodeId = requireNotNull(parentId), - nodeBodyCreate = nodeBodyCreate, - autoRename = autoRename, - ) + suspend fun createFolder( + name: String, + description: String, + parentId: String?, + autoRename: Boolean = true, + ): Entry { + val nodeBodyCreate = + NodeBodyCreate( + name = name, + nodeType = "cm:folder", + properties = mapOf("cm:title" to name, "cm:description" to description), + ) + + val response = + service.createNode( + nodeId = requireNotNull(parentId), + nodeBodyCreate = nodeBodyCreate, + autoRename = autoRename, + ) return Entry.with(response.entry) } @@ -168,12 +184,18 @@ class BrowseRepository(otherSession: Session? = null) { /** * update the file and folders data by calling update node api */ - suspend fun updateFileFolder(name: String, description: String, nodeId: String?, nodeType: String): Entry { - val nodeBodyUpdate = NodeBodyUpdate( - name = name, - nodeType = nodeType, - properties = mapOf("cm:title" to name, "cm:description" to description), - ) + suspend fun updateFileFolder( + name: String, + description: String, + nodeId: String?, + nodeType: String, + ): Entry { + val nodeBodyUpdate = + NodeBodyUpdate( + name = name, + nodeType = nodeType, + properties = mapOf("cm:title" to name, "cm:description" to description), + ) return Entry.with( service.updateNode( @@ -186,7 +208,10 @@ class BrowseRepository(otherSession: Session? = null) { /** * executing api for moving the items (file or folder) */ - suspend fun moveNode(entryId: String, targetParentId: String): Entry { + suspend fun moveNode( + entryId: String, + targetParentId: String, + ): Entry { return Entry.with(service.moveNode(entryId, NodeBodyMove(targetParentId)).entry) } diff --git a/data/src/main/kotlin/com/alfresco/content/data/CommentEntry.kt b/data/src/main/kotlin/com/alfresco/content/data/CommentEntry.kt index 46cff4247..625261ff0 100644 --- a/data/src/main/kotlin/com/alfresco/content/data/CommentEntry.kt +++ b/data/src/main/kotlin/com/alfresco/content/data/CommentEntry.kt @@ -15,7 +15,6 @@ data class CommentEntry( val created: ZonedDateTime? = null, val userGroupDetails: UserGroupDetails? = null, ) : Parcelable { - companion object { /** * return the CommentEntry obj after converting the data from CommentDataEntry obj @@ -32,7 +31,10 @@ data class CommentEntry( /** * returns the CommentEntry obj by adding message */ - fun addComment(message: String, userGroupDetails: UserGroupDetails): CommentEntry { + fun addComment( + message: String, + userGroupDetails: UserGroupDetails, + ): CommentEntry { return CommentEntry( message = message, userGroupDetails = userGroupDetails, diff --git a/data/src/main/kotlin/com/alfresco/content/data/CommonRepository.kt b/data/src/main/kotlin/com/alfresco/content/data/CommonRepository.kt index 36b290a03..f4f93f226 100644 --- a/data/src/main/kotlin/com/alfresco/content/data/CommonRepository.kt +++ b/data/src/main/kotlin/com/alfresco/content/data/CommonRepository.kt @@ -6,45 +6,49 @@ import com.alfresco.content.session.SessionManager import java.net.URL class CommonRepository(val session: Session = SessionManager.requireSession) { - - private val fileMenuSingleActions = listOf( - MenuActions.OpenWith, MenuActions.Download, MenuActions.AddFavourite, MenuActions.RemoveFavourite, MenuActions.StartProcess, - MenuActions.Rename, MenuActions.Move, MenuActions.AddOffline, MenuActions.RemoveOffline, MenuActions.Trash, - ) - - private val fileMenuMultiActions = listOf( - MenuActions.AddFavourite, - MenuActions.RemoveFavourite, - MenuActions.StartProcess, - MenuActions.Move, - MenuActions.AddOffline, - MenuActions.RemoveOffline, - MenuActions.Trash, - ) - - private val folderMenuSingleActions = listOf( - MenuActions.AddFavourite, - MenuActions.RemoveFavourite, - MenuActions.Rename, - MenuActions.Move, - MenuActions.AddOffline, - MenuActions.RemoveOffline, - MenuActions.Trash, - ) - - private val folderMenuMultiActions = listOf( - MenuActions.AddFavourite, - MenuActions.RemoveFavourite, - MenuActions.Move, - MenuActions.AddOffline, - MenuActions.RemoveOffline, - MenuActions.Trash, - ) - - private val trashMenuActions = listOf( - MenuActions.PermanentlyDelete, - MenuActions.Restore, - ) + private val fileMenuSingleActions = + listOf( + MenuActions.OpenWith, MenuActions.Download, MenuActions.AddFavourite, MenuActions.RemoveFavourite, MenuActions.StartProcess, + MenuActions.Rename, MenuActions.Move, MenuActions.AddOffline, MenuActions.RemoveOffline, MenuActions.Trash, + ) + + private val fileMenuMultiActions = + listOf( + MenuActions.AddFavourite, + MenuActions.RemoveFavourite, + MenuActions.StartProcess, + MenuActions.Move, + MenuActions.AddOffline, + MenuActions.RemoveOffline, + MenuActions.Trash, + ) + + private val folderMenuSingleActions = + listOf( + MenuActions.AddFavourite, + MenuActions.RemoveFavourite, + MenuActions.Rename, + MenuActions.Move, + MenuActions.AddOffline, + MenuActions.RemoveOffline, + MenuActions.Trash, + ) + + private val folderMenuMultiActions = + listOf( + MenuActions.AddFavourite, + MenuActions.RemoveFavourite, + MenuActions.Move, + MenuActions.AddOffline, + MenuActions.RemoveOffline, + MenuActions.Trash, + ) + + private val trashMenuActions = + listOf( + MenuActions.PermanentlyDelete, + MenuActions.Restore, + ) private val service: MobileConfigApi by lazy { session.createService(MobileConfigApi::class.java) @@ -60,7 +64,10 @@ class CommonRepository(val session: Session = SessionManager.requireSession) { saveJsonToSharedPrefs(session.context, KEY_FEATURES_MOBILE, data) } - fun isAllMultiActionsEnabled(serverList: List?, entries: List = emptyList()): Boolean { + fun isAllMultiActionsEnabled( + serverList: List?, + entries: List = emptyList(), + ): Boolean { val list = mutableListOf() if (entries.isEmpty() || serverList?.isEmpty() == true) { @@ -72,33 +79,40 @@ class CommonRepository(val session: Session = SessionManager.requireSession) { val hasFoldersOnly = entries.all { it.isFile } if (hasTrashedFilesFolders) { - val enabledActions = trashMenuActions.filter { menuAction -> - serverList?.any { it.id.equals(menuAction.value(), ignoreCase = true) && it.enabled } == true - } + val enabledActions = + trashMenuActions.filter { menuAction -> + serverList?.any { it.id.equals(menuAction.value(), ignoreCase = true) && it.enabled } == true + } list.addAll(enabledActions) return list.isNotEmpty() } else if (hasFilesOnly) { - val enabledActions = fileMenuMultiActions.filter { menuAction -> - serverList?.any { it.id.equals(menuAction.value(), ignoreCase = true) && it.enabled } == true - } + val enabledActions = + fileMenuMultiActions.filter { menuAction -> + serverList?.any { it.id.equals(menuAction.value(), ignoreCase = true) && it.enabled } == true + } list.addAll(enabledActions) return list.isNotEmpty() } else if (hasFoldersOnly) { - val enabledActions = folderMenuMultiActions.filter { menuAction -> - serverList?.any { it.id.equals(menuAction.value(), ignoreCase = true) && it.enabled } == true - } + val enabledActions = + folderMenuMultiActions.filter { menuAction -> + serverList?.any { it.id.equals(menuAction.value(), ignoreCase = true) && it.enabled } == true + } list.addAll(enabledActions) return list.isNotEmpty() } else { - val enabledActions = folderMenuMultiActions.filter { menuAction -> - serverList?.any { it.id.equals(menuAction.value(), ignoreCase = true) && it.enabled } == true - } + val enabledActions = + folderMenuMultiActions.filter { menuAction -> + serverList?.any { it.id.equals(menuAction.value(), ignoreCase = true) && it.enabled } == true + } list.addAll(enabledActions) return list.isNotEmpty() } } - fun isAllSingleActionsEnabled(serverList: List?, entry: Entry): Boolean { + fun isAllSingleActionsEnabled( + serverList: List?, + entry: Entry, + ): Boolean { val list = mutableListOf() if (serverList?.isEmpty() == true) { @@ -106,27 +120,33 @@ class CommonRepository(val session: Session = SessionManager.requireSession) { } if (entry.isTrashed) { - val enabledActions = trashMenuActions.filter { menuAction -> - serverList?.any { it.id.equals(menuAction.value(), ignoreCase = true) && it.enabled } == true - } + val enabledActions = + trashMenuActions.filter { menuAction -> + serverList?.any { it.id.equals(menuAction.value(), ignoreCase = true) && it.enabled } == true + } list.addAll(enabledActions) return list.isNotEmpty() } else if (entry.isFile) { - val enabledActions = fileMenuSingleActions.filter { menuAction -> - serverList?.any { it.id.equals(menuAction.value(), ignoreCase = true) && it.enabled } == true - } + val enabledActions = + fileMenuSingleActions.filter { menuAction -> + serverList?.any { it.id.equals(menuAction.value(), ignoreCase = true) && it.enabled } == true + } list.addAll(enabledActions) return list.isNotEmpty() } else { - val enabledActions = folderMenuSingleActions.filter { menuAction -> - serverList?.any { it.id.equals(menuAction.value(), ignoreCase = true) && it.enabled } == true - } + val enabledActions = + folderMenuSingleActions.filter { menuAction -> + serverList?.any { it.id.equals(menuAction.value(), ignoreCase = true) && it.enabled } == true + } list.addAll(enabledActions) return list.isNotEmpty() } } - fun isActionEnabled(action: MenuActions, serverList: List?): Boolean { + fun isActionEnabled( + action: MenuActions, + serverList: List?, + ): Boolean { return serverList?.find { it.id.lowercase() == action.value() }?.enabled == true } diff --git a/data/src/main/kotlin/com/alfresco/content/data/ContentEntry.kt b/data/src/main/kotlin/com/alfresco/content/data/ContentEntry.kt index 2886d6acf..d52a183cb 100644 --- a/data/src/main/kotlin/com/alfresco/content/data/ContentEntry.kt +++ b/data/src/main/kotlin/com/alfresco/content/data/ContentEntry.kt @@ -22,7 +22,6 @@ data class ContentEntry( val previewStatus: String? = "", val thumbnailStatus: String? = "", ) : Parcelable { - companion object { /** * return the ContentEntry obj after converting the data from ContentDataEntry obj diff --git a/data/src/main/kotlin/com/alfresco/content/data/ContextualActionData.kt b/data/src/main/kotlin/com/alfresco/content/data/ContextualActionData.kt index 01e352ce0..fdaf5ac19 100644 --- a/data/src/main/kotlin/com/alfresco/content/data/ContextualActionData.kt +++ b/data/src/main/kotlin/com/alfresco/content/data/ContextualActionData.kt @@ -10,8 +10,11 @@ data class ContextualActionData( val appMenu: List = emptyList(), ) : Parcelable { companion object { - - fun withEntries(entries: List, isMultiSelection: Boolean = false, mobileConfigData: MobileConfigDataEntry? = null): ContextualActionData { + fun withEntries( + entries: List, + isMultiSelection: Boolean = false, + mobileConfigData: MobileConfigDataEntry? = null, + ): ContextualActionData { return ContextualActionData( entries = entries, isMultiSelection = isMultiSelection, diff --git a/data/src/main/kotlin/com/alfresco/content/data/Entry.kt b/data/src/main/kotlin/com/alfresco/content/data/Entry.kt index 75b64d9c5..dca69fef4 100644 --- a/data/src/main/kotlin/com/alfresco/content/data/Entry.kt +++ b/data/src/main/kotlin/com/alfresco/content/data/Entry.kt @@ -75,7 +75,7 @@ data class Entry( val isDocFile: Boolean = false, @Transient val parentPaths: MutableList = mutableListOf(), - /*APS content data*/ + // APS content data @Convert(converter = BoxDateConverter::class, dbType = Long::class) val created: ZonedDateTime? = null, @Transient @@ -95,7 +95,6 @@ data class Entry( var observerID: String = "", var isMultiple: Boolean = false, ) : ParentEntry(), Parcelable { - val isSynced: Boolean get() = offlineStatus == OfflineStatus.SYNCED @@ -164,7 +163,11 @@ data class Entry( ; companion object { - fun from(value: String, isFile: Boolean = false, isFolder: Boolean = false): Type { + fun from( + value: String, + isFile: Boolean = false, + isFolder: Boolean = false, + ): Type { when (value) { "cm:content" -> return FILE "cm:folder" -> return FOLDER @@ -221,7 +224,10 @@ data class Entry( /** * return the Entry obj with isExtension value true */ - fun with(result: ResultNode, isExtension: Boolean): Entry { + fun with( + result: ResultNode, + isExtension: Boolean, + ): Entry { return Entry( result.id, result.parentId, @@ -263,7 +269,10 @@ data class Entry( /** * returns the Entry obj with extension value true */ - fun with(node: NodeChildAssociation, isExtension: Boolean): Entry { + fun with( + node: NodeChildAssociation, + isExtension: Boolean, + ): Entry { return Entry( node.id, node.parentId, @@ -388,7 +397,11 @@ data class Entry( ).withOfflineStatus() } - fun with(contentEntry: ContentEntry, observerID: String, parentId: String?): Entry { + fun with( + contentEntry: ContentEntry, + observerID: String, + parentId: String?, + ): Entry { return Entry( parentId = parentId, id = contentEntry.id.toString(), @@ -401,14 +414,17 @@ data class Entry( observerID = observerID, uploadServer = UploadServerType.UPLOAD_TO_PROCESS, offlineStatus = OfflineStatus.UNDEFINED, - ) } /** * return the Entry obj by using the contentEntry obj. */ - fun convertContentEntryToEntry(contentEntry: Entry, isDocFile: Boolean, uploadServer: UploadServerType): Entry { + fun convertContentEntryToEntry( + contentEntry: Entry, + isDocFile: Boolean, + uploadServer: UploadServerType, + ): Entry { val id = if (contentEntry.sourceId.isNullOrEmpty()) contentEntry.id else contentEntry.sourceId return Entry( id = id, @@ -424,7 +440,12 @@ data class Entry( /** * return the ContentEntry obj after converting the data from ContentDataEntry obj */ - fun with(data: ContentDataEntry, parentId: String? = null, uploadServer: UploadServerType, observerID: String = ""): Entry { + fun with( + data: ContentDataEntry, + parentId: String? = null, + uploadServer: UploadServerType, + observerID: String = "", + ): Entry { return Entry( id = data.id?.toString() ?: "", parentId = parentId, @@ -448,7 +469,11 @@ data class Entry( /** * return the Entry obj */ - fun with(data: Entry, parentId: String? = null, observerID: String = ""): Entry { + fun with( + data: Entry, + parentId: String? = null, + observerID: String = "", + ): Entry { return Entry( id = data.id, parentId = parentId, @@ -475,7 +500,10 @@ data class Entry( /** * update entry after downloading content from process services. */ - fun updateDownloadEntry(entry: Entry, path: String): Entry { + fun updateDownloadEntry( + entry: Entry, + path: String, + ): Entry { return Entry(id = entry.id, name = entry.name, mimeType = entry.mimeType, path = path, uploadServer = entry.uploadServer) } @@ -489,7 +517,11 @@ data class Entry( /** * return the default Workflow content entry obj */ - fun defaultWorkflowEntry(id: String?, fieldId: String = "", isMultiple: Boolean = false): Entry { + fun defaultWorkflowEntry( + id: String?, + fieldId: String = "", + isMultiple: Boolean = false, + ): Entry { return Entry(uploadServer = UploadServerType.UPLOAD_TO_PROCESS, parentId = id, observerID = fieldId, isMultiple = isMultiple) } @@ -505,14 +537,11 @@ data class Entry( ?.reduce { out, el -> "$out \u203A $el" } } - private fun canDelete(operations: List?) = - operations?.contains("delete") ?: false + private fun canDelete(operations: List?) = operations?.contains("delete") ?: false - private fun canCreate(operations: List?) = - operations?.contains("create") ?: false + private fun canCreate(operations: List?) = operations?.contains("create") ?: false - private fun canCreateUpdate(operations: List?) = - listOf("create", "update").any { operations?.contains(it) == true } + private fun canCreateUpdate(operations: List?) = listOf("create", "update").any { operations?.contains(it) == true } private fun propertiesCompat(src: Map?): MutableMap { val map = mutableMapOf() @@ -588,8 +617,7 @@ class BoxOfflineStatusConverter : PropertyConverter { OfflineStatus.UNDEFINED } - override fun convertToDatabaseValue(entityProperty: OfflineStatus?) = - entityProperty?.value() + override fun convertToDatabaseValue(entityProperty: OfflineStatus?) = entityProperty?.value() } class BoxEntryTypeConverter : PropertyConverter { @@ -600,8 +628,7 @@ class BoxEntryTypeConverter : PropertyConverter { Entry.Type.UNKNOWN } - override fun convertToDatabaseValue(entityProperty: Entry.Type?) = - entityProperty?.name?.lowercase() + override fun convertToDatabaseValue(entityProperty: Entry.Type?) = entityProperty?.name?.lowercase() } /** @@ -615,8 +642,7 @@ class BoxUploadServerTypeConverter : PropertyConverter UploadServerType.NONE } - override fun convertToDatabaseValue(entityProperty: UploadServerType?) = - entityProperty?.value() + override fun convertToDatabaseValue(entityProperty: UploadServerType?) = entityProperty?.value() } object DateParceler : Parceler { @@ -626,7 +652,10 @@ object DateParceler : Parceler { return ZonedDateTime.ofInstant(Instant.ofEpochSecond(parcel.readLong()), zone) } - override fun ZonedDateTime.write(parcel: Parcel, flags: Int) { + override fun ZonedDateTime.write( + parcel: Parcel, + flags: Int, + ) { parcel.writeLong(this.toInstant().epochSecond) } } @@ -641,17 +670,17 @@ class BoxDateConverter : PropertyConverter { null } - override fun convertToDatabaseValue(entityProperty: ZonedDateTime?) = - entityProperty?.toInstant()?.epochSecond + override fun convertToDatabaseValue(entityProperty: ZonedDateTime?) = entityProperty?.toInstant()?.epochSecond } class PropertiesConverter : PropertyConverter, String> { private var moshi = Moshi.Builder().build() - private var type = Types.newParameterizedType( - Map::class.java, - String::class.java, - String::class.java, - ) + private var type = + Types.newParameterizedType( + Map::class.java, + String::class.java, + String::class.java, + ) private var jsonAdapter: JsonAdapter> = moshi.adapter(type) override fun convertToEntityProperty(databaseValue: String?): Map { diff --git a/data/src/main/kotlin/com/alfresco/content/data/FacetContext.kt b/data/src/main/kotlin/com/alfresco/content/data/FacetContext.kt index 153e774b0..4b02a9b11 100644 --- a/data/src/main/kotlin/com/alfresco/content/data/FacetContext.kt +++ b/data/src/main/kotlin/com/alfresco/content/data/FacetContext.kt @@ -104,7 +104,9 @@ data class Facets( result.type, if (result.label?.lowercase().equals("search.facet_fields.size")) { result.buckets?.map { Buckets.updateBucketLabel(it) } ?: emptyList() - } else result.buckets, + } else { + result.buckets + }, ) } } @@ -128,7 +130,13 @@ data class Buckets( * returns the Buckets type of data using ResultBucketsBuckets */ fun with(result: ResultBucketsBuckets): Buckets { - return Buckets(originalLabel = result.label, label = result.label, filterQuery = result.filterQuery, count = result.count, display = result.display) + return Buckets( + originalLabel = result.label, + label = result.label, + filterQuery = result.filterQuery, + count = result.count, + display = result.display, + ) } /** diff --git a/data/src/main/kotlin/com/alfresco/content/data/FavoritesRepository.kt b/data/src/main/kotlin/com/alfresco/content/data/FavoritesRepository.kt index 270b55a8f..f83e5ff3a 100644 --- a/data/src/main/kotlin/com/alfresco/content/data/FavoritesRepository.kt +++ b/data/src/main/kotlin/com/alfresco/content/data/FavoritesRepository.kt @@ -13,7 +13,6 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch class FavoritesRepository { - lateinit var session: Session private val coroutineScope = CoroutineScope(Dispatchers.Main) @@ -32,7 +31,10 @@ class FavoritesRepository { session.createService(FavoritesApi::class.java) } - suspend fun getFavorites(skipCount: Int, maxItems: Int): ResponsePaging { + suspend fun getFavorites( + skipCount: Int, + maxItems: Int, + ): ResponsePaging { val where = "(EXISTS(target/file) OR EXISTS(target/folder))" val include = AlfrescoApi.csvQueryParam("path", "allowableOperations") val orderBy = listOf("title ASC") @@ -49,7 +51,10 @@ class FavoritesRepository { ) } - suspend fun getFavoriteLibraries(skipCount: Int, maxItems: Int): ResponsePaging { + suspend fun getFavoriteLibraries( + skipCount: Int, + maxItems: Int, + ): ResponsePaging { val where = "(EXISTS(target/site))" val orderBy = listOf("title ASC") return ResponsePaging.with( @@ -64,12 +69,13 @@ class FavoritesRepository { } suspend fun addFavorite(entry: Entry) { - val key = when (entry.type) { - Entry.Type.FILE -> "file" - Entry.Type.FOLDER -> "folder" - Entry.Type.SITE -> "site" - else -> "" - } + val key = + when (entry.type) { + Entry.Type.FILE -> "file" + Entry.Type.FOLDER -> "folder" + Entry.Type.SITE -> "site" + else -> "" + } service.createFavorite( AlfrescoApi.CURRENT_USER, @@ -81,8 +87,7 @@ class FavoritesRepository { service.deleteFavorite(AlfrescoApi.CURRENT_USER, entry.id) } - suspend fun getFavoriteSite(id: String) = - Entry.with(service.getFavorite(AlfrescoApi.CURRENT_USER, id).entry) + suspend fun getFavoriteSite(id: String) = Entry.with(service.getFavorite(AlfrescoApi.CURRENT_USER, id).entry) companion object { fun getRepoInstance(): FavoritesRepository? { diff --git a/data/src/main/kotlin/com/alfresco/content/data/LocationData.kt b/data/src/main/kotlin/com/alfresco/content/data/LocationData.kt index a2aab066e..047cfe33d 100644 --- a/data/src/main/kotlin/com/alfresco/content/data/LocationData.kt +++ b/data/src/main/kotlin/com/alfresco/content/data/LocationData.kt @@ -12,7 +12,6 @@ import com.google.android.gms.location.LocationResult import com.google.android.gms.location.LocationServices class LocationData(context: Context) : LiveData() { - private val fusedLocationClient: FusedLocationProviderClient by lazy { LocationServices.getFusedLocationProviderClient(context) } @@ -43,27 +42,30 @@ class LocationData(context: Context) : LiveData() { ) } - private val locationCallback = object : LocationCallback() { - override fun onLocationResult(locationResult: LocationResult) { - for (location in locationResult.locations) { - setLocationData(location) + private val locationCallback = + object : LocationCallback() { + override fun onLocationResult(locationResult: LocationResult) { + for (location in locationResult.locations) { + setLocationData(location) + } } } - } private fun setLocationData(location: Location) { - value = LocationModel( - longitude = location.longitude, - latitude = location.latitude, - ) + value = + LocationModel( + longitude = location.longitude, + latitude = location.latitude, + ) } companion object { - val locationRequest: LocationRequest = LocationRequest.create().apply { - interval = 10000 - fastestInterval = 1000 - priority = LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY - } + val locationRequest: LocationRequest = + LocationRequest.create().apply { + interval = 10000 + fastestInterval = 1000 + priority = LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY + } } } diff --git a/data/src/main/kotlin/com/alfresco/content/data/MobileConfigDataEntry.kt b/data/src/main/kotlin/com/alfresco/content/data/MobileConfigDataEntry.kt index c7ab06a0f..e95878a0c 100644 --- a/data/src/main/kotlin/com/alfresco/content/data/MobileConfigDataEntry.kt +++ b/data/src/main/kotlin/com/alfresco/content/data/MobileConfigDataEntry.kt @@ -10,9 +10,7 @@ import kotlinx.parcelize.Parcelize data class MobileConfigDataEntry( val featuresMobile: MobileFeatures? = null, ) : Parcelable { - companion object { - fun with(configData: MobileConfigData): MobileConfigDataEntry { return MobileConfigDataEntry( featuresMobile = MobileFeatures.with(configData.featuresMobile), @@ -29,15 +27,15 @@ data class MobileConfigDataEntry( data class MobileFeatures( val menus: List = emptyList(), ) : Parcelable { - companion object { fun with(features: Features?): MobileFeatures { return MobileFeatures( - menus = features?.dynamicMenus?.map { - AppMenu.with( - it, - ) - } ?: emptyList(), + menus = + features?.dynamicMenus?.map { + AppMenu.with( + it, + ) + } ?: emptyList(), ) } } diff --git a/data/src/main/kotlin/com/alfresco/content/data/MultiSelection.kt b/data/src/main/kotlin/com/alfresco/content/data/MultiSelection.kt index e2a683561..f2aa13293 100644 --- a/data/src/main/kotlin/com/alfresco/content/data/MultiSelection.kt +++ b/data/src/main/kotlin/com/alfresco/content/data/MultiSelection.kt @@ -4,11 +4,12 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow object MultiSelection { - val multiSelectionChangedFlow = MutableSharedFlow(extraBufferCapacity = 1) val clearSelectionChangedFlow = MutableSharedFlow(extraBufferCapacity = 1) + fun observeMultiSelection(): Flow = multiSelectionChangedFlow + fun observeClearSelection(): Flow = clearSelectionChangedFlow } diff --git a/data/src/main/kotlin/com/alfresco/content/data/OfflineRepository.kt b/data/src/main/kotlin/com/alfresco/content/data/OfflineRepository.kt index 9cce927e1..f4d0d04ca 100644 --- a/data/src/main/kotlin/com/alfresco/content/data/OfflineRepository.kt +++ b/data/src/main/kotlin/com/alfresco/content/data/OfflineRepository.kt @@ -22,7 +22,6 @@ import kotlinx.coroutines.launch import java.io.File class OfflineRepository(otherSession: Session? = null) { - lateinit var session: Session private val coroutineScope = CoroutineScope(Dispatchers.Main) @@ -63,7 +62,10 @@ class OfflineRepository(otherSession: Session? = null) { .build() .findFirst() - fun markForSync(entry: Entry) = update(entry.copy(isOffline = true, offlineStatus = OfflineStatus.PENDING, isSelectedForMultiSelection = false)) + fun markForSync(entry: Entry) = + update( + entry.copy(isOffline = true, offlineStatus = OfflineStatus.PENDING, isSelectedForMultiSelection = false), + ) fun removeFromSync(entry: Entry) = update(entry.copy(isOffline = false, isSelectedForMultiSelection = false)) @@ -75,11 +77,12 @@ class OfflineRepository(otherSession: Session? = null) { * updating the total transfer count */ fun updateTransferSize(size: Int) { - val list = box.query() - .equal(Entry_.isTotalEntry, true) - .equal(Entry_.uploadServer, UploadServerType.DEFAULT.value(), StringOrder.CASE_SENSITIVE) - .build() - .find() + val list = + box.query() + .equal(Entry_.isTotalEntry, true) + .equal(Entry_.uploadServer, UploadServerType.DEFAULT.value(), StringOrder.CASE_SENSITIVE) + .build() + .find() if (list.isEmpty()) { val entry = Entry(totalCount = size, isTotalEntry = true) entry.also { box.put(it) } @@ -94,33 +97,36 @@ class OfflineRepository(otherSession: Session? = null) { * returns the transfer size count */ fun getTotalTransfersSize(): Int { - val list = box.query() - .equal(Entry_.isTotalEntry, true) - .equal(Entry_.uploadServer, UploadServerType.DEFAULT.value(), StringOrder.CASE_SENSITIVE) - .build() - .find() + val list = + box.query() + .equal(Entry_.isTotalEntry, true) + .equal(Entry_.uploadServer, UploadServerType.DEFAULT.value(), StringOrder.CASE_SENSITIVE) + .build() + .find() return if (list.isEmpty()) 0 else list[0].totalCount } - fun offlineEntries(parentId: String?): Flow = callbackFlow { - val query = offlineEntriesQuery(parentId) - val subscription = query.subscribe().observer { data -> - val count = data.count().toLong() - trySendBlocking( - ResponsePaging( - data, - Pagination( - count, - false, - 0, - count, - count, - ), - ), - ) + fun offlineEntries(parentId: String?): Flow = + callbackFlow { + val query = offlineEntriesQuery(parentId) + val subscription = + query.subscribe().observer { data -> + val count = data.count().toLong() + trySendBlocking( + ResponsePaging( + data, + Pagination( + count, + false, + 0, + count, + count, + ), + ), + ) + } + awaitClose { subscription.cancel() } } - awaitClose { subscription.cancel() } - } private fun offlineEntriesQuery(parentId: String?) = box.query() @@ -209,17 +215,18 @@ class OfflineRepository(otherSession: Session? = null) { requireNotNull(name) requireNotNull(mimeType) - val entry = Entry( - parentId = parentId, - name = name, - type = Entry.Type.FILE, - mimeType = mimeType, - isUpload = true, - offlineStatus = OfflineStatus.PENDING, - isExtension = isExtension, - uploadServer = uploadServerType, - observerID = observerId ?: "", - ) + val entry = + Entry( + parentId = parentId, + name = name, + type = Entry.Type.FILE, + mimeType = mimeType, + isUpload = true, + offlineStatus = OfflineStatus.PENDING, + isExtension = isExtension, + uploadServer = uploadServerType, + observerID = observerId ?: "", + ) if (observerId == null) { clearData() @@ -253,17 +260,18 @@ class OfflineRepository(otherSession: Session? = null) { observerId: String? = null, ) { // TODO: This process may fail resulting in an orphan file? or node? - val entry = Entry( - parentId = parentId, - name = name, - type = Entry.Type.FILE, - mimeType = mimeType, - properties = mapOf("cm:description" to description), - isUpload = true, - offlineStatus = OfflineStatus.PENDING, - uploadServer = uploadServerType, - observerID = observerId ?: "", - ) + val entry = + Entry( + parentId = parentId, + name = name, + type = Entry.Type.FILE, + mimeType = mimeType, + properties = mapOf("cm:description" to description), + isUpload = true, + offlineStatus = OfflineStatus.PENDING, + uploadServer = uploadServerType, + observerID = observerId ?: "", + ) if (observerId == null) { clearData() @@ -288,62 +296,82 @@ class OfflineRepository(otherSession: Session? = null) { .build() .find() - fun fetchProcessEntries(parentId: String, observerId: String): MutableList { - val query = box.query() - .equal(Entry_.parentId, parentId, StringOrder.CASE_SENSITIVE) - .equal(Entry_.observerID, observerId, StringOrder.CASE_SENSITIVE) - .equal(Entry_.uploadServer, UploadServerType.UPLOAD_TO_PROCESS.value(), StringOrder.CASE_SENSITIVE) - .order(Entry_.name) - .build() + fun fetchProcessEntries( + parentId: String, + observerId: String, + ): MutableList { + val query = + box.query() + .equal(Entry_.parentId, parentId, StringOrder.CASE_SENSITIVE) + .equal(Entry_.observerID, observerId, StringOrder.CASE_SENSITIVE) + .equal(Entry_.uploadServer, UploadServerType.UPLOAD_TO_PROCESS.value(), StringOrder.CASE_SENSITIVE) + .order(Entry_.name) + .build() return query.find() } /** * returns the list of uploads which is being uploaded on the server. */ - fun observeUploads(parentId: String, uploadServerType: UploadServerType = UploadServerType.DEFAULT): Flow> = callbackFlow { - val query = box.query() - .equal(Entry_.parentId, parentId, StringOrder.CASE_SENSITIVE) - .equal(Entry_.isUpload, true) - .equal(Entry_.uploadServer, uploadServerType.value(), StringOrder.CASE_SENSITIVE) - .order(Entry_.name) - .build() - val subscription = query.subscribe().observer { - trySendBlocking(it) + fun observeUploads( + parentId: String, + uploadServerType: UploadServerType = UploadServerType.DEFAULT, + ): Flow> = + callbackFlow { + val query = + box.query() + .equal(Entry_.parentId, parentId, StringOrder.CASE_SENSITIVE) + .equal(Entry_.isUpload, true) + .equal(Entry_.uploadServer, uploadServerType.value(), StringOrder.CASE_SENSITIVE) + .order(Entry_.name) + .build() + val subscription = + query.subscribe().observer { + trySendBlocking(it) + } + awaitClose { subscription.cancel() } } - awaitClose { subscription.cancel() } - } /** * returns the list of uploads which is being uploaded on the server. */ - fun observeProcessUploads(parentId: String, uploadServerType: UploadServerType = UploadServerType.DEFAULT, isUpload: Boolean = true): Flow> = callbackFlow { - val query = box.query() - .equal(Entry_.parentId, parentId, StringOrder.CASE_SENSITIVE) - .equal(Entry_.uploadServer, uploadServerType.value(), StringOrder.CASE_SENSITIVE) - .order(Entry_.name) - .build() - val subscription = query.subscribe().observer { - trySendBlocking(it) + fun observeProcessUploads( + parentId: String, + uploadServerType: UploadServerType = UploadServerType.DEFAULT, + isUpload: Boolean = true, + ): Flow> = + callbackFlow { + val query = + box.query() + .equal(Entry_.parentId, parentId, StringOrder.CASE_SENSITIVE) + .equal(Entry_.uploadServer, uploadServerType.value(), StringOrder.CASE_SENSITIVE) + .order(Entry_.name) + .build() + val subscription = + query.subscribe().observer { + trySendBlocking(it) + } + awaitClose { subscription.cancel() } } - awaitClose { subscription.cancel() } - } /** * observer for transfer uploads */ - fun observeTransferUploads(): Flow> = callbackFlow { - val query = box.query() - .equal(Entry_.isUpload, true) - .equal(Entry_.uploadServer, UploadServerType.DEFAULT.value(), StringOrder.CASE_SENSITIVE) - .equal(Entry_.id, "", StringOrder.CASE_SENSITIVE) - .order(Entry_.name) - .build() - val subscription = query.subscribe().observer { - trySendBlocking(it) + fun observeTransferUploads(): Flow> = + callbackFlow { + val query = + box.query() + .equal(Entry_.isUpload, true) + .equal(Entry_.uploadServer, UploadServerType.DEFAULT.value(), StringOrder.CASE_SENSITIVE) + .equal(Entry_.id, "", StringOrder.CASE_SENSITIVE) + .order(Entry_.name) + .build() + val subscription = + query.subscribe().observer { + trySendBlocking(it) + } + awaitClose { subscription.cancel() } } - awaitClose { subscription.cancel() } - } /** * Removes a completed upload with id @@ -395,11 +423,12 @@ class OfflineRepository(otherSession: Session? = null) { fun contentUri(entry: Entry): String = "file://${contentFile(entry).absolutePath}" - fun contentFile(entry: Entry): File = if (entry.isUpload) { - File(session.uploadDir, entry.boxId.toString()) - } else { - File(contentDir(entry), entry.name) - } + fun contentFile(entry: Entry): File = + if (entry.isUpload) { + File(session.uploadDir, entry.boxId.toString()) + } else { + File(contentDir(entry), entry.name) + } fun contentDir(entry: Entry): File = File(session.filesDir, entry.id) diff --git a/data/src/main/kotlin/com/alfresco/content/data/OptionsModel.kt b/data/src/main/kotlin/com/alfresco/content/data/OptionsModel.kt index 6906907ad..965628369 100644 --- a/data/src/main/kotlin/com/alfresco/content/data/OptionsModel.kt +++ b/data/src/main/kotlin/com/alfresco/content/data/OptionsModel.kt @@ -17,12 +17,10 @@ data class OptionsModel( val value: String = "", val default: Boolean = false, ) : Parcelable { - val outcome: String get() = name companion object { - /** * return the updated OptionsModel obj by using Options obj * @param raw @@ -54,5 +52,6 @@ enum class DefaultOutcomesID { DEFAULT_RELEASE, DEFAULT_COMPLETE, ; + fun value() = name.lowercase() } diff --git a/data/src/main/kotlin/com/alfresco/content/data/PeopleRepository.kt b/data/src/main/kotlin/com/alfresco/content/data/PeopleRepository.kt index 334724843..8da7c5b4e 100644 --- a/data/src/main/kotlin/com/alfresco/content/data/PeopleRepository.kt +++ b/data/src/main/kotlin/com/alfresco/content/data/PeopleRepository.kt @@ -8,13 +8,11 @@ import com.alfresco.content.session.Session import com.alfresco.content.session.SessionManager class PeopleRepository(session: Session = SessionManager.requireSession) { - private val service: PeopleApi by lazy { session.createService(PeopleApi::class.java) } - suspend fun me(): Person = - service.getPerson(AlfrescoApi.CURRENT_USER).entry + suspend fun me(): Person = service.getPerson(AlfrescoApi.CURRENT_USER).entry companion object { fun myPicture(): Uri { diff --git a/data/src/main/kotlin/com/alfresco/content/data/ProcessDefinitionDataEntry.kt b/data/src/main/kotlin/com/alfresco/content/data/ProcessDefinitionDataEntry.kt index ee0930118..72d7e939b 100644 --- a/data/src/main/kotlin/com/alfresco/content/data/ProcessDefinitionDataEntry.kt +++ b/data/src/main/kotlin/com/alfresco/content/data/ProcessDefinitionDataEntry.kt @@ -20,7 +20,6 @@ data class ProcessDefinitionDataEntry( val hasStartForm: Boolean? = null, ) : ParentEntry(), Parcelable { companion object { - /** * return RuntimeProcessDefinitionDataEntry by using RuntimeProcessDefinitionEntry */ diff --git a/data/src/main/kotlin/com/alfresco/content/data/ProcessEntry.kt b/data/src/main/kotlin/com/alfresco/content/data/ProcessEntry.kt index ae4c4de05..f49fe2b4e 100644 --- a/data/src/main/kotlin/com/alfresco/content/data/ProcessEntry.kt +++ b/data/src/main/kotlin/com/alfresco/content/data/ProcessEntry.kt @@ -37,13 +37,14 @@ data class ProcessEntry( val reviewerType: ReviewerType = ReviewerType.OTHER, val taskEntry: TaskEntry = TaskEntry(), ) : ParentEntry(), Parcelable { - companion object { - /** * return the ProcessEntry using ProcessInstanceEntry */ - fun with(data: ProcessInstanceEntry, apsUser: UserGroupDetails? = null): ProcessEntry { + fun with( + data: ProcessInstanceEntry, + apsUser: UserGroupDetails? = null, + ): ProcessEntry { val isAssigneeUser = apsUser?.id == data.startedBy?.id return ProcessEntry( id = data.id ?: "", @@ -53,7 +54,14 @@ data class ProcessEntry( tenantId = data.tenantId, started = data.started, ended = data.ended, - startedBy = if (isAssigneeUser) apsUser?.let { UserGroupDetails.with(it) } else data.startedBy?.let { UserGroupDetails.with(it) } ?: UserGroupDetails(), + startedBy = + if (isAssigneeUser) { + apsUser?.let { + UserGroupDetails.with(it) + } + } else { + data.startedBy?.let { UserGroupDetails.with(it) } ?: UserGroupDetails() + }, processDefinitionName = data.processDefinitionName, processDefinitionDescription = data.processDefinitionDescription, processDefinitionKey = data.processDefinitionKey, @@ -69,7 +77,10 @@ data class ProcessEntry( /** * return the ProcessEntry using RuntimeProcessDefinitionDataEntry */ - fun with(data: RuntimeProcessDefinitionDataEntry, entries: List): ProcessEntry { + fun with( + data: RuntimeProcessDefinitionDataEntry, + entries: List, + ): ProcessEntry { return ProcessEntry( id = data.id?.toString() ?: "", name = data.name ?: "", @@ -81,7 +92,10 @@ data class ProcessEntry( /** * return the ProcessEntry using RuntimeProcessDefinitionDataEntry */ - fun with(data: ProcessEntry, entries: List): ProcessEntry { + fun with( + data: ProcessEntry, + entries: List, + ): ProcessEntry { return data.copy(defaultEntries = entries) } @@ -101,7 +115,10 @@ data class ProcessEntry( /** * return the ProcessEntry using RuntimeProcessDefinitionDataEntry */ - fun with(dataObj: ProcessDefinitionDataEntry, processEntry: ProcessEntry?): ProcessEntry { + fun with( + dataObj: ProcessDefinitionDataEntry, + processEntry: ProcessEntry?, + ): ProcessEntry { return ProcessEntry( id = dataObj.id ?: "", name = dataObj.name ?: "", @@ -116,7 +133,10 @@ data class ProcessEntry( /** * updating the priority into existing object */ - fun updatePriority(data: ProcessEntry, priority: Int): ProcessEntry { + fun updatePriority( + data: ProcessEntry, + priority: Int, + ): ProcessEntry { return ProcessEntry( id = data.id, name = data.name, @@ -146,7 +166,10 @@ data class ProcessEntry( * updating the due date into existing object */ - fun updateDueDate(data: ProcessEntry, formattedDate: String?): ProcessEntry { + fun updateDueDate( + data: ProcessEntry, + formattedDate: String?, + ): ProcessEntry { return ProcessEntry( id = data.id, name = data.name, @@ -208,7 +231,10 @@ data class ProcessEntry( /** * updating the task assignee into existing object */ - fun updateAssignee(data: ProcessEntry, assignee: UserGroupDetails): ProcessEntry { + fun updateAssignee( + data: ProcessEntry, + assignee: UserGroupDetails, + ): ProcessEntry { return ProcessEntry( id = data.id, name = data.name, @@ -237,7 +263,10 @@ data class ProcessEntry( /** * update reviewerType into existing ProcessEntry obj */ - fun updateReviewerType(data: ProcessEntry, listFields: List): ProcessEntry { + fun updateReviewerType( + data: ProcessEntry, + listFields: List, + ): ProcessEntry { var reviewerType: ReviewerType = ReviewerType.PEOPLE listFields.forEach { if (it.type == ReviewerType.FUNCTIONAL_GROUP.value()) { @@ -270,7 +299,10 @@ data class ProcessEntry( ) } - fun withProcess(data: ProcessEntry, fieldType: String): ProcessEntry { + fun withProcess( + data: ProcessEntry, + fieldType: String, + ): ProcessEntry { var reviewerType: ReviewerType = ReviewerType.PEOPLE if (fieldType == FieldType.FUNCTIONAL_GROUP.value()) { diff --git a/data/src/main/kotlin/com/alfresco/content/data/Rendition.kt b/data/src/main/kotlin/com/alfresco/content/data/Rendition.kt index eef7932aa..3acf5ed5d 100644 --- a/data/src/main/kotlin/com/alfresco/content/data/Rendition.kt +++ b/data/src/main/kotlin/com/alfresco/content/data/Rendition.kt @@ -7,11 +7,12 @@ data class Rendition( val mimeType: String, ) { val offlineFileName: String - get() = when (mimeType) { - PDF_MIME_TYPE -> PDF_FILE_NAME - IMG_MIME_TYPE -> IMG_FILE_NAME - else -> throw UnsupportedOperationException() - } + get() = + when (mimeType) { + PDF_MIME_TYPE -> PDF_FILE_NAME + IMG_MIME_TYPE -> IMG_FILE_NAME + else -> throw UnsupportedOperationException() + } companion object { private const val PDF_MIME_TYPE = "application/pdf" diff --git a/data/src/main/kotlin/com/alfresco/content/data/RenditionRepository.kt b/data/src/main/kotlin/com/alfresco/content/data/RenditionRepository.kt index e09db54fc..23636d4df 100644 --- a/data/src/main/kotlin/com/alfresco/content/data/RenditionRepository.kt +++ b/data/src/main/kotlin/com/alfresco/content/data/RenditionRepository.kt @@ -15,7 +15,6 @@ import kotlinx.coroutines.launch import com.alfresco.content.models.Rendition as RenditionModel class RenditionRepository { - lateinit var session: Session private val coroutineScope = CoroutineScope(Dispatchers.Main) @@ -77,16 +76,19 @@ class RenditionRepository { mimeType, ) } - } catch (_: Exception) { } + } catch (_: Exception) { + } delay(RETRY_DELAY) } } return null } - private fun renditionUri(id: String, renditionId: String) = - "${session.baseUrl}alfresco/versions/1/nodes/$id/renditions/$renditionId/content" + - "?attachment=false&alf_ticket=${session.ticket}" + private fun renditionUri( + id: String, + renditionId: String, + ) = "${session.baseUrl}alfresco/versions/1/nodes/$id/renditions/$renditionId/content" + + "?attachment=false&alf_ticket=${session.ticket}" companion object { private const val MAX_TRIES = 10 diff --git a/data/src/main/kotlin/com/alfresco/content/data/ResponseComments.kt b/data/src/main/kotlin/com/alfresco/content/data/ResponseComments.kt index 22db91818..6bce25692 100644 --- a/data/src/main/kotlin/com/alfresco/content/data/ResponseComments.kt +++ b/data/src/main/kotlin/com/alfresco/content/data/ResponseComments.kt @@ -12,7 +12,6 @@ data class ResponseComments( val listComments: List, ) { companion object { - /** * return the ResponseComments obj using ResultComments */ diff --git a/data/src/main/kotlin/com/alfresco/content/data/ResponseContents.kt b/data/src/main/kotlin/com/alfresco/content/data/ResponseContents.kt index 39274d5b3..5bb494b6d 100644 --- a/data/src/main/kotlin/com/alfresco/content/data/ResponseContents.kt +++ b/data/src/main/kotlin/com/alfresco/content/data/ResponseContents.kt @@ -12,7 +12,6 @@ data class ResponseContents( val listContents: List, ) { companion object { - /** * return the ResponseComments obj using ResultComments */ diff --git a/data/src/main/kotlin/com/alfresco/content/data/ResponseList.kt b/data/src/main/kotlin/com/alfresco/content/data/ResponseList.kt index e1fdaa474..db7206eba 100644 --- a/data/src/main/kotlin/com/alfresco/content/data/ResponseList.kt +++ b/data/src/main/kotlin/com/alfresco/content/data/ResponseList.kt @@ -14,11 +14,13 @@ data class ResponseList( val listProcesses: List = emptyList(), ) { companion object { - /** * return the ResponseList obj using ResultList */ - fun with(raw: ResultList, apsUser: UserGroupDetails): ResponseList { + fun with( + raw: ResultList, + apsUser: UserGroupDetails, + ): ResponseList { return ResponseList( size = raw.size ?: 0, total = raw.total ?: 0, @@ -30,7 +32,10 @@ data class ResponseList( /** * return the ResponseList obj using ResultListProcessInstances */ - fun with(raw: ResultListProcessInstances, apsUser: UserGroupDetails): ResponseList { + fun with( + raw: ResultListProcessInstances, + apsUser: UserGroupDetails, + ): ResponseList { return ResponseList( size = raw.size ?: 0, total = raw.total ?: 0, diff --git a/data/src/main/kotlin/com/alfresco/content/data/ResponseListProcessDefinition.kt b/data/src/main/kotlin/com/alfresco/content/data/ResponseListProcessDefinition.kt index 6ea6117eb..7ff3fe426 100644 --- a/data/src/main/kotlin/com/alfresco/content/data/ResponseListProcessDefinition.kt +++ b/data/src/main/kotlin/com/alfresco/content/data/ResponseListProcessDefinition.kt @@ -12,7 +12,6 @@ data class ResponseListProcessDefinition( val listProcessDefinitions: List = emptyList(), ) { companion object { - /** * return the ResponseListProcessDefinition obj using ResultListProcessDefinitions */ diff --git a/data/src/main/kotlin/com/alfresco/content/data/ResponseListRuntimeProcessDefinition.kt b/data/src/main/kotlin/com/alfresco/content/data/ResponseListRuntimeProcessDefinition.kt index 0fa5bc198..b4442e030 100644 --- a/data/src/main/kotlin/com/alfresco/content/data/ResponseListRuntimeProcessDefinition.kt +++ b/data/src/main/kotlin/com/alfresco/content/data/ResponseListRuntimeProcessDefinition.kt @@ -12,7 +12,6 @@ data class ResponseListRuntimeProcessDefinition( val listRuntimeProcessDefinitions: List = emptyList(), ) { companion object { - /** * return the ResponseListProcessDefinitions obj using ResultListRuntimeProcessDefinitions */ @@ -21,7 +20,10 @@ data class ResponseListRuntimeProcessDefinition( size = raw.size ?: 0, total = raw.total ?: 0, start = raw.start ?: 0, - listRuntimeProcessDefinitions = raw.data?.filter { it.deploymentId != null }?.map { RuntimeProcessDefinitionDataEntry.with(it) } ?: emptyList(), + listRuntimeProcessDefinitions = + raw.data?.filter { + it.deploymentId != null + }?.map { RuntimeProcessDefinitionDataEntry.with(it) } ?: emptyList(), ) } } diff --git a/data/src/main/kotlin/com/alfresco/content/data/ResponseUserGroupList.kt b/data/src/main/kotlin/com/alfresco/content/data/ResponseUserGroupList.kt index 95cdb5be4..af9720cfa 100644 --- a/data/src/main/kotlin/com/alfresco/content/data/ResponseUserGroupList.kt +++ b/data/src/main/kotlin/com/alfresco/content/data/ResponseUserGroupList.kt @@ -14,7 +14,6 @@ data class ResponseUserGroupList( val listUserGroup: List = emptyList(), ) { companion object { - /** * return the ResponseUserList obj using ResultUserList */ diff --git a/data/src/main/kotlin/com/alfresco/content/data/RuntimeProcessDefinitionDataEntry.kt b/data/src/main/kotlin/com/alfresco/content/data/RuntimeProcessDefinitionDataEntry.kt index 603a52cc7..e6b848484 100644 --- a/data/src/main/kotlin/com/alfresco/content/data/RuntimeProcessDefinitionDataEntry.kt +++ b/data/src/main/kotlin/com/alfresco/content/data/RuntimeProcessDefinitionDataEntry.kt @@ -20,7 +20,6 @@ data class RuntimeProcessDefinitionDataEntry( val tenantId: Int? = null, ) : ParentEntry(), Parcelable { companion object { - /** * return RuntimeProcessDefinitionDataEntry by using RuntimeProcessDefinitionEntry */ diff --git a/data/src/main/kotlin/com/alfresco/content/data/SearchFilters.kt b/data/src/main/kotlin/com/alfresco/content/data/SearchFilters.kt index 44fff60a1..8b09a594a 100644 --- a/data/src/main/kotlin/com/alfresco/content/data/SearchFilters.kt +++ b/data/src/main/kotlin/com/alfresco/content/data/SearchFilters.kt @@ -24,8 +24,11 @@ typealias SearchFilters = EnumSet typealias AdvanceSearchFilters = MutableList infix fun SearchFilter.and(other: SearchFilter): SearchFilters = SearchFilters.of(this, other) + infix fun SearchFilters.allOf(other: SearchFilters) = this.containsAll(other) + infix fun SearchFilters.and(other: SearchFilter): SearchFilters = SearchFilters.of(other, *this.toTypedArray()) + fun emptyFilters(): SearchFilters = SearchFilters.noneOf(SearchFilter::class.java) /** diff --git a/data/src/main/kotlin/com/alfresco/content/data/SearchRepository.kt b/data/src/main/kotlin/com/alfresco/content/data/SearchRepository.kt index 08bf7aca8..ee5e19a6c 100644 --- a/data/src/main/kotlin/com/alfresco/content/data/SearchRepository.kt +++ b/data/src/main/kotlin/com/alfresco/content/data/SearchRepository.kt @@ -31,7 +31,6 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext class SearchRepository { - lateinit var session: Session private val coroutineScope = CoroutineScope(Dispatchers.Main) @@ -110,37 +109,43 @@ class SearchRepository { filters: SearchFilters, skipCount: Int, maxItems: Int, - ) = - ResponsePaging.withExtension( - searchService.simpleSearch( - terms, - if (filters.contains(SearchFilter.Contextual)) nodeId else null, - skipCount, - maxItems, - includeFrom(filters), - ), - ) + ) = ResponsePaging.withExtension( + searchService.simpleSearch( + terms, + if (filters.contains(SearchFilter.Contextual)) nodeId else null, + skipCount, + maxItems, + includeFrom(filters), + ), + ) /** * returns the ResponsePaging obj after filtering the data on the basis of files and folders */ - fun offlineSearch(name: String, listFacetFields: AdvanceSearchFilters): ResponsePaging { + fun offlineSearch( + name: String, + listFacetFields: AdvanceSearchFilters, + ): ResponsePaging { val folderSearchData = listFacetFields.find { it.query.contains("cm:folder") } val fileSearchData = listFacetFields.find { it.query.contains("cm:content") } - val list = if (fileSearchData != null && folderSearchData != null) { - offlineRepository.offlineSearch(name) - } else if (folderSearchData != null) { - offlineRepository.offlineSearch(name).filter { it.isFolder } - } else if (fileSearchData != null) { - offlineRepository.offlineSearch(name).filter { it.isFile } - } else offlineRepository.offlineSearch(name) + val list = + if (fileSearchData != null && folderSearchData != null) { + offlineRepository.offlineSearch(name) + } else if (folderSearchData != null) { + offlineRepository.offlineSearch(name).filter { it.isFolder } + } else if (fileSearchData != null) { + offlineRepository.offlineSearch(name).filter { it.isFile } + } else { + offlineRepository.offlineSearch(name) + } return ResponsePaging.with(list) } private fun getNodeID(advanceSearchFilters: AdvanceSearchFilters): Boolean { - val isContextual = advanceSearchFilters.find { - it.query.contains(SearchFilter.Contextual.name) - } + val isContextual = + advanceSearchFilters.find { + it.query.contains(SearchFilter.Contextual.name) + } return isContextual != null } @@ -156,9 +161,10 @@ class SearchRepository { private fun includeFrom(advanceSearchFilters: AdvanceSearchFilters): MutableSet { val listFilter = advanceSearchFilters.filter { it.query != SearchFilter.Contextual.name } - val advanceSet = listFilter.mapTo(mutableSetOf()) { - AdvanceSearchInclude(name = it.name, query = it.query) - } + val advanceSet = + listFilter.mapTo(mutableSetOf()) { + AdvanceSearchInclude(name = it.name, query = it.query) + } return advanceSet } @@ -216,7 +222,10 @@ class SearchRepository { null } - suspend fun getRecents(skipCount: Int, maxItems: Int): ResponsePaging { + suspend fun getRecents( + skipCount: Int, + maxItems: Int, + ): ResponsePaging { val version = getServerVersion() if (version.toInt() >= SERVER_VERSION_NUMBER) { @@ -264,9 +273,10 @@ class SearchRepository { val discoveryService = DiscoveryService(context, config) - val contentServiceDetailsObj = withContext(Dispatchers.IO) { - discoveryService.getContentServiceDetails(Uri.parse(acc.serverUrl).host ?: "") - } + val contentServiceDetailsObj = + withContext(Dispatchers.IO) { + discoveryService.getContentServiceDetails(Uri.parse(acc.serverUrl).host ?: "") + } val serverVersion = contentServiceDetailsObj?.version?.split(".")?.get(0) ?: "" saveServerVersion(serverVersion) @@ -315,7 +325,6 @@ class SearchRepository { val context: Context, val onChange: () -> Unit, ) : SharedPreferences.OnSharedPreferenceChangeListener { - init { PreferenceManager.getDefaultSharedPreferences(context) .registerOnSharedPreferenceChangeListener(this) diff --git a/data/src/main/kotlin/com/alfresco/content/data/SharedLinksRepository.kt b/data/src/main/kotlin/com/alfresco/content/data/SharedLinksRepository.kt index 73c581aac..4b0dc7bf6 100644 --- a/data/src/main/kotlin/com/alfresco/content/data/SharedLinksRepository.kt +++ b/data/src/main/kotlin/com/alfresco/content/data/SharedLinksRepository.kt @@ -12,7 +12,6 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch class SharedLinksRepository() { - lateinit var session: Session private val coroutineScope = CoroutineScope(Dispatchers.Main) @@ -31,12 +30,14 @@ class SharedLinksRepository() { session.createService(SharedLinksApi::class.java) } - suspend fun getSharedLinks(skipCount: Int, maxItems: Int) = - ResponsePaging.with( - service.listSharedLinks( - skipCount, - maxItems, - include = AlfrescoApi.csvQueryParam("path", "isFavorite", "allowableOperations"), - ), - ) + suspend fun getSharedLinks( + skipCount: Int, + maxItems: Int, + ) = ResponsePaging.with( + service.listSharedLinks( + skipCount, + maxItems, + include = AlfrescoApi.csvQueryParam("path", "isFavorite", "allowableOperations"), + ), + ) } diff --git a/data/src/main/kotlin/com/alfresco/content/data/SharedPrefsExt.kt b/data/src/main/kotlin/com/alfresco/content/data/SharedPrefsExt.kt index 2e67a4ddb..38ff98233 100644 --- a/data/src/main/kotlin/com/alfresco/content/data/SharedPrefsExt.kt +++ b/data/src/main/kotlin/com/alfresco/content/data/SharedPrefsExt.kt @@ -2,7 +2,10 @@ package com.alfresco.content.data import android.content.SharedPreferences -fun SharedPreferences.Editor.putStringList(key: String, list: List) { +fun SharedPreferences.Editor.putStringList( + key: String, + list: List, +) { this.putString(key, list.joinToString("\n")) } diff --git a/data/src/main/kotlin/com/alfresco/content/data/SitesRepository.kt b/data/src/main/kotlin/com/alfresco/content/data/SitesRepository.kt index 9e4e17dc9..c16ef441d 100644 --- a/data/src/main/kotlin/com/alfresco/content/data/SitesRepository.kt +++ b/data/src/main/kotlin/com/alfresco/content/data/SitesRepository.kt @@ -12,7 +12,6 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch class SitesRepository { - lateinit var session: Session private val coroutineScope = CoroutineScope(Dispatchers.Main) @@ -31,15 +30,16 @@ class SitesRepository { session.createService(SitesApi::class.java) } - suspend fun getMySites(skipCount: Int, maxItems: Int) = - ResponsePaging.with( - service.listSiteMembershipsForPerson( - AlfrescoApi.CURRENT_USER, - skipCount, - maxItems, - ), - ) + suspend fun getMySites( + skipCount: Int, + maxItems: Int, + ) = ResponsePaging.with( + service.listSiteMembershipsForPerson( + AlfrescoApi.CURRENT_USER, + skipCount, + maxItems, + ), + ) - suspend fun deleteSite(entry: Entry) = - service.deleteSite(entry.otherId ?: "", null) + suspend fun deleteSite(entry: Entry) = service.deleteSite(entry.otherId ?: "", null) } diff --git a/data/src/main/kotlin/com/alfresco/content/data/StringExt.kt b/data/src/main/kotlin/com/alfresco/content/data/StringExt.kt index b8266ac14..978dc0d26 100644 --- a/data/src/main/kotlin/com/alfresco/content/data/StringExt.kt +++ b/data/src/main/kotlin/com/alfresco/content/data/StringExt.kt @@ -4,12 +4,17 @@ import android.content.Context import java.math.RoundingMode import java.text.DecimalFormat -const val chipTextDisplayLimit = 30 +const val CHIP_TEXT_DISPLAY_LIMIT = 30 /** * returns the formatted text string as per chip display conditions */ -fun String.wrapWithLimit(context: Context, limit: Int, delimiter: String? = null, multipleValue: Boolean = false): String { +fun String.wrapWithLimit( + context: Context, + limit: Int, + delimiter: String? = null, + multipleValue: Boolean = false, +): String { if (this.length <= limit && delimiter == null) { return this } @@ -19,18 +24,23 @@ fun String.wrapWithLimit(context: Context, limit: Int, delimiter: String? = null val splitStringArray = this.split(delimiter) val chip1stString = splitStringArray[0] if (chip1stString.length > limit) { - return context.getString(R.string.name_truncate_in_end, chip1stString.wrapWithLimit(context, chipTextDisplayLimit, multipleValue = true), splitStringArray.size.minus(1)) + return context.getString( + R.string.name_truncate_in_end, + chip1stString.wrapWithLimit(context, CHIP_TEXT_DISPLAY_LIMIT, multipleValue = true), + splitStringArray.size.minus(1), + ) } return context.getString(R.string.name_truncate_in_end, chip1stString, splitStringArray.size.minus(1)) } else { - return this.wrapWithLimit(context, chipTextDisplayLimit) + return this.wrapWithLimit(context, CHIP_TEXT_DISPLAY_LIMIT) } } return if (multipleValue) { context.getString(R.string.name_truncate_in, this.take(5), this.takeLast(5)) - } else - context.getString(R.string.name_truncate_end, this.take(chipTextDisplayLimit)) + } else { + context.getString(R.string.name_truncate_end, this.take(CHIP_TEXT_DISPLAY_LIMIT)) + } } /** diff --git a/data/src/main/kotlin/com/alfresco/content/data/SyncService.kt b/data/src/main/kotlin/com/alfresco/content/data/SyncService.kt index 1350e7821..3d24aaedb 100644 --- a/data/src/main/kotlin/com/alfresco/content/data/SyncService.kt +++ b/data/src/main/kotlin/com/alfresco/content/data/SyncService.kt @@ -48,10 +48,11 @@ class SyncService( fun sync() { cancelPendingSync() cancelScheduledSync() - pendingSync = scope.launch { - delay(TRIGGER_DELAY) - execute() - } + pendingSync = + scope.launch { + delay(TRIGGER_DELAY) + execute() + } } /** @@ -68,10 +69,11 @@ class SyncService( */ fun scheduleForegroundSync() { cancelScheduledSync() - scheduledSync = scope.launch { - delay(FOREGROUND_DELAY) - syncOrWait() - } + scheduledSync = + scope.launch { + delay(FOREGROUND_DELAY) + syncOrWait() + } } /** @@ -114,26 +116,30 @@ class SyncService( } private fun scheduleSyncWork(overrideNetwork: Boolean) { - val networkType = if (Settings(context).canSyncOverMeteredNetwork || overrideNetwork) { - NetworkType.CONNECTED - } else { - NetworkType.UNMETERED - } + val networkType = + if (Settings(context).canSyncOverMeteredNetwork || overrideNetwork) { + NetworkType.CONNECTED + } else { + NetworkType.UNMETERED + } - val policy = if (latestWorkInfo?.state == WorkInfo.State.RUNNING) { - ExistingWorkPolicy.APPEND - } else { - // Existing work may start before scheduling new work causing a cancellation - ExistingWorkPolicy.REPLACE - } + val policy = + if (latestWorkInfo?.state == WorkInfo.State.RUNNING) { + ExistingWorkPolicy.APPEND + } else { + // Existing work may start before scheduling new work causing a cancellation + ExistingWorkPolicy.REPLACE + } - val constraints = Constraints.Builder() - .setRequiredNetworkType(networkType) - .build() + val constraints = + Constraints.Builder() + .setRequiredNetworkType(networkType) + .build() - val request = OneTimeWorkRequestBuilder() - .setConstraints(constraints) - .build() + val request = + OneTimeWorkRequestBuilder() + .setConstraints(constraints) + .build() workManager .beginUniqueWork(UNIQUE_SYNC_WORK_NAME, policy, request) @@ -155,21 +161,24 @@ class SyncService( private fun scheduleUploadWork() { val networkType = NetworkType.CONNECTED - val policy = if (latestUploadWorkInfo?.state == WorkInfo.State.RUNNING) { - ExistingWorkPolicy.APPEND - } else { - // Existing work may start before scheduling new work causing a cancellation - ExistingWorkPolicy.REPLACE - } + val policy = + if (latestUploadWorkInfo?.state == WorkInfo.State.RUNNING) { + ExistingWorkPolicy.APPEND + } else { + // Existing work may start before scheduling new work causing a cancellation + ExistingWorkPolicy.REPLACE + } - val constraints = Constraints.Builder() - .setRequiredNetworkType(networkType) - .build() + val constraints = + Constraints.Builder() + .setRequiredNetworkType(networkType) + .build() - val request = OneTimeWorkRequestBuilder() - .setConstraints(constraints) - .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, UPLOAD_BACKOFF_DELAY, TimeUnit.SECONDS) - .build() + val request = + OneTimeWorkRequestBuilder() + .setConstraints(constraints) + .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, UPLOAD_BACKOFF_DELAY, TimeUnit.SECONDS) + .build() workManager .beginUniqueWork(UNIQUE_UPLOAD_WORK_NAME, policy, request) diff --git a/data/src/main/kotlin/com/alfresco/content/data/SyncWorker.kt b/data/src/main/kotlin/com/alfresco/content/data/SyncWorker.kt index c77d10ec4..9f9b7bafc 100644 --- a/data/src/main/kotlin/com/alfresco/content/data/SyncWorker.kt +++ b/data/src/main/kotlin/com/alfresco/content/data/SyncWorker.kt @@ -11,7 +11,6 @@ import java.io.File class SyncWorker(appContext: Context, params: WorkerParameters) : CoroutineWorker(appContext, params) { - private val repository = OfflineRepository() override suspend fun doWork(): Result { @@ -28,34 +27,35 @@ class SyncWorker(appContext: Context, params: WorkerParameters) : return Result.success() } - private fun buildLocalList(): List = - repository.fetchAllOfflineEntries() + private fun buildLocalList(): List = repository.fetchAllOfflineEntries() private suspend fun buildRemoteList(): List = continuousMap(repository.fetchTopLevelOfflineEntries()) { entry, produce -> // need updated metadata only for local items - val remote = if (entry.hasOfflineStatus) { - try { - BrowseRepository().fetchEntry(entry.id) - } catch (ex: HttpException) { - if (ex.code() == HTTP_STATUS_FORBIDDEN || - ex.code() == HTTP_STATUS_NOT_FOUND - ) { - null - } else { - throw ex + val remote = + if (entry.hasOfflineStatus) { + try { + BrowseRepository().fetchEntry(entry.id) + } catch (ex: HttpException) { + if (ex.code() == HTTP_STATUS_FORBIDDEN || + ex.code() == HTTP_STATUS_NOT_FOUND + ) { + null + } else { + throw ex + } } + } else { + entry } - } else { - entry - } if (remote?.isFolder == true) { // TODO: parallel fetch after the first page var page: ResponsePaging? var skip = 0L do { - page = BrowseRepository() - .fetchFolderItems(remote.id, skip.toInt(), MAX_PAGE_SIZE) + page = + BrowseRepository() + .fetchFolderItems(remote.id, skip.toInt(), MAX_PAGE_SIZE) page.entries.map { produce(it) } skip = page.pagination.skipCount + page.pagination.count } while (page?.pagination?.hasMoreItems == true) @@ -63,7 +63,10 @@ class SyncWorker(appContext: Context, params: WorkerParameters) : remote }.distinctBy { it.id } - private suspend fun continuousMap(initial: Collection, f: suspend (T, suspend(T) -> Unit) -> R?): List { + private suspend fun continuousMap( + initial: Collection, + f: suspend (T, suspend(T) -> Unit) -> R?, + ): List { val queue = ArrayDeque(initial) val result = mutableListOf() @@ -82,7 +85,10 @@ class SyncWorker(appContext: Context, params: WorkerParameters) : return result } - private fun calculateDiff(localList: List, remoteList: List): List { + private fun calculateDiff( + localList: List, + remoteList: List, + ): List { val localMap = localList.associateBy({ it.id }, { it }) val remoteMap = remoteList.associateBy({ it.id }, { it }) val operations = arrayListOf() @@ -118,9 +124,11 @@ class SyncWorker(appContext: Context, params: WorkerParameters) : return operations } - private fun contentHasChanged(local: Entry, remote: Entry) = - remote.modified?.toEpochSecond() != local.modified?.toEpochSecond() || - (local.isFile && local.offlineStatus != OfflineStatus.SYNCED) + private fun contentHasChanged( + local: Entry, + remote: Entry, + ) = remote.modified?.toEpochSecond() != local.modified?.toEpochSecond() || + (local.isFile && local.offlineStatus != OfflineStatus.SYNCED) private suspend fun processOperations(operations: List) = operations.asyncMap(MAX_CONCURRENT_OPERATIONS) { @@ -143,11 +151,12 @@ class SyncWorker(appContext: Context, params: WorkerParameters) : } } - private fun createEntry(entry: Entry) = - repository.update(entry.copy(offlineStatus = OfflineStatus.PENDING)) + private fun createEntry(entry: Entry) = repository.update(entry.copy(offlineStatus = OfflineStatus.PENDING)) - private fun updateEntryMetadata(local: Entry, remote: Entry) = - repository.update(local.copyWithMetadata(remote)) + private fun updateEntryMetadata( + local: Entry, + remote: Entry, + ) = repository.update(local.copyWithMetadata(remote)) private fun removeEntry(entry: Entry): Boolean { val dir = repository.contentDir(entry) @@ -194,8 +203,7 @@ class SyncWorker(appContext: Context, params: WorkerParameters) : } } - private fun typeSupported(entry: Entry) = - previewRegistry?.isPreviewSupported(entry.mimeType) ?: false + private fun typeSupported(entry: Entry) = previewRegistry?.isPreviewSupported(entry.mimeType) ?: false companion object { private const val MAX_CONCURRENT_OPERATIONS = 3 @@ -211,8 +219,11 @@ class SyncWorker(appContext: Context, params: WorkerParameters) : sealed class Operation { class Create(val remote: Entry) : Operation() + class UpdateMetadata(val local: Entry, val remote: Entry) : Operation() + class UpdateContent(val local: Entry, val remote: Entry) : Operation() + class Delete(val local: Entry) : Operation() } } diff --git a/data/src/main/kotlin/com/alfresco/content/data/TaskEntry.kt b/data/src/main/kotlin/com/alfresco/content/data/TaskEntry.kt index 2031c5355..ba55b22b0 100644 --- a/data/src/main/kotlin/com/alfresco/content/data/TaskEntry.kt +++ b/data/src/main/kotlin/com/alfresco/content/data/TaskEntry.kt @@ -33,23 +33,32 @@ data class TaskEntry( val processInstanceStartUserId: String? = null, val memberOfCandidateGroup: Boolean? = null, ) : ParentEntry(), Parcelable { - val localDueDate: String? get() = formattedDueDate ?: dueDate?.toLocalDate()?.toString() companion object { - /** * return the TaskEntry obj using TaskDataEntry */ - fun with(data: TaskDataEntry, apsUser: UserGroupDetails? = null, isNewTaskCreated: Boolean = false): TaskEntry { + fun with( + data: TaskDataEntry, + apsUser: UserGroupDetails? = null, + isNewTaskCreated: Boolean = false, + ): TaskEntry { val isAssigneeUser = apsUser?.id == data.assignee?.id return TaskEntry( id = data.id ?: "", name = data.name ?: "", description = data.description, created = data.created, - assignee = if (isAssigneeUser) apsUser?.let { UserGroupDetails.with(it) } else data.assignee?.let { UserGroupDetails.with(it) } ?: UserGroupDetails(), + assignee = + if (isAssigneeUser) { + apsUser?.let { + UserGroupDetails.with(it) + } + } else { + data.assignee?.let { UserGroupDetails.with(it) } ?: UserGroupDetails() + }, priority = data.priority?.toInt() ?: 0, endDate = data.endDate, dueDate = data.dueDate, @@ -67,7 +76,10 @@ data class TaskEntry( /** * return the TaskEntry obj using ResponseListForm and existing TaskEntry */ - fun withTaskForm(response: ResponseListForm, parent: TaskEntry): TaskEntry { + fun withTaskForm( + response: ResponseListForm, + parent: TaskEntry, + ): TaskEntry { val formFields = response.fields.first().fields var description: String? = null var comment: String? = null @@ -103,7 +115,10 @@ data class TaskEntry( } TaskFields.ITEMS.value() -> { - listContents = (it.value as? List<*>)?.map { mapData -> gson.fromJson(JSONObject(mapData as Map).toString(), Entry::class.java) } ?: emptyList() + listContents = (it.value as? List<*>)?.map { + mapData -> + gson.fromJson(JSONObject(mapData as Map).toString(), Entry::class.java) + } ?: emptyList() } } } @@ -156,7 +171,11 @@ data class TaskEntry( /** * updating the task due date into existing object */ - fun updateTaskDueDate(data: TaskEntry, formattedDueDate: String?, isClearDueDate: Boolean): TaskEntry { + fun updateTaskDueDate( + data: TaskEntry, + formattedDueDate: String?, + isClearDueDate: Boolean, + ): TaskEntry { return TaskEntry( id = data.id, name = data.name, @@ -175,7 +194,10 @@ data class TaskEntry( /** * updating the task priority into existing object */ - fun updateTaskPriority(data: TaskEntry, priority: Int): TaskEntry { + fun updateTaskPriority( + data: TaskEntry, + priority: Int, + ): TaskEntry { return TaskEntry( id = data.id, name = data.name, @@ -194,7 +216,10 @@ data class TaskEntry( /** * updating the task assignee into existing object */ - fun updateAssignee(data: TaskEntry, assignee: UserGroupDetails): TaskEntry { + fun updateAssignee( + data: TaskEntry, + assignee: UserGroupDetails, + ): TaskEntry { return TaskEntry( id = data.id, name = data.name, @@ -212,7 +237,10 @@ data class TaskEntry( /** * updating the task status into existing object */ - fun updateTaskStatus(data: TaskEntry, status: String?): TaskEntry { + fun updateTaskStatus( + data: TaskEntry, + status: String?, + ): TaskEntry { return TaskEntry( id = data.id, name = data.name, @@ -236,7 +264,11 @@ data class TaskEntry( /** * update the status and comment and return the TaskEntry */ - fun updateTaskStatusAndComment(data: TaskEntry, status: String?, comment: String?): TaskEntry { + fun updateTaskStatusAndComment( + data: TaskEntry, + status: String?, + comment: String?, + ): TaskEntry { return TaskEntry( id = data.id, name = data.name, @@ -264,7 +296,6 @@ data class TaskEntry( * Marked as TaskFields enum */ enum class TaskFields { - MESSAGE, ITEMS, PRIORITY, diff --git a/data/src/main/kotlin/com/alfresco/content/data/TaskFilterData.kt b/data/src/main/kotlin/com/alfresco/content/data/TaskFilterData.kt index 01d151c83..8b343e9ff 100644 --- a/data/src/main/kotlin/com/alfresco/content/data/TaskFilterData.kt +++ b/data/src/main/kotlin/com/alfresco/content/data/TaskFilterData.kt @@ -27,7 +27,6 @@ data class TaskFilterData( val isSelected: Boolean = false, ) : Parcelable { companion object { - /** * update to default values on reset * @param obj @@ -49,7 +48,11 @@ data class TaskFilterData( * @param selectedName * @param selectedQuery */ - fun with(obj: TaskFilterData?, selectedName: String, selectedQuery: String): TaskFilterData { + fun with( + obj: TaskFilterData?, + selectedName: String, + selectedQuery: String, + ): TaskFilterData { return TaskFilterData( id = obj?.id, name = obj?.name, @@ -69,7 +72,11 @@ data class TaskFilterData( * @param selectedName * @param selectedQueryMap */ - fun with(obj: TaskFilterData?, selectedName: String, selectedQueryMap: Map): TaskFilterData { + fun with( + obj: TaskFilterData?, + selectedName: String, + selectedQueryMap: Map, + ): TaskFilterData { return TaskFilterData( id = obj?.id, name = obj?.name, @@ -117,7 +124,10 @@ data class TaskFilterData( * @param obj * @param isSelected */ - fun updateData(obj: TaskFilterData, isSelected: Boolean): TaskFilterData { + fun updateData( + obj: TaskFilterData, + isSelected: Boolean, + ): TaskFilterData { return TaskFilterData( id = obj.id, name = obj.name, diff --git a/data/src/main/kotlin/com/alfresco/content/data/TaskRepository.kt b/data/src/main/kotlin/com/alfresco/content/data/TaskRepository.kt index 7d879bab4..d425506f4 100644 --- a/data/src/main/kotlin/com/alfresco/content/data/TaskRepository.kt +++ b/data/src/main/kotlin/com/alfresco/content/data/TaskRepository.kt @@ -39,7 +39,6 @@ import java.net.URL * Marked as TaskRepository class */ class TaskRepository { - lateinit var session: Session private val coroutineScope = CoroutineScope(Dispatchers.Main) @@ -72,22 +71,24 @@ class TaskRepository { /** * execute the task list api and returns the response as ResponseList obj */ - suspend fun getTasks(filters: TaskProcessFiltersPayload) = ResponseList.with( - tasksService.taskList( - includeTaskFilters(filters), - ), - getAPSUser(), - ) + suspend fun getTasks(filters: TaskProcessFiltersPayload) = + ResponseList.with( + tasksService.taskList( + includeTaskFilters(filters), + ), + getAPSUser(), + ) /** * execute the task list api and returns the response as ResponseList obj */ - suspend fun getProcesses(filters: TaskProcessFiltersPayload) = ResponseList.with( - processesService.processInstancesQuery( - includeProcessFilters(filters), - ), - getAPSUser(), - ) + suspend fun getProcesses(filters: TaskProcessFiltersPayload) = + ResponseList.with( + processesService.processInstancesQuery( + includeProcessFilters(filters), + ), + getAPSUser(), + ) /** * returns the content uri to fetch the content data from server @@ -105,30 +106,36 @@ class TaskRepository { /** * execute the task details api and returns the response as TaskDataEntry obj */ - suspend fun getTaskDetails(taskID: String) = TaskEntry.with( - tasksService.getTaskDetails(taskID), - ) + suspend fun getTaskDetails(taskID: String) = + TaskEntry.with( + tasksService.getTaskDetails(taskID), + ) /** * execute the get comments api and returns the response as ResponseComments obj */ - suspend fun getComments(taskID: String) = ResponseComments.with( - tasksService.getComments(taskID), - ) + suspend fun getComments(taskID: String) = + ResponseComments.with( + tasksService.getComments(taskID), + ) /** * execute the get comments api and returns the response as ResponseComments obj */ - suspend fun addComments(taskID: String, payload: CommentPayload) = CommentEntry.with( + suspend fun addComments( + taskID: String, + payload: CommentPayload, + ) = CommentEntry.with( tasksService.addComment(taskID, includeComment(payload)), ) /** * execute the get contents api and returns the response as ResponseContents obj */ - suspend fun getContents(taskID: String) = ResponseContents.with( - tasksService.getContents(taskID), - ) + suspend fun getContents(taskID: String) = + ResponseContents.with( + tasksService.getContents(taskID), + ) /** * execute the get complete task api and returns the response as Unit obj @@ -234,7 +241,10 @@ class TaskRepository { /** * It will call the api to create the task and return the TaskEntry type obj */ - suspend fun createTask(name: String, description: String): TaskEntry { + suspend fun createTask( + name: String, + description: String, + ): TaskEntry { return TaskEntry.with( tasksService.createTask(TaskBodyCreate(name = name, description = description)), isNewTaskCreated = true, @@ -244,7 +254,10 @@ class TaskRepository { /** * It will call the api to search the user by name or email and returns the ResponseUserList type obj */ - suspend fun searchUser(name: String, email: String): ResponseUserGroupList { + suspend fun searchUser( + name: String, + email: String, + ): ResponseUserGroupList { return ResponseUserGroupList.with( tasksService.searchUser(filter = name, email = email), ) @@ -280,7 +293,10 @@ class TaskRepository { /** * It will call the api to update the task api and return the TaskEntry obj */ - suspend fun assignUser(taskID: String, assigneeID: String): TaskEntry { + suspend fun assignUser( + taskID: String, + assigneeID: String, + ): TaskEntry { return TaskEntry.with( tasksService.assignUser(taskID, AssignUserBody(assignee = assigneeID)), ) @@ -294,7 +310,11 @@ class TaskRepository { /** * It will call the api to upload the raw content on process services. */ - suspend fun createEntry(local: Entry, file: File, uploadServerType: UploadServerType): Entry { + suspend fun createEntry( + local: Entry, + file: File, + uploadServerType: UploadServerType, + ): Entry { // TODO: Support creating empty entries and folders requireNotNull(local.mimeType) requireNotNull(local.parentId) @@ -321,14 +341,15 @@ class TaskRepository { ) } - UploadServerType.UPLOAD_TO_PROCESS -> Entry.with( - processesService.uploadRawContent( - multipartBody, - ), - local.parentId, - uploadServer = uploadServerType, - observerID = local.observerID, - ) + UploadServerType.UPLOAD_TO_PROCESS -> + Entry.with( + processesService.uploadRawContent( + multipartBody, + ), + local.parentId, + uploadServer = uploadServerType, + observerID = local.observerID, + ) else -> Entry() } @@ -373,14 +394,19 @@ class TaskRepository { /** * execute the start-form apis to fetch the form presentation */ - suspend fun startForm(processDefinitionId: String) = ResponseListForm.with( - processesService.startForm(processDefinitionId), - ) + suspend fun startForm(processDefinitionId: String) = + ResponseListForm.with( + processesService.startForm(processDefinitionId), + ) /** * Execute the start flow integration */ - suspend fun startWorkflow(processEntry: ProcessEntry?, items: String, values: Map) = ProcessEntry.with( + suspend fun startWorkflow( + processEntry: ProcessEntry?, + items: String, + values: Map, + ) = ProcessEntry.with( processesService.createProcessInstance( RequestProcessInstances( name = processEntry?.name, @@ -417,7 +443,11 @@ class TaskRepository { /** * Call to perform the outcomes */ - suspend fun actionOutcomes(outcome: String, taskEntry: TaskEntry, values: Map) = tasksService.taskFormAction( + suspend fun actionOutcomes( + outcome: String, + taskEntry: TaskEntry, + values: Map, + ) = tasksService.taskFormAction( taskEntry.id, RequestOutcomes( outcome = outcome, @@ -428,7 +458,10 @@ class TaskRepository { /** * Call to perform the outcomes */ - suspend fun actionCompleteOutcome(taskID: String, values: Map) = tasksService.taskFormAction( + suspend fun actionCompleteOutcome( + taskID: String, + values: Map, + ) = tasksService.taskFormAction( taskID, RequestOutcomes( outcome = null, @@ -439,7 +472,10 @@ class TaskRepository { /** * Call to save the form data */ - suspend fun saveForm(taskID: String, values: Map) = tasksService.saveForm( + suspend fun saveForm( + taskID: String, + values: Map, + ) = tasksService.saveForm( taskID, RequestSaveForm( values = values, diff --git a/data/src/main/kotlin/com/alfresco/content/data/TrashCanRepository.kt b/data/src/main/kotlin/com/alfresco/content/data/TrashCanRepository.kt index 34343d66d..e5faeadc3 100644 --- a/data/src/main/kotlin/com/alfresco/content/data/TrashCanRepository.kt +++ b/data/src/main/kotlin/com/alfresco/content/data/TrashCanRepository.kt @@ -13,7 +13,6 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch class TrashCanRepository { - lateinit var session: Session private val coroutineScope = CoroutineScope(Dispatchers.Main) @@ -36,17 +35,18 @@ class TrashCanRepository { session.createService(TrashcanApiExt::class.java) } - suspend fun getDeletedNodes(skipCount: Int, maxItems: Int) = - ResponsePaging.with( - service.listDeletedNodes( - skipCount, - maxItems, - AlfrescoApi.csvQueryParam("path"), - ), - ) - - suspend fun restoreEntry(entry: Entry) = - Entry.with(serviceExt.restoreDeletedNode(entry.id).entry) + suspend fun getDeletedNodes( + skipCount: Int, + maxItems: Int, + ) = ResponsePaging.with( + service.listDeletedNodes( + skipCount, + maxItems, + AlfrescoApi.csvQueryParam("path"), + ), + ) + + suspend fun restoreEntry(entry: Entry) = Entry.with(serviceExt.restoreDeletedNode(entry.id).entry) suspend fun deleteForeverEntry(entry: Entry) { service.deleteDeletedNode(entry.id) diff --git a/data/src/main/kotlin/com/alfresco/content/data/UploadWorker.kt b/data/src/main/kotlin/com/alfresco/content/data/UploadWorker.kt index d80c22671..ef1c0bb9b 100644 --- a/data/src/main/kotlin/com/alfresco/content/data/UploadWorker.kt +++ b/data/src/main/kotlin/com/alfresco/content/data/UploadWorker.kt @@ -11,7 +11,6 @@ class UploadWorker( appContext: Context, params: WorkerParameters, ) : CoroutineWorker(appContext, params) { - private val repository = OfflineRepository() override suspend fun doWork(): Result { @@ -23,8 +22,7 @@ class UploadWorker( return if (result.any { !it }) Result.retry() else Result.success() } - private fun pendingUploads(): List = - repository.fetchPendingUploads() + private fun pendingUploads(): List = repository.fetchPendingUploads() private suspend fun createItem(entry: Entry): Boolean { val file = repository.contentFile(entry) @@ -35,7 +33,15 @@ class UploadWorker( status = true, size = "${file.length().div(1024).div(1024)} MB", ) - val res = if (entry.uploadServer == UploadServerType.DEFAULT) BrowseRepository().createEntry(entry, file) else TaskRepository().createEntry(entry, file, entry.uploadServer) + val res = + if (entry.uploadServer == UploadServerType.DEFAULT) { + BrowseRepository().createEntry( + entry, + file, + ) + } else { + TaskRepository().createEntry(entry, file, entry.uploadServer) + } file.delete() // TODO: what if delete fails? repository.update( entry.copyWithMetadata(res) diff --git a/data/src/main/kotlin/com/alfresco/content/data/UserGroupDetails.kt b/data/src/main/kotlin/com/alfresco/content/data/UserGroupDetails.kt index d301c478d..5a41ea5a2 100644 --- a/data/src/main/kotlin/com/alfresco/content/data/UserGroupDetails.kt +++ b/data/src/main/kotlin/com/alfresco/content/data/UserGroupDetails.kt @@ -22,12 +22,18 @@ data class UserGroupDetails( val groups: String = "", val isGroup: Boolean? = false, ) : Parcelable { - val name: String get() = if (isAssigneeUser) "me_title" else "$firstName $lastName" private val firstNameInitial: String - get() = if (firstName.isNotEmpty()) firstName.substring(0, 1) else if (groupName.isNotEmpty()) getGroupInitial(groupName) else "" + get() = + if (firstName.isNotEmpty()) { + firstName.substring(0, 1) + } else if (groupName.isNotEmpty()) { + getGroupInitial(groupName) + } else { + "" + } private val lastNameInitial: String get() = if (lastName.isNotEmpty()) lastName.substring(0, 1) else "" @@ -45,7 +51,6 @@ data class UserGroupDetails( } companion object { - /** * return the UserDetails obj using UserInfo */ diff --git a/data/src/main/kotlin/com/alfresco/content/data/payloads/ConvertDataToMapValues.kt b/data/src/main/kotlin/com/alfresco/content/data/payloads/ConvertDataToMapValues.kt index e85ac1d70..6a2781962 100644 --- a/data/src/main/kotlin/com/alfresco/content/data/payloads/ConvertDataToMapValues.kt +++ b/data/src/main/kotlin/com/alfresco/content/data/payloads/ConvertDataToMapValues.kt @@ -34,13 +34,17 @@ fun convertModelToMapValues(data: FieldsData): Map { ) } -fun convertModelToMapValues(data: TaskEntry, commentEntry: String? = null): Map = +fun convertModelToMapValues( + data: TaskEntry, + commentEntry: String? = null, +): Map = if (data.taskFormStatus != null) { mapOf( - "status" to mapOf( - "id" to data.taskFormStatus, - "name" to data.taskFormStatus, - ), + "status" to + mapOf( + "id" to data.taskFormStatus, + "name" to data.taskFormStatus, + ), "comment" to (commentEntry ?: data.comment), ) } else { diff --git a/data/src/main/kotlin/com/alfresco/content/data/payloads/FieldsData.kt b/data/src/main/kotlin/com/alfresco/content/data/payloads/FieldsData.kt index fcd9cbc54..c84f0eeb7 100644 --- a/data/src/main/kotlin/com/alfresco/content/data/payloads/FieldsData.kt +++ b/data/src/main/kotlin/com/alfresco/content/data/payloads/FieldsData.kt @@ -44,10 +44,11 @@ data class FieldsData( var displayText: String? = null, var errorData: Pair = Pair(false, ""), ) : Parcelable { - fun getContentList(parentId: String? = null): List { return if (((value as? List<*>)?.firstOrNull() is Map<*, *>)) { - (value as? List<*>)?.map { Gson().fromJson(JSONObject(it as Map).toString(), ContentEntry::class.java) }?.map { Entry.with(it, id, parentId) } ?: emptyList() + (value as? List<*>)?.map { + Gson().fromJson(JSONObject(it as Map).toString(), ContentEntry::class.java) + }?.map { Entry.with(it, id, parentId) } ?: emptyList() } else { (value as? List<*>)?.mapNotNull { it as? Entry } ?: emptyList() } @@ -58,11 +59,12 @@ data class FieldsData( return null } - val userGroupDetails: UserGroupDetails? = if ((value is Map<*, *>)) { - Gson().fromJson(JSONObject(value as Map).toString(), UserGroupDetails::class.java) - } else { - value as? UserGroupDetails - } + val userGroupDetails: UserGroupDetails? = + if ((value is Map<*, *>)) { + Gson().fromJson(JSONObject(value as Map).toString(), UserGroupDetails::class.java) + } else { + value as? UserGroupDetails + } val isAssigneeUser = apsUser?.id == userGroupDetails?.id @@ -73,7 +75,10 @@ data class FieldsData( return userGroupDetails } - fun getDate(serverFormat: String, localFormat: String): Pair { + fun getDate( + serverFormat: String, + localFormat: String, + ): Pair { val dateTime = value as? String ?: "" if (dateTime.isNotEmpty() && dateTime.contains("T")) { @@ -119,7 +124,11 @@ data class FieldsData( /** * returns the FieldsData obj by using Fields obj */ - fun withUpdateField(raw: FieldsData, value: Any?, errorData: Pair): FieldsData { + fun withUpdateField( + raw: FieldsData, + value: Any?, + errorData: Pair, + ): FieldsData { return FieldsData( fieldType = raw.fieldType, id = raw.id, @@ -200,9 +209,7 @@ data class FileSourceData( val name: String = "", ) : Parcelable { companion object { - fun with( - raw: FieldSource?, - ): FileSourceData { + fun with(raw: FieldSource?): FileSourceData { return FileSourceData(raw?.serviceId ?: "", raw?.name ?: "") } } diff --git a/data/src/main/kotlin/com/alfresco/content/data/payloads/LinkContentPayload.kt b/data/src/main/kotlin/com/alfresco/content/data/payloads/LinkContentPayload.kt index db5767c42..83994964c 100644 --- a/data/src/main/kotlin/com/alfresco/content/data/payloads/LinkContentPayload.kt +++ b/data/src/main/kotlin/com/alfresco/content/data/payloads/LinkContentPayload.kt @@ -12,11 +12,13 @@ data class LinkContentPayload( val name: String = "", ) { companion object { - /** * returns the LinkContentPayload as obj */ - fun with(entry: Entry, sourceName: String): LinkContentPayload { + fun with( + entry: Entry, + sourceName: String, + ): LinkContentPayload { return LinkContentPayload( source = sourceName, sourceId = entry.id, diff --git a/data/src/main/kotlin/com/alfresco/content/data/payloads/TaskProcessFiltersPayload.kt b/data/src/main/kotlin/com/alfresco/content/data/payloads/TaskProcessFiltersPayload.kt index 545d6fbe4..6a2902463 100644 --- a/data/src/main/kotlin/com/alfresco/content/data/payloads/TaskProcessFiltersPayload.kt +++ b/data/src/main/kotlin/com/alfresco/content/data/payloads/TaskProcessFiltersPayload.kt @@ -16,11 +16,13 @@ data class TaskProcessFiltersPayload( var dueAfter: String = "", ) { companion object { - /** * update the filters and return the payload obj */ - fun updateFilters(obj: TaskProcessFiltersPayload, page: Int = 0): TaskProcessFiltersPayload { + fun updateFilters( + obj: TaskProcessFiltersPayload, + page: Int = 0, + ): TaskProcessFiltersPayload { return TaskProcessFiltersPayload( assignment = obj.assignment, sort = obj.sort, @@ -36,7 +38,11 @@ data class TaskProcessFiltersPayload( /** * */ - fun updateFilters(obj: TaskProcessFiltersPayload, state: String, page: Int = 0): TaskProcessFiltersPayload { + fun updateFilters( + obj: TaskProcessFiltersPayload, + state: String, + page: Int = 0, + ): TaskProcessFiltersPayload { return TaskProcessFiltersPayload( assignment = obj.assignment, sort = obj.sort, diff --git a/data/src/main/kotlin/com/alfresco/content/data/rooted/CheckForRootWorker.kt b/data/src/main/kotlin/com/alfresco/content/data/rooted/CheckForRootWorker.kt index e148f63ba..56faf2bc4 100644 --- a/data/src/main/kotlin/com/alfresco/content/data/rooted/CheckForRootWorker.kt +++ b/data/src/main/kotlin/com/alfresco/content/data/rooted/CheckForRootWorker.kt @@ -4,23 +4,23 @@ import android.content.Context import com.scottyab.rootbeer.RootBeer class CheckForRootWorker(context: Context) { - private val rootBeer = RootBeer(context) operator fun invoke(): List = getRootResults() - private fun getRootResults() = listOf( - RootItemResult("Root Management Apps", rootBeer.detectRootManagementApps()), - RootItemResult("Potentially Dangerous Apps", rootBeer.detectPotentiallyDangerousApps()), - RootItemResult("Root Cloaking Apps", rootBeer.detectRootCloakingApps()), - RootItemResult("TestKeys", rootBeer.detectTestKeys()), - RootItemResult("BusyBoxBinary", rootBeer.checkForBusyBoxBinary()), + private fun getRootResults() = + listOf( + RootItemResult("Root Management Apps", rootBeer.detectRootManagementApps()), + RootItemResult("Potentially Dangerous Apps", rootBeer.detectPotentiallyDangerousApps()), + RootItemResult("Root Cloaking Apps", rootBeer.detectRootCloakingApps()), + RootItemResult("TestKeys", rootBeer.detectTestKeys()), + RootItemResult("BusyBoxBinary", rootBeer.checkForBusyBoxBinary()), // RootItemResult("SU Binary", rootBeer.checkForSuBinary()), - RootItemResult("2nd SU Binary check", rootBeer.checkSuExists()), - RootItemResult("For RW Paths", rootBeer.checkForRWPaths()), + RootItemResult("2nd SU Binary check", rootBeer.checkSuExists()), + RootItemResult("For RW Paths", rootBeer.checkForRWPaths()), // RootItemResult("Dangerous Props", rootBeer.checkForDangerousProps()), - RootItemResult("Root via native check", rootBeer.checkForRootNative()), + RootItemResult("Root via native check", rootBeer.checkForRootNative()), // RootItemResult("SE linux Flag Is Enabled", !Utils.isSelinuxFlagInEnabled()), - RootItemResult("Magisk specific checks", rootBeer.checkForMagiskBinary()), - ) + RootItemResult("Magisk specific checks", rootBeer.checkForMagiskBinary()), + ) } diff --git a/download/src/main/kotlin/com/alfresco/download/ContentDownloader.kt b/download/src/main/kotlin/com/alfresco/download/ContentDownloader.kt index 193ee5618..3e6f14df4 100644 --- a/download/src/main/kotlin/com/alfresco/download/ContentDownloader.kt +++ b/download/src/main/kotlin/com/alfresco/download/ContentDownloader.kt @@ -19,17 +19,23 @@ import java.io.IOException import kotlin.coroutines.resumeWithException object ContentDownloader { - const val FILE_PROVIDER_AUTHORITY = "com.alfresco.content.fileprovider" - suspend fun downloadFileTo(uri: String, outputPath: String, httpClient: OkHttpClient? = null) { + suspend fun downloadFileTo( + uri: String, + outputPath: String, + httpClient: OkHttpClient? = null, + ) { Logger.d("Downloading: $uri to: $outputPath") val req = Request.Builder().get().url(uri).build() val client = httpClient ?: OkHttpClient() client.newCall(req).downloadAndSaveTo(File(outputPath)) } - suspend fun downloadFile(uri: String, outputPath: String): Flow { + suspend fun downloadFile( + uri: String, + outputPath: String, + ): Flow { return flow { downloadFileTo(uri, outputPath) emit(outputPath) @@ -53,54 +59,63 @@ suspend fun Call.downloadAndSaveTo( bufferSize: Long = DEFAULT_BUFFER_SIZE.toLong(), blockingDispatcher: CoroutineDispatcher = OK_IO, progress: ((downloaded: Long, total: Long) -> Unit)? = null, -): File = withContext(blockingDispatcher) { - suspendCancellableCoroutine { cont -> - enqueue(object : Callback { - override fun onFailure(call: Call, e: IOException) { - cont.resumeWithException(e) - } - - override fun onResponse(call: Call, response: Response) { - if (!response.isSuccessful) { - cont.resumeWithException(IOException("Unexpected HTTP code: ${response.code}")) - return - } - try { - val body = response.body - if (body == null) { - cont.resumeWithException(IllegalStateException("Body is null")) - return +): File = + withContext(blockingDispatcher) { + suspendCancellableCoroutine { cont -> + enqueue( + object : Callback { + override fun onFailure( + call: Call, + e: IOException, + ) { + cont.resumeWithException(e) } - val contentLength = body.contentLength() - var finished = false - output.sink().buffer().use { out -> - body.source().use { source -> - val buffer = out.buffer - var totalLength = 0L - while (cont.isActive) { - val read = source.read(buffer, bufferSize) - if (read == -1L) { - finished = true - break + + override fun onResponse( + call: Call, + response: Response, + ) { + if (!response.isSuccessful) { + cont.resumeWithException(IOException("Unexpected HTTP code: ${response.code}")) + return + } + try { + val body = response.body + if (body == null) { + cont.resumeWithException(IllegalStateException("Body is null")) + return + } + val contentLength = body.contentLength() + var finished = false + output.sink().buffer().use { out -> + body.source().use { source -> + val buffer = out.buffer + var totalLength = 0L + while (cont.isActive) { + val read = source.read(buffer, bufferSize) + if (read == -1L) { + finished = true + break + } + out.emit() + totalLength += read + progress?.invoke(totalLength, contentLength) + } + out.flush() } - out.emit() - totalLength += read - progress?.invoke(totalLength, contentLength) } - out.flush() - } - } - if (finished) { - cont.resume(output) { - cancel() + if (finished) { + cont.resume(output) { + cancel() + } + } else { + cont.resumeWithException(IOException("Download cancelled")) + } + } catch (e: Exception) { + cont.resumeWithException(e) } - } else { - cont.resumeWithException(IOException("Download cancelled")) } - } catch (e: Exception) { - cont.resumeWithException(e) - } - } - }) + }, + ) + } } -} diff --git a/download/src/main/kotlin/com/alfresco/download/DownloadMonitor.kt b/download/src/main/kotlin/com/alfresco/download/DownloadMonitor.kt index e941b8cee..3b8f5e068 100644 --- a/download/src/main/kotlin/com/alfresco/download/DownloadMonitor.kt +++ b/download/src/main/kotlin/com/alfresco/download/DownloadMonitor.kt @@ -27,11 +27,13 @@ object DownloadMonitor { private var tint: Int? = null private val notificationId = AtomicInteger(0) - fun smallIcon(@DrawableRes drawableResId: Int) = - apply { smallIcon = drawableResId } + fun smallIcon( + @DrawableRes drawableResId: Int, + ) = apply { smallIcon = drawableResId } - fun tint(@ColorInt color: Int) = - apply { tint = color } + fun tint( + @ColorInt color: Int, + ) = apply { tint = color } fun observe(context: Context) = apply { @@ -65,13 +67,17 @@ object DownloadMonitor { ?.createNotificationChannel(channel) } - private fun onDownloadComplete(context: Context?, downloadId: Long) { + private fun onDownloadComplete( + context: Context?, + downloadId: Long, + ) { if (context == null) return if (downloadId == -1L) return - val query = DownloadManager.Query().apply { - setFilterById(downloadId) - } + val query = + DownloadManager.Query().apply { + setFilterById(downloadId) + } val manager = ContextCompat.getSystemService(context, DownloadManager::class.java) ?: return @@ -83,39 +89,43 @@ object DownloadMonitor { cursor.close() val formattedSize = Formatter.formatFileSize(context, size) - val content = if (status == DownloadManager.STATUS_SUCCESSFUL) { - context.getString(R.string.notification_download_content, formattedSize) - } else { - context.getString(R.string.notification_download_content_error) - } + val content = + if (status == DownloadManager.STATUS_SUCCESSFUL) { + context.getString(R.string.notification_download_content, formattedSize) + } else { + context.getString(R.string.notification_download_content_error) + } - val intent = if (status == DownloadManager.STATUS_SUCCESSFUL) { - Intent(DownloadManager.ACTION_VIEW_DOWNLOADS).apply { - flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + val intent = + if (status == DownloadManager.STATUS_SUCCESSFUL) { + Intent(DownloadManager.ACTION_VIEW_DOWNLOADS).apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + } + } else { + context.packageManager.getLaunchIntentForPackage(context.applicationContext.packageName) } - } else { - context.packageManager.getLaunchIntentForPackage(context.applicationContext.packageName) - } - val pendingIntent = PendingIntent.getActivity( - context, - 0, - intent, - when { - Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE - else -> FLAG_UPDATE_CURRENT - }, - ) + val pendingIntent = + PendingIntent.getActivity( + context, + 0, + intent, + when { + Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE + else -> FLAG_UPDATE_CURRENT + }, + ) val smallIcon = this.smallIcon ?: return - val builder = NotificationCompat.Builder(context, CHANNEL_ID) - .setSmallIcon(smallIcon) - .setContentTitle(title) - .setContentText(content) - .setColor(tint ?: Color.WHITE) - .setPriority(NotificationCompat.PRIORITY_DEFAULT) - .setAutoCancel(true) - .setContentIntent(pendingIntent) + val builder = + NotificationCompat.Builder(context, CHANNEL_ID) + .setSmallIcon(smallIcon) + .setContentTitle(title) + .setContentText(content) + .setColor(tint ?: Color.WHITE) + .setPriority(NotificationCompat.PRIORITY_DEFAULT) + .setAutoCancel(true) + .setContentIntent(pendingIntent) with(NotificationManagerCompat.from(context)) { notify(notificationId.incrementAndGet(), builder.build()) @@ -126,6 +136,8 @@ object DownloadMonitor { class DownloadCompleteReceiver( private val onComplete: (context: Context?, downloadId: Long) -> Unit, ) : BroadcastReceiver() { - override fun onReceive(context: Context?, intent: Intent?) = - onComplete(context, intent?.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1) ?: -1) + override fun onReceive( + context: Context?, + intent: Intent?, + ) = onComplete(context, intent?.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1) ?: -1) } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 01b928bed..8a86bec7c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,42 +1,41 @@ [versions] camera = "1.2.3" coil = "2.4.0" -coroutines = "1.7.1" -epoxy = "5.1.3" +coroutines = "1.9.0" +epoxy = "5.1.4" exoplayer = "2.14.0" -kotlin = "1.9.20" -lifecycle = "2.6.1" -navigation = "2.6.0" -okhttp = "4.11.0" -test = "1.5.0" -activity-compose = "1.7.0" -compose-bom = "2023.10.01" -ui-tooling = "1.5.4" -appcompat = "1.6.1" -material = "1.11.0" +kotlin = "2.0.20" +lifecycle = "2.8.6" +navigation = "2.8.2" +okhttp = "4.12.0" +test = "1.6.1" +activity-compose = "1.9.2" +compose-bom = "2024.09.03" +ui-tooling = "1.7.3" constraintlayout = "2.1.4" +moshi = "1.15.1" +retrofit = "2.11.0" [libraries] -alfresco-auth = "com.alfresco.android:auth:0.8.1-SNAPSHOT" -alfresco-content = "com.alfresco.android:content:0.3.5-SNAPSHOT" -alfresco-contentKtx = "com.alfresco.android:content-ktx:0.3.2-SNAPSHOT" -alfresco-process = "com.alfresco.android:process:0.1.3-SNAPSHOT" +alfresco-auth = "com.alfresco.android:auth:0.8.3-SNAPSHOT" +alfresco-content = "com.alfresco.android:content:0.3.6-SNAPSHOT" +alfresco-contentKtx = "com.alfresco.android:content-ktx:0.3.3-SNAPSHOT" +alfresco-process = "com.alfresco.android:process:0.1.4-SNAPSHOT" -android-desugar = "com.android.tools:desugar_jdk_libs:2.0.3" -android-gradle = "com.android.tools.build:gradle:8.0.2" +android-desugar = "com.android.tools:desugar_jdk_libs:2.1.2" +android-gradle = "com.android.tools.build:gradle:8.7.0" -androidx-activity = "androidx.activity:activity-ktx:1.7.2" -androidx-appcompat = "androidx.appcompat:appcompat:1.6.1" +androidx-activity = "androidx.activity:activity-ktx:1.9.2" +androidx-appcompat = "androidx.appcompat:appcompat:1.7.0" androidx-camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "camera" } androidx-camera-core = { module = "androidx.camera:camera-core", version.ref = "camera" } androidx-camera-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "camera" } androidx-camera-view = { module = "androidx.camera:camera-view", version.ref = "camera" } -androidx-constraintlayout = "androidx.constraintlayout:constraintlayout:2.1.4" androidx-coordinatorlayout = "androidx.coordinatorlayout:coordinatorlayout:1.2.0" -androidx-core = "androidx.core:core-ktx:1.10.1" -androidx-fragment = "androidx.fragment:fragment-ktx:1.6.0" +androidx-core = "androidx.core:core-ktx:1.13.1" +androidx-fragment = "androidx.fragment:fragment-ktx:1.8.4" androidx-lifecycle-livedata = { module = "androidx.lifecycle:lifecycle-livedata-ktx", version.ref = "lifecycle" } androidx-lifecycle-process = { module = "androidx.lifecycle:lifecycle-process", version.ref = "lifecycle" } @@ -46,20 +45,18 @@ androidx-lifecycle-viewmodel = { module = "androidx.lifecycle:lifecycle-viewmode androidx-navigation-fragment = { module = "androidx.navigation:navigation-fragment-ktx", version.ref = "navigation" } androidx-navigation-ui = { module = "androidx.navigation:navigation-ui-ktx", version.ref = "navigation" } -androidx-preference = "androidx.preference:preference:1.2.0" -androidx-recyclerview = "androidx.recyclerview:recyclerview:1.3.0" +androidx-preference = "androidx.preference:preference:1.2.1" +androidx-recyclerview = "androidx.recyclerview:recyclerview:1.3.2" androidx-recyclerview-selection = "androidx.recyclerview:recyclerview-selection:1.1.0" androidx-swiperefreshlayout = "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0" -androidx-security-crypto = 'androidx.security:security-crypto:1.1.0-alpha03' +androidx-security-crypto = 'androidx.security:security-crypto:1.1.0-alpha06' androidx-test-core = { module = "androidx.test:core", version.ref = "test" } -androidx-test-espresso-core = "androidx.test.espresso:espresso-core:3.5.1" -androidx-test-rules = { module = "androidx.test:rules", version.ref = "test" } -androidx-test-runner = { module = "androidx.test:runner", version.ref = "test" } +androidx-test-espresso-core = "androidx.test.espresso:espresso-core:3.6.1" -androidx-webkit = "androidx.webkit:webkit:1.7.0" -androidx-work = "androidx.work:work-runtime-ktx:2.8.1" +androidx-webkit = "androidx.webkit:webkit:1.12.1" +androidx-work = "androidx.work:work-runtime-ktx:2.9.1" coil-core = { module = "io.coil-kt:coil", version.ref = "coil" } coil-gif = { module = "io.coil-kt:coil-gif", version.ref = "coil" } @@ -74,23 +71,23 @@ epoxy-processor = { module = "com.airbnb.android:epoxy-processor", version.ref = exoplayer-core = { module = "com.google.android.exoplayer:exoplayer-core", version.ref = "exoplayer" } exoplayer-ui = { module = "com.google.android.exoplayer:exoplayer-ui", version.ref = "exoplayer" } -firebase-analytics = "com.google.firebase:firebase-analytics-ktx:21.3.0" -firebase-crashlytics-core = "com.google.firebase:firebase-crashlytics-ktx:18.3.7" -firebase-crashlytics-gradle = "com.google.firebase:firebase-crashlytics-gradle:2.9.6" +firebase-analytics = "com.google.firebase:firebase-analytics-ktx:22.1.2" +firebase-crashlytics-core = "com.google.firebase:firebase-crashlytics-ktx:19.2.0" +firebase-crashlytics-gradle = "com.google.firebase:firebase-crashlytics-gradle:3.0.2" -google-material = "com.google.android.material:material:1.9.0" +google-material = "com.google.android.material:material:1.12.0" -google-playservices-location = "com.google.android.gms:play-services-location:21.0.1" +google-playservices-location = "com.google.android.gms:play-services-location:21.3.0" -google-services-gradle = "com.google.gms:google-services:4.3.15" +google-services-gradle = "com.google.gms:google-services:4.4.2" -gradleVersionsPlugin = "com.github.ben-manes:gradle-versions-plugin:0.47.0" +gradleVersionsPlugin = "com.github.ben-manes:gradle-versions-plugin:0.51.0" -gradleLicensePlugin = "com.jaredsburrows:gradle-license-plugin:0.9.3" +gradleLicensePlugin = "com.jaredsburrows:gradle-license-plugin:0.9.8" -gson = "com.google.code.gson:gson:2.10.1" +gson = "com.google.code.gson:gson:2.11.0" -guava = "com.google.guava:guava:31.1-jre" +guava = "com.google.guava:guava:32.1.3-jre" listenablefuture = "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava" junit = "junit:junit:4.13.2" @@ -98,28 +95,26 @@ junit = "junit:junit:4.13.2" kotlin-gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" } kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.ref = "kotlin" } -kotlinx-coroutines-test = "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.2" +kotlinx-coroutines-test = "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.9.0" -leakcanary = "com.squareup.leakcanary:leakcanary-android:2.7" +mavericks = "com.airbnb.android:mavericks:3.0.9" +mavericks-compose = "com.airbnb.android:mavericks-compose:3.0.9" +mavericks-testing = "com.airbnb.android:mavericks-testing:3.0.9" -mavericks = "com.airbnb.android:mavericks:3.0.3" -mavericks-compose = "com.airbnb.android:mavericks-compose:3.0.3" -mavericks-testing = "com.airbnb.android:mavericks-testing:3.0.3" +mockito-core = "org.mockito:mockito-core:5.14.1" +mockito-kotlin = "org.mockito.kotlin:mockito-kotlin:5.4.0" +mockk = "io.mockk:mockk:1.13.12" -mockito-core = "org.mockito:mockito-core:5.4.0" -mockito-kotlin = "org.mockito.kotlin:mockito-kotlin:5.0.0" -mockk = "io.mockk:mockk:1.13.5" - -objectbox = "io.objectbox:objectbox-gradle-plugin:3.6.0" +objectbox = "io.objectbox:objectbox-gradle-plugin:4.0.2" okhttp-core = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" } okhttp-logginginterceptor = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp" } photoview = "com.github.chrisbanes:PhotoView:2.3.0" -rooted = "com.scottyab:rootbeer-lib:0.1.0" +rooted = "com.scottyab:rootbeer-lib:0.1.1" -spotless = "com.diffplug.spotless:spotless-plugin-gradle:6.19.0" +spotless = "com.diffplug.spotless:spotless-plugin-gradle:6.25.0" subsamplingimageview = "com.davemorrissey.labs:subsampling-scale-image-view:3.10.0" @@ -131,9 +126,9 @@ ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" } ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" } material3 = { group = "androidx.compose.material3", name = "material3" } androidx-compose-material-iconsExtended = { module = "androidx.compose.material:material-icons-extended" } -ui-compose-viewbinding = "androidx.compose.ui:ui-viewbinding:1.6.3" +ui-compose-viewbinding = "androidx.compose.ui:ui-viewbinding:1.7.3" -navigation-compose = "androidx.navigation:navigation-compose:2.7.6" -compose-runtime = "androidx.compose.runtime:runtime:1.5.4" +navigation-compose = "androidx.navigation:navigation-compose:2.8.2" +compose-runtime = "androidx.compose.runtime:runtime:1.7.3" androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling", version.ref = "ui-tooling" } constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" } \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index faac2f524..c5b2e5cec 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Sat Jun 18 20:47:24 IST 2022 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME diff --git a/listview/build.gradle b/listview/build.gradle index 5814484e9..a1936cf06 100644 --- a/listview/build.gradle +++ b/listview/build.gradle @@ -1,8 +1,9 @@ plugins{ - id('com.android.library') - id('kotlin-android') - id('kotlin-kapt') - id('kotlin-parcelize') + id 'com.android.library' + id 'kotlin-android' +// id 'kotlin-kapt' + id 'kotlin-parcelize' + id 'com.google.devtools.ksp' } android { @@ -39,5 +40,5 @@ dependencies { implementation libs.mavericks implementation libs.epoxy.core - kapt libs.epoxy.processor + ksp libs.epoxy.processor } diff --git a/listview/src/main/kotlin/com/alfresco/content/listview/ListFragment.kt b/listview/src/main/kotlin/com/alfresco/content/listview/ListFragment.kt index c4a3daba6..08a5cd24f 100644 --- a/listview/src/main/kotlin/com/alfresco/content/listview/ListFragment.kt +++ b/listview/src/main/kotlin/com/alfresco/content/listview/ListFragment.kt @@ -80,7 +80,6 @@ interface ListViewState : MavericksState { abstract class ListViewModel( initialState: S, ) : MavericksViewModel(initialState) { - private val _sharedFlow = MutableSharedFlow() val sharedFlow = _sharedFlow.asSharedFlow() private var folderListener: EntryListener? = null @@ -98,48 +97,53 @@ abstract class ListViewModel( viewModelScope.on { onStartProcess(it.entries.ifEmpty { listOf(it.entry) }) } } - private fun onStartProcess(entries: List) = entries.run { - if (entries.all { it.isFile }) { - folderListener?.onProcessStart(entries) + private fun onStartProcess(entries: List) = + entries.run { + if (entries.all { it.isFile }) { + folderListener?.onProcessStart(entries) + } } - } - private fun onDelete(action: Action) = action.run { - if (action.entries.isNotEmpty()) { - refresh() - } else { - val entry = (action.entry as Entry) - if (entry.isFile) { - removeEntry(entry) - } else { + private fun onDelete(action: Action) = + action.run { + if (action.entries.isNotEmpty()) { refresh() + } else { + val entry = (action.entry as Entry) + if (entry.isFile) { + removeEntry(entry) + } else { + refresh() + } } } - } - private fun onCreateFolder(entry: Entry) = entry.run { - refresh() - if (entry.isFolder) { - folderListener?.onEntryCreated(entry) + private fun onCreateFolder(entry: Entry) = + entry.run { + refresh() + if (entry.isFolder) { + folderListener?.onEntryCreated(entry) + } } - } - private fun onMove(action: ActionMoveFilesFolders) = action.run { - action.entries.forEach { - removeEntry(it) + private fun onMove(action: ActionMoveFilesFolders) = + action.run { + action.entries.forEach { + removeEntry(it) + } + refresh() } - refresh() - } @Suppress("UNCHECKED_CAST") - fun removeEntry(entry: Entry) = - setState { copyRemoving(entry) as S } + fun removeEntry(entry: Entry) = setState { copyRemoving(entry) as S } @Suppress("UNCHECKED_CAST") - private fun updateEntry(entry: Entry) = - setState { copyUpdating(entry) as S } + private fun updateEntry(entry: Entry) = setState { copyUpdating(entry) as S } - private fun updateActionEntries(entry: Entry, entries: List) { + private fun updateActionEntries( + entry: Entry, + entries: List, + ) { if (entries.isNotEmpty()) { entries.forEach { obj -> updateEntry(obj) @@ -149,7 +153,10 @@ abstract class ListViewModel( } } - private fun List.replace(newValue: T, block: (T) -> Boolean): List { + private fun List.replace( + newValue: T, + block: (T) -> Boolean, + ): List { return map { if (block(it)) newValue else it } @@ -163,8 +170,11 @@ abstract class ListViewModel( } abstract fun refresh() + abstract fun fetchNextPage() + abstract fun emptyMessageArgs(state: ListViewState): Triple + open fun resetMaxLimitError() {} companion object { @@ -196,7 +206,10 @@ abstract class ListFragment, S : ListViewState>(layoutID: private var isViewRequiredMultiSelection = false var bottomMoveButtonLayout: ConstraintLayout? = null - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { super.onViewCreated(view, savedInstanceState) loadingAnimation = view.findViewById(R.id.loading_animation) @@ -221,52 +234,62 @@ abstract class ListFragment, S : ListViewState>(layoutID: viewModel.setListener(this) - epoxyController.adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { - override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { - if (positionStart == 0) { - // @see: https://github.com/airbnb/epoxy/issues/224 - recyclerView.layoutManager?.scrollToPosition(0) + epoxyController.adapter.registerAdapterDataObserver( + object : RecyclerView.AdapterDataObserver() { + override fun onItemRangeInserted( + positionStart: Int, + itemCount: Int, + ) { + if (positionStart == 0) { + // @see: https://github.com/airbnb/epoxy/issues/224 + recyclerView.layoutManager?.scrollToPosition(0) + } } - } - }) + }, + ) } - override fun invalidate() = withState(viewModel) { state -> + override fun invalidate() = + withState(viewModel) { state -> + + if (viewModel.longPressHandled) { + if (state.selectedEntries.isEmpty()) { + MultiSelection.multiSelectionChangedFlow.tryEmit( + MultiSelectionData( + selectedEntries = state.selectedEntries, + isMultiSelectionEnabled = false, + ), + ) + } else { + MultiSelection.multiSelectionChangedFlow.tryEmit( + MultiSelectionData( + selectedEntries = state.selectedEntries, + isMultiSelectionEnabled = true, + ), + ) + } + } - if (viewModel.longPressHandled) { - if (state.selectedEntries.isEmpty()) { - MultiSelection.multiSelectionChangedFlow.tryEmit( - MultiSelectionData( - selectedEntries = state.selectedEntries, - isMultiSelectionEnabled = false, - ), - ) - } else { - MultiSelection.multiSelectionChangedFlow.tryEmit( - MultiSelectionData( - selectedEntries = state.selectedEntries, - isMultiSelectionEnabled = true, - ), - ) + if (state.maxLimitReachedForMultiSelection) { + Snackbar.make( + recyclerView, + String.format(getString(R.string.warning_max_item_multi_selection), MULTI_SELECTION_LIMIT), + Snackbar.LENGTH_SHORT, + ).show() + viewModel.resetMaxLimitError() } - } + loadingAnimation.isVisible = + state.request is Loading && state.entries.isEmpty() && !refreshLayout.isRefreshing - if (state.maxLimitReachedForMultiSelection) { - Snackbar.make(recyclerView, String.format(getString(R.string.warning_max_item_multi_selection), MULTI_SELECTION_LIMIT), Snackbar.LENGTH_SHORT).show() - viewModel.resetMaxLimitError() - } - loadingAnimation.isVisible = - state.request is Loading && state.entries.isEmpty() && !refreshLayout.isRefreshing + uploadButton?.isEnabled = state.request is Success - uploadButton?.isEnabled = state.request is Success + if (state.request.complete) { + refreshLayout.isRefreshing = false + } - if (state.request.complete) { - refreshLayout.isRefreshing = false + epoxyController.requestModelBuild() } - epoxyController.requestModelBuild() - } - /** * Disable refresh layout while sharing files */ @@ -290,93 +313,98 @@ abstract class ListFragment, S : ListViewState>(layoutID: this.isViewRequiredMultiSelection = isViewRequiredMultiSelection } - private fun epoxyController() = simpleController(viewModel) { state -> - if (state.entries.isEmpty() && state.request.complete) { - val args = viewModel.emptyMessageArgs(state) - listViewMessage { - id("empty_message") - iconRes(args.first) - title(args.second) - message(args.third) - } - } else if (state.entries.isNotEmpty()) { - val selectedEntries = state.entries.find { obj -> obj.isSelectedForMultiSelection } - if (selectedEntries == null) { - disableLongPress() - } + private fun epoxyController() = + simpleController(viewModel) { state -> + if (state.entries.isEmpty() && state.request.complete) { + val args = viewModel.emptyMessageArgs(state) + listViewMessage { + id("empty_message") + iconRes(args.first) + title(args.second) + message(args.third) + } + } else if (state.entries.isNotEmpty()) { + val selectedEntries = state.entries.find { obj -> obj.isSelectedForMultiSelection } + if (selectedEntries == null) { + disableLongPress() + } - state.entries.forEach { - if (it.type == Entry.Type.GROUP) { - listViewGroupHeader { - id(it.name) - title(it.name) - } - } else { - val menus = getJsonFromSharedPrefs(requireContext(), KEY_FEATURES_MOBILE)?.featuresMobile - ?.menus - val menuActionsEnabled = CommonRepository().isAllSingleActionsEnabled(menus, it) - - listViewRow { - id(stableId(it)) - data(it) - compact(state.isCompact) - menuAction(menuActionsEnabled) - multiSelection(state.selectedEntries.isNotEmpty()) - clickListener { model, _, _, _ -> - if (!viewModel.longPressHandled) { - onItemClicked(model.data()) - } else if (model.data().id.isNotEmpty()) { - onItemLongClicked(model.data()) - } + state.entries.forEach { + if (it.type == Entry.Type.GROUP) { + listViewGroupHeader { + id(it.name) + title(it.name) } - if (isViewRequiredMultiSelection) { - longClickListener { model, _, _, _ -> - if (!viewModel.longPressHandled && model.data().id.isNotEmpty()) { - enableLongPress() + } else { + val menus = + getJsonFromSharedPrefs(requireContext(), KEY_FEATURES_MOBILE)?.featuresMobile + ?.menus + val menuActionsEnabled = CommonRepository().isAllSingleActionsEnabled(menus, it) + + listViewRow { + id(stableId(it)) + data(it) + compact(state.isCompact) + menuAction(menuActionsEnabled) + multiSelection(state.selectedEntries.isNotEmpty()) + clickListener { model, _, _, _ -> + if (!viewModel.longPressHandled) { + onItemClicked(model.data()) + } else if (model.data().id.isNotEmpty()) { onItemLongClicked(model.data()) - true - } else { - false } } + if (isViewRequiredMultiSelection) { + longClickListener { model, _, _, _ -> + if (!viewModel.longPressHandled && model.data().id.isNotEmpty()) { + enableLongPress() + onItemLongClicked(model.data()) + true + } else { + false + } + } + } + moreClickListener { model, _, _, _ -> onItemMoreClicked(model.data()) } } - moreClickListener { model, _, _, _ -> onItemMoreClicked(model.data()) } } } - } - if (state.hasMoreItems) { - if (state.request is Loading) { - listViewPageLoading { - id("loading at ${state.entries.size}") - } - } else { - // On failure delay creating the boundary so that the list scrolls up - // and the user has to scroll back down to activate a retry - val isFail = state.request is Fail - if (isFail && !delayedBoundary) { - delayedBoundary = true - Handler(Looper.getMainLooper()).postDelayed({ this.requestModelBuild() }, 300) - } else { - if (isFail) { - delayedBoundary = false + if (state.hasMoreItems) { + if (state.request is Loading) { + listViewPageLoading { + id("loading at ${state.entries.size}") } - listViewPageBoundary { - id("boundary at ${state.entries.size}") - onBind { _, _, _ -> viewModel.fetchNextPage() } + } else { + // On failure delay creating the boundary so that the list scrolls up + // and the user has to scroll back down to activate a retry + val isFail = state.request is Fail + if (isFail && !delayedBoundary) { + delayedBoundary = true + Handler(Looper.getMainLooper()).postDelayed({ this.requestModelBuild() }, 300) + } else { + if (isFail) { + delayedBoundary = false + } + listViewPageBoundary { + id("boundary at ${state.entries.size}") + onBind { _, _, _ -> viewModel.fetchNextPage() } + } } } } } } - } private fun stableId(entry: Entry): String = if (entry.isUpload) { entry.boxId.toString() - } else entry.id + } else { + entry.id + } abstract fun onItemClicked(entry: Entry) + open fun onItemLongClicked(entry: Entry) {} open fun onItemMoreClicked(entry: Entry) { diff --git a/listview/src/main/kotlin/com/alfresco/content/listview/ListViewGroupHeader.kt b/listview/src/main/kotlin/com/alfresco/content/listview/ListViewGroupHeader.kt index 80134f49f..4adba6ac7 100644 --- a/listview/src/main/kotlin/com/alfresco/content/listview/ListViewGroupHeader.kt +++ b/listview/src/main/kotlin/com/alfresco/content/listview/ListViewGroupHeader.kt @@ -9,17 +9,18 @@ import com.airbnb.epoxy.TextProp import com.alfresco.content.listview.databinding.ViewListGroupHeaderBinding @ModelView(autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT) -class ListViewGroupHeader @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0, -) : FrameLayout(context, attrs, defStyleAttr) { +class ListViewGroupHeader + @JvmOverloads + constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + ) : FrameLayout(context, attrs, defStyleAttr) { + private val binding = ViewListGroupHeaderBinding.inflate(LayoutInflater.from(context), this, true) - private val binding = ViewListGroupHeaderBinding.inflate(LayoutInflater.from(context), this, true) - - @TextProp - fun setTitle(text: CharSequence) { - binding.title.text = text - binding.parent.contentDescription = context.getString(R.string.accessibility_text_header, text) + @TextProp + fun setTitle(text: CharSequence) { + binding.title.text = text + binding.parent.contentDescription = context.getString(R.string.accessibility_text_header, text) + } } -} diff --git a/listview/src/main/kotlin/com/alfresco/content/listview/ListViewMessage.kt b/listview/src/main/kotlin/com/alfresco/content/listview/ListViewMessage.kt index 3aac0932e..1c1bd528b 100644 --- a/listview/src/main/kotlin/com/alfresco/content/listview/ListViewMessage.kt +++ b/listview/src/main/kotlin/com/alfresco/content/listview/ListViewMessage.kt @@ -11,31 +11,38 @@ import com.airbnb.epoxy.ModelView import com.alfresco.content.listview.databinding.ViewListMessageBinding @ModelView(autoLayout = ModelView.Size.MATCH_WIDTH_MATCH_HEIGHT) -class ListViewMessage @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0, -) : FrameLayout(context, attrs, defStyleAttr) { +class ListViewMessage + @JvmOverloads + constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + ) : FrameLayout(context, attrs, defStyleAttr) { + private val binding = ViewListMessageBinding.inflate(LayoutInflater.from(context), this) - private val binding = ViewListMessageBinding.inflate(LayoutInflater.from(context), this) + @ModelProp + fun setIconRes( + @DrawableRes drawableRes: Int, + ) { + binding.icon.setImageResource(drawableRes) + } - @ModelProp - fun setIconRes(@DrawableRes drawableRes: Int) { - binding.icon.setImageResource(drawableRes) - } - - @ModelProp - fun setTitle(@StringRes stringRes: Int) { - binding.title.text = resources.getText(stringRes) - } + @ModelProp + fun setTitle( + @StringRes stringRes: Int, + ) { + binding.title.text = resources.getText(stringRes) + } - @ModelProp - fun setMessage(@StringRes stringRes: Int) { - binding.message.text = resources.getText(stringRes) - } + @ModelProp + fun setMessage( + @StringRes stringRes: Int, + ) { + binding.message.text = resources.getText(stringRes) + } - @ModelProp - fun setMessage(stringRes: String) { - binding.message.text = stringRes + @ModelProp + fun setMessage(stringRes: String) { + binding.message.text = stringRes + } } -} diff --git a/listview/src/main/kotlin/com/alfresco/content/listview/ListViewPageBoundary.kt b/listview/src/main/kotlin/com/alfresco/content/listview/ListViewPageBoundary.kt index f5020d0ec..fa492ae5c 100644 --- a/listview/src/main/kotlin/com/alfresco/content/listview/ListViewPageBoundary.kt +++ b/listview/src/main/kotlin/com/alfresco/content/listview/ListViewPageBoundary.kt @@ -7,13 +7,14 @@ import android.widget.FrameLayout import com.airbnb.epoxy.ModelView @ModelView(autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT) -class ListViewPageBoundary @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0, -) : FrameLayout(context, attrs, defStyleAttr) { - - init { - LayoutInflater.from(context).inflate(R.layout.view_list_page_boundary, this, true) +class ListViewPageBoundary + @JvmOverloads + constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + ) : FrameLayout(context, attrs, defStyleAttr) { + init { + LayoutInflater.from(context).inflate(R.layout.view_list_page_boundary, this, true) + } } -} diff --git a/listview/src/main/kotlin/com/alfresco/content/listview/ListViewPageLoading.kt b/listview/src/main/kotlin/com/alfresco/content/listview/ListViewPageLoading.kt index a72b35757..7b990b0cb 100644 --- a/listview/src/main/kotlin/com/alfresco/content/listview/ListViewPageLoading.kt +++ b/listview/src/main/kotlin/com/alfresco/content/listview/ListViewPageLoading.kt @@ -6,13 +6,14 @@ import android.widget.FrameLayout import com.airbnb.epoxy.ModelView @ModelView(autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT) -class ListViewPageLoading @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0, -) : FrameLayout(context, attrs, defStyleAttr) { - - init { - inflate(context, R.layout.view_list_page_loading, this) +class ListViewPageLoading + @JvmOverloads + constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + ) : FrameLayout(context, attrs, defStyleAttr) { + init { + inflate(context, R.layout.view_list_page_loading, this) + } } -} diff --git a/listview/src/main/kotlin/com/alfresco/content/listview/ListViewRow.kt b/listview/src/main/kotlin/com/alfresco/content/listview/ListViewRow.kt index 87837d1fd..c36a3d759 100644 --- a/listview/src/main/kotlin/com/alfresco/content/listview/ListViewRow.kt +++ b/listview/src/main/kotlin/com/alfresco/content/listview/ListViewRow.kt @@ -20,193 +20,199 @@ import com.alfresco.content.mimetype.MimeType import com.alfresco.ui.getDrawableForAttribute @ModelView(autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT) -class ListViewRow @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0, -) : FrameLayout(context, attrs, defStyleAttr) { - - private val binding = ViewListRowBinding.inflate(LayoutInflater.from(context), this, true) - private var isCompact: Boolean = false - private lateinit var entry: Entry - private var multiSelectionEnabled = false - private var menuActionsEnabled = false - - @ModelProp - fun setData(entry: Entry) { - this.entry = entry - binding.title.text = entry.name - binding.subtitle.text = entry.path - updateSubtitleVisibility() - - val type = when (entry.type) { - Entry.Type.SITE -> MimeType.LIBRARY - Entry.Type.FOLDER -> MimeType.FOLDER - Entry.Type.FILE_LINK -> MimeType.FILE_LINK - Entry.Type.FOLDER_LINK -> MimeType.FOLDER_LINK - else -> MimeType.with(entry.mimeType) - } +class ListViewRow + @JvmOverloads + constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + ) : FrameLayout(context, attrs, defStyleAttr) { + private val binding = ViewListRowBinding.inflate(LayoutInflater.from(context), this, true) + private var isCompact: Boolean = false + private lateinit var entry: Entry + private var multiSelectionEnabled = false + private var menuActionsEnabled = false + + @ModelProp + fun setData(entry: Entry) { + this.entry = entry + binding.title.text = entry.name + binding.subtitle.text = entry.path + updateSubtitleVisibility() + + val type = + when (entry.type) { + Entry.Type.SITE -> MimeType.LIBRARY + Entry.Type.FOLDER -> MimeType.FOLDER + Entry.Type.FILE_LINK -> MimeType.FILE_LINK + Entry.Type.FOLDER_LINK -> MimeType.FOLDER_LINK + else -> MimeType.with(entry.mimeType) + } + + if (entry.isExtension && !entry.isFolder) { + binding.parent.alpha = 0.5f + binding.parent.isEnabled = false + } else { + binding.parent.alpha = 1.0f + binding.parent.isEnabled = true + } + + binding.moreButton.isEnabled = !entry.isExtension - if (entry.isExtension && !entry.isFolder) { - binding.parent.alpha = 0.5f - binding.parent.isEnabled = false - } else { - binding.parent.alpha = 1.0f - binding.parent.isEnabled = true + binding.icon.setImageDrawable(ResourcesCompat.getDrawable(resources, type.icon, context.theme)) + + val accessibilityText = + if (entry.path.isNullOrEmpty()) { + context.getString( + R.string.accessibility_text_title, + entry.name, + ) + } else { + context.getString( + R.string.accessibility_text_simple_row, + entry.name, + entry.path, + ) + } + binding.parent.contentDescription = accessibilityText } - binding.moreButton.isEnabled = !entry.isExtension - - binding.icon.setImageDrawable(ResourcesCompat.getDrawable(resources, type.icon, context.theme)) - - val accessibilityText = if (entry.path.isNullOrEmpty()) { - context.getString( - R.string.accessibility_text_title, - entry.name, - ) - } else context.getString( - R.string.accessibility_text_simple_row, - entry.name, - entry.path, - ) - binding.parent.contentDescription = accessibilityText - } + private fun configureOfflineStatus(entry: Entry) { + // Outside offline screen + if (entry.isOffline && !entry.hasOfflineStatus) { + binding.offlineIcon.isVisible = true + binding.offlineIcon.setImageDrawable( + ResourcesCompat.getDrawable( + resources, + R.drawable.ic_offline_marked, + context.theme, + ), + ) + } else { + // Offline screen items and uploads + if (entry.isFile && entry.hasOfflineStatus) { + val config = makeOfflineStatusConfig(entry) + val drawableRes = config.first + if (drawableRes != null) { + val drawable = + ResourcesCompat.getDrawable(resources, drawableRes, context.theme) + if (drawable is AnimatedVectorDrawable) { + drawable.start() + } + binding.offlineIcon.setImageDrawable(drawable) + binding.offlineIcon.isVisible = true + } else { + binding.offlineIcon.isVisible = false + } - private fun configureOfflineStatus(entry: Entry) { - // Outside offline screen - if (entry.isOffline && !entry.hasOfflineStatus) { - binding.offlineIcon.isVisible = true - binding.offlineIcon.setImageDrawable( - ResourcesCompat.getDrawable( - resources, - R.drawable.ic_offline_marked, - context.theme, - ), - ) - } else { - // Offline screen items and uploads - if (entry.isFile && entry.hasOfflineStatus) { - val config = makeOfflineStatusConfig(entry) - val drawableRes = config.first - if (drawableRes != null) { - val drawable = - ResourcesCompat.getDrawable(resources, drawableRes, context.theme) - if (drawable is AnimatedVectorDrawable) { - drawable.start() + val stringRes = config.second + if (stringRes != null) { + binding.subtitle.text = context.getString(stringRes) } - binding.offlineIcon.setImageDrawable(drawable) - binding.offlineIcon.isVisible = true } else { binding.offlineIcon.isVisible = false } - - val stringRes = config.second - if (stringRes != null) { - binding.subtitle.text = context.getString(stringRes) - } - } else { - binding.offlineIcon.isVisible = false } } - } - private fun makeOfflineStatusConfig(entry: Entry): Pair = - when (entry.offlineStatus) { - OfflineStatus.PENDING -> - if (entry.isUpload) { - Pair(R.drawable.ic_offline_upload, null) - } else { - Pair(R.drawable.ic_offline_status_pending, null) - } + private fun makeOfflineStatusConfig(entry: Entry): Pair = + when (entry.offlineStatus) { + OfflineStatus.PENDING -> + if (entry.isUpload) { + Pair(R.drawable.ic_offline_upload, null) + } else { + Pair(R.drawable.ic_offline_status_pending, null) + } - OfflineStatus.SYNCING -> - Pair(R.drawable.ic_offline_status_in_progress_anim, R.string.offline_status_in_progress) + OfflineStatus.SYNCING -> + Pair(R.drawable.ic_offline_status_in_progress_anim, R.string.offline_status_in_progress) - OfflineStatus.SYNCED -> - Pair(R.drawable.ic_offline_status_synced, null) + OfflineStatus.SYNCED -> + Pair(R.drawable.ic_offline_status_synced, null) - OfflineStatus.ERROR -> - Pair(R.drawable.ic_offline_status_error, R.string.offline_status_error) + OfflineStatus.ERROR -> + Pair(R.drawable.ic_offline_status_error, R.string.offline_status_error) - else -> - Pair(R.drawable.ic_offline_status_synced, null) - } + else -> + Pair(R.drawable.ic_offline_status_synced, null) + } - private fun actionButtonVisibility(entry: Entry) = - !entry.isLink && !entry.isUpload && - // Child folder in offline tab - !(entry.isFolder && entry.hasOfflineStatus && !entry.isOffline) + private fun actionButtonVisibility(entry: Entry) = + !entry.isLink && !entry.isUpload && + // Child folder in offline tab + !(entry.isFolder && entry.hasOfflineStatus && !entry.isOffline) - @ModelProp - fun setCompact(compact: Boolean) { - this.isCompact = compact + @ModelProp + fun setCompact(compact: Boolean) { + this.isCompact = compact - val heightResId = if (compact) R.dimen.list_row_compact_height else R.dimen.list_row_height - binding.parent.layoutParams = LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - resources.getDimension(heightResId).toInt(), - ) + val heightResId = if (compact) R.dimen.list_row_compact_height else R.dimen.list_row_height + binding.parent.layoutParams = + LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + resources.getDimension(heightResId).toInt(), + ) - updateSubtitleVisibility() - } + updateSubtitleVisibility() + } - private fun updateSubtitleVisibility() { - binding.subtitle.isVisible = binding.subtitle.text.isNotEmpty() && !isCompact - } + private fun updateSubtitleVisibility() { + binding.subtitle.isVisible = binding.subtitle.text.isNotEmpty() && !isCompact + } - @ModelProp - fun setMultiSelection(isMultiSelection: Boolean) { - this.multiSelectionEnabled = isMultiSelection - } + @ModelProp + fun setMultiSelection(isMultiSelection: Boolean) { + this.multiSelectionEnabled = isMultiSelection + } - @ModelProp - fun setMenuAction(enabled: Boolean) { - menuActionsEnabled = enabled - } + @ModelProp + fun setMenuAction(enabled: Boolean) { + menuActionsEnabled = enabled + } - @AfterPropsSet - fun bind() { - binding.checkBox.isChecked = entry.isSelectedForMultiSelection + @AfterPropsSet + fun bind() { + binding.checkBox.isChecked = entry.isSelectedForMultiSelection - if (entry.isSelectedForMultiSelection) { - binding.parent.setBackgroundColor(ContextCompat.getColor(context, R.color.colorBackgroundMultiSelection)) - } else { - binding.parent.background = context.getDrawableForAttribute(R.attr.selectableItemBackground) - } + if (entry.isSelectedForMultiSelection) { + binding.parent.setBackgroundColor(ContextCompat.getColor(context, R.color.colorBackgroundMultiSelection)) + } else { + binding.parent.background = context.getDrawableForAttribute(R.attr.selectableItemBackground) + } - if (multiSelectionEnabled) { - binding.moreButton.isVisible = false - binding.offlineIcon.isVisible = false - binding.checkBox.isVisible = true - } else { - postDataSet() + if (multiSelectionEnabled) { + binding.moreButton.isVisible = false + binding.offlineIcon.isVisible = false + binding.checkBox.isVisible = true + } else { + postDataSet() + } } - } - private fun postDataSet() { - binding.checkBox.isVisible = false - binding.checkBox.isChecked = false + private fun postDataSet() { + binding.checkBox.isVisible = false + binding.checkBox.isChecked = false - binding.moreButton.isEnabled = menuActionsEnabled - binding.moreButton.isVisible = actionButtonVisibility(entry) - configureOfflineStatus(entry) - } + binding.moreButton.isEnabled = menuActionsEnabled + binding.moreButton.isVisible = actionButtonVisibility(entry) + configureOfflineStatus(entry) + } - @CallbackProp - fun setClickListener(listener: OnClickListener?) { - binding.parent.setOnClickListener(listener) - } + @CallbackProp + fun setClickListener(listener: OnClickListener?) { + binding.parent.setOnClickListener(listener) + } - /** - * long press gesture for the row - */ - @CallbackProp - fun setLongClickListener(listener: OnLongClickListener?) { - binding.parent.setOnLongClickListener(listener) - } + /** + * long press gesture for the row + */ + @CallbackProp + fun setLongClickListener(listener: OnLongClickListener?) { + binding.parent.setOnLongClickListener(listener) + } - @CallbackProp - fun setMoreClickListener(listener: OnClickListener?) { - binding.moreButton.setOnClickListener(listener) + @CallbackProp + fun setMoreClickListener(listener: OnClickListener?) { + binding.moreButton.setOnClickListener(listener) + } } -} diff --git a/listview/src/main/kotlin/com/alfresco/content/listview/NonSwipeableViewPager.kt b/listview/src/main/kotlin/com/alfresco/content/listview/NonSwipeableViewPager.kt index 82acab561..f6569c8d9 100644 --- a/listview/src/main/kotlin/com/alfresco/content/listview/NonSwipeableViewPager.kt +++ b/listview/src/main/kotlin/com/alfresco/content/listview/NonSwipeableViewPager.kt @@ -6,7 +6,6 @@ import android.view.MotionEvent import androidx.viewpager.widget.ViewPager class NonSwipeableViewPager(context: Context, attrs: AttributeSet?) : ViewPager(context, attrs) { - private var isSwipeEnabled = false override fun onInterceptTouchEvent(event: MotionEvent): Boolean { diff --git a/listview/src/main/kotlin/com/alfresco/content/listview/processes/ListRowProcessDefinitions.kt b/listview/src/main/kotlin/com/alfresco/content/listview/processes/ListRowProcessDefinitions.kt index cff90cb9a..8a6edabb9 100644 --- a/listview/src/main/kotlin/com/alfresco/content/listview/processes/ListRowProcessDefinitions.kt +++ b/listview/src/main/kotlin/com/alfresco/content/listview/processes/ListRowProcessDefinitions.kt @@ -11,23 +11,25 @@ import com.alfresco.content.actions.databinding.ViewProcessDefinitionsListRowBin import com.alfresco.content.data.RuntimeProcessDefinitionDataEntry @ModelView(autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT) -internal class ListRowProcessDefinitions @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0, -) : FrameLayout(context, attrs, defStyleAttr) { - private val binding = ViewProcessDefinitionsListRowBinding.inflate(LayoutInflater.from(context), this, true) +internal class ListRowProcessDefinitions + @JvmOverloads + constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + ) : FrameLayout(context, attrs, defStyleAttr) { + private val binding = ViewProcessDefinitionsListRowBinding.inflate(LayoutInflater.from(context), this, true) - @ModelProp(options = [ModelProp.Option.IgnoreRequireHashCode]) - fun setProcessDefinition(data: RuntimeProcessDefinitionDataEntry) { - binding.apply { - title.text = data.name - subtitle.text = data.description + @ModelProp(options = [ModelProp.Option.IgnoreRequireHashCode]) + fun setProcessDefinition(data: RuntimeProcessDefinitionDataEntry) { + binding.apply { + title.text = data.name + subtitle.text = data.description + } } - } - @CallbackProp - fun setClickListener(listener: OnClickListener?) { - setOnClickListener(listener) + @CallbackProp + fun setClickListener(listener: OnClickListener?) { + setOnClickListener(listener) + } } -} diff --git a/listview/src/main/kotlin/com/alfresco/content/listview/processes/ListViewProcessRow.kt b/listview/src/main/kotlin/com/alfresco/content/listview/processes/ListViewProcessRow.kt index 7ad1984cc..39d9bbebc 100644 --- a/listview/src/main/kotlin/com/alfresco/content/listview/processes/ListViewProcessRow.kt +++ b/listview/src/main/kotlin/com/alfresco/content/listview/processes/ListViewProcessRow.kt @@ -21,37 +21,39 @@ import com.alfresco.content.listview.databinding.ViewListProcessRowBinding * Marked as ListViewProcessRow class */ @ModelView(autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT) -class ListViewProcessRow @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0, -) : FrameLayout(context, attrs, defStyleAttr) { +class ListViewProcessRow + @JvmOverloads + constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + ) : FrameLayout(context, attrs, defStyleAttr) { + private val binding = ViewListProcessRowBinding.inflate(LayoutInflater.from(context), this, true) - private val binding = ViewListProcessRowBinding.inflate(LayoutInflater.from(context), this, true) + /** + * set the data on view + */ + @RequiresApi(Build.VERSION_CODES.O) + @ModelProp + fun setData(entry: ProcessEntry) { + binding.title.text = entry.name + val localizedName = context.getLocalizedName(entry.startedBy?.name ?: "") + binding.subtitle.text = localizedName + binding.timeStamp.text = entry.started?.toLocalDateTime().toString().getLocalFormattedDate(DATE_FORMAT_6, DATE_FORMAT_7) + val accessibilityText = + context.getString( + R.string.accessibility_text_process_row, + entry.name, + localizedName, + ) + binding.parent.contentDescription = accessibilityText + } - /** - * set the data on view - */ - @RequiresApi(Build.VERSION_CODES.O) - @ModelProp - fun setData(entry: ProcessEntry) { - binding.title.text = entry.name - val localizedName = context.getLocalizedName(entry.startedBy?.name ?: "") - binding.subtitle.text = localizedName - binding.timeStamp.text = entry.started?.toLocalDateTime().toString().getLocalFormattedDate(DATE_FORMAT_6, DATE_FORMAT_7) - val accessibilityText = context.getString( - R.string.accessibility_text_process_row, - entry.name, - localizedName, - ) - binding.parent.contentDescription = accessibilityText + /** + * row click listener + */ + @CallbackProp + fun setClickListener(listener: OnClickListener?) { + setOnClickListener(listener) + } } - - /** - * row click listener - */ - @CallbackProp - fun setClickListener(listener: OnClickListener?) { - setOnClickListener(listener) - } -} diff --git a/listview/src/main/kotlin/com/alfresco/content/listview/processes/ProcessListFragment.kt b/listview/src/main/kotlin/com/alfresco/content/listview/processes/ProcessListFragment.kt index efc8403f7..5fe1606a6 100644 --- a/listview/src/main/kotlin/com/alfresco/content/listview/processes/ProcessListFragment.kt +++ b/listview/src/main/kotlin/com/alfresco/content/listview/processes/ProcessListFragment.kt @@ -51,7 +51,6 @@ interface ProcessListViewState : MavericksState { abstract class ProcessListViewModel( initialState: S, ) : MavericksViewModel(initialState) { - private var folderListener: EntryListener? = null /** @@ -94,7 +93,10 @@ abstract class ProcessListFragment, S : ProcessList lateinit var rlFilters: RelativeLayout lateinit var filterTitle: TextView - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { super.onViewCreated(view, savedInstanceState) loadingAnimation = view.findViewById(R.id.loading_animation) @@ -111,14 +113,19 @@ abstract class ProcessListFragment, S : ProcessList recyclerView.setController(epoxyController) viewModel.setListener(this) - epoxyController.adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { - override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { - if (positionStart == 0) { - // @see: https://github.com/airbnb/epoxy/issues/224 - recyclerView.layoutManager?.scrollToPosition(0) + epoxyController.adapter.registerAdapterDataObserver( + object : RecyclerView.AdapterDataObserver() { + override fun onItemRangeInserted( + positionStart: Int, + itemCount: Int, + ) { + if (positionStart == 0) { + // @see: https://github.com/airbnb/epoxy/issues/224 + recyclerView.layoutManager?.scrollToPosition(0) + } } - } - }) + }, + ) } /** @@ -126,41 +133,43 @@ abstract class ProcessListFragment, S : ProcessList */ abstract fun onItemClicked(entry: ProcessEntry) - override fun invalidate() = withState(viewModel) { state -> + override fun invalidate() = + withState(viewModel) { state -> - loadingAnimation.isVisible = - state.request is Loading && state.processEntries.isEmpty() && !refreshLayout.isRefreshing + loadingAnimation.isVisible = + state.request is Loading && state.processEntries.isEmpty() && !refreshLayout.isRefreshing - if (state.request.complete) { - refreshLayout.isRefreshing = false + if (state.request.complete) { + refreshLayout.isRefreshing = false + } + epoxyController.requestModelBuild() } - epoxyController.requestModelBuild() - } - private fun epoxyController() = simpleController(viewModel) { state -> - if (state.request.complete && state.request is Fail) { - visibleFilters(false) - } - if (state.processEntries.isEmpty() && state.request.complete) { - if (state.request is Success) visibleFilters(true) - val args = viewModel.emptyMessageArgs(state) - listViewMessage { - id("empty_message") - iconRes(args.first) - title(args.second) - message(args.third) + private fun epoxyController() = + simpleController(viewModel) { state -> + if (state.request.complete && state.request is Fail) { + visibleFilters(false) } - } else if (state.processEntries.isNotEmpty()) { - visibleFilters(true) - state.processEntries.forEach { - listViewProcessRow { - id(it.id) - data(it) - clickListener { model, _, _, _ -> onItemClicked(model.data()) } + if (state.processEntries.isEmpty() && state.request.complete) { + if (state.request is Success) visibleFilters(true) + val args = viewModel.emptyMessageArgs(state) + listViewMessage { + id("empty_message") + iconRes(args.first) + title(args.second) + message(args.third) + } + } else if (state.processEntries.isNotEmpty()) { + visibleFilters(true) + state.processEntries.forEach { + listViewProcessRow { + id(it.id) + data(it) + clickListener { model, _, _, _ -> onItemClicked(model.data()) } + } } } } - } private fun visibleFilters(isVisible: Boolean) { GlobalScope.launch { diff --git a/listview/src/main/kotlin/com/alfresco/content/listview/tasks/ListViewTaskRow.kt b/listview/src/main/kotlin/com/alfresco/content/listview/tasks/ListViewTaskRow.kt index c5ca9ad27..e30069ff4 100644 --- a/listview/src/main/kotlin/com/alfresco/content/listview/tasks/ListViewTaskRow.kt +++ b/listview/src/main/kotlin/com/alfresco/content/listview/tasks/ListViewTaskRow.kt @@ -24,41 +24,43 @@ import com.alfresco.content.listview.databinding.ViewListTaskRowBinding * Marked as ListViewTaskRow class */ @ModelView(autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT) -class ListViewTaskRow @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0, -) : FrameLayout(context, attrs, defStyleAttr) { +class ListViewTaskRow + @JvmOverloads + constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + ) : FrameLayout(context, attrs, defStyleAttr) { + private val binding = ViewListTaskRowBinding.inflate(LayoutInflater.from(context), this, true) - private val binding = ViewListTaskRowBinding.inflate(LayoutInflater.from(context), this, true) + /** + * set the data on view + */ + @RequiresApi(Build.VERSION_CODES.O) + @ModelProp + fun setData(entry: TaskEntry) { + binding.title.text = entry.name.ifEmpty { context.getString(R.string.title_no_name) } + val localizedName = context.getLocalizedName(entry.assignee?.name ?: "") + binding.subtitle.visibility = if (localizedName.trim().isNotEmpty()) View.VISIBLE else View.GONE + binding.subtitle.text = localizedName + binding.timeStamp.text = entry.created?.toLocalDateTime().toString().getLocalFormattedDate(DATE_FORMAT_6, DATE_FORMAT_7) + val accessibilityText = + context.getString( + R.string.accessibility_text_task_row, + entry.name, + localizedName, + getTaskPriority(entry.priority).value, + ) + binding.parent.contentDescription = accessibilityText - /** - * set the data on view - */ - @RequiresApi(Build.VERSION_CODES.O) - @ModelProp - fun setData(entry: TaskEntry) { - binding.title.text = entry.name.ifEmpty { context.getString(R.string.title_no_name) } - val localizedName = context.getLocalizedName(entry.assignee?.name ?: "") - binding.subtitle.visibility = if (localizedName.trim().isNotEmpty()) View.VISIBLE else View.GONE - binding.subtitle.text = localizedName - binding.timeStamp.text = entry.created?.toLocalDateTime().toString().getLocalFormattedDate(DATE_FORMAT_6, DATE_FORMAT_7) - val accessibilityText = context.getString( - R.string.accessibility_text_task_row, - entry.name, - localizedName, - getTaskPriority(entry.priority).value, - ) - binding.parent.contentDescription = accessibilityText + binding.tvPriority.updatePriorityView(entry.priority) + } - binding.tvPriority.updatePriorityView(entry.priority) + /** + * row click listener + */ + @CallbackProp + fun setClickListener(listener: OnClickListener?) { + setOnClickListener(listener) + } } - - /** - * row click listener - */ - @CallbackProp - fun setClickListener(listener: OnClickListener?) { - setOnClickListener(listener) - } -} diff --git a/listview/src/main/kotlin/com/alfresco/content/listview/tasks/TaskListFragment.kt b/listview/src/main/kotlin/com/alfresco/content/listview/tasks/TaskListFragment.kt index 8fb3eacb9..1262e9f01 100644 --- a/listview/src/main/kotlin/com/alfresco/content/listview/tasks/TaskListFragment.kt +++ b/listview/src/main/kotlin/com/alfresco/content/listview/tasks/TaskListFragment.kt @@ -60,7 +60,6 @@ interface TaskListViewState : MavericksState { abstract class TaskListViewModel( initialState: S, ) : MavericksViewModel(initialState) { - private var folderListener: EntryListener? = null init { @@ -115,7 +114,10 @@ abstract class TaskListFragment, S : TaskListViewState lateinit var actionReset: ImageView lateinit var toolbar: Toolbar - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { super.onViewCreated(view, savedInstanceState) loadingAnimation = view.findViewById(R.id.loading_animation) @@ -135,14 +137,19 @@ abstract class TaskListFragment, S : TaskListViewState recyclerView.setController(epoxyController) viewModel.setListener(this) - epoxyController.adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { - override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { - if (positionStart == 0) { - // @see: https://github.com/airbnb/epoxy/issues/224 - recyclerView.layoutManager?.scrollToPosition(0) + epoxyController.adapter.registerAdapterDataObserver( + object : RecyclerView.AdapterDataObserver() { + override fun onItemRangeInserted( + positionStart: Int, + itemCount: Int, + ) { + if (positionStart == 0) { + // @see: https://github.com/airbnb/epoxy/issues/224 + recyclerView.layoutManager?.scrollToPosition(0) + } } - } - }) + }, + ) } private fun visibleFilters(isVisible: Boolean) { @@ -158,68 +165,70 @@ abstract class TaskListFragment, S : TaskListViewState */ abstract fun onItemClicked(entry: TaskEntry) - override fun invalidate() = withState(viewModel) { state -> + override fun invalidate() = + withState(viewModel) { state -> - toolbar.visibility = if (state.processEntry != null) View.VISIBLE else View.GONE + toolbar.visibility = if (state.processEntry != null) View.VISIBLE else View.GONE - loadingAnimation.isVisible = - state.request is Loading && state.taskEntries.isEmpty() && !refreshLayout.isRefreshing + loadingAnimation.isVisible = + state.request is Loading && state.taskEntries.isEmpty() && !refreshLayout.isRefreshing - topLoadingIndicator.isVisible = - state.request is Loading && state.taskEntries.isNotEmpty() && !refreshLayout.isRefreshing + topLoadingIndicator.isVisible = + state.request is Loading && state.taskEntries.isNotEmpty() && !refreshLayout.isRefreshing - if (state.request.complete) { - refreshLayout.isRefreshing = false + if (state.request.complete) { + refreshLayout.isRefreshing = false + } + epoxyController.requestModelBuild() } - epoxyController.requestModelBuild() - } - private fun epoxyController() = simpleController(viewModel) { state -> - if (state.request.complete && state.request is Fail) { - visibleFilters(false) - } - if (state.taskEntries.isEmpty() && state.request.complete) { - visibleFilters((state.request is Success && state.processEntry == null)) - val args = viewModel.emptyMessageArgs(state) - listViewMessage { - id("empty_message") - iconRes(args.first) - title(args.second) - message(args.third) + private fun epoxyController() = + simpleController(viewModel) { state -> + if (state.request.complete && state.request is Fail) { + visibleFilters(false) } - } else if (state.taskEntries.isNotEmpty()) { - visibleFilters((state.taskEntries.isNotEmpty() && state.processEntry == null)) - state.taskEntries.forEach { - listViewTaskRow { - id(it.id) - data(it) - clickListener { model, _, _, _ -> onItemClicked(model.data()) } + if (state.taskEntries.isEmpty() && state.request.complete) { + visibleFilters((state.request is Success && state.processEntry == null)) + val args = viewModel.emptyMessageArgs(state) + listViewMessage { + id("empty_message") + iconRes(args.first) + title(args.second) + message(args.third) + } + } else if (state.taskEntries.isNotEmpty()) { + visibleFilters((state.taskEntries.isNotEmpty() && state.processEntry == null)) + state.taskEntries.forEach { + listViewTaskRow { + id(it.id) + data(it) + clickListener { model, _, _, _ -> onItemClicked(model.data()) } + } } } - } - if (state.hasMoreItems) { - if (state.request is Loading) { - listViewPageLoading { - id("loading at ${state.taskEntries.size}") - } - } else { - // On failure delay creating the boundary so that the list scrolls up - // and the user has to scroll back down to activate a retry - val isFail = state.request is Fail - if (isFail && !delayedBoundary) { - delayedBoundary = true - Handler(Looper.getMainLooper()).postDelayed({ this.requestModelBuild() }, 300) - } else { - if (isFail) { - delayedBoundary = false + if (state.hasMoreItems) { + if (state.request is Loading) { + listViewPageLoading { + id("loading at ${state.taskEntries.size}") } - listViewPageBoundary { - id("boundary at ${state.taskEntries.size}") - onBind { _, _, _ -> viewModel.fetchNextPage() } + } else { + // On failure delay creating the boundary so that the list scrolls up + // and the user has to scroll back down to activate a retry + val isFail = state.request is Fail + if (isFail && !delayedBoundary) { + delayedBoundary = true + Handler(Looper.getMainLooper()).postDelayed({ this.requestModelBuild() }, 300) + } else { + if (isFail) { + delayedBoundary = false + } + listViewPageBoundary { + id("boundary at ${state.taskEntries.size}") + onBind { _, _, _ -> viewModel.fetchNextPage() } + } } } } } - } } diff --git a/move/build.gradle b/move/build.gradle index 774983c42..90430a953 100644 --- a/move/build.gradle +++ b/move/build.gradle @@ -1,8 +1,9 @@ plugins { id 'com.android.library' id 'kotlin-android' - id 'kotlin-kapt' +// id 'kotlin-kapt' id 'kotlin-parcelize' + id 'com.google.devtools.ksp' } android { @@ -36,5 +37,5 @@ dependencies { implementation libs.gson implementation libs.androidx.preference - kapt libs.epoxy.processor + ksp libs.epoxy.processor } diff --git a/move/src/main/java/com/alfresco/content/move/BrowseMoveFragment.kt b/move/src/main/java/com/alfresco/content/move/BrowseMoveFragment.kt index ba1e36998..b7fe7c417 100644 --- a/move/src/main/java/com/alfresco/content/move/BrowseMoveFragment.kt +++ b/move/src/main/java/com/alfresco/content/move/BrowseMoveFragment.kt @@ -27,7 +27,6 @@ import kotlinx.coroutines.flow.collectLatest * Mark as BrowseMoveFragment */ class BrowseMoveFragment : ListFragment(R.layout.fragment_move_list) { - private lateinit var args: BrowseArgs @OptIn(InternalMavericksApi::class) @@ -42,7 +41,10 @@ class BrowseMoveFragment : ListFragment(R.layo setHasOptionsMenu(true) } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { super.onViewCreated(view, savedInstanceState) if (args.isProcess) { @@ -60,9 +62,10 @@ class BrowseMoveFragment : ListFragment(R.layo requireActivity().finish() } else { val activity = requireActivity() - val intent = Intent().apply { - putExtra(MoveResultContract.OUTPUT_KEY, state.nodeId) - } + val intent = + Intent().apply { + putExtra(MoveResultContract.OUTPUT_KEY, state.nodeId) + } activity.setResult(Activity.RESULT_OK, intent) activity.finish() } @@ -80,7 +83,10 @@ class BrowseMoveFragment : ListFragment(R.layo } } - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + override fun onCreateOptionsMenu( + menu: Menu, + inflater: MenuInflater, + ) { if (args.isProcess) { inflater.inflate(R.menu.menu_browse_folder, menu) } else { @@ -118,18 +124,19 @@ class BrowseMoveFragment : ListFragment(R.layo } } - override fun invalidate() = withState(viewModel) { state -> - if (state.path == getString(R.string.nav_path_move)) { - super.disableRefreshLayout() - } + override fun invalidate() = + withState(viewModel) { state -> + if (state.path == getString(R.string.nav_path_move)) { + super.disableRefreshLayout() + } - if (state.title != null) { - bottomMoveButtonLayout?.visibility = View.VISIBLE - } else { - bottomMoveButtonLayout?.visibility = View.INVISIBLE + if (state.title != null) { + bottomMoveButtonLayout?.visibility = View.VISIBLE + } else { + bottomMoveButtonLayout?.visibility = View.INVISIBLE + } + super.invalidate() } - super.invalidate() - } /** * return callback for list item diff --git a/move/src/main/java/com/alfresco/content/move/MoveFragment.kt b/move/src/main/java/com/alfresco/content/move/MoveFragment.kt index 1db155082..4bf3f02ac 100644 --- a/move/src/main/java/com/alfresco/content/move/MoveFragment.kt +++ b/move/src/main/java/com/alfresco/content/move/MoveFragment.kt @@ -62,6 +62,7 @@ class MoveFragment : Fragment(), MavericksView { args = MoveArgs.with(requireArguments()) } - override fun invalidate() = withState(viewModel) { state -> - } + override fun invalidate() = + withState(viewModel) { state -> + } } diff --git a/move/src/main/java/com/alfresco/content/move/MoveViewModel.kt b/move/src/main/java/com/alfresco/content/move/MoveViewModel.kt index 6645cd7e7..8abe99b1e 100644 --- a/move/src/main/java/com/alfresco/content/move/MoveViewModel.kt +++ b/move/src/main/java/com/alfresco/content/move/MoveViewModel.kt @@ -21,14 +21,12 @@ class MoveViewModel( state: MoveViewState, val context: Context, ) : MavericksViewModel(state) { - /** * returns the nodeID for my files */ fun getMyFilesNodeId() = BrowseRepository().myFilesNodeId companion object : MavericksViewModelFactory { - override fun create( viewModelContext: ViewModelContext, state: MoveViewState, diff --git a/process-app/build.gradle b/process-app/build.gradle index 2d1a0000e..63b2a8afd 100644 --- a/process-app/build.gradle +++ b/process-app/build.gradle @@ -1,7 +1,9 @@ plugins { id 'com.android.library' id 'org.jetbrains.kotlin.android' - id 'kotlin-kapt' +// id 'kotlin-kapt' + id 'com.google.devtools.ksp' + id 'org.jetbrains.kotlin.plugin.compose' version '2.0.20' } android { @@ -79,10 +81,8 @@ dependencies { implementation libs.androidx.compose.material.iconsExtended debugImplementation libs.androidx.ui.tooling - implementation libs.androidx.swiperefreshlayout implementation libs.mavericks implementation libs.epoxy.core - kapt libs.epoxy.processor - + ksp libs.epoxy.processor } diff --git a/process-app/src/main/AndroidManifest.xml b/process-app/src/main/AndroidManifest.xml index a1c319b56..e40768108 100644 --- a/process-app/src/main/AndroidManifest.xml +++ b/process-app/src/main/AndroidManifest.xml @@ -2,10 +2,6 @@ - diff --git a/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/ComposeTopBar.kt b/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/ComposeTopBar.kt deleted file mode 100644 index 8a5da1fd5..000000000 --- a/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/ComposeTopBar.kt +++ /dev/null @@ -1,32 +0,0 @@ -import android.app.Activity -import androidx.compose.foundation.layout.Column -import androidx.compose.material3.Divider -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar -import androidx.compose.runtime.Composable -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp -import com.alfresco.content.process.R -import com.alfresco.content.process.ui.composeviews.BackButton -import com.alfresco.content.process.ui.theme.SeparateColorGrayLT - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun ComposeTopBar() { - val context = LocalContext.current - Column { - TopAppBar( - title = { - Text( - text = stringResource(id = R.string.action_start_workflow), - ) - }, - navigationIcon = { - BackButton(onClick = { (context as Activity).finish() }) - }, - ) - Divider(color = SeparateColorGrayLT, thickness = 1.dp) - } -} diff --git a/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/CustomLinearProgressIndicator.kt b/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/CustomLinearProgressIndicator.kt deleted file mode 100644 index 63f696e00..000000000 --- a/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/CustomLinearProgressIndicator.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.alfresco.content.process.ui.components - -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.LinearProgressIndicator -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.unit.dp - -@Composable -fun CustomLinearProgressIndicator(padding: PaddingValues) { - Box( - modifier = Modifier - .fillMaxWidth() - .height(2.dp) - .padding(padding), // Adjust padding as needed - ) { - LinearProgressIndicator( - color = Color.Blue, // Set your desired color here - modifier = Modifier.align(Alignment.TopCenter), - ) - } -} diff --git a/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/InputChip.kt b/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/InputChip.kt index 23a3ddda3..1001bb307 100644 --- a/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/InputChip.kt +++ b/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/InputChip.kt @@ -67,6 +67,8 @@ fun InputChip( shape = RoundedCornerShape(24.dp), border = InputChipDefaults.inputChipBorder( selectedBorderWidth = 0.dp, + enabled = true, // or false depending on your use case + selected = false // or true if the chip is selected ), colors = getInputChipColors(), diff --git a/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/RadioButtonField.kt b/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/RadioButtonField.kt deleted file mode 100644 index 644a7bd9b..000000000 --- a/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/RadioButtonField.kt +++ /dev/null @@ -1,87 +0,0 @@ -package com.alfresco.content.process.ui.components - -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.RadioButton -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.SpanStyle -import androidx.compose.ui.text.buildAnnotatedString -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.text.withStyle -import androidx.compose.ui.unit.dp -import com.alfresco.content.data.payloads.FieldsData -import com.alfresco.content.process.R -import com.alfresco.content.process.ui.theme.AlfrescoError - -@Composable -fun RadioButtonField( - selectedOption: String = "", - selectedQuery: String = "", - onSelectedOption: (Pair) -> Unit = {}, - fieldsData: FieldsData = FieldsData(), -) { - val labelWithAsterisk = buildAnnotatedString { - append(fieldsData.name) - if (fieldsData.required) { - withStyle(style = SpanStyle(color = AlfrescoError)) { - append(" *") // Adding a red asterisk for mandatory fields - } - } - } - - var showError by remember { mutableStateOf(false) } - - Column( - modifier = Modifier - .fillMaxWidth() - .padding(start = 24.dp), - horizontalAlignment = Alignment.Start, - ) { - Column( - horizontalAlignment = Alignment.Start, - modifier = Modifier - .fillMaxWidth(), - ) { - Text( - text = labelWithAsterisk, - modifier = Modifier.padding(end = 4.dp), - ) - fieldsData.options.forEach { option -> - Row(verticalAlignment = Alignment.CenterVertically) { - RadioButton( - selected = (option.id == selectedQuery), - onClick = { - onSelectedOption(Pair(option.name, option.id)) - }, - ) - Text( - text = option.name, - style = MaterialTheme.typography.bodyMedium, - ) - } - } - } - - if (showError) { - Text( - text = stringResource(R.string.error_required_field), - color = AlfrescoError, - modifier = Modifier - .padding(start = 16.dp, top = 0.dp), // Adjust padding as needed - style = MaterialTheme.typography.titleSmall, - textAlign = TextAlign.Start, - ) - } - } -} diff --git a/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/SearchBar.kt b/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/SearchBar.kt deleted file mode 100644 index 109be72ec..000000000 --- a/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/SearchBar.kt +++ /dev/null @@ -1,104 +0,0 @@ -package com.alfresco.content.process.ui.components - -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.ExperimentalAnimationApi -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.text.KeyboardActions -import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.ArrowBack -import androidx.compose.material.icons.filled.Close -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.OutlinedTextField -import androidx.compose.material3.Text -import androidx.compose.material3.TextFieldDefaults -import androidx.compose.material3.TopAppBar -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.ExperimentalComposeUiApi -import androidx.compose.ui.Modifier -import androidx.compose.ui.focus.FocusRequester -import androidx.compose.ui.focus.focusRequester -import androidx.compose.ui.focus.onFocusChanged -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalSoftwareKeyboardController -import androidx.compose.ui.text.input.ImeAction -import androidx.compose.ui.unit.dp - -@OptIn(ExperimentalMaterial3Api::class) -@ExperimentalAnimationApi -@ExperimentalComposeUiApi -@Composable -fun SearchBar( - searchText: String, - placeholderText: String = "", - onSearchTextChanged: (String) -> Unit = {}, - onClearClick: () -> Unit = {}, - onNavigateBack: () -> Unit = {}, -) { - var showClearButton by remember { mutableStateOf(false) } - val keyboardController = LocalSoftwareKeyboardController.current - val focusRequester = remember { FocusRequester() } - - TopAppBar(title = { Text("") }, navigationIcon = { - IconButton(onClick = { onNavigateBack() }) { - Icon( - imageVector = Icons.Filled.ArrowBack, - modifier = Modifier, - contentDescription = "", - ) - } - }, actions = { - OutlinedTextField( - modifier = Modifier - .fillMaxWidth() - .padding(vertical = 2.dp) - .onFocusChanged { focusState -> - showClearButton = (focusState.isFocused) - } - .focusRequester(focusRequester), - value = searchText, - onValueChange = onSearchTextChanged, - placeholder = { - Text(text = placeholderText) - }, - colors = TextFieldDefaults.textFieldColors( - focusedIndicatorColor = Color.Transparent, - unfocusedIndicatorColor = Color.Transparent, - ), - trailingIcon = { - AnimatedVisibility( - visible = showClearButton, - enter = fadeIn(), - exit = fadeOut(), - ) { - IconButton(onClick = { onClearClick() }) { - Icon( - imageVector = Icons.Filled.Close, - contentDescription = "", - ) - } - } - }, - maxLines = 1, - singleLine = true, - keyboardOptions = KeyboardOptions.Default.copy(imeAction = ImeAction.Done), - keyboardActions = KeyboardActions(onDone = { - keyboardController?.hide() - }), - ) - }) - - LaunchedEffect(Unit) { - focusRequester.requestFocus() - } -} diff --git a/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/TextLayoutHandler.kt b/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/TextLayoutHandler.kt deleted file mode 100644 index bef2f1f43..000000000 --- a/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/TextLayoutHandler.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.alfresco.content.process.ui.components - -import androidx.compose.ui.text.TextLayoutResult - -class TextLayoutHandler( - private val minimumLineLength: Int, - private val onEllipsisChanged: (Boolean) -> Unit, -) { - fun handleTextLayout(textLayoutResult: TextLayoutResult) { - if (textLayoutResult.lineCount > minimumLineLength - 1) { - if (textLayoutResult.isLineEllipsized(minimumLineLength - 1)) { - onEllipsisChanged(true) - } else { - onEllipsisChanged(false) - } - } else { - onEllipsisChanged(false) - } - } -} diff --git a/process-app/src/main/kotlin/com/alfresco/content/process/ui/composeviews/ProcessAttachedFiles.kt b/process-app/src/main/kotlin/com/alfresco/content/process/ui/composeviews/ProcessAttachedFiles.kt index 4e6e7f202..c4da40e6a 100644 --- a/process-app/src/main/kotlin/com/alfresco/content/process/ui/composeviews/ProcessAttachedFiles.kt +++ b/process-app/src/main/kotlin/com/alfresco/content/process/ui/composeviews/ProcessAttachedFiles.kt @@ -3,7 +3,7 @@ package com.alfresco.content.process.ui.composeviews import android.view.LayoutInflater import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.runtime.Composable @@ -25,6 +25,6 @@ fun ProcessAttachedFiles() { @Composable fun BackButton(onClick: () -> Unit) { IconButton(onClick = onClick) { - Icon(imageVector = Icons.Default.ArrowBack, contentDescription = "Back") + Icon(imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back") } } diff --git a/search/build.gradle b/search/build.gradle index 58278fe1e..e0eed3567 100644 --- a/search/build.gradle +++ b/search/build.gradle @@ -1,8 +1,9 @@ plugins{ id('com.android.library') id('kotlin-android') - id('kotlin-kapt') +// id('kotlin-kapt') id('kotlin-parcelize') + id 'com.google.devtools.ksp' } android { @@ -14,10 +15,6 @@ android { } } -kapt { - correctErrorTypes = true -} - dependencies { implementation project(':base') implementation project(':common') @@ -40,5 +37,5 @@ dependencies { implementation libs.mavericks implementation libs.epoxy.core - kapt libs.epoxy.processor + ksp libs.epoxy.processor } diff --git a/search/src/main/kotlin/com/alfresco/content/search/RecentSearchFragment.kt b/search/src/main/kotlin/com/alfresco/content/search/RecentSearchFragment.kt index a4b238b3b..46e99cf45 100644 --- a/search/src/main/kotlin/com/alfresco/content/search/RecentSearchFragment.kt +++ b/search/src/main/kotlin/com/alfresco/content/search/RecentSearchFragment.kt @@ -11,7 +11,6 @@ import com.airbnb.mvrx.withState import com.alfresco.content.search.databinding.FragmentRecentSearchBinding class RecentSearchFragment : Fragment(), MavericksView { - private val viewModel: RecentSearchViewModel by fragmentViewModel() private lateinit var binding: FragmentRecentSearchBinding var onEntrySelected: ((String) -> Unit)? = null @@ -31,15 +30,16 @@ class RecentSearchFragment : Fragment(), MavericksView { } } - override fun invalidate() = withState(viewModel) { - binding.recyclerView.withModels { - it.entries.forEach { - recentSearchRow { - id(it) - title(it) - clickListener { _ -> onEntrySelected?.invoke(it) } + override fun invalidate() = + withState(viewModel) { + binding.recyclerView.withModels { + it.entries.forEach { + recentSearchRow { + id(it) + title(it) + clickListener { _ -> onEntrySelected?.invoke(it) } + } } } } - } } diff --git a/search/src/main/kotlin/com/alfresco/content/search/RecentSearchRow.kt b/search/src/main/kotlin/com/alfresco/content/search/RecentSearchRow.kt index ab6e33569..8e31423a3 100644 --- a/search/src/main/kotlin/com/alfresco/content/search/RecentSearchRow.kt +++ b/search/src/main/kotlin/com/alfresco/content/search/RecentSearchRow.kt @@ -10,21 +10,22 @@ import com.airbnb.epoxy.ModelView import com.alfresco.content.search.databinding.ViewRecentSearchRowBinding @ModelView(autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT) -class RecentSearchRow @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0, -) : FrameLayout(context, attrs, defStyleAttr) { +class RecentSearchRow + @JvmOverloads + constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + ) : FrameLayout(context, attrs, defStyleAttr) { + private val binding = ViewRecentSearchRowBinding.inflate(LayoutInflater.from(context), this, true) - private val binding = ViewRecentSearchRowBinding.inflate(LayoutInflater.from(context), this, true) + @ModelProp + fun setTitle(text: String) { + binding.title.text = text + } - @ModelProp - fun setTitle(text: String) { - binding.title.text = text + @CallbackProp + fun setClickListener(listener: OnClickListener?) { + setOnClickListener(listener) + } } - - @CallbackProp - fun setClickListener(listener: OnClickListener?) { - setOnClickListener(listener) - } -} diff --git a/search/src/main/kotlin/com/alfresco/content/search/RecentSearchViewModel.kt b/search/src/main/kotlin/com/alfresco/content/search/RecentSearchViewModel.kt index b4ce7ea5d..452da13f6 100644 --- a/search/src/main/kotlin/com/alfresco/content/search/RecentSearchViewModel.kt +++ b/search/src/main/kotlin/com/alfresco/content/search/RecentSearchViewModel.kt @@ -15,7 +15,6 @@ class RecentSearchViewModel( viewState: RecentSearchViewState, val context: Context, ) : MavericksViewModel(viewState) { - @Suppress("unused") val changeListener = SearchRepository.RecentSearchesChangeListener(context) { refresh() } diff --git a/search/src/main/kotlin/com/alfresco/content/search/SearchFragment.kt b/search/src/main/kotlin/com/alfresco/content/search/SearchFragment.kt index 232b319a6..014cea391 100644 --- a/search/src/main/kotlin/com/alfresco/content/search/SearchFragment.kt +++ b/search/src/main/kotlin/com/alfresco/content/search/SearchFragment.kt @@ -83,7 +83,6 @@ data class ContextualSearchArgs( } class SearchFragment : Fragment(), MavericksView { - @OptIn(InternalMavericksApi::class) private val viewModel: SearchViewModel by fragmentViewModelWithArgs { ContextualSearchArgs.with(arguments) @@ -131,7 +130,10 @@ class SearchFragment : Fragment(), MavericksView { return binding.root } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { super.onViewCreated(view, savedInstanceState) AnalyticsManager().screenViewEvent(PageView.Search) binding.recyclerViewChips.setController(epoxyController) @@ -169,17 +171,18 @@ class SearchFragment : Fragment(), MavericksView { } } - override fun invalidate() = withState(viewModel) { state -> - if (!state.isExtension) { - if (state.isOnline) { - disableOfflineSearch() - } else { - enableOfflineSearch() + override fun invalidate() = + withState(viewModel) { state -> + if (!state.isExtension) { + if (state.isOnline) { + disableOfflineSearch() + } else { + enableOfflineSearch() + } } + setSearchFilterLocalizedName(state) + epoxyController.requestModelBuild() } - setSearchFilterLocalizedName(state) - epoxyController.requestModelBuild() - } private fun disableOfflineSearch() { setAdvanceSearchFiltersData() @@ -197,7 +200,10 @@ class SearchFragment : Fragment(), MavericksView { } } - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + override fun onCreateOptionsMenu( + menu: Menu, + inflater: MenuInflater, + ) { inflater.inflate(R.menu.menu_search, menu) val searchItem: MenuItem = menu.findItem(R.id.search) @@ -209,31 +215,35 @@ class SearchFragment : Fragment(), MavericksView { searchView.setQuery(viewModel.getSearchQuery(), false) updateFragmentVisibility(viewModel.getSearchQuery()) - searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener { - override fun onQueryTextChange(newText: String?): Boolean { - if (isResumed) { - setSearchQuery(newText ?: "") + searchView.setOnQueryTextListener( + object : SearchView.OnQueryTextListener { + override fun onQueryTextChange(newText: String?): Boolean { + if (isResumed) { + setSearchQuery(newText ?: "") + } + return true } - return true - } - override fun onQueryTextSubmit(query: String?): Boolean { - resultsFragment.saveCurrentSearch() - hideSoftInput() - return true - } - }) + override fun onQueryTextSubmit(query: String?): Boolean { + resultsFragment.saveCurrentSearch() + hideSoftInput() + return true + } + }, + ) - searchItem.setOnActionExpandListener(object : MenuItem.OnActionExpandListener { - override fun onMenuItemActionExpand(item: MenuItem): Boolean { - return true - } + searchItem.setOnActionExpandListener( + object : MenuItem.OnActionExpandListener { + override fun onMenuItemActionExpand(item: MenuItem): Boolean { + return true + } - override fun onMenuItemActionCollapse(item: MenuItem): Boolean { - this@SearchFragment.findNavController().navigateUp() - return true - } - }) + override fun onMenuItemActionCollapse(item: MenuItem): Boolean { + this@SearchFragment.findNavController().navigateUp() + return true + } + }, + ) } fun setSearchQuery(query: String) { @@ -323,11 +333,12 @@ class SearchFragment : Fragment(), MavericksView { binding.actionReset.setOnClickListener { resetAllFilters() } } - private fun resetAllFilters() = withState(viewModel) { state -> - val listReset = viewModel.resetChips(state) - scrollToTop() - applyAdvanceFilters(state.selectedFilterIndex, listReset) - } + private fun resetAllFilters() = + withState(viewModel) { state -> + val listReset = viewModel.resetChips(state) + scrollToTop() + applyAdvanceFilters(state.selectedFilterIndex, listReset) + } private fun scrollToTop() { if (isResumed) { @@ -404,51 +415,64 @@ class SearchFragment : Fragment(), MavericksView { } } - private fun epoxyController() = simpleController(viewModel) { state -> - - val filterIndex = state.selectedFilterIndex - - if (filterIndex != -1) { - val searchChipCategory = state.listSearchCategoryChips?.toMutableList() - - if (!searchChipCategory.isNullOrEmpty()) { - searchChipCategory.forEach { item -> - val modelID = filterIndex.toString() + item.category?.id - listViewFilterChips { - id(modelID) - data(item) - clickListener { model, _, chipView, _ -> - if (model.data().category?.component != null) { - val entry = model.data() - AnalyticsManager().searchFacets(entry.category?.component?.selector ?: "") - onChipClicked(model.data(), chipView) - } else { - onContextualChipClicked(model.data(), chipView) + private fun epoxyController() = + simpleController(viewModel) { state -> + + val filterIndex = state.selectedFilterIndex + + if (filterIndex != -1) { + val searchChipCategory = state.listSearchCategoryChips?.toMutableList() + + if (!searchChipCategory.isNullOrEmpty()) { + searchChipCategory.forEach { item -> + val modelID = filterIndex.toString() + item.category?.id + listViewFilterChips { + id(modelID) + data(item) + clickListener { model, _, chipView, _ -> + if (model.data().category?.component != null) { + val entry = model.data() + AnalyticsManager().searchFacets(entry.category?.component?.selector ?: "") + onChipClicked(model.data(), chipView) + } else { + onContextualChipClicked(model.data(), chipView) + } } } } } } } - } - private fun onContextualChipClicked(data: SearchChipCategory, chipView: View) { + private fun onContextualChipClicked( + data: SearchChipCategory, + chipView: View, + ) { if (binding.recyclerViewChips.isEnabled) { binding.recyclerViewChips.isEnabled = false withState(viewModel) { state -> - val result: ComponentMetaData = if (state.isContextual && (chipView as FilterChip).isChecked) { - ComponentMetaData(requireContext().getString(R.string.search_chip_contextual, state.contextTitle), SearchFilter.Contextual.name) - } else { - ComponentMetaData("", "") - } + val result: ComponentMetaData = + if (state.isContextual && (chipView as FilterChip).isChecked) { + ComponentMetaData( + requireContext().getString(R.string.search_chip_contextual, state.contextTitle), + SearchFilter.Contextual.name, + ) + } else { + ComponentMetaData("", "") + } binding.recyclerViewChips.isEnabled = true val resultList = viewModel.updateChipComponentResult(state, data, result) applyAdvanceFilters(state.selectedFilterIndex, resultList) } - } else (chipView as FilterChip).isChecked = false + } else { + (chipView as FilterChip).isChecked = false + } } - private fun onChipClicked(data: SearchChipCategory, chipView: View) = withState(viewModel) { + private fun onChipClicked( + data: SearchChipCategory, + chipView: View, + ) = withState(viewModel) { hideSoftInput() if (binding.recyclerViewChips.isEnabled) { binding.recyclerViewChips.isEnabled = false @@ -469,7 +493,9 @@ class SearchFragment : Fragment(), MavericksView { } } } - } else (chipView as FilterChip).isChecked = false + } else { + (chipView as FilterChip).isChecked = false + } } private suspend fun showComponentSheetDialog( @@ -477,17 +503,20 @@ class SearchFragment : Fragment(), MavericksView { searchChipCategory: SearchChipCategory, ) = withContext(dispatcher) { suspendCoroutine { - val componentData = if (searchChipCategory.facets == null) { - ComponentData.with( - searchChipCategory.category, - searchChipCategory.selectedName, - searchChipCategory.selectedQuery, - ) - } else ComponentData.with( - searchChipCategory.facets, - searchChipCategory.selectedName, - searchChipCategory.selectedQuery, - ) + val componentData = + if (searchChipCategory.facets == null) { + ComponentData.with( + searchChipCategory.category, + searchChipCategory.selectedName, + searchChipCategory.selectedQuery, + ) + } else { + ComponentData.with( + searchChipCategory.facets, + searchChipCategory.selectedName, + searchChipCategory.selectedQuery, + ) + } ComponentBuilder(context, componentData) .onApply { name, query, _ -> executeContinuation(it, name, query) @@ -502,7 +531,11 @@ class SearchFragment : Fragment(), MavericksView { } } - private fun executeContinuation(continuation: Continuation, name: String, query: String) { + private fun executeContinuation( + continuation: Continuation, + name: String, + query: String, + ) { continuation.resume(ComponentMetaData(name = name, query = query)) } @@ -523,13 +556,17 @@ class SearchFragment : Fragment(), MavericksView { resultsFragment.setFilters(filter) } - private fun applyAdvanceFilters(position: Int, list: MutableList) { + private fun applyAdvanceFilters( + position: Int, + list: MutableList, + ) { val advanceSearchFilter = emptyAdvanceFilters() - val facetData = SearchFacetData( - viewModel.defaultFacetFields(position), - viewModel.defaultFacetQueries(position), - viewModel.defaultFacetIntervals(position), - ) + val facetData = + SearchFacetData( + viewModel.defaultFacetFields(position), + viewModel.defaultFacetQueries(position), + viewModel.defaultFacetIntervals(position), + ) advanceSearchFilter.addAll(viewModel.initAdvanceFilters(position)) diff --git a/search/src/main/kotlin/com/alfresco/content/search/SearchResultsFragment.kt b/search/src/main/kotlin/com/alfresco/content/search/SearchResultsFragment.kt index 461e9e609..49b361475 100644 --- a/search/src/main/kotlin/com/alfresco/content/search/SearchResultsFragment.kt +++ b/search/src/main/kotlin/com/alfresco/content/search/SearchResultsFragment.kt @@ -26,7 +26,6 @@ import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch class SearchResultsFragment : ListFragment() { - override val viewModel: SearchViewModel by parentFragmentViewModel() var topLoadingIndicator: View? = null @@ -43,7 +42,10 @@ class SearchResultsFragment : ListFragment( } } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { super.onViewCreated(view, savedInstanceState) setViewRequiredMultiSelection(true) @@ -64,7 +66,10 @@ class SearchResultsFragment : ListFragment( /** * set the advance filters on the viewModel */ - fun setFilters(filters: AdvanceSearchFilters, facetData: SearchFacetData) { + fun setFilters( + filters: AdvanceSearchFilters, + facetData: SearchFacetData, + ) { scrollToTop() viewModel.setFilters(filters, facetData) } @@ -97,7 +102,9 @@ class SearchResultsFragment : ListFragment( val parentId = entry.parentPaths.find { it == state.moveId } if (parentId.isNullOrEmpty()) { findNavController().navigateToFolder(entry, state.moveId) - } else Toast.makeText(requireContext(), getString(R.string.search_move_warning), Toast.LENGTH_SHORT).show() + } else { + Toast.makeText(requireContext(), getString(R.string.search_move_warning), Toast.LENGTH_SHORT).show() + } } else -> findNavController().navigateToExtensionFolder(entry) diff --git a/search/src/main/kotlin/com/alfresco/content/search/SearchResultsState.kt b/search/src/main/kotlin/com/alfresco/content/search/SearchResultsState.kt index 7e2a1fee0..4d4e765d4 100644 --- a/search/src/main/kotlin/com/alfresco/content/search/SearchResultsState.kt +++ b/search/src/main/kotlin/com/alfresco/content/search/SearchResultsState.kt @@ -42,7 +42,6 @@ data class SearchResultsState( val isProcess: Boolean? = null, val moveId: String = "", ) : ListViewState { - constructor(args: ContextualSearchArgs) : this( contextId = args.id, contextTitle = args.title, @@ -69,11 +68,12 @@ data class SearchResultsState( val nextPage = response.pagination.skipCount > 0 val pageEntries = response.entries.filter { it.id != moveId } - val newEntries = if (nextPage) { - entries + pageEntries - } else { - pageEntries - } + val newEntries = + if (nextPage) { + entries + pageEntries + } else { + pageEntries + } val facets = response.facetContext?.facetResponse?.facets val list: MutableList? = listSearchCategoryChips?.toMutableList() @@ -111,7 +111,11 @@ data class SearchResultsState( } } - private fun isChipSelected(list: MutableList?, newEntries: List, facets: List?): MutableList? { + private fun isChipSelected( + list: MutableList?, + newEntries: List, + facets: List?, + ): MutableList? { val facetChipObj = list?.find { filterChip -> filterChip.category?.component != null && filterChip.isSelected } if (facetChipObj != null && newEntries.isEmpty()) { @@ -141,7 +145,10 @@ data class SearchResultsState( return null } - private fun getFacetBucketList(obj: SearchChipCategory, facets: Facets): List { + private fun getFacetBucketList( + obj: SearchChipCategory, + facets: Facets, + ): List { val listBuckets: MutableList = mutableListOf() obj.facets?.buckets?.forEach { oldBucket -> val bucketObj = Buckets.updateIntervalBucketCount(oldBucket) diff --git a/search/src/main/kotlin/com/alfresco/content/search/SearchViewModel.kt b/search/src/main/kotlin/com/alfresco/content/search/SearchViewModel.kt index 662781767..05bdbac17 100644 --- a/search/src/main/kotlin/com/alfresco/content/search/SearchViewModel.kt +++ b/search/src/main/kotlin/com/alfresco/content/search/SearchViewModel.kt @@ -68,16 +68,17 @@ class SearchViewModel( appConfigModel = repository.getAppConfig() // TODO: move search params to state object val defaultIndex = getDefaultIndex(state) - params = SearchParams( - "", - state.contextId, - defaultFilters(state), - defaultAdvanceFilters(state), - defaultFacetQueries(defaultIndex), - defaultFacetIntervals(defaultIndex), - defaultFacetFields(defaultIndex), - 0, - ) + params = + SearchParams( + "", + state.contextId, + defaultFilters(state), + defaultAdvanceFilters(state), + defaultFacetQueries(defaultIndex), + defaultFacetIntervals(defaultIndex), + defaultFacetFields(defaultIndex), + 0, + ) liveSearchEvents = MutableStateFlow(params) searchEvents = MutableStateFlow(params) @@ -106,20 +107,24 @@ class SearchViewModel( it.skipCount, it.maxItems, ) - } else repository.search( - it.terms, - it.contextId, - it.filters, - it.advanceSearchFilter, - SearchFacetData( - searchFacetFields = it.listFacetFields, - searchFacetQueries = it.listFacetQueries, - searchFacetIntervals = it.listFacetIntervals, - ), - it.skipCount, - it.maxItems, - ) - } else repository.offlineSearch(it.terms, it.advanceSearchFilter) + } else { + repository.search( + it.terms, + it.contextId, + it.filters, + it.advanceSearchFilter, + SearchFacetData( + searchFacetFields = it.listFacetFields, + searchFacetQueries = it.listFacetQueries, + searchFacetIntervals = it.listFacetIntervals, + ), + it.skipCount, + it.maxItems, + ) + } + } else { + repository.offlineSearch(it.terms, it.advanceSearchFilter) + } }) { if (it is Loading) { copy(request = it) @@ -134,7 +139,13 @@ class SearchViewModel( * returns the all available search filters */ fun getSearchFilterList(): List? { - return if (!canSearchOverCurrentNetwork()) appConfigModel.search else appConfigModel.search?.filter { it.name?.lowercase() != "CATEGORY.OFFLINE".lowercase() } + return if (!canSearchOverCurrentNetwork()) { + appConfigModel.search + } else { + appConfigModel.search?.filter { + it.name?.lowercase() != "CATEGORY.OFFLINE".lowercase() + } + } } /** @@ -172,32 +183,36 @@ class SearchViewModel( return list?.indexOf(list.find { it.name?.lowercase() == "CATEGORY.OFFLINE".lowercase() }) ?: -1 } - private fun updateSearchChipCategoryList(index: Int) = withState { state -> - val list = mutableListOf() + private fun updateSearchChipCategoryList(index: Int) = + withState { state -> + val list = mutableListOf() - getSearchFilterList()?.get(index)?.categories?.forEach { categoryItem -> - list.add(SearchChipCategory(categoryItem, isSelected = false)) - } + getSearchFilterList()?.get(index)?.categories?.forEach { categoryItem -> + list.add(SearchChipCategory(categoryItem, isSelected = false)) + } - if (list.isNotEmpty() && state.filters.contains(SearchFilter.Contextual)) { - list.add( - 0, - SearchChipCategory.withContextual( - context.getString(R.string.search_chip_contextual, state.contextTitle), - SearchFilter.Contextual, - ), - ) - } + if (list.isNotEmpty() && state.filters.contains(SearchFilter.Contextual)) { + list.add( + 0, + SearchChipCategory.withContextual( + context.getString(R.string.search_chip_contextual, state.contextTitle), + SearchFilter.Contextual, + ), + ) + } - setState { - copy(listSearchCategoryChips = list) + setState { + copy(listSearchCategoryChips = list) + } } - } /** * returns filter data on the selection on item in filter menu */ - fun getSelectedFilter(index: Int, state: SearchResultsState): SearchItem? { + fun getSelectedFilter( + index: Int, + state: SearchResultsState, + ): SearchItem? { return state.listSearchFilters?.get(index) } @@ -256,11 +271,12 @@ class SearchViewModel( return list } - private fun getDefaultIndex(state: SearchResultsState) = if (canSearchOverCurrentNetwork()) { - appConfigModel.search?.indexOfFirst { it.default == true } - } else { - getDefaultSearchFilterIndex(state.listSearchFilters) - } + private fun getDefaultIndex(state: SearchResultsState) = + if (canSearchOverCurrentNetwork()) { + appConfigModel.search?.indexOfFirst { it.default == true } + } else { + getDefaultSearchFilterIndex(state.listSearchFilters) + } /** * return the default facet fields list from app config json using the index @@ -336,24 +352,29 @@ class SearchViewModel( /** * set advance filters to search params */ - fun setFilters(advanceSearchFilter: AdvanceSearchFilters, facetData: SearchFacetData) { + fun setFilters( + advanceSearchFilter: AdvanceSearchFilters, + facetData: SearchFacetData, + ) { // Avoid triggering refresh when filters don't change if (advanceSearchFilter != params.advanceSearchFilter) { - params = params.copy( - advanceSearchFilter = advanceSearchFilter, - listFacetFields = facetData.searchFacetFields, - listFacetIntervals = facetData.searchFacetIntervals, - listFacetQueries = facetData.searchFacetQueries, - ) + params = + params.copy( + advanceSearchFilter = advanceSearchFilter, + listFacetFields = facetData.searchFacetFields, + listFacetIntervals = facetData.searchFacetIntervals, + listFacetQueries = facetData.searchFacetQueries, + ) refresh() isRefreshSearch = false } else if (isRefreshSearch) { - params = params.copy( - advanceSearchFilter = emptyAdvanceFilters(), - listFacetFields = facetData.searchFacetFields, - listFacetIntervals = facetData.searchFacetIntervals, - listFacetQueries = facetData.searchFacetQueries, - ) + params = + params.copy( + advanceSearchFilter = emptyAdvanceFilters(), + listFacetFields = facetData.searchFacetFields, + listFacetIntervals = facetData.searchFacetIntervals, + listFacetQueries = facetData.searchFacetQueries, + ) refresh() isRefreshSearch = false } @@ -362,7 +383,11 @@ class SearchViewModel( /** * update chip component data after apply or reset the component */ - fun updateChipComponentResult(state: SearchResultsState, model: SearchChipCategory, metaData: ComponentMetaData): MutableList { + fun updateChipComponentResult( + state: SearchResultsState, + model: SearchChipCategory, + metaData: ComponentMetaData, + ): MutableList { val list = mutableListOf() state.listSearchCategoryChips?.forEach { obj -> @@ -376,7 +401,9 @@ class SearchViewModel( selectedQuery = metaData.query ?: "", ), ) - } else list.add(obj) + } else { + list.add(obj) + } } setState { copy(listSearchCategoryChips = list) } @@ -392,7 +419,9 @@ class SearchViewModel( state.listSearchCategoryChips?.forEach { obj -> if (obj.selectedQuery == SearchFilter.Contextual.name) { list.add(obj) - } else list.add(SearchChipCategory.resetData(obj)) + } else { + list.add(SearchChipCategory.resetData(obj)) + } } setState { copy(listSearchCategoryChips = list) } @@ -402,7 +431,11 @@ class SearchViewModel( /** * update isSelected when chip is selected */ - fun updateSelected(state: SearchResultsState, model: SearchChipCategory, isSelected: Boolean) { + fun updateSelected( + state: SearchResultsState, + model: SearchChipCategory, + isSelected: Boolean, + ) { val list = mutableListOf() state.listSearchCategoryChips?.forEach { obj -> @@ -416,7 +449,9 @@ class SearchViewModel( selectedQuery = obj.selectedQuery, ), ) - } else list.add(obj) + } else { + list.add(obj) + } } setState { copy(listSearchCategoryChips = list) } @@ -438,36 +473,40 @@ class SearchViewModel( } } - fun toggleSelection(entry: Entry) = setState { - val hasReachedLimit = selectedEntries.size == MULTI_SELECTION_LIMIT - if (!entry.isSelectedForMultiSelection && hasReachedLimit) { - copy(maxLimitReachedForMultiSelection = true) - } else { - val updatedEntries = entries.map { - if (it.id == entry.id && it.type != Entry.Type.GROUP) { - it.copy(isSelectedForMultiSelection = !it.isSelectedForMultiSelection) - } else { - it - } + fun toggleSelection(entry: Entry) = + setState { + val hasReachedLimit = selectedEntries.size == MULTI_SELECTION_LIMIT + if (!entry.isSelectedForMultiSelection && hasReachedLimit) { + copy(maxLimitReachedForMultiSelection = true) + } else { + val updatedEntries = + entries.map { + if (it.id == entry.id && it.type != Entry.Type.GROUP) { + it.copy(isSelectedForMultiSelection = !it.isSelectedForMultiSelection) + } else { + it + } + } + copy( + entries = updatedEntries, + selectedEntries = updatedEntries.filter { it.isSelectedForMultiSelection }, + maxLimitReachedForMultiSelection = false, + ) } + } + + fun resetMultiSelection() = + setState { + val resetMultiEntries = + entries.map { + it.copy(isSelectedForMultiSelection = false) + } copy( - entries = updatedEntries, - selectedEntries = updatedEntries.filter { it.isSelectedForMultiSelection }, + entries = resetMultiEntries, + selectedEntries = emptyList(), maxLimitReachedForMultiSelection = false, ) } - } - - fun resetMultiSelection() = setState { - val resetMultiEntries = entries.map { - it.copy(isSelectedForMultiSelection = false) - } - copy( - entries = resetMultiEntries, - selectedEntries = emptyList(), - maxLimitReachedForMultiSelection = false, - ) - } override fun resetMaxLimitError() = setState { copy(maxLimitReachedForMultiSelection = false) } @@ -476,7 +515,12 @@ class SearchViewModel( */ fun canSearchOverCurrentNetwork() = ConnectivityTracker.isActiveNetwork(context) - override fun emptyMessageArgs(state: ListViewState) = Triple(R.drawable.ic_empty_search, R.string.search_empty_title, R.string.search_empty_message) + override fun emptyMessageArgs(state: ListViewState) = + Triple( + R.drawable.ic_empty_search, + R.string.search_empty_title, + R.string.search_empty_message, + ) companion object : MavericksViewModelFactory { const val MIN_QUERY_LENGTH = 3 diff --git a/session/src/main/kotlin/com/alfresco/content/session/HttpLoggingInterceptor.kt b/session/src/main/kotlin/com/alfresco/content/session/HttpLoggingInterceptor.kt index 2fbacdd7d..9be6142a1 100644 --- a/session/src/main/kotlin/com/alfresco/content/session/HttpLoggingInterceptor.kt +++ b/session/src/main/kotlin/com/alfresco/content/session/HttpLoggingInterceptor.kt @@ -19,299 +19,312 @@ import java.util.concurrent.TimeUnit /** * A fork of the [okhttp3.logging.HttpLoggingInterceptor] which omits binary multipart request body. */ -class HttpLoggingInterceptor @JvmOverloads constructor( - private val logger: Logger = Logger.DEFAULT, -) : Interceptor { +class HttpLoggingInterceptor + @JvmOverloads + constructor( + private val logger: Logger = Logger.DEFAULT, + ) : Interceptor { + @Volatile + private var headersToRedact = emptySet() + + @set:JvmName("level") + @Volatile + var level = Level.NONE + + enum class Level { + /** No logs. */ + NONE, + + /** + * Logs request and response lines. + * + * Example: + * ``` + * --> POST /greeting http/1.1 (3-byte body) + * + * <-- 200 OK (22ms, 6-byte body) + * ``` + */ + BASIC, + + /** + * Logs request and response lines and their respective headers. + * + * Example: + * ``` + * --> POST /greeting http/1.1 + * Host: example.com + * Content-Type: plain/text + * Content-Length: 3 + * --> END POST + * + * <-- 200 OK (22ms) + * Content-Type: plain/text + * Content-Length: 6 + * <-- END HTTP + * ``` + */ + HEADERS, + + /** + * Logs request and response lines and their respective headers and bodies (if present). + * + * Example: + * ``` + * --> POST /greeting http/1.1 + * Host: example.com + * Content-Type: plain/text + * Content-Length: 3 + * + * Hi? + * --> END POST + * + * <-- 200 OK (22ms) + * Content-Type: plain/text + * Content-Length: 6 + * + * Hello! + * <-- END HTTP + * ``` + */ + BODY, + } - @Volatile private var headersToRedact = emptySet() + interface Logger { + fun log(message: String) - @set:JvmName("level") - @Volatile - var level = Level.NONE + companion object { + /** A [Logger] defaults output appropriate for the current platform. */ + @JvmField + val DEFAULT: Logger = DefaultLogger() - enum class Level { - /** No logs. */ - NONE, + private class DefaultLogger : Logger { + override fun log(message: String) { + Platform.get().log(message) + } + } + } + } - /** - * Logs request and response lines. - * - * Example: - * ``` - * --> POST /greeting http/1.1 (3-byte body) - * - * <-- 200 OK (22ms, 6-byte body) - * ``` - */ - BASIC, + fun redactHeader(name: String) { + val newHeadersToRedact = TreeSet(String.CASE_INSENSITIVE_ORDER) + newHeadersToRedact += headersToRedact + newHeadersToRedact += name + headersToRedact = newHeadersToRedact + } /** - * Logs request and response lines and their respective headers. - * - * Example: - * ``` - * --> POST /greeting http/1.1 - * Host: example.com - * Content-Type: plain/text - * Content-Length: 3 - * --> END POST + * Sets the level and returns this. * - * <-- 200 OK (22ms) - * Content-Type: plain/text - * Content-Length: 6 - * <-- END HTTP - * ``` + * This was deprecated in OkHttp 4.0 in favor of the [level] val. In OkHttp 4.3 it is + * un-deprecated because Java callers can't chain when assigning Kotlin vals. (The getter remains + * deprecated). */ - HEADERS, + fun setLevel(level: Level) = + apply { + this.level = level + } - /** - * Logs request and response lines and their respective headers and bodies (if present). - * - * Example: - * ``` - * --> POST /greeting http/1.1 - * Host: example.com - * Content-Type: plain/text - * Content-Length: 3 - * - * Hi? - * --> END POST - * - * <-- 200 OK (22ms) - * Content-Type: plain/text - * Content-Length: 6 - * - * Hello! - * <-- END HTTP - * ``` - */ - BODY, - } + @JvmName("-deprecated_level") + @Deprecated( + message = "moved to var", + replaceWith = ReplaceWith(expression = "level"), + level = DeprecationLevel.ERROR, + ) + fun getLevel(): Level = level - interface Logger { - fun log(message: String) + @Throws(IOException::class) + override fun intercept(chain: Interceptor.Chain): Response { + val level = this.level - companion object { - /** A [Logger] defaults output appropriate for the current platform. */ - @JvmField - val DEFAULT: Logger = DefaultLogger() - private class DefaultLogger : Logger { - override fun log(message: String) { - Platform.get().log(message) - } + val request = chain.request() + if (level == Level.NONE) { + return chain.proceed(request) } - } - } - fun redactHeader(name: String) { - val newHeadersToRedact = TreeSet(String.CASE_INSENSITIVE_ORDER) - newHeadersToRedact += headersToRedact - newHeadersToRedact += name - headersToRedact = newHeadersToRedact - } - - /** - * Sets the level and returns this. - * - * This was deprecated in OkHttp 4.0 in favor of the [level] val. In OkHttp 4.3 it is - * un-deprecated because Java callers can't chain when assigning Kotlin vals. (The getter remains - * deprecated). - */ - fun setLevel(level: Level) = apply { - this.level = level - } + val logBody = level == Level.BODY + val logHeaders = logBody || level == Level.HEADERS - @JvmName("-deprecated_level") - @Deprecated( - message = "moved to var", - replaceWith = ReplaceWith(expression = "level"), - level = DeprecationLevel.ERROR, - ) - fun getLevel(): Level = level - - @Throws(IOException::class) - override fun intercept(chain: Interceptor.Chain): Response { - val level = this.level - - val request = chain.request() - if (level == Level.NONE) { - return chain.proceed(request) - } + val requestBody = request.body - val logBody = level == Level.BODY - val logHeaders = logBody || level == Level.HEADERS + val connection = chain.connection() + var requestStartMessage = + ("--> ${request.method} ${request.url}${if (connection != null) " " + connection.protocol() else ""}") + if (!logHeaders && requestBody != null) { + requestStartMessage += " (${requestBody.contentLength()}-byte body)" + } + logger.log(requestStartMessage) + + if (logHeaders) { + val headers = request.headers + + if (requestBody != null) { + // Request body headers are only present when installed as a network interceptor. When not + // already present, force them to be included (if available) so their values are known. + requestBody.contentType()?.let { + if (headers["Content-Type"] == null) { + logger.log("Content-Type: $it") + } + } + if (requestBody.contentLength() != -1L) { + if (headers["Content-Length"] == null) { + logger.log("Content-Length: ${requestBody.contentLength()}") + } + } + } - val requestBody = request.body + for (i in 0 until headers.size) { + logHeader(headers, i) + } - val connection = chain.connection() - var requestStartMessage = - ("--> ${request.method} ${request.url}${if (connection != null) " " + connection.protocol() else ""}") - if (!logHeaders && requestBody != null) { - requestStartMessage += " (${requestBody.contentLength()}-byte body)" - } - logger.log(requestStartMessage) + if (!logBody || requestBody == null) { + logger.log("--> END ${request.method}") + } else if (bodyHasUnknownEncoding(request.headers)) { + logger.log("--> END ${request.method} (encoded body omitted)") + } else if (requestBody.isDuplex()) { + logger.log("--> END ${request.method} (duplex request body omitted)") + } else if (requestBody.isOneShot()) { + logger.log("--> END ${request.method} (one-shot body omitted)") + } else if (bodyHasBinaryParts(requestBody)) { + logger.log("--> END ${request.method} (binary ${requestBody.contentLength()}-byte multipart body omitted)") + } else { + val buffer = Buffer() + requestBody.writeTo(buffer) - if (logHeaders) { - val headers = request.headers + val contentType = requestBody.contentType() + val charset: Charset = contentType?.charset(UTF_8) ?: UTF_8 - if (requestBody != null) { - // Request body headers are only present when installed as a network interceptor. When not - // already present, force them to be included (if available) so their values are known. - requestBody.contentType()?.let { - if (headers["Content-Type"] == null) { - logger.log("Content-Type: $it") - } - } - if (requestBody.contentLength() != -1L) { - if (headers["Content-Length"] == null) { - logger.log("Content-Length: ${requestBody.contentLength()}") + logger.log("") + if (buffer.isProbablyUtf8()) { + logger.log(buffer.readString(charset)) + logger.log("--> END ${request.method} (${requestBody.contentLength()}-byte body)") + } else { + logger.log( + "--> END ${request.method} (binary ${requestBody.contentLength()}-byte body omitted)", + ) } } } - for (i in 0 until headers.size) { - logHeader(headers, i) + val startNs = System.nanoTime() + val response: Response + try { + response = chain.proceed(request) + } catch (e: Exception) { + logger.log("<-- HTTP FAILED: $e") + throw e } - if (!logBody || requestBody == null) { - logger.log("--> END ${request.method}") - } else if (bodyHasUnknownEncoding(request.headers)) { - logger.log("--> END ${request.method} (encoded body omitted)") - } else if (requestBody.isDuplex()) { - logger.log("--> END ${request.method} (duplex request body omitted)") - } else if (requestBody.isOneShot()) { - logger.log("--> END ${request.method} (one-shot body omitted)") - } else if (bodyHasBinaryParts(requestBody)) { - logger.log("--> END ${request.method} (binary ${requestBody.contentLength()}-byte multipart body omitted)") - } else { - val buffer = Buffer() - requestBody.writeTo(buffer) - - val contentType = requestBody.contentType() - val charset: Charset = contentType?.charset(UTF_8) ?: UTF_8 - - logger.log("") - if (buffer.isProbablyUtf8()) { - logger.log(buffer.readString(charset)) - logger.log("--> END ${request.method} (${requestBody.contentLength()}-byte body)") - } else { - logger.log( - "--> END ${request.method} (binary ${requestBody.contentLength()}-byte body omitted)", - ) + val tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs) + + val responseBody = response.body!! + val contentLength = responseBody.contentLength() + val bodySize = if (contentLength != -1L) "$contentLength-byte" else "unknown-length" + logger.log( + "<-- ${response.code}${ + if (response.message.isEmpty()) { + "" + } else { + ' ' + response.message + } + } ${response.request.url} (${tookMs}ms${if (!logHeaders) ", $bodySize body" else ""})", + ) + + if (logHeaders) { + val headers = response.headers + for (i in 0 until headers.size) { + logHeader(headers, i) } - } - } - val startNs = System.nanoTime() - val response: Response - try { - response = chain.proceed(request) - } catch (e: Exception) { - logger.log("<-- HTTP FAILED: $e") - throw e - } + if (!logBody || !response.promisesBody()) { + logger.log("<-- END HTTP") + } else if (bodyHasUnknownEncoding(response.headers)) { + logger.log("<-- END HTTP (encoded body omitted)") + } else { + val source = responseBody.source() + source.request(Long.MAX_VALUE) // Buffer the entire body. + var buffer = source.buffer + + var gzippedLength: Long? = null + if ("gzip".equals(headers["Content-Encoding"], ignoreCase = true)) { + gzippedLength = buffer.size + GzipSource(buffer.clone()).use { gzippedResponseBody -> + buffer = Buffer() + buffer.writeAll(gzippedResponseBody) + } + } - val tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs) + val contentType = responseBody.contentType() + val charset: Charset = contentType?.charset(UTF_8) ?: UTF_8 - val responseBody = response.body!! - val contentLength = responseBody.contentLength() - val bodySize = if (contentLength != -1L) "$contentLength-byte" else "unknown-length" - logger.log( - "<-- ${response.code}${if (response.message.isEmpty()) "" else ' ' + response.message} ${response.request.url} (${tookMs}ms${if (!logHeaders) ", $bodySize body" else ""})", - ) + if (!buffer.isProbablyUtf8()) { + logger.log("") + logger.log("<-- END HTTP (binary ${buffer.size}-byte body omitted)") + return response + } - if (logHeaders) { - val headers = response.headers - for (i in 0 until headers.size) { - logHeader(headers, i) - } + if (contentLength != 0L) { + logger.log("") + logger.log(buffer.clone().readString(charset)) + } - if (!logBody || !response.promisesBody()) { - logger.log("<-- END HTTP") - } else if (bodyHasUnknownEncoding(response.headers)) { - logger.log("<-- END HTTP (encoded body omitted)") - } else { - val source = responseBody.source() - source.request(Long.MAX_VALUE) // Buffer the entire body. - var buffer = source.buffer - - var gzippedLength: Long? = null - if ("gzip".equals(headers["Content-Encoding"], ignoreCase = true)) { - gzippedLength = buffer.size - GzipSource(buffer.clone()).use { gzippedResponseBody -> - buffer = Buffer() - buffer.writeAll(gzippedResponseBody) + if (gzippedLength != null) { + logger.log("<-- END HTTP (${buffer.size}-byte, $gzippedLength-gzipped-byte body)") + } else { + logger.log("<-- END HTTP (${buffer.size}-byte body)") } } + } - val contentType = responseBody.contentType() - val charset: Charset = contentType?.charset(UTF_8) ?: UTF_8 - - if (!buffer.isProbablyUtf8()) { - logger.log("") - logger.log("<-- END HTTP (binary ${buffer.size}-byte body omitted)") - return response - } + return response + } - if (contentLength != 0L) { - logger.log("") - logger.log(buffer.clone().readString(charset)) - } + private fun logHeader( + headers: Headers, + i: Int, + ) { + val value = if (headers.name(i) in headersToRedact) "██" else headers.value(i) + logger.log(headers.name(i) + ": " + value) + } - if (gzippedLength != null) { - logger.log("<-- END HTTP (${buffer.size}-byte, $gzippedLength-gzipped-byte body)") - } else { - logger.log("<-- END HTTP (${buffer.size}-byte body)") + private fun bodyHasBinaryParts(body: RequestBody): Boolean { + if (body is MultipartBody) { + for (part in body.parts) { + val transferEncoding = part.headers?.get("Content-Transfer-Encoding") + if (transferEncoding.equals("binary")) return true } } + return false } - return response - } - - private fun logHeader(headers: Headers, i: Int) { - val value = if (headers.name(i) in headersToRedact) "██" else headers.value(i) - logger.log(headers.name(i) + ": " + value) - } - - private fun bodyHasBinaryParts(body: RequestBody): Boolean { - if (body is MultipartBody) { - for (part in body.parts) { - val transferEncoding = part.headers?.get("Content-Transfer-Encoding") - if (transferEncoding.equals("binary")) return true - } + private fun bodyHasUnknownEncoding(headers: Headers): Boolean { + val contentEncoding = headers["Content-Encoding"] ?: return false + return !contentEncoding.equals("identity", ignoreCase = true) && + !contentEncoding.equals("gzip", ignoreCase = true) } - return false - } - - private fun bodyHasUnknownEncoding(headers: Headers): Boolean { - val contentEncoding = headers["Content-Encoding"] ?: return false - return !contentEncoding.equals("identity", ignoreCase = true) && - !contentEncoding.equals("gzip", ignoreCase = true) - } - /** - * Returns true if the body in question probably contains human readable text. Uses a small - * sample of code points to detect unicode control characters commonly used in binary file - * signatures. - */ - private fun Buffer.isProbablyUtf8(): Boolean { - try { - val prefix = Buffer() - val byteCount = size.coerceAtMost(64) - copyTo(prefix, 0, byteCount) - for (i in 0 until 16) { - if (prefix.exhausted()) { - break - } - val codePoint = prefix.readUtf8CodePoint() - if (Character.isISOControl(codePoint) && !Character.isWhitespace(codePoint)) { - return false + /** + * Returns true if the body in question probably contains human readable text. Uses a small + * sample of code points to detect unicode control characters commonly used in binary file + * signatures. + */ + private fun Buffer.isProbablyUtf8(): Boolean { + try { + val prefix = Buffer() + val byteCount = size.coerceAtMost(64) + copyTo(prefix, 0, byteCount) + for (i in 0 until 16) { + if (prefix.exhausted()) { + break + } + val codePoint = prefix.readUtf8CodePoint() + if (Character.isISOControl(codePoint) && !Character.isWhitespace(codePoint)) { + return false + } } + return true + } catch (_: EOFException) { + return false // Truncated UTF-8 sequence. } - return true - } catch (_: EOFException) { - return false // Truncated UTF-8 sequence. } } -} diff --git a/session/src/main/kotlin/com/alfresco/content/session/Session.kt b/session/src/main/kotlin/com/alfresco/content/session/Session.kt index 8b1781c1a..57adc40e3 100644 --- a/session/src/main/kotlin/com/alfresco/content/session/Session.kt +++ b/session/src/main/kotlin/com/alfresco/content/session/Session.kt @@ -27,50 +27,61 @@ class Session( init { require(context == context.applicationContext) - authInterceptor = AuthInterceptor( - context, - account.id, - account.authType, - account.authState, - account.authConfig, - ) - - authInterceptor.setListener(object : AuthInterceptor.Listener { - override fun onAuthFailure(accountId: String, url: String) { - Logger.d("onAuthFailure") - if (!url.contains("activiti-app") && onSignedOut != null) { - onSignedOut?.invoke() + authInterceptor = + AuthInterceptor( + context, + account.id, + account.authType, + account.authState, + account.authConfig, + ) + + authInterceptor.setListener( + object : AuthInterceptor.Listener { + override fun onAuthFailure( + accountId: String, + url: String, + ) { + Logger.d("onAuthFailure") + if (!url.contains("activiti-app") && onSignedOut != null) { + onSignedOut?.invoke() + } } - } - override fun onAuthStateChange(accountId: String, authState: String) { - Logger.d("onAuthStateChange") - Account.update(context, accountId, authState) - this@Session.account = Account.getAccount(context)!! - } - }) + override fun onAuthStateChange( + accountId: String, + authState: String, + ) { + Logger.d("onAuthStateChange") + Account.update(context, accountId, authState) + this@Session.account = Account.getAccount(context)!! + } + }, + ) if (BuildConfig.DEBUG) { - loggingInterceptor = HttpLoggingInterceptor().apply { - redactHeader("Authorization") - setLevel(HttpLoggingInterceptor.Level.BODY) - } + loggingInterceptor = + HttpLoggingInterceptor().apply { + redactHeader("Authorization") + setLevel(HttpLoggingInterceptor.Level.BODY) + } } - val imageLoader = ImageLoader.Builder(context) - .diskCache { - DiskCache.Builder() - .directory(context.cacheDir.resolve("image_cache")) - .build() - } - .crossfade(true) - .okHttpClient { - OkHttpClient.Builder() - .addInterceptor(authInterceptor) - .addOptionalInterceptor(loggingInterceptor) - .build() - } - .build() + val imageLoader = + ImageLoader.Builder(context) + .diskCache { + DiskCache.Builder() + .directory(context.cacheDir.resolve("image_cache")) + .build() + } + .crossfade(true) + .okHttpClient { + OkHttpClient.Builder() + .addInterceptor(authInterceptor) + .addOptionalInterceptor(loggingInterceptor) + .build() + } + .build() Coil.setImageLoader(imageLoader) } @@ -78,17 +89,19 @@ class Session( val processBaseUrl get() = account.serverUrl.replace("/alfresco", "/activiti-app/") fun createService(service: Class): T { - val okHttpClient: OkHttpClient = OkHttpClient() - .newBuilder() - .addInterceptor(authInterceptor) - .addOptionalInterceptor(loggingInterceptor) - .build() - - val retrofit = Retrofit.Builder() - .client(okHttpClient) - .addConverterFactory(GeneratedCodeConverters.converterFactory()) - .baseUrl(baseUrl) - .build() + val okHttpClient: OkHttpClient = + OkHttpClient() + .newBuilder() + .addInterceptor(authInterceptor) + .addOptionalInterceptor(loggingInterceptor) + .build() + + val retrofit = + Retrofit.Builder() + .client(okHttpClient) + .addConverterFactory(GeneratedCodeConverters.converterFactory()) + .baseUrl(baseUrl) + .build() return retrofit.create(service) } @@ -107,17 +120,19 @@ class Session( * it creates the retrofit builder to access the process service apis */ fun createProcessService(service: Class): T { - val okHttpClient: OkHttpClient = OkHttpClient() - .newBuilder() - .addInterceptor(authInterceptor) - .addOptionalInterceptor(loggingInterceptor) - .build() - - val retrofit = Retrofit.Builder() - .client(okHttpClient) - .addConverterFactory(GeneratedCodeConverters.converterFactory()) - .baseUrl(processBaseUrl) - .build() + val okHttpClient: OkHttpClient = + OkHttpClient() + .newBuilder() + .addInterceptor(authInterceptor) + .addOptionalInterceptor(loggingInterceptor) + .build() + + val retrofit = + Retrofit.Builder() + .client(okHttpClient) + .addConverterFactory(GeneratedCodeConverters.converterFactory()) + .baseUrl(processBaseUrl) + .build() return retrofit.create(service) } @@ -126,17 +141,19 @@ class Session( * This service is only built to fetch app config from server */ fun createServiceConfig(service: Class): T { - val okHttpClient: OkHttpClient = OkHttpClient() - .newBuilder() - .addInterceptor(authInterceptor) - .addOptionalInterceptor(loggingInterceptor) - .build() - - val retrofit = Retrofit.Builder() - .client(okHttpClient) - .addConverterFactory(GeneratedCodeConverters.converterFactory()) - .baseUrl("https://mobileapps.envalfresco.com/adf/") - .build() + val okHttpClient: OkHttpClient = + OkHttpClient() + .newBuilder() + .addInterceptor(authInterceptor) + .addOptionalInterceptor(loggingInterceptor) + .build() + + val retrofit = + Retrofit.Builder() + .client(okHttpClient) + .addConverterFactory(GeneratedCodeConverters.converterFactory()) + .baseUrl("https://mobileapps.envalfresco.com/adf/") + .build() return retrofit.create(service) } diff --git a/session/src/main/kotlin/com/alfresco/content/session/SessionManager.kt b/session/src/main/kotlin/com/alfresco/content/session/SessionManager.kt index cfa147af1..41c733964 100644 --- a/session/src/main/kotlin/com/alfresco/content/session/SessionManager.kt +++ b/session/src/main/kotlin/com/alfresco/content/session/SessionManager.kt @@ -14,14 +14,15 @@ object SessionManager { // Create a new session val account = Account.getAccount(context) - currentSession = if (account != null) { - Session( - context.applicationContext, - account, - ) - } else { - null - } + currentSession = + if (account != null) { + Session( + context.applicationContext, + account, + ) + } else { + null + } return currentSession } } diff --git a/shareextension/build.gradle b/shareextension/build.gradle index 39d31b263..1c83a9836 100644 --- a/shareextension/build.gradle +++ b/shareextension/build.gradle @@ -1,8 +1,9 @@ plugins{ - id('com.android.library') - id('kotlin-android') - id('kotlin-kapt') - id('kotlin-parcelize') + id 'com.android.library' + id 'kotlin-android' +// id('kotlin-kapt') + id 'kotlin-parcelize' + id 'com.google.devtools.ksp' } android { @@ -37,5 +38,5 @@ dependencies { implementation libs.gson implementation libs.androidx.preference - kapt libs.epoxy.processor + ksp libs.epoxy.processor } diff --git a/shareextension/src/main/kotlin/com/alfresco/content/shareextension/BrowseExtensionFragment.kt b/shareextension/src/main/kotlin/com/alfresco/content/shareextension/BrowseExtensionFragment.kt index f35c616c5..171e406e8 100644 --- a/shareextension/src/main/kotlin/com/alfresco/content/shareextension/BrowseExtensionFragment.kt +++ b/shareextension/src/main/kotlin/com/alfresco/content/shareextension/BrowseExtensionFragment.kt @@ -22,7 +22,6 @@ import com.alfresco.content.navigateToExtensionFolder * Mark as BrowseExtensionFragment */ class BrowseExtensionFragment : ListFragment(R.layout.fragment_extension_list) { - private lateinit var args: BrowseArgs @OptIn(InternalMavericksApi::class) @@ -37,7 +36,10 @@ class BrowseExtensionFragment : ListFragment(R setHasOptionsMenu(true) } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { super.onViewCreated(view, savedInstanceState) uploadButton?.setOnClickListener { @@ -51,7 +53,10 @@ class BrowseExtensionFragment : ListFragment(R } } - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + override fun onCreateOptionsMenu( + menu: Menu, + inflater: MenuInflater, + ) { inflater.inflate(R.menu.menu_browse_extension, menu) } @@ -77,12 +82,13 @@ class BrowseExtensionFragment : ListFragment(R } } - override fun invalidate() = withState(viewModel) { state -> - if (state.path == getString(com.alfresco.content.browse.R.string.nav_path_extension)) { - super.disableRefreshLayout() + override fun invalidate() = + withState(viewModel) { state -> + if (state.path == getString(com.alfresco.content.browse.R.string.nav_path_extension)) { + super.disableRefreshLayout() + } + super.invalidate() } - super.invalidate() - } /** * return callback for list item diff --git a/shareextension/src/main/kotlin/com/alfresco/content/shareextension/ExtensionFragment.kt b/shareextension/src/main/kotlin/com/alfresco/content/shareextension/ExtensionFragment.kt index ead59ded3..4f7aca4ea 100644 --- a/shareextension/src/main/kotlin/com/alfresco/content/shareextension/ExtensionFragment.kt +++ b/shareextension/src/main/kotlin/com/alfresco/content/shareextension/ExtensionFragment.kt @@ -51,6 +51,7 @@ class ExtensionFragment : Fragment(), MavericksView { findNavController().navigateToParent(nodeId, "") } - override fun invalidate() = withState(viewModel) { state -> - } + override fun invalidate() = + withState(viewModel) { state -> + } } diff --git a/shareextension/src/main/kotlin/com/alfresco/content/shareextension/ExtensionViewModel.kt b/shareextension/src/main/kotlin/com/alfresco/content/shareextension/ExtensionViewModel.kt index 16396624a..58313d3ed 100644 --- a/shareextension/src/main/kotlin/com/alfresco/content/shareextension/ExtensionViewModel.kt +++ b/shareextension/src/main/kotlin/com/alfresco/content/shareextension/ExtensionViewModel.kt @@ -21,14 +21,12 @@ class ExtensionViewModel( state: ExtensionViewState, val context: Context, ) : MavericksViewModel(state) { - /** * returns the nodeID for my files */ fun getMyFilesNodeId() = BrowseRepository().myFilesNodeId companion object : MavericksViewModelFactory { - override fun create( viewModelContext: ViewModelContext, state: ExtensionViewState, diff --git a/viewer-common/src/main/kotlin/com/alfresco/content/viewer/common/ChildViewerFragment.kt b/viewer-common/src/main/kotlin/com/alfresco/content/viewer/common/ChildViewerFragment.kt index 2e52b68d1..1bc838fa7 100644 --- a/viewer-common/src/main/kotlin/com/alfresco/content/viewer/common/ChildViewerFragment.kt +++ b/viewer-common/src/main/kotlin/com/alfresco/content/viewer/common/ChildViewerFragment.kt @@ -5,10 +5,13 @@ import androidx.fragment.app.Fragment interface LoadingListener { fun onContentLoaded() + fun onContentError() } -abstract class ChildViewerFragment(@LayoutRes contentLayoutId: Int = 0) : +abstract class ChildViewerFragment( + @LayoutRes contentLayoutId: Int = 0, +) : Fragment(contentLayoutId) { var loadingListener: LoadingListener? = null diff --git a/viewer-common/src/main/kotlin/com/alfresco/content/viewer/common/PreviewProvider.kt b/viewer-common/src/main/kotlin/com/alfresco/content/viewer/common/PreviewProvider.kt index bcf095461..f28137c1b 100644 --- a/viewer-common/src/main/kotlin/com/alfresco/content/viewer/common/PreviewProvider.kt +++ b/viewer-common/src/main/kotlin/com/alfresco/content/viewer/common/PreviewProvider.kt @@ -1,7 +1,6 @@ package com.alfresco.content.viewer.common interface PreviewProvider { - fun isMimeTypeSupported(mimeType: String): Boolean fun createViewer(): ChildViewerFragment diff --git a/viewer-image/src/main/kotlin/com/alfresco/content/viewer/image/ImagePreviewProvider.kt b/viewer-image/src/main/kotlin/com/alfresco/content/viewer/image/ImagePreviewProvider.kt index 13044cfef..89f345879 100644 --- a/viewer-image/src/main/kotlin/com/alfresco/content/viewer/image/ImagePreviewProvider.kt +++ b/viewer-image/src/main/kotlin/com/alfresco/content/viewer/image/ImagePreviewProvider.kt @@ -4,22 +4,22 @@ import android.os.Build import com.alfresco.content.viewer.common.PreviewProvider object ImagePreviewProvider : PreviewProvider { - private val supportedImageFormats = mutableSetOf( - "image/bmp", - "image/jpeg", - "image/png", - "image/gif", - "image/webp", - "image/gif", - "image/svg+xml", - ).apply { - if (Build.VERSION.SDK_INT >= 26) { - add("image/heic") + private val supportedImageFormats = + mutableSetOf( + "image/bmp", + "image/jpeg", + "image/png", + "image/gif", + "image/webp", + "image/gif", + "image/svg+xml", + ).apply { + if (Build.VERSION.SDK_INT >= 26) { + add("image/heic") + } } - } - override fun isMimeTypeSupported(mimeType: String): Boolean = - supportedImageFormats.contains(mimeType) + override fun isMimeTypeSupported(mimeType: String): Boolean = supportedImageFormats.contains(mimeType) override fun createViewer() = ImageViewerFragment() } diff --git a/viewer-image/src/main/kotlin/com/alfresco/content/viewer/image/ImageViewerFragment.kt b/viewer-image/src/main/kotlin/com/alfresco/content/viewer/image/ImageViewerFragment.kt index 0079f5f3f..beba3941d 100644 --- a/viewer-image/src/main/kotlin/com/alfresco/content/viewer/image/ImageViewerFragment.kt +++ b/viewer-image/src/main/kotlin/com/alfresco/content/viewer/image/ImageViewerFragment.kt @@ -23,13 +23,15 @@ import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView import com.github.chrisbanes.photoview.PhotoView class ImageViewerFragment : ChildViewerFragment(R.layout.viewer_image), MavericksView { - private val viewModel: ImageViewerViewModel by fragmentViewModel() private lateinit var largeScaleViewer: SubsamplingScaleImageView private lateinit var compatViewer: PhotoView - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { super.onViewCreated(view, savedInstanceState) val container = view.findViewById(R.id.container) @@ -43,24 +45,26 @@ class ImageViewerFragment : ChildViewerFragment(R.layout.viewer_image), Maverick } } - override fun invalidate() = withState(viewModel) { state -> - if (state.largeScale) { - if (state.path is Success) { - largeScaleViewer.loadImage(state.path() ?: "") + override fun invalidate() = + withState(viewModel) { state -> + if (state.largeScale) { + if (state.path is Success) { + largeScaleViewer.loadImage(state.path() ?: "") + } + } else { + compatViewer.loadImage(state.uri) } - } else { - compatViewer.loadImage(state.uri) } - } private fun setupLargeScalePreview(container: FrameLayout): SubsamplingScaleImageView { val view = SubsamplingScaleImageView(container.context) view.orientation = SubsamplingScaleImageView.ORIENTATION_USE_EXIF - view.layoutParams = FrameLayout.LayoutParams( - FrameLayout.LayoutParams.MATCH_PARENT, - FrameLayout.LayoutParams.MATCH_PARENT, - ) + view.layoutParams = + FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, + FrameLayout.LayoutParams.MATCH_PARENT, + ) container.addView(view) return view @@ -75,63 +79,70 @@ class ImageViewerFragment : ChildViewerFragment(R.layout.viewer_image), Maverick val view = PhotoView(container.context) // Override double-tap behavior to bypass medium zoom - view.setOnDoubleTapListener(object : GestureDetector.OnDoubleTapListener { - override fun onSingleTapConfirmed(e: MotionEvent): Boolean { - return false - } - - override fun onDoubleTap(e: MotionEvent): Boolean { - if (e == null) return false - - if (view.scale > view.minimumScale) { - view.setScale(view.minimumScale, e.x, e.y, true) - } else { - view.setScale(view.maximumScale, e.x, e.y, true) + view.setOnDoubleTapListener( + object : GestureDetector.OnDoubleTapListener { + override fun onSingleTapConfirmed(e: MotionEvent): Boolean { + return false } - return true - } + override fun onDoubleTap(e: MotionEvent): Boolean { + if (e == null) return false - override fun onDoubleTapEvent(e: MotionEvent): Boolean { - return false - } - }) + if (view.scale > view.minimumScale) { + view.setScale(view.minimumScale, e.x, e.y, true) + } else { + view.setScale(view.maximumScale, e.x, e.y, true) + } - view.layoutParams = FrameLayout.LayoutParams( - FrameLayout.LayoutParams.MATCH_PARENT, - FrameLayout.LayoutParams.MATCH_PARENT, + return true + } + + override fun onDoubleTapEvent(e: MotionEvent): Boolean { + return false + } + }, ) + view.layoutParams = + FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, + FrameLayout.LayoutParams.MATCH_PARENT, + ) + container.addView(view) return view } private fun PhotoView.loadImage(uri: String) { - val imageLoader = ImageLoader.Builder(requireContext()) - .components { - if (Build.VERSION.SDK_INT >= 28) { - add(ImageDecoderDecoder.Factory()) - } else { - add(GifDecoder.Factory()) - } - add(SvgDecoder.Factory()) - } - .build() - - val request = ImageRequest.Builder(context) - .data(uri) - .target(object : ImageViewTarget(this) { - override fun onSuccess(result: Drawable) { - super.onSuccess(result) - loadingListener?.onContentLoaded() - } - - override fun onError(error: Drawable?) { - super.onError(error) - loadingListener?.onContentError() + val imageLoader = + ImageLoader.Builder(requireContext()) + .components { + if (Build.VERSION.SDK_INT >= 28) { + add(ImageDecoderDecoder.Factory()) + } else { + add(GifDecoder.Factory()) + } + add(SvgDecoder.Factory()) } - }) - .build() + .build() + + val request = + ImageRequest.Builder(context) + .data(uri) + .target( + object : ImageViewTarget(this) { + override fun onSuccess(result: Drawable) { + super.onSuccess(result) + loadingListener?.onContentLoaded() + } + + override fun onError(error: Drawable?) { + super.onError(error) + loadingListener?.onContentError() + } + }, + ) + .build() imageLoader.enqueue(request) } } diff --git a/viewer-image/src/main/kotlin/com/alfresco/content/viewer/image/ImageViewerViewModel.kt b/viewer-image/src/main/kotlin/com/alfresco/content/viewer/image/ImageViewerViewModel.kt index b808dc19c..e13072f96 100644 --- a/viewer-image/src/main/kotlin/com/alfresco/content/viewer/image/ImageViewerViewModel.kt +++ b/viewer-image/src/main/kotlin/com/alfresco/content/viewer/image/ImageViewerViewModel.kt @@ -31,7 +31,6 @@ class ImageViewerViewModel( state: ImageViewerState, context: Context, ) : MavericksViewModel(state) { - init { if (state.largeScale && !state.uri.isLocalPath()) { val output = File(context.cacheDir, TMP_FILE_NAME) diff --git a/viewer-media/src/main/kotlin/com/alfresco/content/viewer/media/MediaPreviewProvider.kt b/viewer-media/src/main/kotlin/com/alfresco/content/viewer/media/MediaPreviewProvider.kt index 5d52d1f1d..499556afc 100644 --- a/viewer-media/src/main/kotlin/com/alfresco/content/viewer/media/MediaPreviewProvider.kt +++ b/viewer-media/src/main/kotlin/com/alfresco/content/viewer/media/MediaPreviewProvider.kt @@ -3,8 +3,7 @@ package com.alfresco.content.viewer.media import com.alfresco.content.viewer.common.PreviewProvider object MediaPreviewProvider : PreviewProvider { - override fun isMimeTypeSupported(mimeType: String) = - mimeType.startsWith("audio/") || mimeType.startsWith("video/") + override fun isMimeTypeSupported(mimeType: String) = mimeType.startsWith("audio/") || mimeType.startsWith("video/") override fun createViewer() = MediaViewerFragment() } diff --git a/viewer-media/src/main/kotlin/com/alfresco/content/viewer/media/MediaViewerFragment.kt b/viewer-media/src/main/kotlin/com/alfresco/content/viewer/media/MediaViewerFragment.kt index 4e4e41a14..e617e446a 100644 --- a/viewer-media/src/main/kotlin/com/alfresco/content/viewer/media/MediaViewerFragment.kt +++ b/viewer-media/src/main/kotlin/com/alfresco/content/viewer/media/MediaViewerFragment.kt @@ -40,7 +40,6 @@ import com.google.android.exoplayer2.util.Util import kotlin.math.max class MediaViewerFragment : ChildViewerFragment(), MavericksView { - private lateinit var args: ChildViewerArgs private val viewModel: MediaViewerViewModel by fragmentViewModel() @@ -61,15 +60,19 @@ class MediaViewerFragment : ChildViewerFragment(), MavericksView { savedInstanceState: Bundle?, ): View? { args = requireNotNull(arguments?.getParcelable(Mavericks.KEY_ARG)) - val layout = if (args.type.startsWith("audio/")) { - R.layout.fragment_viewer_audio - } else { - R.layout.fragment_viewer_video - } + val layout = + if (args.type.startsWith("audio/")) { + R.layout.fragment_viewer_audio + } else { + R.layout.fragment_viewer_video + } return inflater.inflate(layout, container, false) } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { super.onViewCreated(view, savedInstanceState) dataSourceFactory = DefaultDataSourceFactory(requireContext().applicationContext) @@ -163,10 +166,11 @@ class MediaViewerFragment : ChildViewerFragment(), MavericksView { val trackSelector = DefaultTrackSelector(context) trackSelector.parameters = trackSelectorParameters!! lastSeenTrackGroupArray = null - player = SimpleExoPlayer.Builder(requireContext(), renderersFactory) - .setMediaSourceFactory(mediaSourceFactory) - .setTrackSelector(trackSelector) - .build() + player = + SimpleExoPlayer.Builder(requireContext(), renderersFactory) + .setMediaSourceFactory(mediaSourceFactory) + .setTrackSelector(trackSelector) + .build() player.addListener(PlayerEventListener()) player.addAnalyticsListener(EventLogger(trackSelector)) player.setAudioAttributes(AudioAttributes.DEFAULT, true) @@ -245,7 +249,6 @@ class MediaViewerFragment : ChildViewerFragment(), MavericksView { } private inner class PlayerEventListener : Player.EventListener { - override fun onPlaybackStateChanged(state: Int) { if (state == PlaybackState.STATE_PLAYING || state == PlaybackState.STATE_STOPPED @@ -254,7 +257,7 @@ class MediaViewerFragment : ChildViewerFragment(), MavericksView { loadingListener?.onContentLoaded() } if (state == Player.STATE_ENDED) { - /*player?.seekTo(0)*/ + // player?.seekTo(0) resetToDefault() } } @@ -289,30 +292,31 @@ class MediaViewerFragment : ChildViewerFragment(), MavericksView { val cause = e.rendererException if (cause is DecoderInitializationException) { // Special case for decoder initialization failures. - errorString = if (cause.codecInfo == null) { - when { - cause.cause is DecoderQueryException -> { - getString(R.string.error_querying_decoders) - } - cause.secureDecoderRequired -> { - getString( - R.string.error_no_secure_decoder, - cause.mimeType, - ) - } - else -> { - getString( - R.string.error_no_decoder, - cause.mimeType, - ) + errorString = + if (cause.codecInfo == null) { + when { + cause.cause is DecoderQueryException -> { + getString(R.string.error_querying_decoders) + } + cause.secureDecoderRequired -> { + getString( + R.string.error_no_secure_decoder, + cause.mimeType, + ) + } + else -> { + getString( + R.string.error_no_decoder, + cause.mimeType, + ) + } } + } else { + getString( + R.string.error_instantiating_decoder, + cause.codecInfo?.name, + ) } - } else { - getString( - R.string.error_instantiating_decoder, - cause.codecInfo?.name, - ) - } } } return Pair.create(0, errorString) @@ -325,7 +329,7 @@ class MediaViewerFragment : ChildViewerFragment(), MavericksView { return ( trackInfo.getTypeSupport(C.TRACK_TYPE_VIDEO) != MappedTrackInfo.RENDERER_SUPPORT_NO_TRACKS - ) + ) } return false } diff --git a/viewer-media/src/main/kotlin/com/alfresco/content/viewer/media/TimeBar.kt b/viewer-media/src/main/kotlin/com/alfresco/content/viewer/media/TimeBar.kt index 4678cc94e..02bb16c5b 100644 --- a/viewer-media/src/main/kotlin/com/alfresco/content/viewer/media/TimeBar.kt +++ b/viewer-media/src/main/kotlin/com/alfresco/content/viewer/media/TimeBar.kt @@ -12,11 +12,11 @@ class TimeBar( defStyleAttr: Int, timeBarAttrs: AttributeSet?, ) : DefaultTimeBar( - context, - attrs, - defStyleAttr, - timeBarAttrs, -) { + context, + attrs, + defStyleAttr, + timeBarAttrs, + ) { init { val a = context.theme.obtainStyledAttributes(timeBarAttrs, R.styleable.DefaultTimeBar, 0, 0) try { @@ -39,7 +39,10 @@ class TimeBar( constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : this(context, attrs, defStyleAttr, attrs) - private fun getColorWithAlpha(color: Int, ratio: Float): Int { + private fun getColorWithAlpha( + color: Int, + ratio: Float, + ): Int { val alpha = (Color.alpha(color) * ratio).roundToInt() val r: Int = Color.red(color) val g: Int = Color.green(color) diff --git a/viewer-pdf/src/main/kotlin/com/alfresco/content/viewer/pdf/EglExt.kt b/viewer-pdf/src/main/kotlin/com/alfresco/content/viewer/pdf/EglExt.kt index 23a00627e..c13c6c8de 100644 --- a/viewer-pdf/src/main/kotlin/com/alfresco/content/viewer/pdf/EglExt.kt +++ b/viewer-pdf/src/main/kotlin/com/alfresco/content/viewer/pdf/EglExt.kt @@ -13,13 +13,14 @@ object EglExt { val version = IntArray(2) EGL14.eglInitialize(dpy, version, 0, version, 1) - val configAttr = intArrayOf( - EGL14.EGL_COLOR_BUFFER_TYPE, EGL14.EGL_RGB_BUFFER, - EGL14.EGL_LEVEL, 0, - EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT, - EGL14.EGL_SURFACE_TYPE, EGL14.EGL_PBUFFER_BIT, - EGL14.EGL_NONE, - ) + val configAttr = + intArrayOf( + EGL14.EGL_COLOR_BUFFER_TYPE, EGL14.EGL_RGB_BUFFER, + EGL14.EGL_LEVEL, 0, + EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT, + EGL14.EGL_SURFACE_TYPE, EGL14.EGL_PBUFFER_BIT, + EGL14.EGL_NONE, + ) val configs: Array = arrayOfNulls(1) val numConfig = IntArray(1) EGL14.eglChooseConfig( @@ -38,20 +39,22 @@ object EglExt { } val config: EGLConfig? = configs[0] - val surfAttr = intArrayOf( - EGL14.EGL_WIDTH, - 64, - EGL14.EGL_HEIGHT, - 64, - EGL14.EGL_NONE, - ) + val surfAttr = + intArrayOf( + EGL14.EGL_WIDTH, + 64, + EGL14.EGL_HEIGHT, + 64, + EGL14.EGL_NONE, + ) val surf: EGLSurface = EGL14.eglCreatePbufferSurface(dpy, config, surfAttr, 0) - val ctxAttrib = intArrayOf( - EGL14.EGL_CONTEXT_CLIENT_VERSION, - 2, - EGL14.EGL_NONE, - ) + val ctxAttrib = + intArrayOf( + EGL14.EGL_CONTEXT_CLIENT_VERSION, + 2, + EGL14.EGL_NONE, + ) val ctx: EGLContext = EGL14.eglCreateContext(dpy, config, EGL14.EGL_NO_CONTEXT, ctxAttrib, 0) diff --git a/viewer-pdf/src/main/kotlin/com/alfresco/content/viewer/pdf/PdfPreviewProvider.kt b/viewer-pdf/src/main/kotlin/com/alfresco/content/viewer/pdf/PdfPreviewProvider.kt index d986ca64a..a7278ddca 100644 --- a/viewer-pdf/src/main/kotlin/com/alfresco/content/viewer/pdf/PdfPreviewProvider.kt +++ b/viewer-pdf/src/main/kotlin/com/alfresco/content/viewer/pdf/PdfPreviewProvider.kt @@ -3,8 +3,7 @@ package com.alfresco.content.viewer.pdf import com.alfresco.content.viewer.common.PreviewProvider object PdfPreviewProvider : PreviewProvider { - override fun isMimeTypeSupported(mimeType: String) = - mimeType == "application/pdf" + override fun isMimeTypeSupported(mimeType: String) = mimeType == "application/pdf" override fun createViewer() = PdfViewerFragment() } diff --git a/viewer-pdf/src/main/kotlin/com/alfresco/content/viewer/pdf/PdfViewerFragment.kt b/viewer-pdf/src/main/kotlin/com/alfresco/content/viewer/pdf/PdfViewerFragment.kt index b418493a9..5c2f3868d 100644 --- a/viewer-pdf/src/main/kotlin/com/alfresco/content/viewer/pdf/PdfViewerFragment.kt +++ b/viewer-pdf/src/main/kotlin/com/alfresco/content/viewer/pdf/PdfViewerFragment.kt @@ -21,7 +21,6 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.textfield.TextInputEditText class PdfViewerFragment : ChildViewerFragment(), MavericksView { - private val viewModel: PdfViewerViewModel by fragmentViewModel() private lateinit var webView: WebView @@ -34,7 +33,10 @@ class PdfViewerFragment : ChildViewerFragment(), MavericksView { } @SuppressLint("SetJavaScriptEnabled") - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { super.onViewCreated(view, savedInstanceState) webView = view.findViewById(R.id.webview) @@ -42,13 +44,14 @@ class PdfViewerFragment : ChildViewerFragment(), MavericksView { WebView.setWebContentsDebuggingEnabled(true) } - val jsBridge = withState(viewModel) { - NativeBridge(EglExt.maxTextureSize, viewModel.assetUrl(it)) { reason -> - activity?.runOnUiThread { - showPasswordPrompt(reason) + val jsBridge = + withState(viewModel) { + NativeBridge(EglExt.maxTextureSize, viewModel.assetUrl(it)) { reason -> + activity?.runOnUiThread { + showPasswordPrompt(reason) + } } } - } webView.settings.apply { allowContentAccess = false @@ -69,51 +72,53 @@ class PdfViewerFragment : ChildViewerFragment(), MavericksView { webView.addJavascriptInterface(jsBridge, "bridge") - webView.webViewClient = object : WebViewClient() { + webView.webViewClient = + object : WebViewClient() { + override fun shouldOverrideUrlLoading( + view: WebView?, + request: WebResourceRequest?, + ): Boolean { + // Open URIs in external browser + startActivity(Intent(Intent.ACTION_VIEW, request?.url)) + return true + } - override fun shouldOverrideUrlLoading( - view: WebView?, - request: WebResourceRequest?, - ): Boolean { - // Open URIs in external browser - startActivity(Intent(Intent.ACTION_VIEW, request?.url)) - return true - } + override fun shouldInterceptRequest( + view: WebView?, + request: WebResourceRequest?, + ): WebResourceResponse? { + if (request?.method != "GET") { + return null + } - override fun shouldInterceptRequest( - view: WebView?, - request: WebResourceRequest?, - ): WebResourceResponse? { - if (request?.method != "GET") { - return null + return assetLoader.shouldInterceptRequest(request.url) } - - return assetLoader.shouldInterceptRequest(request.url) } - } // Loading state is handled by pdf viewer loadingListener?.onContentLoaded() } - private fun makeAssetLoader() = withState(viewModel) { - val ctx = requireContext() - val builder = WebViewAssetLoader.Builder() - .setDomain(viewModel.assetDomain(it)) - .setHttpAllowed(true) - .addPathHandler( - "/${PdfViewerViewModel.RESERVED_ASSETS_PATH}/", - WebViewAssetLoader.AssetsPathHandler(ctx), - ) - val docPath = viewModel.localDir(it) - if (docPath != null) { - builder.addPathHandler( - "/${PdfViewerViewModel.RESERVED_FILES_PATH}/", - WebViewAssetLoader.InternalStoragePathHandler(ctx, docPath), - ) + private fun makeAssetLoader() = + withState(viewModel) { + val ctx = requireContext() + val builder = + WebViewAssetLoader.Builder() + .setDomain(viewModel.assetDomain(it)) + .setHttpAllowed(true) + .addPathHandler( + "/${PdfViewerViewModel.RESERVED_ASSETS_PATH}/", + WebViewAssetLoader.AssetsPathHandler(ctx), + ) + val docPath = viewModel.localDir(it) + if (docPath != null) { + builder.addPathHandler( + "/${PdfViewerViewModel.RESERVED_FILES_PATH}/", + WebViewAssetLoader.InternalStoragePathHandler(ctx, docPath), + ) + } + builder.build() } - builder.build() - } class NativeBridge( @get:JavascriptInterface val maxTextureSize: Int, @@ -152,22 +157,23 @@ class PdfViewerFragment : ChildViewerFragment(), MavericksView { val title = if (reason == 1) getString(R.string.password_prompt_title) else getString(R.string.password_prompt_fail_title) val message = if (reason == 1) getString(R.string.password_prompt_message) else getString(R.string.password_prompt_fail_message) - val alert = MaterialAlertDialogBuilder(context) - .setTitle(title) - .setView(view) - .setMessage(message) - .setPositiveButton(getString(R.string.password_prompt_positive)) { _, _ -> - webView.evaluateJavascript( - "PDFViewerApplication.onPassword(\"${input.text}\")", - null, - ) - } - .setNegativeButton(getString(R.string.password_prompt_negative)) { dialog, _ -> - dialog.cancel() - } - .setOnCancelListener { - requireActivity().onBackPressed() - }.create() + val alert = + MaterialAlertDialogBuilder(context) + .setTitle(title) + .setView(view) + .setMessage(message) + .setPositiveButton(getString(R.string.password_prompt_positive)) { _, _ -> + webView.evaluateJavascript( + "PDFViewerApplication.onPassword(\"${input.text}\")", + null, + ) + } + .setNegativeButton(getString(R.string.password_prompt_negative)) { dialog, _ -> + dialog.cancel() + } + .setOnCancelListener { + requireActivity().onBackPressed() + }.create() alert.show() } diff --git a/viewer-pdf/src/main/kotlin/com/alfresco/content/viewer/pdf/PdfViewerViewModel.kt b/viewer-pdf/src/main/kotlin/com/alfresco/content/viewer/pdf/PdfViewerViewModel.kt index 9216cd8cd..b0fcea80e 100644 --- a/viewer-pdf/src/main/kotlin/com/alfresco/content/viewer/pdf/PdfViewerViewModel.kt +++ b/viewer-pdf/src/main/kotlin/com/alfresco/content/viewer/pdf/PdfViewerViewModel.kt @@ -16,46 +16,57 @@ data class PdfViewerState( } class PdfViewerViewModel(state: PdfViewerState) : MavericksViewModel(state) { - - fun assetDomain(state: PdfViewerState): String = if (state.uri.isLocalPath()) { - WebViewAssetLoader.DEFAULT_DOMAIN - } else { - Uri.parse(state.uri).authority ?: "" - } + fun assetDomain(state: PdfViewerState): String = + if (state.uri.isLocalPath()) { + WebViewAssetLoader.DEFAULT_DOMAIN + } else { + Uri.parse(state.uri).authority ?: "" + } /** * returns the base url. */ - fun baseUrl(state: PdfViewerState) = if (state.uri.isLocalPath()) { - "https://${WebViewAssetLoader.DEFAULT_DOMAIN}" - } else { - val docUri = Uri.parse(state.uri) - "${docUri.scheme}://${docUri.authority}" - } + fun baseUrl(state: PdfViewerState) = + if (state.uri.isLocalPath()) { + "https://${WebViewAssetLoader.DEFAULT_DOMAIN}" + } else { + val docUri = Uri.parse(state.uri) + "${docUri.scheme}://${docUri.authority}" + } /** * returns the asset url */ - fun assetUrl(state: PdfViewerState) = if (state.uri.isLocalPath()) { - val filename = state.uri.filename() - "https://${WebViewAssetLoader.DEFAULT_DOMAIN}/$RESERVED_FILES_PATH/$filename" - } else { - state.uri - } + fun assetUrl(state: PdfViewerState) = + if (state.uri.isLocalPath()) { + val filename = state.uri.filename() + "https://${WebViewAssetLoader.DEFAULT_DOMAIN}/$RESERVED_FILES_PATH/$filename" + } else { + state.uri + } /** * returns the viewer url */ - fun viewerUrl(state: PdfViewerState) = if (state.uri.contains("#/preview")) state.uri.replace("/aca", "").plus("?mobileapps=true") else "${baseUrl(state)}/$RESERVED_ASSETS_PATH/viewer.html" + fun viewerUrl(state: PdfViewerState) = + if (state.uri.contains( + "#/preview", + ) + ) { + state.uri.replace("/aca", "").plus("?mobileapps=true") + } else { + "${baseUrl(state)}/$RESERVED_ASSETS_PATH/viewer.html" + } /** * it returns true if uri is from local directories otherwise false */ - fun localDir(state: PdfViewerState) = if (state.uri.isLocalPath()) { - requireNotNull(state.uri.parentFile()) - } else { - null - } + fun localDir(state: PdfViewerState) = + if (state.uri.isLocalPath()) { + requireNotNull(state.uri.parentFile()) + } else { + null + } companion object { const val RESERVED_ASSETS_PATH = "--assets--" diff --git a/viewer-text/src/main/kotlin/com/alfresco/content/viewer/text/TextPreviewProvider.kt b/viewer-text/src/main/kotlin/com/alfresco/content/viewer/text/TextPreviewProvider.kt index b2a0a66ec..ba543b02b 100644 --- a/viewer-text/src/main/kotlin/com/alfresco/content/viewer/text/TextPreviewProvider.kt +++ b/viewer-text/src/main/kotlin/com/alfresco/content/viewer/text/TextPreviewProvider.kt @@ -3,8 +3,7 @@ package com.alfresco.content.viewer.text import com.alfresco.content.viewer.common.PreviewProvider object TextPreviewProvider : PreviewProvider { - override fun isMimeTypeSupported(mimeType: String) = - mimeType.startsWith("text/") + override fun isMimeTypeSupported(mimeType: String) = mimeType.startsWith("text/") override fun createViewer() = TextViewerFragment() } diff --git a/viewer-text/src/main/kotlin/com/alfresco/content/viewer/text/TextViewerFragment.kt b/viewer-text/src/main/kotlin/com/alfresco/content/viewer/text/TextViewerFragment.kt index d29099a4a..38ca5c3d9 100644 --- a/viewer-text/src/main/kotlin/com/alfresco/content/viewer/text/TextViewerFragment.kt +++ b/viewer-text/src/main/kotlin/com/alfresco/content/viewer/text/TextViewerFragment.kt @@ -21,7 +21,6 @@ import com.alfresco.kotlin.filename * Viewer for displaying plain text backed by a [WebView] */ class TextViewerFragment : ChildViewerFragment(R.layout.viewer_text), MavericksView { - private val viewModel: TextViewerViewModel by fragmentViewModel() private lateinit var webView: WebView @@ -36,26 +35,29 @@ class TextViewerFragment : ChildViewerFragment(R.layout.viewer_text), MavericksV } @Suppress("DEPRECATION") - private fun createWebView(context: Context, assetLoader: WebViewAssetLoader) = - WebView(context).apply { - with(settings) { - setSupportZoom(true) - builtInZoomControls = true - displayZoomControls = false - savePassword = false - saveFormData = false - blockNetworkLoads = true - allowContentAccess = false - allowFileAccess = false - @Suppress("DEPRECATION") - allowFileAccessFromFileURLs = false - @Suppress("DEPRECATION") - allowUniversalAccessFromFileURLs = false - javaScriptEnabled = false - defaultTextEncodingName = "utf-8" - } + private fun createWebView( + context: Context, + assetLoader: WebViewAssetLoader, + ) = WebView(context).apply { + with(settings) { + setSupportZoom(true) + builtInZoomControls = true + displayZoomControls = false + savePassword = false + saveFormData = false + blockNetworkLoads = true + allowContentAccess = false + allowFileAccess = false + @Suppress("DEPRECATION") + allowFileAccessFromFileURLs = false + @Suppress("DEPRECATION") + allowUniversalAccessFromFileURLs = false + javaScriptEnabled = false + defaultTextEncodingName = "utf-8" + } - webViewClient = object : WebViewClient() { + webViewClient = + object : WebViewClient() { override fun shouldInterceptRequest( view: WebView?, request: WebResourceRequest?, @@ -67,7 +69,7 @@ class TextViewerFragment : ChildViewerFragment(R.layout.viewer_text), MavericksV return assetLoader.shouldInterceptRequest(request.url) } } - } + } private fun makeAssetLoader() = WebViewAssetLoader.Builder() @@ -80,12 +82,13 @@ class TextViewerFragment : ChildViewerFragment(R.layout.viewer_text), MavericksV ) .build() - override fun invalidate() = withState(viewModel) { state -> - if (state.path is Success && webView.url != state.path()) { - webView.visibility = View.VISIBLE - val filename = state.path()?.filename() - webView.loadUrl("https://${WebViewAssetLoader.DEFAULT_DOMAIN}/$filename") - loadingListener?.onContentLoaded() + override fun invalidate() = + withState(viewModel) { state -> + if (state.path is Success && webView.url != state.path()) { + webView.visibility = View.VISIBLE + val filename = state.path()?.filename() + webView.loadUrl("https://${WebViewAssetLoader.DEFAULT_DOMAIN}/$filename") + loadingListener?.onContentLoaded() + } } - } } diff --git a/viewer-text/src/main/kotlin/com/alfresco/content/viewer/text/TextViewerViewModel.kt b/viewer-text/src/main/kotlin/com/alfresco/content/viewer/text/TextViewerViewModel.kt index ca348043f..810ea9b44 100644 --- a/viewer-text/src/main/kotlin/com/alfresco/content/viewer/text/TextViewerViewModel.kt +++ b/viewer-text/src/main/kotlin/com/alfresco/content/viewer/text/TextViewerViewModel.kt @@ -28,7 +28,6 @@ class TextViewerViewModel( state: TextViewerState, context: Context, ) : MavericksViewModel(state) { - val docPath: File init { diff --git a/viewer/src/main/kotlin/com/alfresco/content/viewer/ViewerFragment.kt b/viewer/src/main/kotlin/com/alfresco/content/viewer/ViewerFragment.kt index f2e11892b..442bdc842 100644 --- a/viewer/src/main/kotlin/com/alfresco/content/viewer/ViewerFragment.kt +++ b/viewer/src/main/kotlin/com/alfresco/content/viewer/ViewerFragment.kt @@ -53,7 +53,6 @@ data class ViewerArgs( } class ViewerFragment : Fragment(), MavericksView { - private lateinit var args: ViewerArgs @OptIn(InternalMavericksApi::class) @@ -62,15 +61,16 @@ class ViewerFragment : Fragment(), MavericksView { private var childFragment: ChildViewerFragment? = null private var hasLoadingStatus: Boolean = false - private val viewerLoadingListener = object : LoadingListener { - override fun onContentLoaded() { - show(Status.PreviewLoaded) - } + private val viewerLoadingListener = + object : LoadingListener { + override fun onContentLoaded() { + show(Status.PreviewLoaded) + } - override fun onContentError() { - show(Status.NotSupported) + override fun onContentError() { + show(Status.NotSupported) + } } - } override fun onCreateView( inflater: LayoutInflater, @@ -91,9 +91,10 @@ class ViewerFragment : Fragment(), MavericksView { super.onAttachFragment(childFragment) if (childFragment is ChildViewerFragment) { - this.childFragment = childFragment.apply { - loadingListener = viewerLoadingListener - } + this.childFragment = + childFragment.apply { + loadingListener = viewerLoadingListener + } } } @@ -103,51 +104,57 @@ class ViewerFragment : Fragment(), MavericksView { childFragment?.loadingListener = null } - override fun invalidate() = withState(viewModel) { state -> - if (state.entry?.name != null) { - (requireActivity() as AppCompatActivity).supportActionBar?.title = state.entry.name - binding.title.text = state.entry.name - } else binding.title.text = args.title - val type = MimeType.with(state.entry?.mimeType) - binding.icon.setImageDrawable( - ResourcesCompat.getDrawable(resources, type.icon, requireContext().theme), - ) - - if (state.entry != null) { - configureActionBar(state.entry) - } - - if (state.ready) { - if (state.viewerMimeType != null && state.viewerUri != null) { - configureViewer( - state.viewerUri, - state.viewerMimeType, - ) - if (!hasLoadingStatus) { - show(Status.LoadingPreview) - } + override fun invalidate() = + withState(viewModel) { state -> + if (state.entry?.name != null) { + (requireActivity() as AppCompatActivity).supportActionBar?.title = state.entry.name + binding.title.text = state.entry.name } else { - show(Status.NotSupported) + binding.title.text = args.title } - } else { - if (state.entry == null) { - show(Status.LoadingMetadata) + val type = MimeType.with(state.entry?.mimeType) + binding.icon.setImageDrawable( + ResourcesCompat.getDrawable(resources, type.icon, requireContext().theme), + ) + + if (state.entry != null) { + configureActionBar(state.entry) + } + + if (state.ready) { + if (state.viewerMimeType != null && state.viewerUri != null) { + configureViewer( + state.viewerUri, + state.viewerMimeType, + ) + if (!hasLoadingStatus) { + show(Status.LoadingPreview) + } + } else { + show(Status.NotSupported) + } } else { - show(Status.PreparingPreview) + if (state.entry == null) { + show(Status.LoadingMetadata) + } else { + show(Status.PreparingPreview) + } } } - } private fun configureActionBar(entry: Entry) { val mobileConfigDataEntry = getJsonFromSharedPrefs(requireContext(), KEY_FEATURES_MOBILE) - val fragment = ContextualActionsBarFragment().apply { - arguments = bundleOf( - Mavericks.KEY_ARG to ContextualActionData.withEntries( - listOf(entry), - mobileConfigData = mobileConfigDataEntry, - ), - ) - } + val fragment = + ContextualActionsBarFragment().apply { + arguments = + bundleOf( + Mavericks.KEY_ARG to + ContextualActionData.withEntries( + listOf(entry), + mobileConfigData = mobileConfigDataEntry, + ), + ) + } parentFragmentManager.beginTransaction().replace(R.id.action_list_bar, fragment).commit() } @@ -157,10 +164,11 @@ class ViewerFragment : Fragment(), MavericksView { ) { val tag = mimeType if (childFragmentManager.findFragmentByTag(tag) == null) { - val args = ChildViewerArgs( - viewerUri, - mimeType, - ) + val args = + ChildViewerArgs( + viewerUri, + mimeType, + ) val fragment = ViewerRegistry.previewProvider(mimeType)?.createViewer() requireNotNull(fragment) fragment.arguments = bundleOf(Mavericks.KEY_ARG to args) @@ -208,6 +216,10 @@ class ViewerFragment : Fragment(), MavericksView { } private enum class Status { - LoadingMetadata, PreparingPreview, LoadingPreview, PreviewLoaded, NotSupported + LoadingMetadata, + PreparingPreview, + LoadingPreview, + PreviewLoaded, + NotSupported, } } diff --git a/viewer/src/main/kotlin/com/alfresco/content/viewer/ViewerViewModel.kt b/viewer/src/main/kotlin/com/alfresco/content/viewer/ViewerViewModel.kt index 193c8e44b..3371b87b7 100644 --- a/viewer/src/main/kotlin/com/alfresco/content/viewer/ViewerViewModel.kt +++ b/viewer/src/main/kotlin/com/alfresco/content/viewer/ViewerViewModel.kt @@ -24,7 +24,6 @@ data class ViewerState( class ViewerViewModel( state: ViewerState, ) : MavericksViewModel(state) { - private lateinit var offlineRepository: OfflineRepository private lateinit var browseRepository: BrowseRepository private lateinit var renditionRepository: RenditionRepository @@ -58,7 +57,10 @@ class ViewerViewModel( } } - private suspend fun loadContent(id: String, loader: ContentLoader) { + private suspend fun loadContent( + id: String, + loader: ContentLoader, + ) { val entry = loader.fetchEntry(id) requireNotNull(entry) val mimeType = entry.mimeType @@ -97,7 +99,6 @@ class ViewerViewModel( } private interface ContentLoader { - suspend fun fetchEntry(id: String): Entry? fun contentUri(entry: Entry): String @@ -109,26 +110,19 @@ class ViewerViewModel( val browseRepository: BrowseRepository, val renditionRepository: RenditionRepository, ) : ContentLoader { + override suspend fun fetchEntry(id: String) = browseRepository.fetchEntry(id) - override suspend fun fetchEntry(id: String) = - browseRepository.fetchEntry(id) + override fun contentUri(entry: Entry) = browseRepository.contentUri(entry) - override fun contentUri(entry: Entry) = - browseRepository.contentUri(entry) - - override suspend fun rendition(entry: Entry) = - renditionRepository.fetchRendition(entry.id) + override suspend fun rendition(entry: Entry) = renditionRepository.fetchRendition(entry.id) } private class OfflineContentLoader( val offlineRepository: OfflineRepository, ) : ContentLoader { + override suspend fun fetchEntry(id: String) = offlineRepository.entry(id) - override suspend fun fetchEntry(id: String) = - offlineRepository.entry(id) - - override fun contentUri(entry: Entry) = - offlineRepository.contentUri(entry) + override fun contentUri(entry: Entry) = offlineRepository.contentUri(entry) override suspend fun rendition(entry: Entry): Rendition { val dir = offlineRepository.contentDir(entry)