From 32d75ac458d170d1398b5feb8b83861fe1b17a31 Mon Sep 17 00:00:00 2001 From: Amanpal Singh <87360222+aman-alfresco@users.noreply.github.com> Date: Sat, 22 Apr 2023 07:55:21 +0530 Subject: [PATCH] MOBILEAPPS-1735 (#239) * uploaded attachment on the workflow * added view all attachment screen * codacy correction * added content delete feature * removed delete dialog and added process for offline files --- .../com/alfresco/content/actions/Action.kt | 16 ++- .../content/actions/ActionCaptureMedia.kt | 7 +- .../content/actions/ActionOpenWith.kt | 22 +-- .../content/actions/ActionUploadFiles.kt | 21 +-- .../content/actions/ActionUploadMedia.kt | 21 +-- .../actions/ContextualActionsViewModel.kt | 1 + .../content/actions/CreateActionsSheet.kt | 3 +- app/build.gradle | 1 - .../content/app/activity/MainActivity.kt | 1 - browse/src/main/assets/task.filters.json | 1 - .../content/browse/offline/OfflineFragment.kt | 7 + .../browse/preview/LocalPreviewFragment.kt | 11 +- .../ProcessAttachedFilesFragment.kt | 120 ++++++++++++++++ .../details/ProcessDetailExtension.kt | 27 ++-- .../details/ProcessDetailFragment.kt | 77 ++++++++-- .../details/ProcessDetailViewModel.kt | 53 ++++++- .../details/ProcessDetailViewState.kt | 67 ++++++++- .../sheet/ProcessDefinitionsSheet.kt | 2 +- .../sheet/ProcessDefinitionsViewModel.kt | 2 +- .../browse/tasks/BaseDetailFragment.kt | 6 + .../tasks/detail/TaskDetailViewModel.kt | 3 +- .../main/res/navigation/nav_task_paths.xml | 11 +- .../capture/CaptureModeSelectorView.kt | 1 - component/build.gradle | 2 - data/objectbox-models/default.json | 17 ++- data/objectbox-models/default.json.bak | 21 ++- .../content/data/AnalyticsRepository.kt | 1 + .../kotlin/com/alfresco/content/data/Entry.kt | 49 ++++++- .../content/data/OfflineRepository.kt | 22 +-- .../com/alfresco/content/data/ProcessEntry.kt | 8 +- .../alfresco/content/data/ResponseContents.kt | 2 +- .../alfresco/content/data/TaskRepository.kt | 28 ++-- .../com/alfresco/content/data/UploadWorker.kt | 5 +- search/src/main/res/values-es/strings.xml | 136 +++++++++--------- .../alfresco/content/viewer/ViewerFragment.kt | 1 - 35 files changed, 584 insertions(+), 189 deletions(-) create mode 100644 browse/src/main/kotlin/com/alfresco/content/browse/processes/attachments/ProcessAttachedFilesFragment.kt 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 f1cfb8c84..562bba173 100644 --- a/actions/src/main/kotlin/com/alfresco/content/actions/Action.kt +++ b/actions/src/main/kotlin/com/alfresco/content/actions/Action.kt @@ -10,6 +10,7 @@ import com.alfresco.content.data.AnalyticsManager import com.alfresco.content.data.Entry import com.alfresco.content.data.EventName import com.alfresco.content.data.ParentEntry +import com.alfresco.content.data.UploadServerType import com.alfresco.events.EventBus import com.alfresco.events.on import com.google.android.material.snackbar.Snackbar @@ -39,7 +40,9 @@ interface Action { bus.send(newAction) } catch (ex: CancellationException) { // no-op - if (entry is Entry && (entry as Entry).isProcessService && ex.message == ERROR_FILE_SIZE_EXCEED) { + if (entry is Entry && (entry as Entry).uploadServer == UploadServerType.UPLOAD_TO_TASK && + ex.message == ERROR_FILE_SIZE_EXCEED + ) { bus.send(Error(context.getString(R.string.error_file_size_exceed))) } } catch (ex: Exception) { @@ -66,6 +69,17 @@ interface Action { AnalyticsManager().apiTracker(APIEvent.NewFolder, status) } + /** + * returns the parent ID on the basis of uploading server + */ + fun getParentId(entry: Entry): String { + return when (entry.uploadServer) { + UploadServerType.DEFAULT -> entry.id + UploadServerType.UPLOAD_TO_TASK, UploadServerType.UPLOAD_TO_PROCESS -> entry.parentId ?: "" + else -> "" + } + } + fun showToast(view: View, anchorView: View? = null) {} fun maxFileNameInToast(view: View) = 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 a17075e62..b20190358 100644 --- a/actions/src/main/kotlin/com/alfresco/content/actions/ActionCaptureMedia.kt +++ b/actions/src/main/kotlin/com/alfresco/content/actions/ActionCaptureMedia.kt @@ -8,6 +8,7 @@ import com.alfresco.content.data.Entry import com.alfresco.content.data.EventName import com.alfresco.content.data.OfflineRepository import com.alfresco.content.data.ParentEntry +import com.alfresco.content.data.UploadServerType import kotlin.coroutines.cancellation.CancellationException import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -16,7 +17,7 @@ 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.isProcessService) 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() @@ -35,11 +36,11 @@ data class ActionCaptureMedia( result.map { item -> repository.scheduleForUpload( item.uri.toString(), - if (entry.isProcessService) entry.parentId ?: "" else entry.id, + getParentId(entry), item.filename, item.description, item.mimeType, - entry.isProcessService + entry.uploadServer ) } repository.setTotalTransferSize(result.size) 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 ea34fcd1b..7f7fdcb5b 100644 --- a/actions/src/main/kotlin/com/alfresco/content/actions/ActionOpenWith.kt +++ b/actions/src/main/kotlin/com/alfresco/content/actions/ActionOpenWith.kt @@ -11,6 +11,7 @@ import com.alfresco.content.data.EventName import com.alfresco.content.data.OfflineRepository import com.alfresco.content.data.ParentEntry import com.alfresco.content.data.TaskRepository +import com.alfresco.content.data.UploadServerType import com.alfresco.content.mimetype.MimeType import com.alfresco.download.ContentDownloader import com.google.android.material.dialog.MaterialAlertDialogBuilder @@ -41,16 +42,19 @@ data class ActionOpenWith( fetchRemoteFile(context) } - return if (!entry.isProcessService) { - showFileChooserDialog(context, target) - entry - } else { - var path = target.path - if (hasChooser) { + return when (entry.uploadServer) { + UploadServerType.DEFAULT -> { showFileChooserDialog(context, target) - path = "" + entry + } + else -> { + var path = target.path + if (hasChooser) { + showFileChooserDialog(context, target) + path = "" + } + Entry.updateDownloadEntry(entry, path) } - Entry.updateDownloadEntry(entry, path) } } @@ -61,7 +65,7 @@ data class ActionOpenWith( val client: OkHttpClient? val output: File - if (entry.isProcessService) { + if (entry.uploadServer == UploadServerType.UPLOAD_TO_TASK) { uri = TaskRepository().contentUri(entry) client = TaskRepository().getHttpClient() output = TaskRepository().getContentDirectory(entry.fileName) 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 459406de4..d8990ffa0 100644 --- a/actions/src/main/kotlin/com/alfresco/content/actions/ActionUploadFiles.kt +++ b/actions/src/main/kotlin/com/alfresco/content/actions/ActionUploadFiles.kt @@ -10,6 +10,7 @@ import com.alfresco.content.data.Entry import com.alfresco.content.data.EventName import com.alfresco.content.data.OfflineRepository import com.alfresco.content.data.ParentEntry +import com.alfresco.content.data.UploadServerType import kotlinx.coroutines.CancellationException import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -21,7 +22,7 @@ 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.isProcessService) 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() @@ -29,20 +30,24 @@ data class ActionUploadFiles( override suspend fun execute(context: Context): Entry { val result = ContentPickerFragment.pickItems(context, MIME_TYPES) if (result.isNotEmpty()) { - if (entry.isProcessService) - result.forEach { - val fileLength = DocumentFile.fromSingleUri(context, it)?.length() ?: 0L - if (GetMultipleContents.isFileSizeExceed(fileLength)) { - throw CancellationException(ERROR_FILE_SIZE_EXCEED) + when (entry.uploadServer) { + UploadServerType.UPLOAD_TO_TASK, UploadServerType.UPLOAD_TO_PROCESS -> { + result.forEach { + val fileLength = DocumentFile.fromSingleUri(context, it)?.length() ?: 0L + if (GetMultipleContents.isFileSizeExceed(fileLength)) { + throw CancellationException(ERROR_FILE_SIZE_EXCEED) + } } } + else -> {} + } withContext(Dispatchers.IO) { result.map { repository.scheduleContentForUpload( context, it, - if (entry.isProcessService) entry.parentId ?: "" else entry.id, - isProcessService = entry.isProcessService + getParentId(entry), + uploadServerType = entry.uploadServer ) } repository.setTotalTransferSize(result.size) 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 1a7ec91bd..2c3558b7d 100644 --- a/actions/src/main/kotlin/com/alfresco/content/actions/ActionUploadMedia.kt +++ b/actions/src/main/kotlin/com/alfresco/content/actions/ActionUploadMedia.kt @@ -10,6 +10,7 @@ import com.alfresco.content.data.Entry import com.alfresco.content.data.EventName import com.alfresco.content.data.OfflineRepository import com.alfresco.content.data.ParentEntry +import com.alfresco.content.data.UploadServerType import kotlinx.coroutines.CancellationException import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -18,7 +19,7 @@ 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.isProcessService) 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() @@ -26,20 +27,24 @@ data class ActionUploadMedia( override suspend fun execute(context: Context): Entry { val result = ContentPickerFragment.pickItems(context, MIME_TYPES) if (result.isNotEmpty()) { - if (entry.isProcessService) - result.forEach { - val fileLength = DocumentFile.fromSingleUri(context, it)?.length() ?: 0L - if (GetMultipleContents.isFileSizeExceed(fileLength)) { - throw CancellationException(ERROR_FILE_SIZE_EXCEED) + when (entry.uploadServer) { + UploadServerType.UPLOAD_TO_TASK, UploadServerType.UPLOAD_TO_PROCESS -> { + result.forEach { + val fileLength = DocumentFile.fromSingleUri(context, it)?.length() ?: 0L + if (GetMultipleContents.isFileSizeExceed(fileLength)) { + throw CancellationException(ERROR_FILE_SIZE_EXCEED) + } } } + else -> {} + } withContext(Dispatchers.IO) { result.map { repository.scheduleContentForUpload( context, it, - if (entry.isProcessService) entry.parentId ?: "" else entry.id, - isProcessService = entry.isProcessService + getParentId(entry), + uploadServerType = entry.uploadServer ) } repository.setTotalTransferSize(result.size) 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 f6c82f1aa..64ff5009e 100644 --- a/actions/src/main/kotlin/com/alfresco/content/actions/ContextualActionsViewModel.kt +++ b/actions/src/main/kotlin/com/alfresco/content/actions/ContextualActionsViewModel.kt @@ -108,6 +108,7 @@ internal class ContextualActionsViewModel( private fun actionsForOffline(entry: Entry): List = listOf( externalActionsFor(entry), + if (Settings(context).isProcessEnabled && entry.isFile) actionsProcesses(entry) else listOf(), offlineActionFor(entry) ).flatten() 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 4a27b4d01..0b0389eda 100644 --- a/actions/src/main/kotlin/com/alfresco/content/actions/CreateActionsSheet.kt +++ b/actions/src/main/kotlin/com/alfresco/content/actions/CreateActionsSheet.kt @@ -17,6 +17,7 @@ import com.airbnb.mvrx.withState import com.alfresco.content.actions.databinding.SheetActionCreateBinding import com.alfresco.content.data.AnalyticsManager import com.alfresco.content.data.Entry +import com.alfresco.content.data.UploadServerType import com.alfresco.ui.BottomSheetDialogFragment import kotlinx.coroutines.GlobalScope @@ -50,7 +51,7 @@ internal class ActionCreateViewModel( private fun makeActions(parent: Entry): List { val actions = mutableListOf() - if (!parent.isProcessService) + if (parent.uploadServer == UploadServerType.DEFAULT) actions.add(ActionCreateFolder(parent)) actions.add(ActionCaptureMedia(parent)) actions.add(ActionUploadMedia(parent)) diff --git a/app/build.gradle b/app/build.gradle index bb7e13e1c..45b0bfcce 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -124,7 +124,6 @@ dependencies { testImplementation libs.junit androidTestImplementation libs.androidx.test.core androidTestImplementation libs.androidx.test.espresso.core -// implementation("com.airbnb.android:mavericks-mocking:3.0.1") } static Object envOrDef(String varName, Object defaultValue) { 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 e5685abef..0be9d15fe 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 @@ -66,7 +66,6 @@ class MainActivity : AppCompatActivity(), MavericksView { } viewModel.isProcessEnabled = { - println("MainActivity.onCreate APS Enabled $it") val sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this) val editor = sharedPrefs.edit() editor.putBoolean(IS_PROCESS_ENABLED_KEY, it) diff --git a/browse/src/main/assets/task.filters.json b/browse/src/main/assets/task.filters.json index 8464f88ff..18673a0e1 100644 --- a/browse/src/main/assets/task.filters.json +++ b/browse/src/main/assets/task.filters.json @@ -30,7 +30,6 @@ { "label": "filter.option.active", "query": "active", - "default": true, "value": "state" }, { 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 f64ff622c..6cc91b94f 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 @@ -12,7 +12,9 @@ import com.airbnb.mvrx.InternalMavericksApi import com.airbnb.mvrx.withState import com.alfresco.content.actions.ActionSyncNow import com.alfresco.content.browse.R +import com.alfresco.content.browse.processes.sheet.ProcessDefinitionsSheet import com.alfresco.content.data.Entry +import com.alfresco.content.data.ParentEntry import com.alfresco.content.fragmentViewModelWithArgs import com.alfresco.content.listview.ListFragment import com.alfresco.content.navigateTo @@ -99,4 +101,9 @@ class OfflineFragment : ListFragment() { findNavController().navigateTo(entry) } } + + override fun onProcessStart(entry: ParentEntry) { + if (isAdded && isVisible) + ProcessDefinitionsSheet.with(entry as Entry).show(parentFragmentManager, null) + } } 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 5b403ddac..1d5b22f9b 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 @@ -19,6 +19,7 @@ import com.airbnb.mvrx.withState import com.alfresco.content.browse.R import com.alfresco.content.browse.databinding.FragmentLocalPreviewBinding import com.alfresco.content.data.Entry +import com.alfresco.content.data.UploadServerType import com.alfresco.content.fragmentViewModelWithArgs import com.alfresco.content.mimetype.MimeType import com.alfresco.content.viewer.common.ChildViewerArgs @@ -98,8 +99,12 @@ class LocalPreviewFragment : Fragment(), MavericksView { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { withState(viewModel) { state -> - if (state.entry?.isProcessService == true) - inflater.inflate(R.menu.menu_preview, menu) + when (state.entry?.uploadServer) { + UploadServerType.UPLOAD_TO_TASK -> { + inflater.inflate(R.menu.menu_preview, menu) + } + else -> {} + } } } @@ -121,8 +126,6 @@ class LocalPreviewFragment : Fragment(), MavericksView { ResourcesCompat.getDrawable(resources, type.icon, requireContext().theme) ) - println("mime type ${argsLocal.mimeType}") - val fragment = createViewer(argsLocal.mimeType) if (fragment != null) { binding.apply { 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 new file mode 100644 index 000000000..ad5d6f4b4 --- /dev/null +++ b/browse/src/main/kotlin/com/alfresco/content/browse/processes/attachments/ProcessAttachedFilesFragment.kt @@ -0,0 +1,120 @@ +package com.alfresco.content.browse.processes.attachments + +import android.content.Intent +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.browse.R +import com.alfresco.content.browse.databinding.FragmentAttachedFilesBinding +import com.alfresco.content.browse.preview.LocalPreviewActivity +import com.alfresco.content.browse.processes.details.ProcessDetailViewModel +import com.alfresco.content.browse.tasks.BaseDetailFragment +import com.alfresco.content.browse.tasks.attachments.listViewAttachmentRow +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.listview.EntryListener +import com.alfresco.content.simpleController +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() } + + 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.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 + 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) + } + + if (state.listContents.isEmpty()) requireActivity().onBackPressed() + + epoxyController.requestModelBuild() + } + + private fun epoxyController() = simpleController(viewModel) { state -> + + if (state.listContents.isNotEmpty()) { + state.listContents.forEach { obj -> + listViewAttachmentRow { + id(stableId(obj)) + data(obj) + clickListener { _, _, _, _ -> } + deleteContentClickListener { model, _, _, _ -> onConfirmDelete(model.data().id) } + } + } + } + } + + override fun onEntryCreated(entry: ParentEntry) { + if (isAdded) + startActivity( + Intent(requireActivity(), LocalPreviewActivity::class.java) + .putExtra(LocalPreviewActivity.KEY_ENTRY_OBJ, entry as Entry) + ) + } +} 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 d236a87f0..0d5ec0a5a 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 @@ -2,6 +2,7 @@ package com.alfresco.content.browse.processes.details import androidx.core.view.isVisible import androidx.lifecycle.lifecycleScope +import androidx.navigation.fragment.findNavController import com.airbnb.mvrx.withState import com.alfresco.content.DATE_FORMAT_1 import com.alfresco.content.DATE_FORMAT_4 @@ -37,7 +38,7 @@ internal fun ProcessDetailFragment.showStartFormView() { internal fun ProcessDetailFragment.setListeners() { binding.iconTitleEdit.setSafeOnClickListener { withState(viewModel) { state -> - viewModel.execute(ActionUpdateNameDescription(requireNotNull(state.entry))) + viewModel.execute(ActionUpdateNameDescription(requireNotNull(state.parent))) } } binding.tvTitle.setSafeOnClickListener { @@ -55,12 +56,12 @@ internal fun ProcessDetailFragment.setListeners() { } binding.iconPriorityEdit.setSafeOnClickListener { withState(viewModel) { state -> - val dataObj = state.entry + val dataObj = state.parent viewLifecycleOwner.lifecycleScope.launch { val result = showComponentSheetDialog( requireContext(), ComponentData( name = requireContext().getString(R.string.title_priority), - query = dataObj.priority.toString(), + query = dataObj?.priority.toString(), selector = ComponentType.TASK_PROCESS_PRIORITY.value ) ) @@ -73,10 +74,10 @@ internal fun ProcessDetailFragment.setListeners() { } binding.iconAssignedEdit.setSafeOnClickListener { withState(viewModel) { state -> - requireNotNull(state.entry) + requireNotNull(state.parent) viewLifecycleOwner.lifecycleScope.launch { val result = showSearchUserGroupComponentDialog( - requireContext(), state.entry + requireContext(), state.parent ) if (result != null) { viewModel.updateAssignee(result) @@ -84,6 +85,14 @@ internal fun ProcessDetailFragment.setListeners() { } } } + binding.tvAttachmentViewAll.setSafeOnClickListener { + findNavController().navigate(R.id.action_nav_process_details_to_nav_process_attached_files) + } + binding.clAddAttachment.setSafeOnClickListener { + withState(viewModel) { + showCreateSheet(it) + } + } } private fun ProcessDetailFragment.showCalendar(fromDate: String) { @@ -108,7 +117,7 @@ private fun ProcessDetailFragment.showCalendar(fromDate: String) { } internal fun ProcessDetailFragment.updateUI(state: ProcessDetailViewState) { - if (state.entry.formattedDueDate.isNullOrEmpty()) { + if (state.parent?.formattedDueDate.isNullOrEmpty()) { binding.iconDueDateClear.isVisible = false binding.iconDueDateEdit.isVisible = true } else { @@ -119,7 +128,7 @@ internal fun ProcessDetailFragment.updateUI(state: ProcessDetailViewState) { private fun ProcessDetailFragment.formatDateAndShowCalendar() { withState(viewModel) { state -> - val parseDate = state.entry.formattedDueDate?.parseDate(DATE_FORMAT_1) + val parseDate = state.parent?.formattedDueDate?.parseDate(DATE_FORMAT_1) showCalendar(parseDate?.formatDate(DATE_FORMAT_4, parseDate) ?: "") } } @@ -129,8 +138,8 @@ internal fun ProcessDetailFragment.showTitleDescriptionComponent() = withState(v showComponentSheetDialog( requireContext(), ComponentData( name = requireContext().getString(R.string.title_start_workflow), - query = it.entry.name, - value = it.entry.description, + query = it.parent?.name, + value = it.parent?.description, selector = ComponentType.VIEW_TEXT.value ) ) 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 a22b86172..e51151147 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 @@ -2,11 +2,13 @@ package com.alfresco.content.browse.processes.details import android.content.Context 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.fragment.app.Fragment +import com.airbnb.epoxy.AsyncEpoxyController import com.airbnb.mvrx.MavericksView import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.withState @@ -15,6 +17,8 @@ import com.alfresco.content.DATE_FORMAT_4 import com.alfresco.content.browse.R import com.alfresco.content.browse.databinding.FragmentTaskDetailBinding import com.alfresco.content.browse.processes.ProcessDetailActivity +import com.alfresco.content.browse.tasks.BaseDetailFragment +import com.alfresco.content.browse.tasks.attachments.listViewAttachmentRow import com.alfresco.content.common.updatePriorityView import com.alfresco.content.component.ComponentBuilder import com.alfresco.content.component.ComponentData @@ -26,6 +30,7 @@ import com.alfresco.content.data.ProcessEntry import com.alfresco.content.data.UserGroupDetails import com.alfresco.content.getFormattedDate import com.alfresco.content.getLocalizedName +import com.alfresco.content.simpleController import com.alfresco.ui.getDrawableForAttribute import kotlin.coroutines.Continuation import kotlin.coroutines.resume @@ -37,10 +42,11 @@ import kotlinx.coroutines.withContext /** * Marked as ProcessDetailFragment */ -class ProcessDetailFragment : Fragment(), MavericksView { +class ProcessDetailFragment : BaseDetailFragment(), MavericksView { lateinit var binding: FragmentTaskDetailBinding val viewModel: ProcessDetailViewModel by activityViewModel() + private val epoxyAttachmentController: AsyncEpoxyController by lazy { epoxyAttachmentController() } private var viewLayout: View? = null private val dispatcher: CoroutineDispatcher = Dispatchers.Main @@ -60,7 +66,7 @@ class ProcessDetailFragment : Fragment(), MavericksView { navigationContentDescription = getString(R.string.label_navigation_back) navigationIcon = requireContext().getDrawableForAttribute(R.attr.homeAsUpIndicator) setNavigationOnClickListener { - withState(viewModel) { state -> + withState(viewModel) { _ -> requireActivity().onBackPressed() } } @@ -69,30 +75,36 @@ class ProcessDetailFragment : Fragment(), MavericksView { showStartFormView() setListeners() + binding.recyclerViewAttachments.setController(epoxyAttachmentController) + } + + override fun onConfirmDelete(contentId: String) { + viewModel.deleteAttachment(contentId) } override fun invalidate() = withState(viewModel) { state -> binding.loading.isVisible = false setData(state) updateUI(state) + epoxyAttachmentController.requestModelBuild() } private fun setData(state: ProcessDetailViewState) { - val dataEntry = state.entry - binding.tvTitle.text = dataEntry.name - binding.tvDescription.text = dataEntry.description.ifEmpty { requireContext().getString(R.string.empty_description) } + val dataEntry = state.parent + binding.tvTitle.text = dataEntry?.name + binding.tvDescription.text = dataEntry?.description?.ifEmpty { requireContext().getString(R.string.empty_description) } 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.entry.priority) + binding.tvPriorityValue.updatePriorityView(state.parent?.priority ?: -1) binding.tvAssignedValue.apply { - text = if (dataEntry.startedBy?.groupName?.isEmpty() == true && viewModel.getAPSUser().id == dataEntry.startedBy?.id) { + text = if (dataEntry?.startedBy?.groupName?.isEmpty() == true && viewModel.getAPSUser().id == dataEntry.startedBy?.id) { requireContext().getLocalizedName(dataEntry.startedBy?.let { UserGroupDetails.with(it).name } ?: "") - } else if (dataEntry.startedBy?.groupName?.isNotEmpty() == true) + } else if (dataEntry?.startedBy?.groupName?.isNotEmpty() == true) requireContext().getLocalizedName(dataEntry.startedBy?.groupName ?: "") - else requireContext().getLocalizedName(dataEntry.startedBy?.name ?: "") + else requireContext().getLocalizedName(dataEntry?.startedBy?.name ?: "") } } @@ -136,4 +148,47 @@ class ProcessDetailFragment : Fragment(), MavericksView { .show() } } + + 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) + } + + state.listContents.take(4).forEach { obj -> + listViewAttachmentRow { + id(stableId(obj)) + data(obj) + clickListener { _, _, _, _ -> } + deleteContentClickListener { model, _, _, _ -> onConfirmDelete(model.data().id) } + } + } + } 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) + } + } + } } 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 2bb022d54..e9ff606d9 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 @@ -3,15 +3,20 @@ package com.alfresco.content.browse.processes.details import android.content.Context import com.airbnb.mvrx.MavericksViewModel import com.airbnb.mvrx.MavericksViewModelFactory +import com.airbnb.mvrx.Success import com.airbnb.mvrx.ViewModelContext import com.alfresco.content.actions.Action import com.alfresco.content.actions.ActionUpdateNameDescription import com.alfresco.content.component.ComponentMetaData +import com.alfresco.content.data.Entry +import com.alfresco.content.data.OfflineRepository 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.events.on import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.Job /** * Marked as ProcessDetailViewModel @@ -22,28 +27,35 @@ class ProcessDetailViewModel( val repository: TaskRepository ) : MavericksViewModel(state) { + private var observeUploadsJob: Job? = null + init { viewModelScope.on { - setState { copy(entry = it.entry as ProcessEntry) } + setState { copy(parent = it.entry as ProcessEntry) } } + updateDefaultEntry(state.parent?.defaultEntry) + observeUploads(state) } + /** * update the priority in the existing ProcessEntry obj and update the UI. */ fun updatePriority(result: ComponentMetaData) { setState { - requireNotNull(this.entry) - copy(entry = ProcessEntry.updatePriority(this.entry, result.query?.toInt() ?: 0)) + requireNotNull(this.parent) + copy(parent = ProcessEntry.updatePriority(this.parent, result.query?.toInt() ?: 0)) } } + private fun updateDefaultEntry(entry: Entry?) = setState { copy(baseEntries = if (entry != null) listOf(entry) else emptyList()) } + /** * update the formatted date in the existing ProcessEntry obj and update the UI. */ fun updateDate(formattedDate: String?, isClearDueDate: Boolean = false) { setState { - requireNotNull(this.entry) - copy(entry = ProcessEntry.updateDueDate(this.entry, formattedDate, isClearDueDate)) + requireNotNull(this.parent) + copy(parent = ProcessEntry.updateDueDate(this.parent, formattedDate, isClearDueDate)) } } @@ -52,8 +64,8 @@ class ProcessDetailViewModel( */ fun updateAssignee(result: UserGroupDetails) { setState { - requireNotNull(this.entry) - copy(entry = ProcessEntry.updateAssignee(this.entry, result)) + requireNotNull(this.parent) + copy(parent = ProcessEntry.updateAssignee(this.parent, result)) } } @@ -67,6 +79,33 @@ class ProcessDetailViewModel( */ fun getAPSUser() = repository.getAPSUser() + private fun observeUploads(state: ProcessDetailViewState) { + if (state.parent?.id == null) return + + val repo = OfflineRepository() + + // On refresh clean completed uploads + repo.removeCompletedUploads() + + observeUploadsJob?.cancel() + observeUploadsJob = repo.observeUploads(state.parent.id, UploadServerType.UPLOAD_TO_PROCESS) + .execute { + if (it is Success) { + println("observer list ${it().size}") + updateUploads(it()) + } else { + this + } + } + } + + /** + * delete content locally + */ + fun deleteAttachment(contentId: String) = stateFlow.execute { + deleteUploads(contentId) + } + companion object : MavericksViewModelFactory { override fun create( 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 2bc913c4c..014725348 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 @@ -1,11 +1,74 @@ package com.alfresco.content.browse.processes.details import com.airbnb.mvrx.MavericksState +import com.alfresco.content.data.Entry +import com.alfresco.content.data.OfflineStatus import com.alfresco.content.data.ProcessEntry /** * Marked as ProcessDetailViewState */ data class ProcessDetailViewState( - val entry: ProcessEntry -) : MavericksState + val parent: ProcessEntry?, + val listContents: List = emptyList(), + val baseEntries: List = emptyList(), + val uploads: List = emptyList() +) : MavericksState { + + constructor(target: ProcessEntry) : this(parent = target) + + /** + * delete content locally and update UI + */ + fun deleteUploads(contentId: String): ProcessDetailViewState { + 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): ProcessDetailViewState { + // 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 + ): ProcessDetailViewState { + 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/browse/src/main/kotlin/com/alfresco/content/browse/processes/sheet/ProcessDefinitionsSheet.kt b/browse/src/main/kotlin/com/alfresco/content/browse/processes/sheet/ProcessDefinitionsSheet.kt index 2aa706c67..f41a92fe1 100644 --- a/browse/src/main/kotlin/com/alfresco/content/browse/processes/sheet/ProcessDefinitionsSheet.kt +++ b/browse/src/main/kotlin/com/alfresco/content/browse/processes/sheet/ProcessDefinitionsSheet.kt @@ -71,7 +71,7 @@ class ProcessDefinitionsSheet : BottomSheetDialogFragment(), MavericksView { id(it.id) processDefinition(it) clickListener { model, _, _, _ -> - val processEntry = ProcessEntry.with(model.processDefinition()) + val processEntry = ProcessEntry.with(model.processDefinition(), state.entry) startActivity( Intent(requireActivity(), ProcessDetailActivity::class.java) .putExtra(Mavericks.KEY_ARG, processEntry) diff --git a/browse/src/main/kotlin/com/alfresco/content/browse/processes/sheet/ProcessDefinitionsViewModel.kt b/browse/src/main/kotlin/com/alfresco/content/browse/processes/sheet/ProcessDefinitionsViewModel.kt index d133dee43..173a292e8 100644 --- a/browse/src/main/kotlin/com/alfresco/content/browse/processes/sheet/ProcessDefinitionsViewModel.kt +++ b/browse/src/main/kotlin/com/alfresco/content/browse/processes/sheet/ProcessDefinitionsViewModel.kt @@ -30,7 +30,7 @@ internal class ProcessDefinitionsViewModel( when (it) { is Success -> { ProcessDefinitionsState( - entry = state.entry, + entry = it().second, listProcessDefinitions = it().first.listRuntimeProcessDefinitions ) } 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 2aa390497..51c748f6b 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 @@ -5,6 +5,7 @@ import androidx.appcompat.app.AlertDialog import androidx.fragment.app.Fragment import com.alfresco.content.actions.CreateActionsSheet import com.alfresco.content.browse.R +import com.alfresco.content.browse.processes.details.ProcessDetailViewState import com.alfresco.content.browse.tasks.detail.TaskDetailViewState import com.alfresco.content.data.AnalyticsManager import com.alfresco.content.data.Entry @@ -49,6 +50,11 @@ abstract class BaseDetailFragment : Fragment(), DeleteContentListener { CreateActionsSheet.with(Entry.defaultAPSEntry(state.parent?.id)).show(childFragmentManager, null) } + internal fun showCreateSheet(state: ProcessDetailViewState) { + AnalyticsManager().taskEvent(EventName.UploadProcessAttachment) + CreateActionsSheet.with(Entry.defaultWorkflowEntry(state.parent?.id)).show(childFragmentManager, null) + } + /** * return the stable id of uploading contents */ 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 e26f91e77..150bb3383 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 @@ -18,6 +18,7 @@ import com.alfresco.content.data.AnalyticsManager import com.alfresco.content.data.OfflineRepository import com.alfresco.content.data.TaskEntry import com.alfresco.content.data.TaskRepository +import com.alfresco.content.data.UploadServerType import com.alfresco.content.data.UserGroupDetails import com.alfresco.content.data.payloads.CommentPayload import com.alfresco.content.getFormattedDate @@ -144,7 +145,7 @@ class TaskDetailViewModel( repo.removeCompletedUploads(taskId) observeUploadsJob?.cancel() - observeUploadsJob = repo.observeUploads(taskId, true) + observeUploadsJob = repo.observeUploads(taskId, UploadServerType.UPLOAD_TO_TASK) .execute { if (it is Success) { updateUploads(it()) diff --git a/browse/src/main/res/navigation/nav_task_paths.xml b/browse/src/main/res/navigation/nav_task_paths.xml index 554e7b3de..e707e3003 100644 --- a/browse/src/main/res/navigation/nav_task_paths.xml +++ b/browse/src/main/res/navigation/nav_task_paths.xml @@ -28,9 +28,18 @@ android:name="com.alfresco.content.browse.tasks.attachments.AttachedFilesFragment" tools:layout="@layout/fragment_attached_files" /> + + + tools:layout="@layout/fragment_task_detail"> + + diff --git a/capture/src/main/kotlin/com/alfresco/capture/CaptureModeSelectorView.kt b/capture/src/main/kotlin/com/alfresco/capture/CaptureModeSelectorView.kt index 1280a874b..0432e0bae 100644 --- a/capture/src/main/kotlin/com/alfresco/capture/CaptureModeSelectorView.kt +++ b/capture/src/main/kotlin/com/alfresco/capture/CaptureModeSelectorView.kt @@ -92,7 +92,6 @@ class CaptureModeSelectorView( } fun updateActive(position: Int) { - println("re position $position") setActive(position) recyclerView.smoothScrollToCenteredPosition(position) } diff --git a/component/build.gradle b/component/build.gradle index 7ee31da91..63d267d15 100644 --- a/component/build.gradle +++ b/component/build.gradle @@ -49,8 +49,6 @@ dependencies { testImplementation("com.airbnb.android:mavericks-testing:3.0.0") -// implementation("com.airbnb.android:mavericks-mocking:3.0.1") - testImplementation("org.mockito.kotlin:mockito-kotlin:4.1.0") // Optional -- Mockito framework diff --git a/data/objectbox-models/default.json b/data/objectbox-models/default.json index c97e9cad0..0ee140335 100644 --- a/data/objectbox-models/default.json +++ b/data/objectbox-models/default.json @@ -5,7 +5,7 @@ "entities": [ { "id": "1:3882697123829827748", - "lastPropertyId": "26:4080731032307588210", + "lastPropertyId": "28:949912739494302928", "name": "Entry", "properties": [ { @@ -84,11 +84,6 @@ "name": "isTotalEntry", "type": 1 }, - { - "id": "16:84855644780396979", - "name": "isProcessService", - "type": 1 - }, { "id": "17:126023913419070141", "name": "isDocFile", @@ -138,6 +133,11 @@ "id": "26:4080731032307588210", "name": "sourceId", "type": 9 + }, + { + "id": "27:4514687495334138822", + "name": "uploadServer", + "type": 9 } ], "relations": [] @@ -151,7 +151,10 @@ "modelVersionParserMinimum": 5, "retiredEntityUids": [], "retiredIndexUids": [], - "retiredPropertyUids": [], + "retiredPropertyUids": [ + 84855644780396979, + 949912739494302928 + ], "retiredRelationUids": [], "version": 1 } \ No newline at end of file diff --git a/data/objectbox-models/default.json.bak b/data/objectbox-models/default.json.bak index c97e9cad0..585e0eeec 100644 --- a/data/objectbox-models/default.json.bak +++ b/data/objectbox-models/default.json.bak @@ -5,7 +5,7 @@ "entities": [ { "id": "1:3882697123829827748", - "lastPropertyId": "26:4080731032307588210", + "lastPropertyId": "28:949912739494302928", "name": "Entry", "properties": [ { @@ -84,11 +84,6 @@ "name": "isTotalEntry", "type": 1 }, - { - "id": "16:84855644780396979", - "name": "isProcessService", - "type": 1 - }, { "id": "17:126023913419070141", "name": "isDocFile", @@ -138,6 +133,16 @@ "id": "26:4080731032307588210", "name": "sourceId", "type": 9 + }, + { + "id": "27:4514687495334138822", + "name": "uploadServer", + "type": 9 + }, + { + "id": "28:949912739494302928", + "name": "isProcessService", + "type": 1 } ], "relations": [] @@ -151,7 +156,9 @@ "modelVersionParserMinimum": 5, "retiredEntityUids": [], "retiredIndexUids": [], - "retiredPropertyUids": [], + "retiredPropertyUids": [ + 84855644780396979 + ], "retiredRelationUids": [], "version": 1 } \ No newline at end of file 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 e0802361a..f98429d4b 100644 --- a/data/src/main/kotlin/com/alfresco/content/data/AnalyticsRepository.kt +++ b/data/src/main/kotlin/com/alfresco/content/data/AnalyticsRepository.kt @@ -114,6 +114,7 @@ enum class EventName(val value: String) { TaskUploadFiles("event_task_upload_files"), CANCEL("event_cancel"), StartWorkflow("start_workflow"), + UploadProcessAttachment("event_upload_workflow_attachment"), None("event_none") } 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 01e77d562..337c42a69 100644 --- a/data/src/main/kotlin/com/alfresco/content/data/Entry.kt +++ b/data/src/main/kotlin/com/alfresco/content/data/Entry.kt @@ -71,7 +71,6 @@ data class Entry( val totalCount: Int = 0, val isTotalEntry: Boolean = false, val isDocFile: Boolean = false, - val isProcessService: Boolean = false, @Transient val parentPaths: MutableList = mutableListOf(), /*APS content data*/ @@ -86,7 +85,9 @@ data class Entry( val source: String? = "", val sourceId: String? = "", val previewStatus: String? = "", - val thumbnailStatus: String? = "" + val thumbnailStatus: String? = "", + @Convert(converter = BoxUploadServerTypeConverter::class, dbType = String::class) + val uploadServer: UploadServerType = UploadServerType.DEFAULT ) : ParentEntry(), Parcelable { val isSynced: Boolean @@ -380,7 +381,7 @@ data class Entry( id = contentEntry.id, name = contentEntry.name, mimeType = contentEntry.mimeType, - isProcessService = true, + uploadServer = UploadServerType.UPLOAD_TO_TASK, isDocFile = isDocFile ) } @@ -388,7 +389,7 @@ data class Entry( /** * return the ContentEntry obj after converting the data from ContentDataEntry obj */ - fun with(data: ContentDataEntry, parentId: String? = null): Entry { + fun with(data: ContentDataEntry, parentId: String? = null, uploadServer: UploadServerType): Entry { return Entry( id = data.id?.toString() ?: "", parentId = parentId, @@ -403,7 +404,7 @@ data class Entry( sourceId = data.sourceId, previewStatus = data.previewStatus, thumbnailStatus = data.thumbnailStatus, - isProcessService = true + uploadServer = uploadServer ) } @@ -411,14 +412,21 @@ data class Entry( * update entry after downloading content from process services. */ fun updateDownloadEntry(entry: Entry, path: String): Entry { - return Entry(id = entry.id, name = entry.name, mimeType = entry.mimeType, path = path, isProcessService = entry.isProcessService) + return Entry(id = entry.id, name = entry.name, mimeType = entry.mimeType, path = path, uploadServer = entry.uploadServer) } /** * return the default APS content entry obj */ fun defaultAPSEntry(taskId: String?): Entry { - return Entry(isProcessService = true, parentId = taskId) + return Entry(uploadServer = UploadServerType.UPLOAD_TO_TASK, parentId = taskId) + } + + /** + * return the default Workflow content entry obj + */ + fun defaultWorkflowEntry(id: String?): Entry { + return Entry(uploadServer = UploadServerType.UPLOAD_TO_PROCESS, parentId = id) } private fun PathInfo.formattedString(): String? { @@ -466,6 +474,18 @@ enum class OfflineStatus { fun value() = name.lowercase() } +/** + * Marked as UploadServerType enum + */ +enum class UploadServerType { + DEFAULT, + UPLOAD_TO_TASK, + UPLOAD_TO_PROCESS, + NONE; + + fun value() = name.lowercase() +} + class BoxOfflineStatusConverter : PropertyConverter { override fun convertToEntityProperty(databaseValue: String?) = try { @@ -490,6 +510,21 @@ class BoxEntryTypeConverter : PropertyConverter { entityProperty?.name?.lowercase() } +/** + * convert the type to string to save in local DB + */ +class BoxUploadServerTypeConverter : PropertyConverter { + override fun convertToEntityProperty(databaseValue: String?) = + try { + UploadServerType.valueOf(databaseValue?.uppercase() ?: "") + } catch (e: Exception) { + UploadServerType.NONE + } + + override fun convertToDatabaseValue(entityProperty: UploadServerType?) = + entityProperty?.value() +} + object DateParceler : Parceler { private val zone = ZonedDateTime.now().zone 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 6f615a471..24f70bbe7 100644 --- a/data/src/main/kotlin/com/alfresco/content/data/OfflineRepository.kt +++ b/data/src/main/kotlin/com/alfresco/content/data/OfflineRepository.kt @@ -57,7 +57,7 @@ class OfflineRepository(val session: Session = SessionManager.requireSession) { fun updateTransferSize(size: Int) { val list = box.query() .equal(Entry_.isTotalEntry, true) - .equal(Entry_.isProcessService, false) + .equal(Entry_.uploadServer, UploadServerType.DEFAULT.value(), StringOrder.CASE_SENSITIVE) .build() .find() if (list.isEmpty()) { @@ -76,7 +76,7 @@ class OfflineRepository(val session: Session = SessionManager.requireSession) { fun getTotalTransfersSize(): Int { val list = box.query() .equal(Entry_.isTotalEntry, true) - .equal(Entry_.isProcessService, false) + .equal(Entry_.uploadServer, UploadServerType.DEFAULT.value(), StringOrder.CASE_SENSITIVE) .build() .find() return if (list.isEmpty()) 0 else list[0].totalCount @@ -137,7 +137,7 @@ class OfflineRepository(val session: Session = SessionManager.requireSession) { box.query() .notEqual(Entry_.offlineStatus, OfflineStatus.UNDEFINED.value(), StringOrder.CASE_SENSITIVE) .equal(Entry_.isUpload, true) - .equal(Entry_.isProcessService, false) + .equal(Entry_.uploadServer, UploadServerType.DEFAULT.value(), StringOrder.CASE_SENSITIVE) .build() .find() @@ -164,7 +164,7 @@ class OfflineRepository(val session: Session = SessionManager.requireSession) { contentUri: Uri, parentId: String, isExtension: Boolean = false, - isProcessService: Boolean = false + uploadServerType: UploadServerType = UploadServerType.DEFAULT ) { val resolver = context.contentResolver var name: String? = null @@ -188,7 +188,7 @@ class OfflineRepository(val session: Session = SessionManager.requireSession) { isUpload = true, offlineStatus = OfflineStatus.PENDING, isExtension = isExtension, - isProcessService = isProcessService + uploadServer = uploadServerType ) clearData() @@ -217,7 +217,7 @@ class OfflineRepository(val session: Session = SessionManager.requireSession) { name: String, description: String, mimeType: String, - isProcessService: Boolean + uploadServerType: UploadServerType ) { // TODO: This process may fail resulting in an orphan file? or node? val entry = Entry( @@ -228,7 +228,7 @@ class OfflineRepository(val session: Session = SessionManager.requireSession) { properties = mapOf("cm:description" to description), isUpload = true, offlineStatus = OfflineStatus.PENDING, - isProcessService = isProcessService + uploadServer = uploadServerType ) clearData() @@ -249,11 +249,11 @@ class OfflineRepository(val session: Session = SessionManager.requireSession) { /** * returns the list of uploads which is being uploaded on the server. */ - fun observeUploads(parentId: String, isProcessService: Boolean = false): Flow> = callbackFlow { + 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_.isProcessService, isProcessService) + .equal(Entry_.uploadServer, uploadServerType.value(), StringOrder.CASE_SENSITIVE) .order(Entry_.name) .build() val subscription = query.subscribe().observer { @@ -268,7 +268,7 @@ class OfflineRepository(val session: Session = SessionManager.requireSession) { fun observeTransferUploads(): Flow> = callbackFlow { val query = box.query() .equal(Entry_.isUpload, true) - .equal(Entry_.isProcessService, false) + .equal(Entry_.uploadServer, UploadServerType.DEFAULT.value(), StringOrder.CASE_SENSITIVE) .equal(Entry_.id, "", StringOrder.CASE_SENSITIVE) .order(Entry_.name) .build() @@ -306,7 +306,7 @@ class OfflineRepository(val session: Session = SessionManager.requireSession) { */ fun removeTaskEntries(parentId: String? = null) = box.query() - .equal(Entry_.isProcessService, true) + .equal(Entry_.uploadServer, UploadServerType.UPLOAD_TO_TASK.value(), StringOrder.CASE_SENSITIVE) .apply { if (parentId != null) { equal(Entry_.parentId, parentId, StringOrder.CASE_SENSITIVE) 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 8acbd0600..97671f344 100644 --- a/data/src/main/kotlin/com/alfresco/content/data/ProcessEntry.kt +++ b/data/src/main/kotlin/com/alfresco/content/data/ProcessEntry.kt @@ -29,7 +29,8 @@ data class ProcessEntry( val startFormDefined: Boolean? = null, val suspended: Boolean? = null, var priority: Int = 0, - val formattedDueDate: String? = null + val formattedDueDate: String? = null, + val defaultEntry: Entry? = null ) : ParentEntry(), Parcelable { companion object { @@ -63,11 +64,12 @@ data class ProcessEntry( /** * return the ProcessEntry using RuntimeProcessDefinitionDataEntry */ - fun with(data: RuntimeProcessDefinitionDataEntry): ProcessEntry { + fun with(data: RuntimeProcessDefinitionDataEntry, entry: Entry): ProcessEntry { return ProcessEntry( id = data.id?.toString() ?: "", name = data.name ?: "", - description = data.description ?: "" + description = data.description ?: "", + defaultEntry = entry ) } 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 7aad68bc7..62cd52d83 100644 --- a/data/src/main/kotlin/com/alfresco/content/data/ResponseContents.kt +++ b/data/src/main/kotlin/com/alfresco/content/data/ResponseContents.kt @@ -21,7 +21,7 @@ data class ResponseContents( size = raw.size ?: 0, total = raw.total ?: 0, start = raw.start ?: 0, - listContents = raw.data?.map { Entry.with(it) } ?: emptyList() + listContents = raw.data?.map { Entry.with(it, uploadServer = UploadServerType.NONE) } ?: emptyList() ) } } 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 c422f4d22..eb28e3e16 100644 --- a/data/src/main/kotlin/com/alfresco/content/data/TaskRepository.kt +++ b/data/src/main/kotlin/com/alfresco/content/data/TaskRepository.kt @@ -261,10 +261,10 @@ class TaskRepository(val session: Session = SessionManager.requireSession) { /** * It will call the api to upload the raw content on process services. */ - suspend fun createEntry(local: Entry, file: File): Entry { + suspend fun createEntry(local: Entry, file: File, uploadServerType: UploadServerType): Entry { // TODO: Support creating empty entries and folders - requireNotNull(local.parentId) requireNotNull(local.mimeType) + requireNotNull(local.parentId) val filePart = file.asRequestBody(local.mimeType.toMediaTypeOrNull()) val properties = mutableMapOf() @@ -276,12 +276,22 @@ class TaskRepository(val session: Session = SessionManager.requireSession) { val multipartBody = MultipartBody.Part.createFormData("file", local.name, filePart) - return Entry.with( - tasksService.uploadRawContent( - local.parentId, - multipartBody - ), local.parentId - ) + return when (uploadServerType) { + UploadServerType.UPLOAD_TO_TASK -> { + Entry.with( + tasksService.uploadRawContent( + local.parentId, + multipartBody + ), local.parentId, uploadServerType + ) + } + UploadServerType.UPLOAD_TO_PROCESS -> Entry.with( + processesService.uploadRawContent( + multipartBody + ), local.parentId, uploadServer = uploadServerType + ) + else -> Entry() + } } /** @@ -296,7 +306,7 @@ class TaskRepository(val session: Session = SessionManager.requireSession) { Entry.with( processesService.linkContentToProcess( includeLinkContent(linkContentPayload) - ) + ), uploadServer = UploadServerType.NONE ) private fun includeLinkContent(payload: LinkContentPayload): RequestLinkContent { 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 b5e731b95..966ee04b8 100644 --- a/data/src/main/kotlin/com/alfresco/content/data/UploadWorker.kt +++ b/data/src/main/kotlin/com/alfresco/content/data/UploadWorker.kt @@ -31,11 +31,11 @@ class UploadWorker( return try { repository.update(entry.copy(offlineStatus = OfflineStatus.SYNCING)) AnalyticsManager().apiTracker( - if (entry.isProcessService) APIEvent.UploadTaskAttachment else APIEvent.UploadFiles, + if (entry.uploadServer == UploadServerType.UPLOAD_TO_TASK) APIEvent.UploadTaskAttachment else APIEvent.UploadFiles, status = true, size = "${file.length().div(1024).div(1024)} MB" ) - val res = if (entry.isProcessService) TaskRepository().createEntry(entry, file) else BrowseRepository().createEntry(entry, file) + 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) @@ -43,6 +43,7 @@ class UploadWorker( ) true } catch (ex: Exception) { + ex.printStackTrace() if ((ex as HttpException).response()?.code() == 404 && (ex as HttpException).response()?.code() == 413) { repository.remove(entry) file.delete() diff --git a/search/src/main/res/values-es/strings.xml b/search/src/main/res/values-es/strings.xml index 7b62f513d..6080b54af 100755 --- a/search/src/main/res/values-es/strings.xml +++ b/search/src/main/res/values-es/strings.xml @@ -1,69 +1,69 @@ - + - - Buscar - - Búsquedas recientes - - Buscando\u2026 - - en: %s - Ficheros - Carpetas - Bibliotecas - - ¡Vaya! - No encontramos el fichero\nque busca. - - Predeterminado - Ninguno - Todos - Carpeta - Documento - %1$s-%2$s-%3$s - %1$s... - %1$s (%2$d) - %1$s (%2$s) - - Tamaño de fichero - Fecha de creación - Tipo de fichero - Modificador - Creador - %1$s ... %2$s - %1$s + %2$d - - - Consultas con filtros por tamaño - Mediano - Grande - Pequeño - Extra pequeño - Extra grande - Supergrande - Anterior - Último año - Año actual - Este año - Tipo MIME - Año de creación - Año de modificación - Tamaño - Fecha de creación - Creador - Modificador - Mis consultas por filtros - - - Fecha de creación (rango) - Tamaño de contenido - Tipo - Nombre del fichero - Introduzca el nombre - Predeterminado - Carpeta - Consultas con filtros por tipo - no puede mover a su propia carpeta y subcarpetas. - Reiniciar - Sin conexión - + + Buscar + + Búsquedas recientes + + Buscando\u2026 + + en: %s + Ficheros + Carpetas + Bibliotecas + + ¡Vaya! + No encontramos el fichero\nque busca. + + Predeterminado + Ninguno + Todos + Carpeta + Documento + %1$s-%2$s-%3$s + %1$s... + %1$s (%2$d) + %1$s (%2$s) + + Tamaño de fichero + Fecha de creación + Tipo de fichero + Modificador + Creador + %1$s ... %2$s + %1$s + %2$d + + + Consultas con filtros por tamaño + Mediano + Grande + Pequeño + Extra pequeño + Extra grande + Supergrande + Anterior + Último año + Año actual + Este año + Tipo MIME + Año de creación + Año de modificación + Tamaño + Fecha de creación + Creador + Modificador + Mis consultas por filtros + + + Fecha de creación (rango) + Tamaño de contenido + Tipo + Nombre del fichero + Introduzca el nombre + Predeterminado + Carpeta + Consultas con filtros por tipo + no puede mover a su propia carpeta y subcarpetas. + Reiniciar + Sin conexión + 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 2a406d025..f8e36cf78 100644 --- a/viewer/src/main/kotlin/com/alfresco/content/viewer/ViewerFragment.kt +++ b/viewer/src/main/kotlin/com/alfresco/content/viewer/ViewerFragment.kt @@ -141,7 +141,6 @@ class ViewerFragment : Fragment(), MavericksView { ) { val tag = mimeType if (childFragmentManager.findFragmentByTag(tag) == null) { - println("Viewer URI == $viewerUri") val args = ChildViewerArgs( viewerUri, mimeType )