diff --git a/actions/build.gradle b/actions/build.gradle index 332383200..5e9ca7295 100644 --- a/actions/build.gradle +++ b/actions/build.gradle @@ -29,6 +29,7 @@ dependencies { implementation libs.androidx.core implementation libs.androidx.lifecycle.viewmodel implementation libs.androidx.lifecycle.runtime + implementation libs.androidx.navigation.fragment implementation libs.google.material implementation libs.epoxy.core diff --git a/actions/src/main/kotlin/com/alfresco/content/actions/ActionMoveFilesFolders.kt b/actions/src/main/kotlin/com/alfresco/content/actions/ActionMoveFilesFolders.kt new file mode 100644 index 000000000..6e01508f4 --- /dev/null +++ b/actions/src/main/kotlin/com/alfresco/content/actions/ActionMoveFilesFolders.kt @@ -0,0 +1,37 @@ +package com.alfresco.content.actions + +import android.content.Context +import android.view.View +import com.alfresco.content.data.BrowseRepository +import com.alfresco.content.data.Entry +import kotlin.coroutines.cancellation.CancellationException +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +/** + * Mark as ActionMoveFilesFolders + */ +data class ActionMoveFilesFolders( + override var entry: Entry, + override val icon: Int = R.drawable.ic_move, + override val title: Int = R.string.action_move_title +) : Action { + + override suspend fun execute(context: Context): Entry { + + val result = ActionMoveFragment.moveItem(context) + if (!result.isNullOrEmpty()) { + withContext(Dispatchers.IO) { + BrowseRepository().moveNode(entry.id, result) + } + } else { + throw CancellationException("User Cancellation") + } + return entry + } + + override fun copy(_entry: Entry): Action = copy(entry = _entry) + + override fun showToast(view: View, anchorView: View?) = + Action.showToast(view, anchorView, R.string.action_move_toast, entry.name) +} diff --git a/actions/src/main/kotlin/com/alfresco/content/actions/ActionMoveFragment.kt b/actions/src/main/kotlin/com/alfresco/content/actions/ActionMoveFragment.kt new file mode 100644 index 000000000..7293e56cf --- /dev/null +++ b/actions/src/main/kotlin/com/alfresco/content/actions/ActionMoveFragment.kt @@ -0,0 +1,48 @@ +package com.alfresco.content.actions + +import android.content.Context +import android.os.Bundle +import androidx.activity.result.ActivityResultLauncher +import androidx.fragment.app.Fragment +import com.alfresco.content.withFragment +import kotlinx.coroutines.CancellableContinuation +import kotlinx.coroutines.suspendCancellableCoroutine + +/** + * Mark as ActionMoveFragment + */ +class ActionMoveFragment : Fragment() { + private lateinit var requestLauncher: ActivityResultLauncher + private var onResult: CancellableContinuation? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + requestLauncher = registerForActivityResult(MoveResultContract()) { + onResult?.resume(it, null) + } + } + + private suspend fun moveItems(): String? = + suspendCancellableCoroutine { continuation -> + onResult = continuation + requestLauncher.launch(Unit) + } + + companion object { + private val TAG = ActionMoveFragment::class.java.simpleName + + /** + * Generating ActionMoveFragment + */ + suspend fun moveItem( + context: Context + ): String? = + withFragment( + context, + TAG, + { it.moveItems() }, + { ActionMoveFragment() } + ) + } +} 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 c49831ac0..09fa3ed12 100644 --- a/actions/src/main/kotlin/com/alfresco/content/actions/ContextualActionsViewModel.kt +++ b/actions/src/main/kotlin/com/alfresco/content/actions/ContextualActionsViewModel.kt @@ -24,10 +24,11 @@ internal class ContextualActionsViewModel( buildModel() // Update the model if necessary - viewModelScope.on (block = ::updateState) - viewModelScope.on (block = ::updateState) - viewModelScope.on (block = ::updateState) - viewModelScope.on (block = ::updateState) + viewModelScope.on(block = ::updateState) + viewModelScope.on(block = ::updateState) + viewModelScope.on(block = ::updateState) + viewModelScope.on(block = ::updateState) + viewModelScope.on(block = ::updateState) } private fun buildModel() = withState { state -> @@ -93,7 +94,8 @@ internal class ContextualActionsViewModel( offlineActionFor(entry), favoriteActionFor(entry), externalActionsFor(entry), - deleteActionFor(entry) + deleteActionFor(entry), + moveActionFor(entry) ).flatten() private fun actionsForTrashed(entry: Entry): List = @@ -127,6 +129,9 @@ internal class ContextualActionsViewModel( private fun deleteActionFor(entry: Entry) = if (entry.canDelete) listOf(ActionDelete(entry)) else listOf() + private fun moveActionFor(entry: Entry) = + if (entry.canDelete) listOf(ActionMoveFilesFolders(entry)) else listOf() + private fun makeTopActions(entry: Entry): List { val actions = mutableListOf() if (!entry.hasOfflineStatus) { 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 36eacfcb8..d72cffa3a 100644 --- a/actions/src/main/kotlin/com/alfresco/content/actions/CreateActionsSheet.kt +++ b/actions/src/main/kotlin/com/alfresco/content/actions/CreateActionsSheet.kt @@ -59,6 +59,7 @@ internal class ActionCreateViewModel( actions.add(ActionCaptureMedia(parent, title = R.string.action_capture_media_title_multiple)) else actions.add(ActionCaptureMedia(parent, title = R.string.action_capture_media_title_single)) actions.add(ActionUploadFiles(parent)) + actions.add(ActionUploadFiles(parent)) return actions } diff --git a/actions/src/main/kotlin/com/alfresco/content/actions/MoveResultContract.kt b/actions/src/main/kotlin/com/alfresco/content/actions/MoveResultContract.kt new file mode 100644 index 000000000..769cc827f --- /dev/null +++ b/actions/src/main/kotlin/com/alfresco/content/actions/MoveResultContract.kt @@ -0,0 +1,34 @@ +package com.alfresco.content.actions + +import android.app.Activity +import android.content.Context +import android.content.Intent +import androidx.activity.result.contract.ActivityResultContract +import androidx.annotation.CallSuper + +/** + * Mark as MoveResultContract + */ +class MoveResultContract : ActivityResultContract() { + @CallSuper + override fun createIntent(context: Context, input: Unit): Intent { + return intent + } + + override fun parseResult(resultCode: Int, intent: Intent?): String? { + return if (intent == null || resultCode != Activity.RESULT_OK) null + else intent.extras?.getString(OUTPUT_KEY) + } + + companion object { + const val OUTPUT_KEY = "targetParentId" + lateinit var intent: Intent + + /** + * adding intent for MoveActivity + */ + fun addMoveIntent(moveIntent: Intent) { + intent = moveIntent + } + } +} diff --git a/actions/src/main/res/drawable/ic_move.xml b/actions/src/main/res/drawable/ic_move.xml new file mode 100644 index 000000000..14db659a4 --- /dev/null +++ b/actions/src/main/res/drawable/ic_move.xml @@ -0,0 +1,9 @@ + + + diff --git a/actions/src/main/res/values-de/strings.xml b/actions/src/main/res/values-de/strings.xml index e9750e1fe..a3d52c2f3 100755 --- a/actions/src/main/res/values-de/strings.xml +++ b/actions/src/main/res/values-de/strings.xml @@ -37,8 +37,7 @@ %s ist offline nicht mehr verfügbar. Bilder oder Videos hochladen - Fotos oder Videos aufnehmen - Foto oder Video aufnehmen + Bilder oder Videos aufnehmen @string/capture_failure_permissions Zum Hochladen vorgesehene Medien. diff --git a/actions/src/main/res/values-es/strings.xml b/actions/src/main/res/values-es/strings.xml index 7c4739727..b1dd7b0b3 100755 --- a/actions/src/main/res/values-es/strings.xml +++ b/actions/src/main/res/values-es/strings.xml @@ -36,8 +36,7 @@ %s ya no estará disponible sin conexión. Cargar fotos o vídeos - - Tomar una foto o un vídeo + Tomar una foto o un vídeo @string/capture_failure_permissions Contenido multimedia programado para cargar. diff --git a/actions/src/main/res/values/strings.xml b/actions/src/main/res/values/strings.xml index e685d856f..bf2930fac 100644 --- a/actions/src/main/res/values/strings.xml +++ b/actions/src/main/res/values/strings.xml @@ -35,7 +35,7 @@ %s will be made available offline. %s will no longer be available offline. - Upload photos or videos2 + Upload photos or videos Take photos or videos Take a photo or video @string/capture_failure_permissions @@ -62,4 +62,7 @@ @string/share_files_failure_permissions To share files, turn on read storage permissions. + Move to folder + %s was moved successfully + diff --git a/app/build.gradle b/app/build.gradle index 86e5f1d90..24254b8e3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,7 +10,7 @@ android { defaultConfig { applicationId "com.alfresco.content.app" versionCode envOrDef('VERSION_CODE', '1') as Integer - versionName "1.2.0" + versionName "1.1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } @@ -84,6 +84,7 @@ dependencies { implementation project(':search') implementation project(':viewer') implementation project(':shareextension') + implementation project(':move') implementation project(':data') implementation libs.alfresco.content diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 79f5b53e7..938b18213 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -62,6 +62,11 @@ + + + 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 eccea2c14..b0ae7bef6 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 @@ -19,6 +19,7 @@ import com.alfresco.auth.DiscoveryService import com.alfresco.auth.activity.LoginViewModel import com.alfresco.auth.activity.LoginViewModel.Companion.DISTRIBUTION_VERSION import com.alfresco.content.actions.Action +import com.alfresco.content.actions.MoveResultContract import com.alfresco.content.activityViewModel import com.alfresco.content.app.R import com.alfresco.content.app.widget.ActionBarController @@ -95,6 +96,7 @@ class MainActivity : AppCompatActivity(), MavericksView { bottomNav.setupWithNavController(navController) setupActionToasts() + MoveResultContract.addMoveIntent(Intent(this, MoveActivity::class.java)) setupDownloadNotifications() } @@ -138,12 +140,11 @@ class MainActivity : AppCompatActivity(), MavericksView { startActivity(i) } - private fun setupActionToasts() = - Action.showActionToasts( - lifecycleScope, - findViewById(android.R.id.content), - bottomNav - ) + private fun setupActionToasts() = Action.showActionToasts( + lifecycleScope, + findViewById(android.R.id.content), + bottomNav + ) private fun setupDownloadNotifications() = DownloadMonitor diff --git a/app/src/main/java/com/alfresco/content/app/activity/MoveActivity.kt b/app/src/main/java/com/alfresco/content/app/activity/MoveActivity.kt new file mode 100644 index 000000000..db1e00604 --- /dev/null +++ b/app/src/main/java/com/alfresco/content/app/activity/MoveActivity.kt @@ -0,0 +1,105 @@ +package com.alfresco.content.app.activity + +import android.content.Intent +import android.os.Bundle +import android.view.View +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.lifecycleScope +import androidx.navigation.findNavController +import com.airbnb.mvrx.MavericksView +import com.airbnb.mvrx.withState +import com.alfresco.auth.activity.LoginViewModel +import com.alfresco.content.actions.Action +import com.alfresco.content.activityViewModel +import com.alfresco.content.app.R +import com.alfresco.content.app.widget.ActionBarController +import com.alfresco.content.session.SessionManager +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import java.lang.ref.WeakReference + +/** + * Marked as MoveActivity class + */ +class MoveActivity : AppCompatActivity(), MavericksView { + + private val viewModel: MainActivityViewModel by activityViewModel() + private val navController by lazy { findNavController(R.id.nav_host_fragment) } + private val bottomView by lazy { findViewById(R.id.bottom_view) } + private lateinit var actionBarController: ActionBarController + private var signedOutDialog = WeakReference(null) + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_move) + + configure() + } + + private fun configure() { + val graph = navController.navInflater.inflate(R.navigation.nav_move_paths) + graph.startDestination = R.id.nav_move + val bundle = Bundle().apply { + } + navController.setGraph(graph, bundle) + setupActionToasts() + actionBarController = ActionBarController(findViewById(R.id.toolbar)) + actionBarController.setupActionBar(this, navController) + } + + override fun onSupportNavigateUp(): Boolean { + return if (navController.currentDestination?.id == R.id.nav_browse_move) { + finish() + false + } else navController.navigateUp() + } + + override fun invalidate() = withState(viewModel) { state -> + if (state.requiresReLogin) { + if (state.isOnline) { + showSignedOutPrompt() + } + } else { + // Only when logged in otherwise triggers re-login prompts + actionBarController.setProfileIcon(viewModel.profileIcon) + } + + actionBarController.setOnline(state.isOnline) + } + + private fun showSignedOutPrompt() { + val oldDialog = signedOutDialog.get() + if (oldDialog != null && oldDialog.isShowing) return + val dialog = MaterialAlertDialogBuilder(this) + .setTitle(resources.getString(R.string.auth_signed_out_title)) + .setMessage(resources.getString(R.string.auth_signed_out_subtitle)) + .setNegativeButton(resources.getString(R.string.sign_out_confirmation_negative), null) + .setPositiveButton(resources.getString(R.string.auth_basic_sign_in_button)) { _, _ -> + navigateToReLogin() + } + .show() + signedOutDialog = WeakReference(dialog) + } + + private fun navigateToReLogin() { + val i = Intent(this, LoginActivity::class.java) + val acc = SessionManager.requireSession.account + i.putExtra(LoginViewModel.EXTRA_IS_EXTENSION, false) + i.putExtra(LoginViewModel.EXTRA_ENDPOINT, acc.serverUrl) + i.putExtra(LoginViewModel.EXTRA_AUTH_TYPE, acc.authType) + i.putExtra(LoginViewModel.EXTRA_AUTH_CONFIG, acc.authConfig) + i.putExtra(LoginViewModel.EXTRA_AUTH_STATE, acc.authState) + startActivity(i) + } + + private fun setupActionToasts() = Action.showActionToasts( + lifecycleScope, + findViewById(android.R.id.content), + bottomView + ) + + override fun onBackPressed() { + super.onBackPressed() + finish() + } +} diff --git a/app/src/main/res/layout/activity_move.xml b/app/src/main/res/layout/activity_move.xml new file mode 100644 index 000000000..29bac4166 --- /dev/null +++ b/app/src/main/res/layout/activity_move.xml @@ -0,0 +1,24 @@ + + + + + + + + + + diff --git a/app/src/main/res/navigation/nav_move_paths.xml b/app/src/main/res/navigation/nav_move_paths.xml new file mode 100644 index 000000000..0b3222525 --- /dev/null +++ b/app/src/main/res/navigation/nav_move_paths.xml @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/browse/src/main/kotlin/com/alfresco/content/browse/BrowseViewModel.kt b/browse/src/main/kotlin/com/alfresco/content/browse/BrowseViewModel.kt index a48a30413..c02fb1ed1 100644 --- a/browse/src/main/kotlin/com/alfresco/content/browse/BrowseViewModel.kt +++ b/browse/src/main/kotlin/com/alfresco/content/browse/BrowseViewModel.kt @@ -185,7 +185,7 @@ class BrowseViewModel( context.getString(R.string.nav_path_folder) -> BrowseRepository()::fetchFolderItems.asFlow(requireNotNull(item), skipCount, maxItems) - context.getString(R.string.nav_path_extension) -> + context.getString(R.string.nav_path_extension), context.getString(R.string.nav_path_move) -> BrowseRepository()::fetchExtensionFolderItems.asFlow(requireNotNull(item), skipCount, maxItems) context.getString(R.string.nav_path_site) -> diff --git a/browse/src/main/kotlin/com/alfresco/content/browse/BrowseViewState.kt b/browse/src/main/kotlin/com/alfresco/content/browse/BrowseViewState.kt index c411cad07..450db1a8b 100644 --- a/browse/src/main/kotlin/com/alfresco/content/browse/BrowseViewState.kt +++ b/browse/src/main/kotlin/com/alfresco/content/browse/BrowseViewState.kt @@ -30,7 +30,7 @@ data class BrowseViewState( override val isCompact: Boolean get() = when (path) { - "site", "folder", "extension", "my-libraries", "fav-libraries" -> true + "site", "folder", "extension", "move", "my-libraries", "fav-libraries" -> true else -> false } diff --git a/browse/src/main/res/values/values.xml b/browse/src/main/res/values/values.xml index 9978e2486..e231d87cd 100644 --- a/browse/src/main/res/values/values.xml +++ b/browse/src/main/res/values/values.xml @@ -11,6 +11,7 @@ folder extension + move folder_extension site diff --git a/build.gradle b/build.gradle index a1004c1ee..6b1c1d90b 100644 --- a/build.gradle +++ b/build.gradle @@ -90,4 +90,4 @@ subprojects { } } -apply from: "$rootDir/config/versions.gradle" +apply from: "$rootDir/config/versions.gradle" \ No newline at end of file diff --git a/common/src/main/kotlin/com/alfresco/content/NavControllerExt.kt b/common/src/main/kotlin/com/alfresco/content/NavControllerExt.kt index b08e7b19d..5a8b4e68d 100644 --- a/common/src/main/kotlin/com/alfresco/content/NavControllerExt.kt +++ b/common/src/main/kotlin/com/alfresco/content/NavControllerExt.kt @@ -58,6 +58,13 @@ fun NavController.navigateToParent(id: String, title: String, mode: String = REM navigate(Uri.parse("$BASE_URI/browse_parent/extension/$mode/$id?title=${Uri.encode(title)}")) } +/** + * navigate to browse move parent folder + */ +fun NavController.navigateToMoveParent(id: String, title: String, mode: String = REMOTE) { + navigate(Uri.parse("$BASE_URI/browse_move_parent/move/$mode/$id?title=${Uri.encode(title)}")) +} + /** * navigate to browse child folder */ diff --git a/data/src/main/kotlin/com/alfresco/content/data/BrowseRepository.kt b/data/src/main/kotlin/com/alfresco/content/data/BrowseRepository.kt index 97ea2ed3b..0f1e5baba 100644 --- a/data/src/main/kotlin/com/alfresco/content/data/BrowseRepository.kt +++ b/data/src/main/kotlin/com/alfresco/content/data/BrowseRepository.kt @@ -7,6 +7,7 @@ import com.alfresco.content.apis.NodesApi import com.alfresco.content.apis.NodesApiExt import com.alfresco.content.apis.getMyNode import com.alfresco.content.models.NodeBodyCreate +import com.alfresco.content.models.NodeBodyMove import com.alfresco.content.session.Session import com.alfresco.content.session.SessionManager import com.google.gson.Gson @@ -140,6 +141,13 @@ class BrowseRepository(val session: Session = SessionManager.requireSession) { ) } + /** + * executing api for moving the items (file or folder) + */ + suspend fun moveNode(entryId: String, targetParentId: String): Entry { + return Entry.with(service.moveNode(entryId, NodeBodyMove(targetParentId)).entry) + } + fun contentUri(entry: Entry): String { val baseUrl = SessionManager.currentSession?.baseUrl return "${baseUrl}alfresco/versions/1/nodes/${entry.id}/content?attachment=false&alf_ticket=${session.ticket}" diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 89e43c8b8..8e545f762 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -108,4 +108,4 @@ spotless = "com.diffplug.spotless:spotless-plugin-gradle:5.12.5" subsamplingimageview = "com.davemorrissey.labs:subsampling-scale-image-view:3.10.0" -timber = "com.jakewharton.timber:timber:4.7.1" +timber = "com.jakewharton.timber:timber:4.7.1" \ No newline at end of file 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 64a0d1a9f..91f173413 100644 --- a/listview/src/main/kotlin/com/alfresco/content/listview/ListFragment.kt +++ b/listview/src/main/kotlin/com/alfresco/content/listview/ListFragment.kt @@ -24,6 +24,7 @@ import com.alfresco.content.actions.ActionAddFavorite import com.alfresco.content.actions.ActionAddOffline import com.alfresco.content.actions.ActionCreateFolder import com.alfresco.content.actions.ActionDelete +import com.alfresco.content.actions.ActionMoveFilesFolders import com.alfresco.content.actions.ActionRemoveFavorite import com.alfresco.content.actions.ActionRemoveOffline import com.alfresco.content.actions.ContextualActionsSheet @@ -65,6 +66,7 @@ abstract class ListViewModel( viewModelScope.on { updateEntry(it.entry) } viewModelScope.on { updateEntry(it.entry) } viewModelScope.on { updateEntry(it.entry) } + viewModelScope.on { onMove(it.entry) } } private fun onDelete(entry: Entry) = entry.run { @@ -79,6 +81,10 @@ abstract class ListViewModel( refresh() } + private fun onMove(entry: Entry) = entry.run { + refresh() + } + @Suppress("UNCHECKED_CAST") fun removeEntry(entry: Entry) = setState { copyRemoving(entry) as S } @@ -117,6 +123,8 @@ abstract class ListFragment, S : ListViewState>(layoutID: var tvPercentage: TextView? = null var bannerTransferData: FrameLayout? = null var uploadButton: MaterialButton? = null + var moveHereButton: MaterialButton? = null + var cancelButton: MaterialButton? = null var percentageFiles: LinearProgressIndicator? = null private val epoxyController: AsyncEpoxyController by lazy { epoxyController() } private var delayedBoundary: Boolean = false @@ -131,6 +139,8 @@ abstract class ListFragment, S : ListViewState>(layoutID: bannerTransferData = view.findViewById(R.id.banner_parent) uploadButton = view.findViewById(R.id.upload_button) + moveHereButton = view.findViewById(R.id.move_here_button) + cancelButton = view.findViewById(R.id.cancel_button) tvUploadingFiles = view.findViewById(R.id.tv_uploading_files) tvPercentage = view.findViewById(R.id.tv_percentage) percentageFiles = view.findViewById(R.id.percentage_files) diff --git a/listview/src/main/res/layout/fragment_move_list.xml b/listview/src/main/res/layout/fragment_move_list.xml new file mode 100644 index 000000000..2ecd4ccba --- /dev/null +++ b/listview/src/main/res/layout/fragment_move_list.xml @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/listview/src/main/res/values-night/colors.xml b/listview/src/main/res/values-night/colors.xml new file mode 100644 index 000000000..add02e303 --- /dev/null +++ b/listview/src/main/res/values-night/colors.xml @@ -0,0 +1,6 @@ + + + + #0DFFFFFF + + diff --git a/listview/src/main/res/values/colors.xml b/listview/src/main/res/values/colors.xml new file mode 100644 index 000000000..eaa3c2b67 --- /dev/null +++ b/listview/src/main/res/values/colors.xml @@ -0,0 +1,6 @@ + + + + #0D212328 + + diff --git a/listview/src/main/res/values/strings.xml b/listview/src/main/res/values/strings.xml index 0bc7a06cb..0c79c0b4b 100644 --- a/listview/src/main/res/values/strings.xml +++ b/listview/src/main/res/values/strings.xml @@ -5,6 +5,7 @@ Syncing… Sync failed Upload + Move here Uploading %d file(s) %d file(s) finished uploading %.1f%% diff --git a/move/build.gradle b/move/build.gradle new file mode 100644 index 000000000..babd33e17 --- /dev/null +++ b/move/build.gradle @@ -0,0 +1,44 @@ +plugins { + id 'com.android.library' + id 'kotlin-android' + id 'kotlin-kapt' + id 'kotlin-parcelize' +} + +android { + defaultConfig { + versionCode 1 + versionName "1.0" + } +} + +dependencies { + + implementation project(':base') + api project(':base-ui') + implementation project(':common') + implementation project(':data') + implementation project(':mimetype') + implementation project(':actions') + implementation project(':listview') + implementation project(':browse') + implementation project(':search') + + implementation libs.kotlin.stdlib + implementation libs.coroutines.core + + implementation libs.androidx.appcompat + implementation libs.androidx.core + implementation libs.androidx.lifecycle.viewmodel + implementation libs.androidx.lifecycle.runtime + implementation libs.androidx.navigation.fragment + implementation libs.androidx.navigation.ui + + implementation libs.google.material + implementation libs.epoxy.core + implementation libs.mavericks + implementation libs.gson + implementation libs.androidx.preference + + kapt libs.epoxy.processor +} diff --git a/move/src/main/AndroidManifest.xml b/move/src/main/AndroidManifest.xml new file mode 100644 index 000000000..7c38dc96c --- /dev/null +++ b/move/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + diff --git a/move/src/main/java/com/alfresco/content/move/BrowseMoveFragment.kt b/move/src/main/java/com/alfresco/content/move/BrowseMoveFragment.kt new file mode 100644 index 000000000..f93914070 --- /dev/null +++ b/move/src/main/java/com/alfresco/content/move/BrowseMoveFragment.kt @@ -0,0 +1,92 @@ +package com.alfresco.content.move + +import android.app.Activity +import android.content.Intent +import android.os.Bundle +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem +import android.view.View +import androidx.navigation.fragment.findNavController +import com.airbnb.mvrx.withState +import com.alfresco.content.actions.MoveResultContract +import com.alfresco.content.browse.BrowseArgs +import com.alfresco.content.browse.BrowseViewModel +import com.alfresco.content.browse.BrowseViewState +import com.alfresco.content.data.Entry +import com.alfresco.content.fragmentViewModelWithArgs +import com.alfresco.content.listview.ListFragment +import com.alfresco.content.navigateTo +import com.alfresco.content.navigateToContextualSearch + +/** + * Mark as BrowseMoveFragment + */ +class BrowseMoveFragment : ListFragment(R.layout.fragment_move_list) { + + private lateinit var args: BrowseArgs + override val viewModel: BrowseViewModel by fragmentViewModelWithArgs { args } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + args = BrowseArgs.with(requireArguments()) + + // Contextual search only in folders/sites + setHasOptionsMenu(true) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + moveHereButton?.setOnClickListener { + withState(viewModel) { state -> + val activity = requireActivity() + val intent = Intent().apply { + putExtra(MoveResultContract.OUTPUT_KEY, state.nodeId) + } + activity.setResult(Activity.RESULT_OK, intent) + activity.finish() + } + } + + cancelButton?.setOnClickListener { + requireActivity().finish() + } + } + + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + inflater.inflate(R.menu.menu_browse_extension, menu) + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return when (item.itemId) { + R.id.search -> { + findNavController().navigateToContextualSearch(args.id ?: "", args.title ?: "", true) + true + } + R.id.new_folder -> { + withState(viewModel) { + viewModel.createFolder(it) + } + true + } + else -> super.onOptionsItemSelected(item) + } + } + + override fun invalidate() = withState(viewModel) { state -> + if (state.path == getString(R.string.nav_path_move)) + super.disableRefreshLayout() + super.invalidate() + } + + /** + * return callback for list item + */ + override fun onItemClicked(entry: Entry) { + if (!entry.isFolder) return + + findNavController().navigateTo(entry) + } +} diff --git a/move/src/main/java/com/alfresco/content/move/MoveFragment.kt b/move/src/main/java/com/alfresco/content/move/MoveFragment.kt new file mode 100644 index 000000000..f481c2a92 --- /dev/null +++ b/move/src/main/java/com/alfresco/content/move/MoveFragment.kt @@ -0,0 +1,53 @@ +package com.alfresco.content.move + +import android.os.Bundle +import android.os.Parcelable +import androidx.fragment.app.Fragment +import androidx.navigation.fragment.findNavController +import com.airbnb.mvrx.MavericksView +import com.airbnb.mvrx.withState +import com.alfresco.content.fragmentViewModelWithArgs +import com.alfresco.content.navigateToMoveParent +import kotlinx.parcelize.Parcelize + +/** + * Mark as MoveArgs + */ +@Parcelize +data class MoveArgs( + val path: String +) : Parcelable { + companion object { + private const val PATH_KEY = "path" + + /** + * return the MoveArgs obj + */ + fun with(args: Bundle): MoveArgs { + return MoveArgs( + args.getString(PATH_KEY, "") + ) + } + } +} + +/** + * Mark as MoveFragment + */ +class MoveFragment : Fragment(), MavericksView { + private lateinit var args: MoveArgs + val viewModel: MoveViewModel by fragmentViewModelWithArgs { args } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + args = MoveArgs.with(requireArguments()) + + val nodeId = viewModel.getMyFilesNodeId() + + findNavController().navigateToMoveParent(nodeId, "Personal Files") + } + + override fun invalidate() = withState(viewModel) { state -> + } +} diff --git a/move/src/main/java/com/alfresco/content/move/MoveViewModel.kt b/move/src/main/java/com/alfresco/content/move/MoveViewModel.kt new file mode 100644 index 000000000..69d691e65 --- /dev/null +++ b/move/src/main/java/com/alfresco/content/move/MoveViewModel.kt @@ -0,0 +1,37 @@ +package com.alfresco.content.move + +import android.content.Context +import com.airbnb.mvrx.MavericksState +import com.airbnb.mvrx.MavericksViewModel +import com.airbnb.mvrx.MavericksViewModelFactory +import com.airbnb.mvrx.ViewModelContext +import com.alfresco.content.data.BrowseRepository + +/** + * Mark as MoveViewState + */ +data class MoveViewState(val path: String) : MavericksState { + constructor(args: MoveArgs) : this(args.path) +} + +/** + * Mark as MoveViewModel + */ +class MoveViewModel( + state: MoveViewState, + val context: Context +) : MavericksViewModel(state) { + + /** + * returns the nodeID for my files + */ + fun getMyFilesNodeId() = BrowseRepository().myFilesNodeId + + companion object : MavericksViewModelFactory { + + override fun create( + viewModelContext: ViewModelContext, + state: MoveViewState + ) = MoveViewModel(state, viewModelContext.app()) + } +} diff --git a/settings.gradle b/settings.gradle index 4f7f32869..616100e32 100644 --- a/settings.gradle +++ b/settings.gradle @@ -20,6 +20,7 @@ include ':viewer-image' include ':viewer-text' include ':viewer-media' include ':shareextension' +include ':move' include ':app' //include ':content' //include ':content-ktx'