diff --git a/actions/src/main/kotlin/com/alfresco/content/actions/ContextualActionsState.kt b/actions/src/main/kotlin/com/alfresco/content/actions/ContextualActionsState.kt index 88547db8..a6a743ce 100644 --- a/actions/src/main/kotlin/com/alfresco/content/actions/ContextualActionsState.kt +++ b/actions/src/main/kotlin/com/alfresco/content/actions/ContextualActionsState.kt @@ -3,9 +3,9 @@ package com.alfresco.content.actions import com.airbnb.mvrx.Async import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.Uninitialized +import com.alfresco.content.data.AppMenu import com.alfresco.content.data.ContextualActionData import com.alfresco.content.data.Entry -import com.alfresco.content.data.MobileConfigDataEntry data class ContextualActionsState( val entries: List = emptyList(), @@ -13,8 +13,11 @@ data class ContextualActionsState( val actions: List = emptyList(), val topActions: List = emptyList(), val fetch: Async = Uninitialized, - val mobileConfigDataEntry: MobileConfigDataEntry? = null, + val appMenu: List? = null, ) : MavericksState { - constructor(target: ContextualActionData) : this(entries = target.entries, isMultiSelection = target.isMultiSelection, - mobileConfigDataEntry = target.mobileConfigData) + constructor(target: ContextualActionData) : this( + entries = target.entries, + isMultiSelection = target.isMultiSelection, + appMenu = target.appMenu, + ) } 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 ce548f8c..c2d5d7f7 100644 --- a/actions/src/main/kotlin/com/alfresco/content/actions/ContextualActionsViewModel.kt +++ b/actions/src/main/kotlin/com/alfresco/content/actions/ContextualActionsViewModel.kt @@ -8,13 +8,12 @@ import com.airbnb.mvrx.Success import com.airbnb.mvrx.ViewModelContext import com.alfresco.content.common.EntryListener import com.alfresco.content.data.BrowseRepository -import com.alfresco.content.data.CommonRepository.Companion.KEY_FEATURES_MOBILE import com.alfresco.content.data.Entry import com.alfresco.content.data.FavoritesRepository +import com.alfresco.content.data.MenuActions import com.alfresco.content.data.SearchRepository import com.alfresco.content.data.SearchRepository.Companion.SERVER_VERSION_NUMBER import com.alfresco.content.data.Settings -import com.alfresco.content.data.getJsonFromSharedPrefs import com.alfresco.coroutines.asFlow import com.alfresco.events.on import kotlinx.coroutines.GlobalScope @@ -22,7 +21,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.launch class ContextualActionsViewModel( - state: ContextualActionsState, + val state: ContextualActionsState, val context: Context, private val settings: Settings, ) : MavericksViewModel(state) { @@ -138,13 +137,17 @@ class ContextualActionsViewModel( when { state.entries.all { it.isTrashed } -> { // Added restore and delete actions - actions.add(ActionRestore(entry, state.entries)) - actions.add(ActionDeleteForever(entry, state.entries)) + actions.addAll( + listOfNotNull( + if (isMenuActionEnabled(MenuActions.Restore)) ActionRestore(entry, state.entries) else null, + if (isMenuActionEnabled(MenuActions.PermanentlyDelete)) ActionDeleteForever(entry, state.entries) else null, + ), + ) } state.entries.all { it.hasOfflineStatus } -> { // Added Offline action - actions.add(offlineMultiActionFor(entry, state.entries)) + actions.addAll(offlineMultiActionFor(entry, state.entries)) } else -> { @@ -161,30 +164,39 @@ class ContextualActionsViewModel( // Added Favorite Action val version = SearchRepository().getPrefsServerVersion() - if (version.toInt() >= SERVER_VERSION_NUMBER) { - if (entries.any { !it.isFavorite }) { - actions.add(ActionAddFavorite(entry, entries)) - } else { - actions.add(ActionRemoveFavorite(entry, entries)) - } + val hasNonFavoriteEntries = entries.any { !it.isFavorite } + + val favouriteActions = when { + version.toInt() < SERVER_VERSION_NUMBER -> null + hasNonFavoriteEntries && isMenuActionEnabled(MenuActions.AddFavourite) -> ActionAddFavorite(entry, entries) + !hasNonFavoriteEntries && isMenuActionEnabled(MenuActions.RemoveFavourite) -> ActionRemoveFavorite(entry, entries) + else -> null } + actions.addAll(listOfNotNull(favouriteActions)) + // Added Start Process Action - processMultiActionFor(entry, entries)?.let { action -> - actions.add(action) - } + actions.addAll(processMultiActionFor(entry, entries)) // Added Move Action if (isMoveDeleteAllowed(entries)) { - actions.add(ActionMoveFilesFolders(entry, entries)) + actions.addAll( + listOfNotNull( + if (isMenuActionEnabled(MenuActions.Move)) ActionMoveFilesFolders(entry, entries) else null, + ), + ) } // Added Offline Action - actions.add(offlineMultiActionFor(entry, entries)) + actions.addAll(offlineMultiActionFor(entry, entries)) // Added Delete Action if (isMoveDeleteAllowed(entries)) { - actions.add((ActionDelete(entry, entries))) + actions.addAll( + listOfNotNull( + if (isMenuActionEnabled(MenuActions.Trash)) ActionDelete(entry, entries) else null, + ), + ) } return actions } @@ -200,7 +212,10 @@ class ContextualActionsViewModel( ).flatten() private fun actionsForTrashed(entry: Entry): List = - listOf(ActionRestore(entry), ActionDeleteForever(entry)) + listOfNotNull( + if (isMenuActionEnabled(MenuActions.Restore)) ActionRestore(entry) else null, + if (isMenuActionEnabled(MenuActions.PermanentlyDelete)) ActionDeleteForever(entry) else null, + ) private fun actionsForOffline(entry: Entry): List = listOf( @@ -210,68 +225,106 @@ class ContextualActionsViewModel( ).flatten() private fun actionsProcesses(entry: Entry): List = - listOf(ActionStartProcess(entry)) - - private fun processMultiActionFor(entry: Entry, entries: List): Action? { - if (settings.isProcessEnabled && (entries.isNotEmpty() && entries.all { it.isFile })) { - return ActionStartProcess(entry, entries) + listOfNotNull( + if (isMenuActionEnabled(MenuActions.StartProcess)) ActionStartProcess(entry) else null, + ) + + private fun processMultiActionFor(entry: Entry, entries: List): List { + return if (settings.isProcessEnabled && (entries.isNotEmpty() && entries.all { it.isFile })) { + listOfNotNull( + if (isMenuActionEnabled(MenuActions.StartProcess)) ActionStartProcess(entry, entries) else null, + ) + } else { + emptyList() } - return null } - private fun offlineActionFor(entry: Entry) = - if (!entry.isFile && !entry.isFolder) { - listOf() - } else if (entry.hasOfflineStatus && !entry.isOffline) { - listOf() - } else { - listOf(if (entry.isOffline) ActionRemoveOffline(entry) else ActionAddOffline(entry)) + private fun offlineActionFor(entry: Entry): List { + if (!entry.isFile && !entry.isFolder || (entry.hasOfflineStatus && !entry.isOffline)) { + return emptyList() } - private fun offlineMultiActionFor(entry: Entry, entries: List): Action { + return listOfNotNull( + when { + entry.isOffline && isMenuActionEnabled(MenuActions.RemoveOffline) -> ActionRemoveOffline(entry) + !entry.isOffline && isMenuActionEnabled(MenuActions.AddOffline) -> ActionAddOffline(entry) + else -> null + }, + ) + } + + private fun offlineMultiActionFor(entry: Entry, entries: List): List { val filteredOffline = entries.filter { it.isFile || it.isFolder }.filter { !it.hasOfflineStatus || it.isOffline } - return if (filteredOffline.any { !it.isOffline }) { - ActionAddOffline(entry, entries) - } else { - ActionRemoveOffline(entry, entries) + return when { + filteredOffline.any { !it.isOffline } -> { + listOfNotNull( + if (isMenuActionEnabled(MenuActions.AddOffline)) ActionAddOffline(entry, entries) else null, + ) + } + + else -> { + listOfNotNull( + if (isMenuActionEnabled(MenuActions.RemoveOffline)) ActionRemoveOffline(entry, entries) else null, + ) + } } } - private fun favoriteActionFor(entry: Entry) = - listOf(if (entry.isFavorite) ActionRemoveFavorite(entry) else ActionAddFavorite(entry)) + private fun favoriteActionFor(entry: Entry): List = + listOfNotNull( + when { + entry.isFavorite && isMenuActionEnabled(MenuActions.RemoveFavourite) -> ActionRemoveFavorite(entry) + !entry.isFavorite && isMenuActionEnabled(MenuActions.AddFavourite) -> ActionAddFavorite(entry) + else -> null + }, + ) - private fun externalActionsFor(entry: Entry) = + private fun externalActionsFor(entry: Entry): List = if (entry.isFile) { - listOf(ActionOpenWith(entry), ActionDownload(entry)) + listOfNotNull( + if (isMenuActionEnabled(MenuActions.OpenWith)) ActionOpenWith(entry) else null, + if (isMenuActionEnabled(MenuActions.Download)) ActionDownload(entry) else null, + ) } else { - listOf() + emptyList() } - private fun deleteActionFor(entry: Entry) = if (entry.canDelete) listOf(ActionDelete(entry)) else listOf() + private fun deleteActionFor(entry: Entry) = if (entry.canDelete) listOfNotNull(if (isMenuActionEnabled(MenuActions.Trash)) ActionDelete(entry) else null) else listOf() private fun renameMoveActionFor(entry: Entry): List { - val actions = mutableListOf() - if (entry.canDelete && (entry.isFile || entry.isFolder)) { - actions.add(ActionUpdateFileFolder(entry)) - actions.add(ActionMoveFilesFolders(entry)) + return if (entry.canDelete && (entry.isFile || entry.isFolder)) { + listOfNotNull( + if (isMenuActionEnabled(MenuActions.Rename)) ActionUpdateFileFolder(entry) else null, + if (isMenuActionEnabled(MenuActions.Move)) ActionMoveFilesFolders(entry) else null, + ) + } else { + emptyList() } - return actions } - private fun makeTopActions(entry: Entry): List { - val actions = mutableListOf() - if (!entry.hasOfflineStatus) { - if (entry.isFavorite) { - actions.add(ActionRemoveFavorite(entry)) - } else { - actions.add(ActionAddFavorite(entry)) - } - } - if (entry.isFile) { - actions.add(ActionDownload(entry)) + private fun makeTopActions(entry: Entry): List = + listOfNotNull( + // Add or Remove favorite actions based on favorite status + when { + !entry.hasOfflineStatus -> when { + entry.isFavorite && isMenuActionEnabled(MenuActions.RemoveFavourite) -> ActionRemoveFavorite(entry) + !entry.isFavorite && isMenuActionEnabled(MenuActions.AddFavourite) -> ActionAddFavorite(entry) + else -> null + } + + else -> null + }, + // Download action if entry is a file + if (entry.isFile && isMenuActionEnabled(MenuActions.Download)) ActionDownload(entry) else null, + ) + + private fun isMenuActionEnabled(menuActions: MenuActions): Boolean { + if (state.appMenu?.isEmpty() == true) { + return true } - return actions + + return state.appMenu?.find { it.id.lowercase() == menuActions.value.lowercase() }?.enabled == true } companion object : MavericksViewModelFactory { diff --git a/app/src/main/java/com/alfresco/content/app/activity/LoginActivity.kt b/app/src/main/java/com/alfresco/content/app/activity/LoginActivity.kt index 0de8363e..06698820 100644 --- a/app/src/main/java/com/alfresco/content/app/activity/LoginActivity.kt +++ b/app/src/main/java/com/alfresco/content/app/activity/LoginActivity.kt @@ -29,15 +29,16 @@ class LoginActivity : com.alfresco.auth.activity.LoginActivity() { val session = Session(context, account) val person = PeopleRepository(session).me() val myFiles = BrowseRepository(session).myFilesNodeId() + AnalyticsManager(session).apiTracker(APIEvent.Login, true) CommonRepository(session).getMobileConfigData() processAccountInformation(person, myFiles, credentials, authConfig, endpoint) - AnalyticsManager(session).apiTracker(APIEvent.Login, true) if (isExtension) { navigateToExtension() } else { navigateToMain() } } catch (ex: Exception) { + ex.printStackTrace() onError(R.string.auth_error_wrong_credentials) } } diff --git a/data/src/main/kotlin/com/alfresco/content/data/AppConfigUtils.kt b/data/src/main/kotlin/com/alfresco/content/data/AppConfigUtils.kt index e8e6f095..f6146f9e 100644 --- a/data/src/main/kotlin/com/alfresco/content/data/AppConfigUtils.kt +++ b/data/src/main/kotlin/com/alfresco/content/data/AppConfigUtils.kt @@ -154,8 +154,6 @@ private val formatter = DateTimeFormatterBuilder() .optionalStart().appendOffset("+HHMM", "Z").optionalEnd() .toFormatter() - - // Function to store a JSON object fun saveJsonToSharedPrefs(context: Context, key: String, obj: Any) { val sharedPreferences: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) @@ -170,7 +168,6 @@ fun saveJsonToSharedPrefs(context: Context, key: String, obj: Any) { editor.apply() } - // Function to retrieve a JSON object inline fun getJsonFromSharedPrefs(context: Context, key: String): T? { val sharedPreferences: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) @@ -186,5 +183,3 @@ inline fun getJsonFromSharedPrefs(context: Context, key: String): T? null } } - - diff --git a/data/src/main/kotlin/com/alfresco/content/data/CommonRepository.kt b/data/src/main/kotlin/com/alfresco/content/data/CommonRepository.kt index 8f3c63fd..1bcaaaac 100644 --- a/data/src/main/kotlin/com/alfresco/content/data/CommonRepository.kt +++ b/data/src/main/kotlin/com/alfresco/content/data/CommonRepository.kt @@ -1,44 +1,20 @@ package com.alfresco.content.data import com.alfresco.content.apis.MobileConfigApi -import com.alfresco.content.session.ActionSessionInvalid import com.alfresco.content.session.Session import com.alfresco.content.session.SessionManager -import com.alfresco.content.session.SessionNotFoundException -import com.alfresco.events.EventBus -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import retrofit2.http.Url import java.net.URL -class CommonRepository(otherSession: Session? = null) { - - lateinit var session: Session - private val coroutineScope = CoroutineScope(Dispatchers.Main) - - init { - try { - session = otherSession ?: SessionManager.requireSession - } catch (e: SessionNotFoundException) { - e.printStackTrace() - coroutineScope.launch { - EventBus.default.send(ActionSessionInvalid(true)) - } - } - } - - private val context get() = session.context +class CommonRepository(val session: Session = SessionManager.requireSession) { private val service: MobileConfigApi by lazy { session.createService(MobileConfigApi::class.java) } suspend fun getMobileConfigData() { - val data = MobileConfigDataEntry.with(service.getMobileConfig("https://${URL(session.account.serverUrl).host}/app-config.json")) - saveJsonToSharedPrefs(context, KEY_FEATURES_MOBILE, data) + saveJsonToSharedPrefs(session.context, KEY_FEATURES_MOBILE, data) } companion object { diff --git a/data/src/main/kotlin/com/alfresco/content/data/ContextualActionData.kt b/data/src/main/kotlin/com/alfresco/content/data/ContextualActionData.kt index 06797b00..01e352ce 100644 --- a/data/src/main/kotlin/com/alfresco/content/data/ContextualActionData.kt +++ b/data/src/main/kotlin/com/alfresco/content/data/ContextualActionData.kt @@ -7,7 +7,7 @@ import kotlinx.parcelize.Parcelize data class ContextualActionData( val entries: List = emptyList(), val isMultiSelection: Boolean = false, - val mobileConfigData: MobileConfigDataEntry? = null, + val appMenu: List = emptyList(), ) : Parcelable { companion object { @@ -15,7 +15,7 @@ data class ContextualActionData( return ContextualActionData( entries = entries, isMultiSelection = isMultiSelection, - mobileConfigData = mobileConfigData + appMenu = mobileConfigData?.featuresMobile?.menus ?: emptyList(), ) } } diff --git a/data/src/main/kotlin/com/alfresco/content/data/MobileConfigDataEntry.kt b/data/src/main/kotlin/com/alfresco/content/data/MobileConfigDataEntry.kt index 83207758..a9bb7c9a 100644 --- a/data/src/main/kotlin/com/alfresco/content/data/MobileConfigDataEntry.kt +++ b/data/src/main/kotlin/com/alfresco/content/data/MobileConfigDataEntry.kt @@ -41,16 +41,29 @@ data class MobileFeatures( @Parcelize data class AppMenu( val id: String, - val name: String, val enabled: Boolean, ) : Parcelable { companion object { fun with(menuData: DynamicMenu): AppMenu { return AppMenu( id = menuData.id ?: "", - name = menuData.name ?: "", enabled = menuData.enabled ?: false, ) } } } + +enum class MenuActions(val value: String) { + AddFavourite("app.menu.addFavourite"), + RemoveFavourite("app.menu.removeFavourite"), + Rename("app.menu.rename"), + Move("app.menu.move"), + OpenWith("app.menu.openWith"), + Download("app.menu.download"), + Trash("app.menu.trash"), + AddOffline("app.menu.addOffline"), + RemoveOffline("app.menu.removeOffline"), + Restore("app.menu.restore"), + PermanentlyDelete("app.menu.permanentlyDelete"), + StartProcess("app.menu.startProcess"), +} diff --git a/listview/src/main/kotlin/com/alfresco/content/listview/ListFragment.kt b/listview/src/main/kotlin/com/alfresco/content/listview/ListFragment.kt index de320f9a..a7840c04 100644 --- a/listview/src/main/kotlin/com/alfresco/content/listview/ListFragment.kt +++ b/listview/src/main/kotlin/com/alfresco/content/listview/ListFragment.kt @@ -36,6 +36,7 @@ import com.alfresco.content.common.EntryListener import com.alfresco.content.data.CommonRepository.Companion.KEY_FEATURES_MOBILE import com.alfresco.content.data.ContextualActionData import com.alfresco.content.data.Entry +import com.alfresco.content.data.MobileConfigDataEntry import com.alfresco.content.data.MultiSelection import com.alfresco.content.data.MultiSelectionData import com.alfresco.content.data.ResponsePaging @@ -193,10 +194,18 @@ abstract class ListFragment, S : ListViewState>(layoutID: private var delayedBoundary: Boolean = false private var isViewRequiredMultiSelection = false var bottomMoveButtonLayout: ConstraintLayout? = null + var menuActionsEnabled: Boolean = false override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + val menus = getJsonFromSharedPrefs(requireContext(), KEY_FEATURES_MOBILE)?.featuresMobile + ?.menus + + println("ListFragment.onViewCreated === ${menus?.size}") + + menuActionsEnabled = menus?.isEmpty() == true || menus?.any { it.enabled } == true + loadingAnimation = view.findViewById(R.id.loading_animation) recyclerView = view.findViewById(R.id.recycler_view) refreshLayout = view.findViewById(R.id.refresh_layout) @@ -314,6 +323,7 @@ abstract class ListFragment, S : ListViewState>(layoutID: id(stableId(it)) data(it) compact(state.isCompact) + menuAction(menuActionsEnabled) multiSelection(state.selectedEntries.isNotEmpty()) clickListener { model, _, _, _ -> if (!viewModel.longPressHandled) { diff --git a/listview/src/main/kotlin/com/alfresco/content/listview/ListViewRow.kt b/listview/src/main/kotlin/com/alfresco/content/listview/ListViewRow.kt index ae9c9140..e28f0419 100644 --- a/listview/src/main/kotlin/com/alfresco/content/listview/ListViewRow.kt +++ b/listview/src/main/kotlin/com/alfresco/content/listview/ListViewRow.kt @@ -4,6 +4,7 @@ import android.content.Context import android.graphics.drawable.AnimatedVectorDrawable import android.util.AttributeSet import android.view.LayoutInflater +import android.view.View import android.view.ViewGroup import android.widget.FrameLayout import androidx.core.content.ContextCompat @@ -30,6 +31,7 @@ class ListViewRow @JvmOverloads constructor( private var isCompact: Boolean = false private lateinit var entry: Entry private var multiSelectionEnabled = false + private var menuActionsEnabled = false @ModelProp fun setData(entry: Entry) { @@ -117,12 +119,16 @@ class ListViewRow @JvmOverloads constructor( } else { Pair(R.drawable.ic_offline_status_pending, null) } + OfflineStatus.SYNCING -> Pair(R.drawable.ic_offline_status_in_progress_anim, R.string.offline_status_in_progress) + OfflineStatus.SYNCED -> Pair(R.drawable.ic_offline_status_synced, null) + OfflineStatus.ERROR -> Pair(R.drawable.ic_offline_status_error, R.string.offline_status_error) + else -> Pair(R.drawable.ic_offline_status_synced, null) } @@ -154,6 +160,11 @@ class ListViewRow @JvmOverloads constructor( this.multiSelectionEnabled = isMultiSelection } + @ModelProp + fun setMenuAction(enabled: Boolean) { + menuActionsEnabled = enabled + } + @AfterPropsSet fun bind() { binding.checkBox.isChecked = entry.isSelectedForMultiSelection @@ -176,7 +187,14 @@ class ListViewRow @JvmOverloads constructor( private fun postDataSet() { binding.checkBox.isVisible = false binding.checkBox.isChecked = false - binding.moreButton.isVisible = actionButtonVisibility(entry) + + if (!menuActionsEnabled) { + binding.moreButton.visibility = View.GONE + binding.moreButton.isVisible = false + } else { + binding.moreButton.visibility = View.VISIBLE + binding.moreButton.isVisible = actionButtonVisibility(entry) + } configureOfflineStatus(entry) }