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 cd9682bbc..5f37bf4e9 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
@@ -83,7 +83,7 @@ class ProcessDefinitionsSheet : BottomSheetDialogFragment(), MavericksView {
val intent = Intent(
requireActivity(),
- Class.forName("com.alfresco.content.process.ProcessFormActivity"),
+ Class.forName("com.alfresco.content.process.ui.ProcessFormActivity"),
)
intent.putExtra(Mavericks.KEY_ARG, processEntry)
startActivity(intent)
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 bc0e95e5c..28f4a73ca 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
@@ -8,6 +8,7 @@ import com.alfresco.content.actions.Action
import com.alfresco.content.browse.R
import com.alfresco.content.browse.databinding.ActivityLocalPreviewBinding
import com.alfresco.content.data.Entry
+import com.alfresco.content.process.ui.fragments.BaseDetailFragment.Companion.KEY_ENTRY_OBJ
/**
* Mark as Preview Activity
@@ -45,8 +46,4 @@ class LocalPreviewActivity : AppCompatActivity() {
fragment.arguments = intent.extras
}
}
-
- companion object {
- const val KEY_ENTRY_OBJ = "entryObj"
- }
}
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 6d862cea2..4c942332a 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
@@ -192,7 +192,7 @@ class ProcessDetailViewModel(
fun startWorkflow() = withState { state ->
val items = state.listContents.joinToString(separator = ",") { it.id }
viewModelScope.launch {
- repository::startWorkflow.asFlow(state.parent, items, emptyList()).execute {
+ repository::startWorkflow.asFlow(state.parent, items, mapOf()).execute {
when (it) {
is Loading -> copy(requestStartWorkflow = Loading())
is Fail -> copy(requestStartWorkflow = Fail(it.error))
diff --git a/browse/src/main/kotlin/com/alfresco/content/browse/processes/details/ProcessDetailViewModelExtension.kt b/browse/src/main/kotlin/com/alfresco/content/browse/processes/details/ProcessDetailViewModelExtension.kt
index ae06a25eb..1c0cb2612 100644
--- a/browse/src/main/kotlin/com/alfresco/content/browse/processes/details/ProcessDetailViewModelExtension.kt
+++ b/browse/src/main/kotlin/com/alfresco/content/browse/processes/details/ProcessDetailViewModelExtension.kt
@@ -1,7 +1,7 @@
package com.alfresco.content.browse.processes.details
import com.alfresco.content.browse.tasks.list.UpdateTasksData
-import com.alfresco.content.process.ui.UpdateProcessData
+import com.alfresco.content.process.ui.models.UpdateProcessData
import com.alfresco.events.EventBus
import kotlinx.coroutines.launch
diff --git a/browse/src/main/kotlin/com/alfresco/content/browse/processes/list/ProcessesViewModel.kt b/browse/src/main/kotlin/com/alfresco/content/browse/processes/list/ProcessesViewModel.kt
index 72a4d7d51..c4474e037 100644
--- a/browse/src/main/kotlin/com/alfresco/content/browse/processes/list/ProcessesViewModel.kt
+++ b/browse/src/main/kotlin/com/alfresco/content/browse/processes/list/ProcessesViewModel.kt
@@ -13,7 +13,7 @@ import com.alfresco.content.data.payloads.TaskProcessFiltersPayload
import com.alfresco.content.getLocalizedName
import com.alfresco.content.listview.processes.ProcessListViewModel
import com.alfresco.content.listview.processes.ProcessListViewState
-import com.alfresco.content.process.ui.UpdateProcessData
+import com.alfresco.content.process.ui.models.UpdateProcessData
import com.alfresco.coroutines.asFlow
import com.alfresco.events.on
import kotlinx.coroutines.launch
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 ad6dbc032..eca91e07c 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
@@ -14,6 +14,7 @@ import com.alfresco.content.browse.tasks.detail.TaskDetailViewState
import com.alfresco.content.data.AnalyticsManager
import com.alfresco.content.data.Entry
import com.alfresco.content.data.EventName
+import com.alfresco.content.process.ui.fragments.BaseDetailFragment.Companion.KEY_ENTRY_OBJ
import com.alfresco.content.viewer.ViewerActivity
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
@@ -84,7 +85,7 @@ abstract class BaseDetailFragment : Fragment(), DeleteContentListener {
*/
fun localViewerIntent(contentEntry: Entry) = startActivity(
Intent(requireActivity(), LocalPreviewActivity::class.java)
- .putExtra(LocalPreviewActivity.KEY_ENTRY_OBJ, contentEntry),
+ .putExtra(KEY_ENTRY_OBJ, contentEntry),
)
/**
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 20b4424d4..a7174d323 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
@@ -30,6 +30,7 @@ import com.alfresco.content.data.PageView
import com.alfresco.content.data.ParentEntry
import com.alfresco.content.data.UploadServerType
import com.alfresco.content.mimetype.MimeType
+import com.alfresco.content.process.ui.fragments.BaseDetailFragment.Companion.KEY_ENTRY_OBJ
import com.alfresco.content.simpleController
import com.alfresco.ui.getDrawableForAttribute
@@ -148,7 +149,7 @@ class AttachedFilesFragment : BaseDetailFragment(), MavericksView, EntryListener
if (isAdded) {
startActivity(
Intent(requireActivity(), LocalPreviewActivity::class.java)
- .putExtra(LocalPreviewActivity.KEY_ENTRY_OBJ, entry as Entry),
+ .putExtra(KEY_ENTRY_OBJ, entry as Entry),
)
}
}
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 e52d784d9..803ee7516 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
@@ -5,7 +5,7 @@ import com.alfresco.content.browse.tasks.list.UpdateTasksData
import com.alfresco.content.data.Entry
import com.alfresco.content.data.OfflineRepository
import com.alfresco.content.data.UserGroupDetails
-import com.alfresco.content.process.ui.UpdateProcessData
+import com.alfresco.content.process.ui.models.UpdateProcessData
import com.alfresco.events.EventBus
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
diff --git a/browse/src/main/res/values-de/strings.xml b/browse/src/main/res/values-de/strings.xml
index 972b3bc62..4975652ab 100755
--- a/browse/src/main/res/values-de/strings.xml
+++ b/browse/src/main/res/values-de/strings.xml
@@ -46,7 +46,6 @@
Account
Symbol für Fälligkeitsdatum
Symbol für Benutzerprofil
- Symbol für Datei
Symbol für Priorität
Zugewiesenes Symbol
Symbol für Status
@@ -63,15 +62,11 @@
Alle anzeigen
%d Kommentare
Angehängte Dateien
- %d Anhänge
Keine angehängten Dateien
Kein Fälligkeitsdatum
- Angehängte Dateien
Abgeschlossen
Aufgabe abschließen
Sind Sie sicher, dass Sie diese Aufgabe abschließen wollen? Sie werden keine Änderungen mehr vornehmen können.
- Abbrechen
- Bestätigen
Schaltfläche „Senden“
Keine Beschreibung
Abgeschlossen-Symbol
@@ -87,9 +82,6 @@
Aufgabe erstellen
Verwerfen
Sollen die Änderungen verworfen werden?
- Symbol \'Anhang löschen\'
- Eine Datei löschen?
- Anhänge hinzufügen
Symbol \'Anhang hinzufügen\'
Sind Sie sicher, dass Sie die Aufgabe abschließen wollen? Nach Abschluss der Aufgabe können einige Dateien nicht hochgeladen werden und es können keine Änderungen vorgenommen werden.
diff --git a/browse/src/main/res/values-es/strings.xml b/browse/src/main/res/values-es/strings.xml
index c15d3a30c..3b2174e55 100755
--- a/browse/src/main/res/values-es/strings.xml
+++ b/browse/src/main/res/values-es/strings.xml
@@ -46,7 +46,6 @@
Cuenta
Icono de fecha de vencimiento
Icono de perfil de usuario
- Icono de fichero
Icono de prioridad
Icono de usuario asignado
Icono de estado
@@ -63,15 +62,11 @@
Ver todo
%d comentarios
Ficheros adjuntos
- %d adjuntos
No hay ficheros adjuntos
Sin fecha de vencimiento
- Ficheros adjuntos
Por completar
Completar tarea
¿Está seguro de que desea completar esta tarea? Ya no podrá realizar ningún cambio.
- Cancelar
- Confirmar
Botón Enviar
Sin descripción
Icono completado
@@ -88,9 +83,6 @@
Crear tarea
Descartar
¿Desea descartar los cambios?
- Icono de eliminación de adjuntos
- ¿Eliminar un fichero?
- Añadir adjuntos
Añadir icono adjunto
¿Está seguro de que quiere completar la tarea? Una vez completada, algunos ficheros no se pudieron cargar y no se pueden realizar cambios.
diff --git a/browse/src/main/res/values-fr/strings.xml b/browse/src/main/res/values-fr/strings.xml
index 2f0ef7b63..110a523f8 100755
--- a/browse/src/main/res/values-fr/strings.xml
+++ b/browse/src/main/res/values-fr/strings.xml
@@ -46,7 +46,6 @@
Compte
Icône Date d\'échéance
Icône du profil d\'utilisateur
- Icône de fichier
Icône de priorité
Icône Assigné
Icône du statut
@@ -63,15 +62,11 @@
Afficher tout
%d commentaires
Fichiers joints
- %d pièces jointes
Aucun fichier joint
Aucune date d\'échéance
- Fichiers joints
Terminé
Terminer la tâche
Voulez-vous vraiment terminer cette tâche ? Vous ne pourrez plus faire de modifications.
- Annuler
- Confirmer
Bouton Envoyer
Aucune description
Icône Terminé
@@ -87,9 +82,6 @@
Créer une tâche
Ignorer
Voulez-vous ignorer les modifications ?
- Icône Supprimer la pièce jointe
- Supprimer un fichier ?
- Ajouter des pièces jointes
Icône Ajouter une pièce jointe
Voulez-vous vraiment terminer la tâche ? Une fois terminée, certains fichiers ne pourront plus être importés et aucun changement ne pourra être effectué.
diff --git a/browse/src/main/res/values-it/strings.xml b/browse/src/main/res/values-it/strings.xml
index b6f7827c3..c85359602 100755
--- a/browse/src/main/res/values-it/strings.xml
+++ b/browse/src/main/res/values-it/strings.xml
@@ -46,7 +46,6 @@
Account
Icona scadenza
Icona profilo utente
- Icona file
Icona priorità
Icona assegnazione
Icona stato
@@ -63,10 +62,8 @@
Visualizza tutto
%d commenti
File allegati
- %d allegati
Nessun file allegato
Nessuna scadenza
- File allegati
Completato
Completa compito
Vuoi completare il compito? Non potrai più apportare modifiche.
@@ -85,11 +82,6 @@
Icona Modifica assegnatario
Icona Modifica nome e descrizione
Crea compito
- Ignora
- Vuoi ignorare le modifiche?
- Icona Elimina allegato
- Vuoi eliminare un file?
- Aggiungi allegati
Icona Aggiungi allegati
Vuoi completare il compito? Una volta completato, non sarà possibile caricare alcuni file né apportare modifiche.
diff --git a/browse/src/main/res/values-nl/strings.xml b/browse/src/main/res/values-nl/strings.xml
index 575f51886..2580d8d5a 100755
--- a/browse/src/main/res/values-nl/strings.xml
+++ b/browse/src/main/res/values-nl/strings.xml
@@ -46,7 +46,6 @@
Account
Pictogram Vervaldatum
Pictogram Gebruikersprofiel
- Pictogram Bestand
Pictogram Prioriteit
Pictogram Toegewezen
Pictogram Status
@@ -63,15 +62,11 @@
Alle weergeven
%d opmerkingen
Bijgevoegde bestanden
- %d bijlagen
Geen bijgevoegde bestanden
Geen vervaldatum
- Bijgevoegde bestanden
Voltooien
Taak voltooien
Weet u zeker dat u de taak wilt voltooien? U kunt geen wijzigingen meer aanbrengen.
- Annuleren
- Bevestigen
Knop Verzenden
Geen beschrijving
Pictogram Voltooid
@@ -87,9 +82,6 @@
Taak creëren
Negeren
Wilt u de wijzigingen negeren?
- Pictogram Bijlage verwijderen
- Bestand verwijderen?
- Bijlagen toevoegen
Pictogram Bijlage toevoegen
Weet u zeker dat u de taak wilt voltooien? Nadat de taak is voltooid, kunnen sommige bestanden niet worden geüpload en kunnen geen wijzigingen worden aangebracht.
diff --git a/browse/src/main/res/values/strings.xml b/browse/src/main/res/values/strings.xml
index 5a2a21b4c..308a7d023 100644
--- a/browse/src/main/res/values/strings.xml
+++ b/browse/src/main/res/values/strings.xml
@@ -46,7 +46,6 @@
Account
Due date icon
User profile icon
- File icon
Priority icon
Assigned icon
Status icon
@@ -63,15 +62,11 @@
View all
%d comments
Attached files
- %d attachments
No Attached Files
No due date
- Attached files
Complete
Complete Task
Are you sure you want to complete this task? You will no longer be able to make any changes.
- Cancel
- Confirm
Send Button
No description
Completed icon
@@ -87,9 +82,6 @@
Create task
Discard
Do you want to discard the changes?
- Icon delete attachment
- Delete a file?
- Add attachments
Add attachment icon
Are you sure you want to complete the task? Once completed, some files could not be uploaded and no changes can be made.
diff --git a/browse/src/main/res/drawable/ic_add_fab.xml b/common/src/main/res/drawable/ic_add_fab.xml
similarity index 100%
rename from browse/src/main/res/drawable/ic_add_fab.xml
rename to common/src/main/res/drawable/ic_add_fab.xml
diff --git a/common/src/main/res/values-de/strings.xml b/common/src/main/res/values-de/strings.xml
index 7eb4c7b51..e0f14d872 100755
--- a/common/src/main/res/values-de/strings.xml
+++ b/common/src/main/res/values-de/strings.xml
@@ -30,4 +30,12 @@
%d Ausgewählt
Bitte überprüfen Sie Ihre Internetverbindung und starten anschließend den Vorgang erneut.
...Alle anzeigen
+ %d Anhänge
+ Anhänge hinzufügen
+ Angehängte Dateien
+ Symbol für Datei
+ Symbol \'Anhang löschen\'
+ Eine Datei löschen?
+ Abbrechen
+ Bestätigen
diff --git a/common/src/main/res/values-es/strings.xml b/common/src/main/res/values-es/strings.xml
index f9b91786b..8bb2593b2 100755
--- a/common/src/main/res/values-es/strings.xml
+++ b/common/src/main/res/values-es/strings.xml
@@ -30,4 +30,12 @@
%d Seleccionado
Verifique su conexión a Internet y vuelva a intentarlo.
...Ver todo
+ %d adjuntos
+ Añadir adjuntos
+ Ficheros adjuntos
+ Icono de fichero
+ Icono de eliminación de adjuntos
+ ¿Eliminar un fichero?
+ Cancelar
+ Confirmar
diff --git a/common/src/main/res/values-fr/strings.xml b/common/src/main/res/values-fr/strings.xml
index 843b09a56..88553c8ed 100755
--- a/common/src/main/res/values-fr/strings.xml
+++ b/common/src/main/res/values-fr/strings.xml
@@ -30,4 +30,13 @@
%d Sélectionné
Veuillez vérifier votre connexion Internet et réessayer.
…Afficher tout
+ %d pièces jointes
+ Ajouter des pièces jointes
+ Fichiers joints
+ Icône de fichier
+ Icône Supprimer la pièce jointe
+ Supprimer un fichier ?
+ Annuler
+ Confirmer
+
diff --git a/common/src/main/res/values-it/strings.xml b/common/src/main/res/values-it/strings.xml
index f4f4c7881..0b96f77ce 100755
--- a/common/src/main/res/values-it/strings.xml
+++ b/common/src/main/res/values-it/strings.xml
@@ -30,4 +30,12 @@
%d Selezionato
Controlla la connessione Internet e riprova.
...Visualizza tutto
+ %d allegati
+ Aggiungi allegati
+ File allegati
+ Icona file
+ Icona Elimina allegato
+ Vuoi eliminare un file?
+ Ignora
+ Vuoi ignorare le modifiche?
diff --git a/common/src/main/res/values-nl/strings.xml b/common/src/main/res/values-nl/strings.xml
index b1120ac49..5195da14c 100755
--- a/common/src/main/res/values-nl/strings.xml
+++ b/common/src/main/res/values-nl/strings.xml
@@ -30,4 +30,12 @@
%d Geselecteerd
Controleer uw internetverbinding en probeer het opnieuw.
...Alle weergeven
+ %d bijlagen
+ Bijlagen toevoegen
+ Bijgevoegde bestanden
+ Pictogram Bestand
+ Pictogram Bijlage verwijderen
+ Bestand verwijderen?
+ Annuleren
+ Bevestigen
diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml
index ddba320d3..71840605d 100644
--- a/common/src/main/res/values/strings.xml
+++ b/common/src/main/res/values/strings.xml
@@ -31,4 +31,12 @@
Please check your internet connection and Try again.
…View all
Workflow
+ %d attachments
+ Add attachments
+ Attached files
+ File icon
+ Icon delete attachment
+ Delete a file?
+ Cancel
+ Confirm
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 1277bfa8e..55a43a0e7 100644
--- a/data/src/main/kotlin/com/alfresco/content/data/TaskRepository.kt
+++ b/data/src/main/kotlin/com/alfresco/content/data/TaskRepository.kt
@@ -4,7 +4,6 @@ import android.content.SharedPreferences
import androidx.preference.PreferenceManager
import com.alfresco.content.data.Settings.Companion.IS_PROCESS_ENABLED_KEY
import com.alfresco.content.data.payloads.CommentPayload
-import com.alfresco.content.data.payloads.FieldType
import com.alfresco.content.data.payloads.FieldsData
import com.alfresco.content.data.payloads.LinkContentPayload
import com.alfresco.content.data.payloads.SystemPropertiesEntry
@@ -383,31 +382,17 @@ class TaskRepository {
/**
* Execute the start flow integration
*/
- suspend fun startWorkflow(processEntry: ProcessEntry?, items: String, fields: List) = ProcessEntry.with(
+ suspend fun startWorkflow(processEntry: ProcessEntry?, items: String, values: Map) = ProcessEntry.with(
processesService.createProcessInstance(
RequestProcessInstances(
name = processEntry?.name,
processDefinitionId = processEntry?.id,
- values = convertFieldsToValues(fields),
+ values = values,
),
),
)
- private fun convertFieldsToValues(fields: List): Map {
- val values = mutableMapOf()
-
- fields.forEach {
- if (it.type == FieldType.PEOPLE.value() || it.type == FieldType.FUNCTIONAL_GROUP.value()) {
- values[it.id] = getUserOrGroup(it.value as UserGroupDetails)
- } else {
- values[it.id] = it.value
- }
- }
-
- return values
- }
-
- private fun getUserOrGroup(userGroupInfo: UserGroupDetails?): Map {
+ fun getUserOrGroup(userGroupInfo: UserGroupDetails?): Map {
return if (userGroupInfo?.isGroup == true) {
mapOf(
"id" to userGroupInfo.id,
@@ -427,6 +412,17 @@ class TaskRepository {
}
}
+ fun mapStringToOptionValues(fieldsData: FieldsData): Map {
+ val id = fieldsData.options.find { it.name == fieldsData.value }?.id
+
+ requireNotNull(id)
+
+ return mapOf(
+ "id" to id,
+ "name" to fieldsData.name,
+ )
+ }
+
/**
* saving the accountInfo data in preferences
*/
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 667e7e62a..960f8dfe2 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
@@ -88,6 +88,7 @@ enum class FieldType {
PEOPLE,
FUNCTIONAL_GROUP,
HYPERLINK,
+ UPLOAD,
;
fun value() = name.lowercase()
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 239643921..066fff2df 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -128,6 +128,7 @@ 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"
navigation-compose = "androidx.navigation:navigation-compose:2.7.6"
compose-runtime = "androidx.compose.runtime:runtime:1.5.4"
diff --git a/process-app/build.gradle b/process-app/build.gradle
index e376b6bf8..ea1707bfa 100644
--- a/process-app/build.gradle
+++ b/process-app/build.gradle
@@ -1,6 +1,7 @@
plugins {
id 'com.android.library'
id 'org.jetbrains.kotlin.android'
+ id 'kotlin-kapt'
}
android {
@@ -29,7 +30,8 @@ android {
}
buildFeatures {
- compose = true
+ compose true
+ viewBinding true
}
composeOptions {
@@ -48,8 +50,15 @@ dependencies {
implementation project(':common')
implementation project(':actions')
implementation project(':data')
+ implementation project(':listview')
implementation project(':component')
+ implementation project(':viewer-common')
+ implementation project(':viewer-image')
+ implementation project(':viewer-media')
+ implementation project(':viewer-pdf')
+ implementation project(':viewer-text')
implementation project(':viewer')
+ implementation project(':mimetype')
implementation libs.androidx.core
implementation libs.androidx.appcompat
@@ -59,6 +68,7 @@ dependencies {
implementation libs.ui
implementation libs.activity.compose
implementation libs.ui.graphics
+ implementation libs.ui.compose.viewbinding
implementation libs.ui.tooling.preview
implementation libs.material3
@@ -67,4 +77,10 @@ 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
+
}
diff --git a/process-app/src/main/AndroidManifest.xml b/process-app/src/main/AndroidManifest.xml
index 577f4d8ba..a1c319b56 100644
--- a/process-app/src/main/AndroidManifest.xml
+++ b/process-app/src/main/AndroidManifest.xml
@@ -3,7 +3,7 @@
diff --git a/process-app/src/main/kotlin/com/alfresco/content/process/FormViewModel.kt b/process-app/src/main/kotlin/com/alfresco/content/process/FormViewModel.kt
deleted file mode 100644
index 97ea1e1a9..000000000
--- a/process-app/src/main/kotlin/com/alfresco/content/process/FormViewModel.kt
+++ /dev/null
@@ -1,158 +0,0 @@
-package com.alfresco.content.process
-
-import android.content.Context
-import com.airbnb.mvrx.Async
-import com.airbnb.mvrx.Fail
-import com.airbnb.mvrx.Loading
-import com.airbnb.mvrx.MavericksState
-import com.airbnb.mvrx.MavericksViewModel
-import com.airbnb.mvrx.MavericksViewModelFactory
-import com.airbnb.mvrx.Success
-import com.airbnb.mvrx.Uninitialized
-import com.airbnb.mvrx.ViewModelContext
-import com.alfresco.content.data.OptionsModel
-import com.alfresco.content.data.ProcessEntry
-import com.alfresco.content.data.ResponseListForm
-import com.alfresco.content.data.ResponseListProcessDefinition
-import com.alfresco.content.data.TaskRepository
-import com.alfresco.content.data.UserGroupDetails
-import com.alfresco.content.data.payloads.FieldsData
-import com.alfresco.coroutines.asFlow
-import kotlinx.coroutines.launch
-
-data class FormViewState(
- val parent: ProcessEntry = ProcessEntry(),
- val requestStartForm: Async = Uninitialized,
- val requestProcessDefinition: Async = Uninitialized,
- val formFields: List = emptyList(),
- val processOutcomes: List = emptyList(),
- val enabledOutcomes: Boolean = false,
- val requestStartWorkflow: Async = Uninitialized,
-) : MavericksState {
- constructor(target: ProcessEntry) : this(parent = target)
-
- /**
- * update the single process definition entry
- */
- fun updateSingleProcessDefinition(response: ResponseListProcessDefinition): FormViewState {
- if (parent == null) {
- return this
- }
- val processEntry = ProcessEntry.with(response.listProcessDefinitions.first(), parent)
- return copy(parent = processEntry)
- }
-}
-
-class FormViewModel(
- val state: FormViewState,
- val context: Context,
- private val repository: TaskRepository,
-) : MavericksViewModel(state) {
-
- init {
- singleProcessDefinition(state.parent.id)
- }
-
- 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())
- getStartForm(updatedState.parent)
- copy(requestProcessDefinition = Success(it()))
- }
-
- else -> {
- this
- }
- }
- }
- }
- }
-
- private fun getStartForm(processEntry: ProcessEntry) {
- requireNotNull(processEntry.id)
- viewModelScope.launch {
- repository::startForm.asFlow(processEntry.id).execute {
- when (it) {
- is Loading -> copy(requestStartForm = Loading())
- is Fail -> {
- it.error.printStackTrace()
- copy(requestStartForm = Fail(it.error))
- }
-
- is Success -> {
- copy(
- parent = processEntry,
- formFields = it().fields.flatMap { listData -> listData.fields },
- processOutcomes = it().outcomes,
- requestStartForm = Success(it()),
- )
- }
-
- else -> {
- this
- }
- }
- }
- }
- }
-
- fun updateFieldValue(fieldId: String, newValue: Any?, state: FormViewState) {
- val updatedState = state.copy(
- formFields = state.formFields.map { field ->
- if (field.id == fieldId) {
- var updateValue = newValue
- when {
- (updateValue is String) && updateValue.isEmpty() -> {
- updateValue = null
- }
-
- (updateValue is Boolean) && !updateValue -> {
- updateValue = null
- }
-
- (updateValue is UserGroupDetails) && updateValue.id == 0 -> {
- updateValue = null
- }
- }
- field.copy(value = updateValue)
- } else {
- field
- }
- },
- )
-
- val hasAllRequiredData = hasFieldRequiredData(updatedState)
-
- setState { updatedState.copy(enabledOutcomes = hasAllRequiredData) }
- }
-
- fun startWorkflow() = withState { state ->
- viewModelScope.launch {
- repository::startWorkflow.asFlow(state.parent, "", state.formFields).execute {
- when (it) {
- is Loading -> copy(requestStartWorkflow = Loading())
- is Fail -> copy(requestStartWorkflow = Fail(it.error))
- is Success -> copy(requestStartWorkflow = Success(it()))
- else -> this
- }
- }
- }
- }
-
- private fun hasFieldRequiredData(state: FormViewState): Boolean {
- return !state.formFields.filter { it.required }.any { it.value == null }
- }
-
- companion object : MavericksViewModelFactory {
-
- override fun create(
- viewModelContext: ViewModelContext,
- state: FormViewState,
- ) = FormViewModel(state, viewModelContext.activity(), TaskRepository())
- }
-}
diff --git a/process-app/src/main/kotlin/com/alfresco/content/process/NavigationComponent.kt b/process-app/src/main/kotlin/com/alfresco/content/process/NavigationComponent.kt
deleted file mode 100644
index a1e59b333..000000000
--- a/process-app/src/main/kotlin/com/alfresco/content/process/NavigationComponent.kt
+++ /dev/null
@@ -1,25 +0,0 @@
-package com.alfresco.content.process
-
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.material3.Surface
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.navigation.compose.NavHost
-import androidx.navigation.compose.composable
-import androidx.navigation.compose.rememberNavController
-import com.alfresco.content.process.ui.components.FormScreen
-
-@Composable
-fun NavigationComponent() {
- val navController = rememberNavController()
-
- Surface(modifier = Modifier.fillMaxSize()) {
- NavHost(navController = navController, startDestination = "first_screen") {
- composable("first_screen") {
- // Replace with the content of your first fragment
- FormScreen(navController)
- }
- // Add more composable entries for other fragments in your navigation graph
- }
- }
-}
diff --git a/process-app/src/main/kotlin/com/alfresco/content/process/ProcessFormActivity.kt b/process-app/src/main/kotlin/com/alfresco/content/process/ProcessFormActivity.kt
deleted file mode 100644
index 622dfc8ac..000000000
--- a/process-app/src/main/kotlin/com/alfresco/content/process/ProcessFormActivity.kt
+++ /dev/null
@@ -1,68 +0,0 @@
-package com.alfresco.content.process
-
-import android.os.Bundle
-import android.view.ViewGroup
-import androidx.activity.compose.setContent
-import androidx.appcompat.app.AppCompatActivity
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Surface
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.tooling.preview.Preview
-import androidx.fragment.app.FragmentContainerView
-import com.alfresco.content.process.ui.ProcessFormFragment
-import com.alfresco.content.process.ui.theme.AlfrescoBaseTheme
-
-class ProcessFormActivity : AppCompatActivity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContent {
- AlfrescoBaseTheme {
- Surface(
- modifier = Modifier.fillMaxSize(),
- color = MaterialTheme.colorScheme.background,
- ) {
- composeApp()
- }
- }
- }
- }
-
- private fun composeApp() {
- val fragmentManager = supportFragmentManager
- val containerId = resources.getIdentifier("frame_container", "id", packageName)
-
- // Check if the fragment is already added
-
- // Create FragmentContainerView and add it to the activity
- val fragmentContainer = FragmentContainerView(this).apply {
- id = containerId
- layoutParams = ViewGroup.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.MATCH_PARENT,
- )
- }
- setContentView(fragmentContainer)
-
- fragmentManager
- .beginTransaction()
- .replace(
- fragmentContainer.id,
- ProcessFormFragment().apply {
- // Set your data using intent extras
- arguments = intent.extras
- },
- "firstFragment",
- )
- .commit()
- }
-}
-
-@Preview(showBackground = true)
-@Composable
-fun GreetingPreview() {
- AlfrescoBaseTheme {
- NavigationComponent()
- }
-}
diff --git a/process-app/src/main/kotlin/com/alfresco/content/process/ui/ProcessFormActivity.kt b/process-app/src/main/kotlin/com/alfresco/content/process/ui/ProcessFormActivity.kt
new file mode 100644
index 000000000..d0049972b
--- /dev/null
+++ b/process-app/src/main/kotlin/com/alfresco/content/process/ui/ProcessFormActivity.kt
@@ -0,0 +1,37 @@
+package com.alfresco.content.process.ui
+
+import android.os.Bundle
+import androidx.activity.compose.setContent
+import androidx.appcompat.app.AppCompatActivity
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import com.alfresco.content.process.ui.composeviews.NavigationComponent
+import com.alfresco.content.process.ui.theme.AlfrescoBaseTheme
+
+class ProcessFormActivity : AppCompatActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContent {
+ AlfrescoBaseTheme {
+ Surface(
+ modifier = Modifier.fillMaxSize(),
+ color = MaterialTheme.colorScheme.background,
+ ) {
+ NavigationComponent()
+ }
+ }
+ }
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+fun GreetingPreview() {
+ AlfrescoBaseTheme {
+ NavigationComponent()
+ }
+}
diff --git a/process-app/src/main/kotlin/com/alfresco/content/process/ui/ProcessFormFragment.kt b/process-app/src/main/kotlin/com/alfresco/content/process/ui/ProcessFormFragment.kt
deleted file mode 100644
index a048b15f7..000000000
--- a/process-app/src/main/kotlin/com/alfresco/content/process/ui/ProcessFormFragment.kt
+++ /dev/null
@@ -1,36 +0,0 @@
-package com.alfresco.content.process.ui
-
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.ArrowBack
-import androidx.compose.material3.Icon
-import androidx.compose.material3.IconButton
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.platform.ComposeView
-import androidx.fragment.app.Fragment
-import com.alfresco.content.process.NavigationComponent
-import com.alfresco.content.process.ui.theme.AlfrescoBaseTheme
-
-class ProcessFormFragment : Fragment() {
-
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
- return ComposeView(requireContext()).apply {
- layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
- setContent {
- AlfrescoBaseTheme {
- NavigationComponent()
- }
- }
- }
- }
-}
-
-@Composable
-fun BackButton(onClick: () -> Unit) {
- IconButton(onClick = onClick) {
- Icon(imageVector = Icons.Default.ArrowBack, contentDescription = "Back")
- }
-}
diff --git a/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/AttachFilesField.kt b/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/AttachFilesField.kt
new file mode 100644
index 000000000..26285c200
--- /dev/null
+++ b/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/AttachFilesField.kt
@@ -0,0 +1,102 @@
+package com.alfresco.content.process.ui.components
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Attachment
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.SpanStyle
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.buildAnnotatedString
+import androidx.compose.ui.text.withStyle
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.navigation.NavController
+import com.alfresco.content.data.Entry
+import com.alfresco.content.data.payloads.FieldsData
+import com.alfresco.content.process.R
+import com.alfresco.content.process.ui.composeviews.NavigationScreen
+import com.alfresco.content.process.ui.theme.AlfrescoBlue300
+import com.alfresco.content.process.ui.theme.AlfrescoError
+
+@Composable
+fun AttachFilesField(
+ contents: List = emptyList(),
+ fieldsData: FieldsData = FieldsData(),
+ navController: NavController,
+) {
+ val labelWithAsterisk = buildAnnotatedString {
+ append(fieldsData.name)
+ if (fieldsData.required) {
+ withStyle(style = SpanStyle(color = AlfrescoError)) {
+ append(" *") // Adding a red asterisk for mandatory fields
+ }
+ }
+ }
+
+ val contentValue = if (contents.isEmpty()) {
+ stringResource(id = R.string.no_attachments)
+ } else {
+ stringResource(id = R.string.text_multiple_attachment, contents.size)
+ }
+
+ val context = LocalContext.current
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(top = 16.dp, bottom = 0.dp, start = 16.dp, end = 16.dp),
+ ) {
+ Row(
+ horizontalArrangement = Arrangement.SpaceBetween,
+ modifier = Modifier.fillMaxWidth(),
+ ) {
+ Text(
+ text = labelWithAsterisk,
+ modifier = Modifier
+ .padding(end = 4.dp)
+ .align(alignment = Alignment.CenterVertically),
+ )
+
+ IconButton(onClick = {
+ navController.navigate(
+ NavigationScreen.ATTACHED_FILES_SCREEN.value(),
+ )
+ }) {
+ Icon(
+ imageVector = Icons.Default.Attachment,
+ tint = AlfrescoBlue300,
+ contentDescription = "",
+ )
+ }
+ }
+ Text(
+ text = contentValue,
+ style = TextStyle(
+ color = MaterialTheme.colorScheme.onSurfaceVariant,
+ fontSize = 12.sp,
+ ),
+ modifier = Modifier
+ .padding(start = 4.dp, top = 0.dp)
+ .align(alignment = Alignment.Start),
+ )
+ }
+}
+
+@Preview
+@Composable
+fun AttachFilesFieldPreview() {
+// AttachFilesField()
+}
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
index 1ffc975d3..8a5da1fd5 100644
--- 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
@@ -9,7 +9,7 @@ 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.BackButton
+import com.alfresco.content.process.ui.composeviews.BackButton
import com.alfresco.content.process.ui.theme.SeparateColorGrayLT
@OptIn(ExperimentalMaterial3Api::class)
diff --git a/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/FloatingActionButton.kt b/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/FloatingActionButton.kt
index a88fb4521..1f12c2668 100644
--- a/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/FloatingActionButton.kt
+++ b/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/FloatingActionButton.kt
@@ -12,8 +12,8 @@ import androidx.compose.ui.res.stringResource
import com.alfresco.content.component.ComponentBuilder
import com.alfresco.content.component.ComponentData
import com.alfresco.content.data.OptionsModel
-import com.alfresco.content.process.FormViewModel
import com.alfresco.content.process.R
+import com.alfresco.content.process.ui.fragments.FormViewModel
@Composable
fun FloatingActionButton(outcomes: List, enabledOutcomes: Boolean, viewModel: FormViewModel) {
diff --git a/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/FormViewModelExtension.kt b/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/FormViewModelExtension.kt
index f3e9dd7b2..c3b8ddd66 100644
--- a/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/FormViewModelExtension.kt
+++ b/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/FormViewModelExtension.kt
@@ -1,7 +1,7 @@
package com.alfresco.content.process.ui.components
-import com.alfresco.content.process.FormViewModel
-import com.alfresco.content.process.ui.UpdateProcessData
+import com.alfresco.content.process.ui.fragments.FormViewModel
+import com.alfresco.content.process.ui.models.UpdateProcessData
import com.alfresco.events.EventBus
import kotlinx.coroutines.launch
diff --git a/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/Outcomes.kt b/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/Outcomes.kt
index df57c8d83..b17561ec0 100644
--- a/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/Outcomes.kt
+++ b/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/Outcomes.kt
@@ -10,9 +10,8 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
-import androidx.lifecycle.viewmodel.compose.viewModel
import com.alfresco.content.data.OptionsModel
-import com.alfresco.content.process.FormViewModel
+import com.alfresco.content.process.ui.fragments.FormViewModel
@Composable
fun Outcomes(outcomes: List, enabledOutcomes: Boolean, viewModel: FormViewModel) {
diff --git a/process-app/src/main/kotlin/com/alfresco/content/process/ui/FormDetailScreen.kt b/process-app/src/main/kotlin/com/alfresco/content/process/ui/composeviews/FormDetailScreen.kt
similarity index 84%
rename from process-app/src/main/kotlin/com/alfresco/content/process/ui/FormDetailScreen.kt
rename to process-app/src/main/kotlin/com/alfresco/content/process/ui/composeviews/FormDetailScreen.kt
index c389e255b..2b7f8619b 100644
--- a/process-app/src/main/kotlin/com/alfresco/content/process/ui/FormDetailScreen.kt
+++ b/process-app/src/main/kotlin/com/alfresco/content/process/ui/composeviews/FormDetailScreen.kt
@@ -1,4 +1,4 @@
-package com.alfresco.content.process.ui
+package com.alfresco.content.process.ui.composeviews
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
@@ -18,16 +18,17 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.tooling.preview.Preview
+import androidx.navigation.NavController
+import androidx.navigation.compose.rememberNavController
import com.alfresco.content.data.OptionsModel
import com.alfresco.content.data.TaskRepository
-import com.alfresco.content.process.FormViewModel
-import com.alfresco.content.process.FormViewState
-import com.alfresco.content.process.ui.components.FormScrollContent
import com.alfresco.content.process.ui.components.Outcomes
+import com.alfresco.content.process.ui.fragments.FormViewModel
+import com.alfresco.content.process.ui.fragments.FormViewState
@OptIn(ExperimentalComposeUiApi::class)
@Composable
-fun FormDetailScreen(state: FormViewState, viewModel: FormViewModel, outcomes: List) {
+fun FormDetailScreen(state: FormViewState, viewModel: FormViewModel, outcomes: List, navController: NavController) {
val keyboardController = LocalSoftwareKeyboardController.current
val focusManager = LocalFocusManager.current
@@ -41,7 +42,7 @@ fun FormDetailScreen(state: FormViewState, viewModel: FormViewModel, outcomes: L
.clickable {
// Hide the keyboard on click outside of input fields
// keyboardController?.hide()
-// focusManager.clearFocus()
+ focusManager.clearFocus()
},
) {
LazyColumn(
@@ -57,7 +58,7 @@ fun FormDetailScreen(state: FormViewState, viewModel: FormViewModel, outcomes: L
},
items = formList,
) { field ->
- FormScrollContent(field, viewModel, state)
+ FormScrollContent(field, viewModel, state, navController)
}
}
@@ -85,5 +86,6 @@ fun PreviewProcessDetailScreen() {
TaskRepository(),
),
emptyList(),
+ rememberNavController(),
)
}
diff --git a/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/FormScreen.kt b/process-app/src/main/kotlin/com/alfresco/content/process/ui/composeviews/FormScreen.kt
similarity index 90%
rename from process-app/src/main/kotlin/com/alfresco/content/process/ui/components/FormScreen.kt
rename to process-app/src/main/kotlin/com/alfresco/content/process/ui/composeviews/FormScreen.kt
index 28a72b104..e70223dd9 100644
--- a/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/FormScreen.kt
+++ b/process-app/src/main/kotlin/com/alfresco/content/process/ui/composeviews/FormScreen.kt
@@ -1,4 +1,4 @@
-package com.alfresco.content.process.ui.components
+package com.alfresco.content.process.ui.composeviews
import ComposeTopBar
import android.app.Activity
@@ -18,9 +18,11 @@ import com.airbnb.mvrx.Success
import com.airbnb.mvrx.compose.collectAsState
import com.airbnb.mvrx.compose.mavericksActivityViewModel
import com.alfresco.content.data.OptionsModel
-import com.alfresco.content.process.FormViewModel
import com.alfresco.content.process.R
-import com.alfresco.content.process.ui.FormDetailScreen
+import com.alfresco.content.process.ui.components.CustomLinearProgressIndicator
+import com.alfresco.content.process.ui.components.FloatingActionButton
+import com.alfresco.content.process.ui.components.updateProcessList
+import com.alfresco.content.process.ui.fragments.FormViewModel
@Composable
fun FormScreen(navController: NavController) {
@@ -63,7 +65,7 @@ fun FormScreen(navController: NavController) {
if (state.requestStartForm is Loading) {
CustomLinearProgressIndicator(padding)
}
- FormDetailScreen(state, viewModel, customOutcomes)
+ FormDetailScreen(state, viewModel, customOutcomes, navController)
}
}
}
@@ -86,7 +88,7 @@ fun FormScreen(navController: NavController) {
if (state.requestStartForm is Loading || state.requestStartWorkflow is Loading) {
CustomLinearProgressIndicator(padding)
}
- FormDetailScreen(state, viewModel, emptyList())
+ FormDetailScreen(state, viewModel, emptyList(), navController)
}
}
}
diff --git a/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/FormScrollContent.kt b/process-app/src/main/kotlin/com/alfresco/content/process/ui/composeviews/FormScrollContent.kt
similarity index 78%
rename from process-app/src/main/kotlin/com/alfresco/content/process/ui/components/FormScrollContent.kt
rename to process-app/src/main/kotlin/com/alfresco/content/process/ui/composeviews/FormScrollContent.kt
index e8bb74690..16db8f035 100644
--- a/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/FormScrollContent.kt
+++ b/process-app/src/main/kotlin/com/alfresco/content/process/ui/composeviews/FormScrollContent.kt
@@ -1,23 +1,33 @@
-package com.alfresco.content.process.ui.components
+package com.alfresco.content.process.ui.composeviews
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.ExperimentalComposeUiApi
import androidx.compose.ui.res.stringResource
+import androidx.navigation.NavController
import com.alfresco.content.data.ProcessEntry
import com.alfresco.content.data.UserGroupDetails
import com.alfresco.content.data.payloads.FieldType
import com.alfresco.content.data.payloads.FieldsData
-import com.alfresco.content.process.FormViewModel
-import com.alfresco.content.process.FormViewState
import com.alfresco.content.process.R
+import com.alfresco.content.process.ui.components.AmountInputField
+import com.alfresco.content.process.ui.components.AttachFilesField
+import com.alfresco.content.process.ui.components.CheckBoxField
+import com.alfresco.content.process.ui.components.DateTimeField
+import com.alfresco.content.process.ui.components.DropdownField
+import com.alfresco.content.process.ui.components.HyperLinkField
+import com.alfresco.content.process.ui.components.IntegerInputField
+import com.alfresco.content.process.ui.components.MultiLineInputField
+import com.alfresco.content.process.ui.components.PeopleField
+import com.alfresco.content.process.ui.components.ReadOnlyField
+import com.alfresco.content.process.ui.components.SingleLineInputField
+import com.alfresco.content.process.ui.fragments.FormViewModel
+import com.alfresco.content.process.ui.fragments.FormViewState
-@OptIn(ExperimentalComposeUiApi::class)
@Composable
-fun FormScrollContent(field: FieldsData, viewModel: FormViewModel, state: FormViewState) {
+fun FormScrollContent(field: FieldsData, viewModel: FormViewModel, state: FormViewState, navController: NavController) {
when (field.type) {
FieldType.TEXT.value() -> {
var textFieldValue by remember { mutableStateOf(field.value as? String ?: "") }
@@ -93,8 +103,9 @@ fun FormScrollContent(field: FieldsData, viewModel: FormViewModel, state: FormVi
}
FieldType.DROPDOWN.value(), FieldType.RADIO_BUTTONS.value() -> {
- var textFieldValue by remember { mutableStateOf(field.placeHolder ?: "") }
- var textFieldQuery by remember { mutableStateOf("") }
+ var textFieldValue by remember { mutableStateOf(field.value as? String ?: "") }
+ var textFieldQuery by remember { mutableStateOf(field.options.find { it.name == textFieldValue }?.id ?: "") }
+
DropdownField(
nameText = textFieldValue,
queryText = textFieldQuery,
@@ -131,5 +142,13 @@ fun FormScrollContent(field: FieldsData, viewModel: FormViewModel, state: FormVi
FieldType.HYPERLINK.value() -> {
HyperLinkField(field)
}
+
+ FieldType.UPLOAD.value() -> {
+ AttachFilesField(
+ contents = state.listContents,
+ fieldsData = field,
+ navController = navController,
+ )
+ }
}
}
diff --git a/process-app/src/main/kotlin/com/alfresco/content/process/ui/composeviews/NavigationComponent.kt b/process-app/src/main/kotlin/com/alfresco/content/process/ui/composeviews/NavigationComponent.kt
new file mode 100644
index 000000000..66c70d778
--- /dev/null
+++ b/process-app/src/main/kotlin/com/alfresco/content/process/ui/composeviews/NavigationComponent.kt
@@ -0,0 +1,77 @@
+package com.alfresco.content.process.ui.composeviews
+
+import android.view.View
+import android.view.ViewGroup
+import androidx.appcompat.app.AppCompatActivity
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material3.Surface
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.viewinterop.AndroidViewBinding
+import androidx.navigation.NavHostController
+import androidx.navigation.compose.NavHost
+import androidx.navigation.compose.composable
+import androidx.navigation.compose.rememberNavController
+import com.alfresco.content.process.R
+import com.alfresco.content.process.databinding.FragmentContainerViewBinding
+import com.alfresco.content.process.ui.fragments.ProcessAttachedFilesFragment
+
+@Composable
+fun NavigationComponent() {
+ val navController = rememberNavController()
+
+ Surface(modifier = Modifier.fillMaxSize()) {
+ NavHost(navController = navController, startDestination = NavigationScreen.FIRST_SCREEN.value()) {
+ composable(NavigationScreen.FIRST_SCREEN.value()) {
+ // Replace with the content of your first fragment
+ FormScreen(navController)
+ }
+ // Add more composable entries for other fragments in your navigation graph
+ composable(NavigationScreen.ATTACHED_FILES_SCREEN.value()) {
+ // Replace with the content of ProcessAttachedFilesFragment
+ ProcessAttachedFilesScreen(navController)
+ }
+ }
+ }
+}
+
+@Composable
+fun ProcessAttachedFilesScreen(navController: NavHostController) {
+ val context = LocalContext.current
+ AndroidViewBinding(
+ FragmentContainerViewBinding::inflate,
+ ) {
+ // Adjust layout properties
+ fragmentContainerView.layoutParams = ViewGroup.MarginLayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ )
+
+ val contextApp = (context as? AppCompatActivity)
+
+ println(".ProcessAttachedFilesScreen $contextApp")
+
+ // Adjust system UI visibility
+ contextApp?.window?.decorView?.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
+ View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
+ View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+
+ fragmentContainerView.setPadding(
+ fragmentContainerView.paddingLeft,
+ context.resources.getDimensionPixelSize(R.dimen.default_status_bar_height),
+ fragmentContainerView.paddingRight,
+ context.resources.getDimensionPixelSize(R.dimen.default_bottom_controller_height),
+ )
+
+ fragmentContainerView.getFragment()
+ }
+}
+
+enum class NavigationScreen() {
+ FIRST_SCREEN,
+ ATTACHED_FILES_SCREEN,
+ ;
+
+ fun value() = this.name.lowercase()
+}
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
new file mode 100644
index 000000000..51b618076
--- /dev/null
+++ b/process-app/src/main/kotlin/com/alfresco/content/process/ui/composeviews/ProcessAttachedFiles.kt
@@ -0,0 +1,30 @@
+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.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.viewinterop.AndroidView
+import com.alfresco.content.process.R
+
+@Composable
+fun ProcessAttachedFiles() {
+ AndroidView(
+ modifier = Modifier.fillMaxSize(),
+ factory = { context ->
+ // Inflate your XML layout here
+ LayoutInflater.from(context).inflate(R.layout.fragment_attached_files, null)
+ },
+ )
+}
+
+@Composable
+fun BackButton(onClick: () -> Unit) {
+ IconButton(onClick = onClick) {
+ Icon(imageVector = Icons.Default.ArrowBack, contentDescription = "Back")
+ }
+}
diff --git a/process-app/src/main/kotlin/com/alfresco/content/process/ui/epoxy/ListViewAttachmentRow.kt b/process-app/src/main/kotlin/com/alfresco/content/process/ui/epoxy/ListViewAttachmentRow.kt
new file mode 100644
index 000000000..07e098b88
--- /dev/null
+++ b/process-app/src/main/kotlin/com/alfresco/content/process/ui/epoxy/ListViewAttachmentRow.kt
@@ -0,0 +1,107 @@
+package com.alfresco.content.process.ui.epoxy
+
+import android.content.Context
+import android.graphics.drawable.AnimatedVectorDrawable
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import android.view.View
+import android.widget.FrameLayout
+import androidx.core.content.res.ResourcesCompat
+import androidx.core.view.isVisible
+import com.airbnb.epoxy.CallbackProp
+import com.airbnb.epoxy.ModelProp
+import com.airbnb.epoxy.ModelView
+import com.alfresco.content.data.Entry
+import com.alfresco.content.data.OfflineStatus
+import com.alfresco.content.listview.R
+import com.alfresco.content.mimetype.MimeType
+import com.alfresco.content.process.databinding.ViewListAttachmentRowBinding
+
+/**
+ * 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) {
+
+ 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))
+
+ configureOfflineStatus(data)
+
+ 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()
+ }
+ 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
+ }
+
+ OfflineStatus.SYNCING ->
+ R.drawable.ic_offline_status_in_progress_anim
+
+ OfflineStatus.SYNCED ->
+ R.drawable.ic_offline_status_synced
+
+ OfflineStatus.ERROR ->
+ R.drawable.ic_offline_status_error
+
+ 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
+
+ /**
+ * list row click listener
+ */
+ @CallbackProp
+ fun setClickListener(listener: OnClickListener?) {
+ setOnClickListener(listener)
+ }
+
+ /**
+ * delete icon click listener
+ */
+ @CallbackProp
+ fun setDeleteContentClickListener(listener: OnClickListener?) {
+ binding.deleteContentButton.setOnClickListener(listener)
+ }
+}
diff --git a/process-app/src/main/kotlin/com/alfresco/content/process/ui/fragments/BaseDetailFragment.kt b/process-app/src/main/kotlin/com/alfresco/content/process/ui/fragments/BaseDetailFragment.kt
new file mode 100644
index 000000000..1905df134
--- /dev/null
+++ b/process-app/src/main/kotlin/com/alfresco/content/process/ui/fragments/BaseDetailFragment.kt
@@ -0,0 +1,88 @@
+package com.alfresco.content.process.ui.fragments
+
+import android.content.Intent
+import android.os.Bundle
+import android.view.View
+import androidx.appcompat.app.AlertDialog
+import androidx.fragment.app.Fragment
+import com.alfresco.content.REMOTE
+import com.alfresco.content.actions.CreateActionsSheet
+import com.alfresco.content.data.AnalyticsManager
+import com.alfresco.content.data.Entry
+import com.alfresco.content.data.EventName
+import com.alfresco.content.viewer.ViewerActivity
+import com.google.android.material.snackbar.Snackbar
+import java.lang.ref.WeakReference
+
+/**
+ * Marked as BaseDetailFragment class
+ */
+abstract class BaseDetailFragment : Fragment(), DeleteContentListener {
+
+ private var deleteContentDialog = WeakReference(null)
+ lateinit var listener: DeleteContentListener
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ listener = this
+ }
+
+ internal fun showCreateSheet(state: FormViewState, observerID: String) {
+ AnalyticsManager().taskEvent(EventName.UploadProcessAttachment)
+ CreateActionsSheet.with(Entry.defaultWorkflowEntry(observerID)).show(childFragmentManager, null)
+ }
+
+ /**
+ * return the stable id of uploading contents
+ */
+ fun stableId(entry: Entry): String =
+ if (entry.isUpload) {
+ entry.boxId.toString()
+ } 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),
+ )
+
+ /**
+ * This intent will open the local file
+ */
+ fun localViewerIntent(contentEntry: Entry) {
+ val intent = Intent(
+ requireActivity(),
+ Class.forName("com.alfresco.content.browse.preview.LocalPreviewActivity"),
+ )
+ intent.putExtra(KEY_ENTRY_OBJ, contentEntry)
+ startActivity(intent)
+ }
+
+ /**
+ * showing Snackbar
+ */
+ fun showSnackar(snackView: View, message: String) = Snackbar.make(
+ snackView,
+ message,
+ Snackbar.LENGTH_SHORT,
+ ).show()
+
+ companion object {
+ const val KEY_ENTRY_OBJ = "entryObj"
+ }
+}
+
+/**
+ * Marked as DeleteContentListener interface
+ */
+interface DeleteContentListener {
+
+ /**
+ * It will get call on confirm delete.
+ */
+ fun onConfirmDelete(contentId: String)
+}
diff --git a/process-app/src/main/kotlin/com/alfresco/content/process/ui/fragments/FormViewModel.kt b/process-app/src/main/kotlin/com/alfresco/content/process/ui/fragments/FormViewModel.kt
new file mode 100644
index 000000000..b3f76bf75
--- /dev/null
+++ b/process-app/src/main/kotlin/com/alfresco/content/process/ui/fragments/FormViewModel.kt
@@ -0,0 +1,222 @@
+package com.alfresco.content.process.ui.fragments
+
+import android.content.Context
+import com.airbnb.mvrx.Fail
+import com.airbnb.mvrx.Loading
+import com.airbnb.mvrx.MavericksViewModel
+import com.airbnb.mvrx.MavericksViewModelFactory
+import com.airbnb.mvrx.Success
+import com.airbnb.mvrx.ViewModelContext
+import com.alfresco.content.DATE_FORMAT_4
+import com.alfresco.content.DATE_FORMAT_5
+import com.alfresco.content.common.EntryListener
+import com.alfresco.content.data.OfflineRepository
+import com.alfresco.content.data.OptionsModel
+import com.alfresco.content.data.ProcessEntry
+import com.alfresco.content.data.TaskRepository
+import com.alfresco.content.data.UploadServerType
+import com.alfresco.content.data.UserGroupDetails
+import com.alfresco.content.data.payloads.FieldType
+import com.alfresco.content.data.payloads.FieldsData
+import com.alfresco.content.getFormattedDate
+import com.alfresco.coroutines.asFlow
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+import java.util.UUID
+
+class FormViewModel(
+ val state: FormViewState,
+ val context: Context,
+ private val repository: TaskRepository,
+) : MavericksViewModel(state) {
+
+ private var observeUploadsJob: Job? = null
+ var entryListener: EntryListener? = null
+ var observerID: String = ""
+ private var isExecuted = false
+
+ init {
+ observerID = UUID.randomUUID().toString()
+ singleProcessDefinition(state.parent.id)
+ }
+
+ /**
+ * returns the current logged in APS user profile data
+ */
+ fun getAPSUser() = repository.getAPSUser()
+
+ /**
+ * delete content locally
+ */
+ fun deleteAttachment(contentId: String) = stateFlow.execute {
+ deleteUploads(contentId)
+ }
+
+ private fun observeUploads(state: FormViewState) {
+ requireNotNull(state.parent)
+
+ val repo = OfflineRepository()
+
+ // On refresh clean completed uploads
+ repo.removeCompletedUploads()
+
+ observeUploadsJob?.cancel()
+ observeUploadsJob = repo.observeUploads(observerID, UploadServerType.UPLOAD_TO_PROCESS)
+ .execute {
+ if (it is Success) {
+ updateUploads(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)
+ }
+ copy(requestProcessDefinition = Success(it()))
+ }
+
+ else -> {
+ this
+ }
+ }
+ }
+ }
+ }
+
+ private fun getStartForm(processEntry: ProcessEntry) {
+ requireNotNull(processEntry.id)
+ viewModelScope.launch {
+ repository::startForm.asFlow(processEntry.id).execute {
+ when (it) {
+ is Loading -> copy(requestStartForm = Loading())
+ is Fail -> {
+ it.error.printStackTrace()
+ copy(requestStartForm = Fail(it.error))
+ }
+
+ is Success -> {
+ val updatedState = copy(
+ parent = processEntry,
+ formFields = it().fields.flatMap { listData -> listData.fields },
+ processOutcomes = it().outcomes,
+ requestStartForm = Success(it()),
+ )
+ enableDisableActions(updatedState)
+ updatedState
+ }
+
+ else -> {
+ this
+ }
+ }
+ }
+ }
+ }
+
+ fun updateFieldValue(fieldId: String, newValue: Any?, state: FormViewState) {
+ val updatedFieldList = state.formFields.map { field ->
+ if (field.id == fieldId) {
+ var updatedValue = newValue
+ when {
+ (updatedValue is String) && updatedValue.isEmpty() -> {
+ updatedValue = null
+ }
+
+ (updatedValue is Boolean) && !updatedValue -> {
+ updatedValue = null
+ }
+
+ (updatedValue is UserGroupDetails) && updatedValue.id == 0 -> {
+ updatedValue = null
+ }
+
+ (updatedValue is OptionsModel) && updatedValue.id.isEmpty() -> {
+ updatedValue = null
+ }
+ }
+ field.copy(value = updatedValue)
+ } else {
+ field
+ }
+ }
+
+ val updatedState = state.copy(
+ formFields = updatedFieldList,
+ )
+
+ enableDisableActions(updatedState)
+ }
+
+ fun startWorkflow() = withState { state ->
+ viewModelScope.launch {
+ repository::startWorkflow.asFlow(state.parent, "", convertFieldsToValues(state.formFields)).execute {
+ when (it) {
+ is Loading -> copy(requestStartWorkflow = Loading())
+ is Fail -> copy(requestStartWorkflow = Fail(it.error))
+ is Success -> copy(requestStartWorkflow = Success(it()))
+ else -> this
+ }
+ }
+ }
+ }
+
+ private fun convertFieldsToValues(fields: List): Map {
+ val values = mutableMapOf()
+
+ fields.forEach {
+ when (it.type) {
+ FieldType.PEOPLE.value(), FieldType.FUNCTIONAL_GROUP.value() -> {
+ values[it.id] = repository.getUserOrGroup(it.value as UserGroupDetails)
+ }
+
+ FieldType.DATETIME.value(), FieldType.DATE.value() -> {
+ val convertedDate = (it.value as? String)?.getFormattedDate(DATE_FORMAT_4, DATE_FORMAT_5)
+ values[it.id] = convertedDate
+ }
+
+ FieldType.RADIO_BUTTONS.value(), FieldType.DROPDOWN.value() -> {
+ values[it.id] = repository.mapStringToOptionValues(it)
+ }
+
+ FieldType.UPLOAD.value() -> {
+ values[it.id] = state.listContents.joinToString(separator = ",") { content -> content.id }
+ }
+
+ else -> {
+ values[it.id] = it.value
+ }
+ }
+ }
+
+ return values
+ }
+
+ private fun enableDisableActions(state: FormViewState) {
+ val hasAllRequiredData = hasFieldRequiredData(state)
+
+ setState { state.copy(enabledOutcomes = hasAllRequiredData) }
+ }
+
+ private fun hasFieldRequiredData(state: FormViewState): Boolean {
+ return !state.formFields.filter { it.required }.any { it.value == null }
+ }
+
+ companion object : MavericksViewModelFactory {
+
+ override fun create(
+ viewModelContext: ViewModelContext,
+ state: FormViewState,
+ ) = FormViewModel(state, viewModelContext.activity(), TaskRepository())
+ }
+}
diff --git a/process-app/src/main/kotlin/com/alfresco/content/process/ui/fragments/FormViewState.kt b/process-app/src/main/kotlin/com/alfresco/content/process/ui/fragments/FormViewState.kt
new file mode 100644
index 000000000..77c64e8b4
--- /dev/null
+++ b/process-app/src/main/kotlin/com/alfresco/content/process/ui/fragments/FormViewState.kt
@@ -0,0 +1,93 @@
+package com.alfresco.content.process.ui.fragments
+
+import com.airbnb.mvrx.Async
+import com.airbnb.mvrx.MavericksState
+import com.airbnb.mvrx.Uninitialized
+import com.alfresco.content.data.Entry
+import com.alfresco.content.data.OfflineStatus
+import com.alfresco.content.data.OptionsModel
+import com.alfresco.content.data.ProcessEntry
+import com.alfresco.content.data.ResponseListForm
+import com.alfresco.content.data.ResponseListProcessDefinition
+import com.alfresco.content.data.payloads.FieldsData
+
+data class FormViewState(
+ val parent: ProcessEntry = ProcessEntry(),
+ val requestStartForm: Async = Uninitialized,
+ val requestProcessDefinition: Async = Uninitialized,
+ val formFields: List = emptyList(),
+ val processOutcomes: List = emptyList(),
+ val enabledOutcomes: Boolean = false,
+ val requestStartWorkflow: Async = Uninitialized,
+ val listContents: List = emptyList(),
+ val baseEntries: List = emptyList(),
+ val uploads: List = emptyList(),
+) : MavericksState {
+ constructor(target: ProcessEntry) : this(parent = target)
+
+ /**
+ * update the single process definition entry
+ */
+ fun updateSingleProcessDefinition(response: ResponseListProcessDefinition): FormViewState {
+ if (parent == null) {
+ return this
+ }
+ val processEntry = ProcessEntry.with(response.listProcessDefinitions.first(), parent)
+ return copy(parent = processEntry)
+ }
+
+ /**
+ * delete content locally and update UI
+ */
+ fun deleteUploads(contentId: String): FormViewState {
+ val listBaseEntries = baseEntries.filter { it.id != contentId }
+ val listUploads = uploads.filter { it.id != contentId }
+ return copyIncludingUploads(listBaseEntries, listUploads)
+ }
+
+ /**
+ * updating the uploads entries with the server entries.
+ */
+ fun updateUploads(uploads: List): FormViewState {
+ // Merge data only after at least the first page loaded
+ // [parent] is a good enough flag for the initial load.
+ return if (parent != null) {
+ copyIncludingUploads(baseEntries, uploads)
+ } else {
+ copy(uploads = uploads)
+ }
+ }
+
+ private fun copyIncludingUploads(
+ entries: List,
+ uploads: List,
+ ): FormViewState {
+ val mixedUploads = uploads.transformCompletedUploads()
+ val mergedEntries = mergeInUploads(entries, mixedUploads)
+ val baseEntries = mergedEntries.filter { !it.isUpload }
+
+ return copy(
+ listContents = mergedEntries,
+ baseEntries = baseEntries,
+ uploads = uploads,
+ )
+ }
+
+ private fun mergeInUploads(base: List, uploads: List): List {
+ return (uploads + base).distinctBy { it.id.ifEmpty { it.boxId } }
+ }
+
+ /*
+ * Transforms completed uploads into network items, so further interaction with them
+ * doesn't require special logic.
+ */
+ private fun List.transformCompletedUploads(): List =
+ map {
+ if (it.isUpload && it.isSynced) {
+ // Marking as partial avoids needing to store allowableOperations
+ it.copy(isUpload = false, offlineStatus = OfflineStatus.UNDEFINED, isPartial = true)
+ } else {
+ it
+ }
+ }
+}
diff --git a/process-app/src/main/kotlin/com/alfresco/content/process/ui/fragments/ProcessAttachedFilesFragment.kt b/process-app/src/main/kotlin/com/alfresco/content/process/ui/fragments/ProcessAttachedFilesFragment.kt
new file mode 100644
index 000000000..33d2dfcaa
--- /dev/null
+++ b/process-app/src/main/kotlin/com/alfresco/content/process/ui/fragments/ProcessAttachedFilesFragment.kt
@@ -0,0 +1,133 @@
+package com.alfresco.content.process.ui.fragments
+
+import android.os.Bundle
+import android.os.Handler
+import android.os.Looper
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.core.view.isVisible
+import androidx.recyclerview.widget.RecyclerView
+import com.airbnb.epoxy.AsyncEpoxyController
+import com.airbnb.mvrx.MavericksView
+import com.airbnb.mvrx.activityViewModel
+import com.airbnb.mvrx.withState
+import com.alfresco.content.common.EntryListener
+import com.alfresco.content.data.AnalyticsManager
+import com.alfresco.content.data.Entry
+import com.alfresco.content.data.PageView
+import com.alfresco.content.data.ParentEntry
+import com.alfresco.content.data.UploadServerType
+import com.alfresco.content.data.payloads.FieldType
+import com.alfresco.content.mimetype.MimeType
+import com.alfresco.content.process.R
+import com.alfresco.content.process.databinding.FragmentAttachedFilesBinding
+import com.alfresco.content.process.ui.epoxy.listViewAttachmentRow
+import com.alfresco.content.simpleController
+import com.alfresco.ui.getDrawableForAttribute
+
+/**
+ * Marked as ProcessAttachedFilesFragment class
+ */
+class ProcessAttachedFilesFragment : BaseDetailFragment(), MavericksView, EntryListener {
+
+ val viewModel: FormViewModel by activityViewModel()
+ private lateinit var binding: FragmentAttachedFilesBinding
+ private val epoxyController: AsyncEpoxyController by lazy { epoxyController() }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?,
+ ): View {
+ binding = FragmentAttachedFilesBinding.inflate(inflater, container, false)
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ AnalyticsManager().screenViewEvent(PageView.AttachedFiles)
+ binding.refreshLayout.isEnabled = false
+ binding.toolbar.apply {
+ navigationContentDescription = getString(R.string.label_navigation_back)
+ navigationIcon = requireContext().getDrawableForAttribute(R.attr.homeAsUpIndicator)
+ setNavigationOnClickListener { requireActivity().onBackPressed() }
+ title = resources.getString(R.string.title_attached_files)
+ }
+
+ 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)
+ }
+ }
+ })
+ }
+
+ 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
+
+ val fields = state.formFields.find { it.type == FieldType.UPLOAD.value() }!!
+
+ handler.post {
+ if (state.listContents.isNotEmpty()) {
+ viewModel.updateFieldValue(fields.id, state.listContents, state)
+ 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)
+ }
+
+ epoxyController.requestModelBuild()
+ }
+
+ 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) }
+ }
+ }
+ }
+ }
+
+ 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,
+ )
+ remoteViewerIntent(entry)
+ }
+ } else {
+ localViewerIntent(contentEntry)
+ }
+ }
+
+ override fun onEntryCreated(entry: ParentEntry) {
+ if (isAdded) {
+ localViewerIntent(entry as Entry)
+ }
+ }
+}
diff --git a/process-app/src/main/kotlin/com/alfresco/content/process/ui/UpdateProcessData.kt b/process-app/src/main/kotlin/com/alfresco/content/process/ui/models/UpdateProcessData.kt
similarity index 68%
rename from process-app/src/main/kotlin/com/alfresco/content/process/ui/UpdateProcessData.kt
rename to process-app/src/main/kotlin/com/alfresco/content/process/ui/models/UpdateProcessData.kt
index c61068eb7..2cdd507f2 100644
--- a/process-app/src/main/kotlin/com/alfresco/content/process/ui/UpdateProcessData.kt
+++ b/process-app/src/main/kotlin/com/alfresco/content/process/ui/models/UpdateProcessData.kt
@@ -1,4 +1,4 @@
-package com.alfresco.content.process.ui
+package com.alfresco.content.process.ui.models
/**
* Mark as UpdateProcessData data class
diff --git a/process-app/src/main/res/layout/fragment_attached_files.xml b/process-app/src/main/res/layout/fragment_attached_files.xml
new file mode 100644
index 000000000..4597c4127
--- /dev/null
+++ b/process-app/src/main/res/layout/fragment_attached_files.xml
@@ -0,0 +1,80 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/process-app/src/main/res/layout/fragment_container_view.xml b/process-app/src/main/res/layout/fragment_container_view.xml
new file mode 100644
index 000000000..63cd22aa1
--- /dev/null
+++ b/process-app/src/main/res/layout/fragment_container_view.xml
@@ -0,0 +1,8 @@
+
+
+
+
diff --git a/process-app/src/main/res/layout/view_list_attachment_row.xml b/process-app/src/main/res/layout/view_list_attachment_row.xml
new file mode 100644
index 000000000..3d1cfaccd
--- /dev/null
+++ b/process-app/src/main/res/layout/view_list_attachment_row.xml
@@ -0,0 +1,78 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/process-app/src/main/res/values/dimens.xml b/process-app/src/main/res/values/dimens.xml
new file mode 100644
index 000000000..31013e734
--- /dev/null
+++ b/process-app/src/main/res/values/dimens.xml
@@ -0,0 +1,5 @@
+
+
+ 24dp
+ 56dp
+
diff --git a/process-app/src/main/res/values/strings.xml b/process-app/src/main/res/values/strings.xml
index 4f64f389d..38c7c3e99 100644
--- a/process-app/src/main/res/values/strings.xml
+++ b/process-app/src/main/res/values/strings.xml
@@ -9,4 +9,5 @@
This is a required field.
Actions
Process Actions Button
+ No Attachments