From 34a4af9527543e4dcf2aa7929270d382490bcae7 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 22 May 2026 16:28:33 +0300 Subject: [PATCH 01/10] Rename .java to .kt Signed-off-by: alperozturk96 --- ...ileDetailSharingFragment.java => FileDetailSharingFragment.kt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename app/src/main/java/com/owncloud/android/ui/fragment/{FileDetailSharingFragment.java => FileDetailSharingFragment.kt} (100%) diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.kt similarity index 100% rename from app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java rename to app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.kt From 57047f420af43f4eceb4be49fc684a82eeac5009 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 22 May 2026 16:28:34 +0300 Subject: [PATCH 02/10] file-detail-sharing-fragment kotlin Signed-off-by: alperozturk96 --- .../ui/fragment/FileDetailSharingFragment.kt | 1237 ++++++++--------- 1 file changed, 592 insertions(+), 645 deletions(-) diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.kt b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.kt index 424f54041076..7b7f93f17a76 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.kt +++ b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.kt @@ -5,867 +5,814 @@ * @author Chris Narkiewicz * @author TSI-mc * + * Copyright (C) 2026 Alper Öztürk * Copyright (C) 2018 Andy Scherzinger * Copyright (C) 2020 Chris Narkiewicz * Copyright (C) 2023 TSI-mc * * SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only */ - -package com.owncloud.android.ui.fragment; - -import android.Manifest; -import android.accounts.AccountManager; -import android.app.Activity; -import android.app.SearchManager; -import android.content.Context; -import android.content.Intent; -import android.database.Cursor; -import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.os.Bundle; -import android.provider.ContactsContract; -import android.text.InputType; -import android.text.TextUtils; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.animation.Animation; -import android.view.animation.AnimationUtils; -import android.widget.LinearLayout; - -import com.nextcloud.android.common.ui.theme.utils.ColorRole; -import com.nextcloud.client.account.User; -import com.nextcloud.client.account.UserAccountManager; -import com.nextcloud.client.database.entity.FileEntity; -import com.nextcloud.client.di.Injectable; -import com.nextcloud.client.network.ClientFactory; -import com.nextcloud.client.utils.IntentUtil; -import com.nextcloud.utils.extensions.BundleExtensionsKt; -import com.nextcloud.utils.extensions.OCShareExtensionsKt; -import com.nextcloud.utils.extensions.ViewExtensionsKt; -import com.nextcloud.utils.mdm.MDMConfig; -import com.owncloud.android.R; -import com.owncloud.android.databinding.FileDetailsSharingFragmentBinding; -import com.owncloud.android.datamodel.FileDataStorageManager; -import com.owncloud.android.datamodel.OCFile; -import com.owncloud.android.datamodel.SharesType; -import com.owncloud.android.datamodel.e2e.v2.decrypted.DecryptedFolderMetadataFile; -import com.owncloud.android.lib.common.OwnCloudAccount; -import com.owncloud.android.lib.common.OwnCloudClient; -import com.owncloud.android.lib.common.operations.RemoteOperationResult; -import com.owncloud.android.lib.common.utils.Log_OC; -import com.owncloud.android.lib.resources.shares.OCShare; -import com.owncloud.android.lib.resources.shares.ShareType; -import com.owncloud.android.lib.resources.status.NextcloudVersion; -import com.owncloud.android.lib.resources.status.OCCapability; -import com.owncloud.android.operations.RefreshFolderOperation; -import com.owncloud.android.providers.UsersAndGroupsSearchConfig; -import com.owncloud.android.ui.activity.FileActivity; -import com.owncloud.android.ui.activity.FileDisplayActivity; -import com.owncloud.android.ui.adapter.ShareeListAdapter; -import com.owncloud.android.ui.adapter.ShareeListAdapterListener; -import com.owncloud.android.ui.asynctasks.RetrieveHoverCardAsyncTask; -import com.owncloud.android.ui.dialog.SharePasswordDialogFragment; -import com.owncloud.android.ui.fragment.share.RemoteShareRepository; -import com.owncloud.android.ui.fragment.share.ShareRepository; -import com.owncloud.android.ui.fragment.util.FileDetailSharingFragmentHelper; -import com.owncloud.android.ui.helpers.FileOperationsHelper; -import com.owncloud.android.utils.ClipboardUtil; -import com.owncloud.android.utils.DisplayUtils; -import com.owncloud.android.utils.PermissionUtil; -import com.owncloud.android.utils.theme.ViewThemeUtils; - -import java.util.ArrayList; -import java.util.List; - -import javax.inject.Inject; - -import androidx.activity.result.ActivityResultLauncher; -import androidx.activity.result.contract.ActivityResultContracts; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.VisibleForTesting; -import androidx.appcompat.widget.SearchView; -import androidx.fragment.app.Fragment; -import androidx.recyclerview.widget.LinearLayoutManager; -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import kotlin.Unit; - -public class FileDetailSharingFragment extends Fragment implements ShareeListAdapterListener, - DisplayUtils.AvatarGenerationListener, - Injectable, FileDetailsSharingMenuBottomSheetActions, QuickSharingPermissionsBottomSheetDialog.QuickPermissionSharingBottomSheetActions { - - private static final String TAG = "FileDetailSharingFragment"; - private static final String ARG_FILE = "FILE"; - private static final String ARG_USER = "USER"; - - private OCFile file; - private User user; - private OCCapability capabilities; - - private FileOperationsHelper fileOperationsHelper; - private FileActivity fileActivity; - private FileDataStorageManager fileDataStorageManager; - - private FileDetailsSharingFragmentBinding binding; - - private OnEditShareListener onEditShareListener; - - private ShareeListAdapter internalShareeListAdapter; - - private ShareeListAdapter externalShareeListAdapter; - - @Inject UserAccountManager accountManager; - @Inject ClientFactory clientFactory; - @Inject ViewThemeUtils viewThemeUtils; - @Inject UsersAndGroupsSearchConfig searchConfig; - - public static FileDetailSharingFragment newInstance(OCFile file, User user) { - FileDetailSharingFragment fragment = new FileDetailSharingFragment(); - Bundle args = new Bundle(); - args.putParcelable(ARG_FILE, file); - args.putParcelable(ARG_USER, user); - fragment.setArguments(args); - return fragment; - } - - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); +package com.owncloud.android.ui.fragment + +import android.Manifest +import android.accounts.AccountManager +import android.app.Activity +import android.app.SearchManager +import android.content.Context +import android.content.Intent +import android.graphics.drawable.Drawable +import android.net.Uri +import android.os.Bundle +import android.provider.ContactsContract +import android.text.InputType +import android.text.TextUtils +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.animation.AnimationUtils +import androidx.activity.result.ActivityResult +import androidx.activity.result.contract.ActivityResultContracts +import androidx.annotation.VisibleForTesting +import androidx.appcompat.widget.SearchView +import androidx.core.view.size +import androidx.fragment.app.Fragment +import androidx.recyclerview.widget.LinearLayoutManager +import com.nextcloud.android.common.ui.theme.utils.ColorRole +import com.nextcloud.client.account.User +import com.nextcloud.client.account.UserAccountManager +import com.nextcloud.client.di.Injectable +import com.nextcloud.client.network.ClientFactory +import com.nextcloud.client.utils.IntentUtil +import com.nextcloud.utils.extensions.getParcelableArgument +import com.nextcloud.utils.extensions.mergeDistinctByToken +import com.nextcloud.utils.extensions.setVisibleIf +import com.nextcloud.utils.mdm.MDMConfig.shareViaUser +import com.owncloud.android.R +import com.owncloud.android.databinding.FileDetailsSharingFragmentBinding +import com.owncloud.android.datamodel.FileDataStorageManager +import com.owncloud.android.datamodel.OCFile +import com.owncloud.android.datamodel.SharesType +import com.owncloud.android.datamodel.e2e.v2.decrypted.DecryptedFolderMetadataFile +import com.owncloud.android.lib.common.OwnCloudAccount +import com.owncloud.android.lib.common.accounts.AccountUtils +import com.owncloud.android.lib.common.operations.RemoteOperationResult +import com.owncloud.android.lib.common.utils.Log_OC +import com.owncloud.android.lib.resources.shares.OCShare +import com.owncloud.android.lib.resources.shares.ShareType +import com.owncloud.android.lib.resources.status.NextcloudVersion +import com.owncloud.android.lib.resources.status.OCCapability +import com.owncloud.android.operations.RefreshFolderOperation +import com.owncloud.android.providers.UsersAndGroupsSearchConfig +import com.owncloud.android.ui.activity.FileActivity +import com.owncloud.android.ui.adapter.ShareeListAdapter +import com.owncloud.android.ui.adapter.ShareeListAdapterListener +import com.owncloud.android.ui.asynctasks.RetrieveHoverCardAsyncTask +import com.owncloud.android.ui.dialog.SharePasswordDialogFragment +import com.owncloud.android.ui.dialog.SharePasswordDialogFragment.Companion.newInstance +import com.owncloud.android.ui.fragment.QuickSharingPermissionsBottomSheetDialog.QuickPermissionSharingBottomSheetActions +import com.owncloud.android.ui.fragment.share.RemoteShareRepository +import com.owncloud.android.ui.fragment.share.ShareRepository +import com.owncloud.android.ui.fragment.util.FileDetailSharingFragmentHelper +import com.owncloud.android.ui.helpers.FileOperationsHelper +import com.owncloud.android.utils.ClipboardUtil.copyToClipboard +import com.owncloud.android.utils.DisplayUtils +import com.owncloud.android.utils.DisplayUtils.AvatarGenerationListener +import com.owncloud.android.utils.PermissionUtil.checkSelfPermission +import com.owncloud.android.utils.theme.ViewThemeUtils +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings +import javax.inject.Inject + +class FileDetailSharingFragment : Fragment(), ShareeListAdapterListener, AvatarGenerationListener, Injectable, + FileDetailsSharingMenuBottomSheetActions, QuickPermissionSharingBottomSheetActions { + private var file: OCFile? = null + private var user: User? = null + private var capabilities: OCCapability? = null + + private var fileOperationsHelper: FileOperationsHelper? = null + private var fileActivity: FileActivity? = null + private var fileDataStorageManager: FileDataStorageManager? = null + + private var binding: FileDetailsSharingFragmentBinding? = null + + private var onEditShareListener: OnEditShareListener? = null + + private var internalShareeListAdapter: ShareeListAdapter? = null + + private var externalShareeListAdapter: ShareeListAdapter? = null + + @Inject + lateinit var accountManager: UserAccountManager + + @Inject + lateinit var clientFactory: ClientFactory + + @Inject + lateinit var viewThemeUtils: ViewThemeUtils + + @Inject + lateinit var searchConfig: UsersAndGroupsSearchConfig + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) if (savedInstanceState != null) { - file = BundleExtensionsKt.getParcelableArgument(savedInstanceState, ARG_FILE, OCFile.class); - user = BundleExtensionsKt.getParcelableArgument(savedInstanceState, ARG_USER, User.class); + file = savedInstanceState.getParcelableArgument(ARG_FILE, OCFile::class.java) + user = savedInstanceState.getParcelableArgument(ARG_USER, User::class.java) } else { - Bundle arguments = getArguments(); + val arguments = getArguments() if (arguments != null) { - file = BundleExtensionsKt.getParcelableArgument(arguments, ARG_FILE, OCFile.class); - user = BundleExtensionsKt.getParcelableArgument(arguments, ARG_USER, User.class); + file = arguments.getParcelableArgument(ARG_FILE, OCFile::class.java) + user = arguments.getParcelableArgument(ARG_USER, User::class.java) } } - if (file == null) { - throw new IllegalArgumentException("File may not be null"); - } - - if (user == null) { - throw new IllegalArgumentException("Account may not be null"); - } - - fileActivity = (FileActivity) getActivity(); + requireNotNull(file) { "File may not be null" } + requireNotNull(user) { "Account may not be null" } - if (fileActivity == null) { - throw new IllegalArgumentException("FileActivity may not be null"); - } + fileActivity = activity as FileActivity? + requireNotNull(fileActivity) { "FileActivity may not be null" } } - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) if (fileActivity == null) { - return; + return } - fileDataStorageManager = fileActivity.getStorageManager(); - fileOperationsHelper = fileActivity.getFileOperationsHelper(); + fileDataStorageManager = fileActivity?.storageManager + fileOperationsHelper = fileActivity?.fileOperationsHelper // start animation before loading process - final Animation blinkAnimation = AnimationUtils.loadAnimation(requireContext(), R.anim.blink); - binding.shimmerLayout.getRoot().startAnimation(blinkAnimation); + val blinkAnimation = AnimationUtils.loadAnimation(requireContext(), R.anim.blink) + binding?.shimmerLayout?.getRoot()?.startAnimation(blinkAnimation) - AccountManager accountManager = AccountManager.get(requireContext()); - String userId = accountManager.getUserData(user.toPlatformAccount(), - com.owncloud.android.lib.common.accounts.AccountUtils.Constants.KEY_USER_ID); + val accountManager = AccountManager.get(requireContext()) + val userId = accountManager.getUserData( + user?.toPlatformAccount(), + AccountUtils.Constants.KEY_USER_ID + ) // internal shares - internalShareeListAdapter = new ShareeListAdapter(fileActivity, - new ArrayList<>(), - this, - userId, - user, - viewThemeUtils, - file.isEncrypted(), - SharesType.INTERNAL); - internalShareeListAdapter.setHasStableIds(true); - binding.sharesListInternal.setAdapter(internalShareeListAdapter); - binding.sharesListInternal.setLayoutManager(new LinearLayoutManager(requireContext())); + internalShareeListAdapter = ShareeListAdapter( + fileActivity!!, + ArrayList(), + this, + userId, + user, + viewThemeUtils, + file?.isEncrypted == true, + SharesType.INTERNAL + ) + internalShareeListAdapter?.setHasStableIds(true) + binding?.sharesListInternal?.setAdapter(internalShareeListAdapter) + binding?.sharesListInternal?.setLayoutManager(LinearLayoutManager(requireContext())) // external shares - externalShareeListAdapter = new ShareeListAdapter(fileActivity, - new ArrayList<>(), - this, - userId, - user, - viewThemeUtils, - file.isEncrypted(), - SharesType.EXTERNAL); - externalShareeListAdapter.setHasStableIds(true); - binding.sharesListExternal.setAdapter(externalShareeListAdapter); - binding.sharesListExternal.setLayoutManager(new LinearLayoutManager(requireContext())); - binding.pickContactEmailBtn.setOnClickListener(v -> checkContactPermission()); + externalShareeListAdapter = ShareeListAdapter( + fileActivity!!, + ArrayList(), + this, + userId, + user, + viewThemeUtils, + file?.isEncrypted == true, + SharesType.EXTERNAL + ) + externalShareeListAdapter!!.setHasStableIds(true) + binding?.sharesListExternal?.setAdapter(externalShareeListAdapter) + binding?.sharesListExternal?.setLayoutManager(LinearLayoutManager(requireContext())) + binding?.pickContactEmailBtn?.setOnClickListener { checkContactPermission() } // start loading process - fetchSharees(); + fetchSharees() - setupView(); + setupView() } - private void fetchSharees() { - final var activity = fileActivity; - if (activity == null) { - return; - } + private fun fetchSharees() { + val activity = fileActivity ?: return + val clientRepository = activity.clientRepository ?: return + val storageManager = fileDataStorageManager ?: return + val remotePath = file?.remotePath ?: return - final var clientRepository = activity.getClientRepository(); - if (clientRepository == null) { - return; - } - - final var storageManager = fileDataStorageManager; - if (storageManager == null) { - return; - } - - ShareRepository shareRepository = new RemoteShareRepository(clientRepository, activity, storageManager); - shareRepository.fetchSharees(file.getRemotePath(), () -> { + val shareRepository: ShareRepository = RemoteShareRepository(clientRepository, activity, storageManager) + shareRepository.fetchSharees(remotePath, { if (binding == null) { - return Unit.INSTANCE; + return@fetchSharees } - - refreshCapabilitiesFromDB(); - refreshSharesFromDB(); - stopLoadingAnimationAndShowShareContainer(); - return Unit.INSTANCE; - }, () -> { + refreshCapabilitiesFromDB() + refreshSharesFromDB() + stopLoadingAnimationAndShowShareContainer() + }, { if (binding == null) { - return Unit.INSTANCE; + return@fetchSharees } - - stopLoadingAnimationAndShowShareContainer(); - DisplayUtils.showSnackMessage(this, R.string.error_fetching_sharees); - return Unit.INSTANCE; - }); + stopLoadingAnimationAndShowShareContainer() + DisplayUtils.showSnackMessage(this, R.string.error_fetching_sharees) + }) } // stop loading animation - private void stopLoadingAnimationAndShowShareContainer() { + private fun stopLoadingAnimationAndShowShareContainer() { if (binding == null) { - return; + return } - final LinearLayout shimmerLayout = binding.shimmerLayout.getRoot(); - shimmerLayout.clearAnimation(); - shimmerLayout.setVisibility(View.GONE); + val shimmerLayout = binding!!.shimmerLayout.getRoot() + shimmerLayout.clearAnimation() + shimmerLayout.visibility = View.GONE - binding.shareContainer.setVisibility(View.VISIBLE); + binding?.shareContainer?.visibility = View.VISIBLE } - @Override - public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - binding = FileDetailsSharingFragmentBinding.inflate(inflater, container, false); - return binding.getRoot(); + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + binding = FileDetailsSharingFragmentBinding.inflate(inflater, container, false) + return binding!!.getRoot() } - @Override - public void onDestroyView() { - super.onDestroyView(); - binding = null; + override fun onDestroyView() { + super.onDestroyView() + binding = null } - @Override - public void onAttach(@NonNull Context context) { - super.onAttach(context); - if (!(getActivity() instanceof FileActivity)) { - throw new IllegalArgumentException("Calling activity must be of type FileActivity"); - } + override fun onAttach(context: Context) { + super.onAttach(context) + require(activity is FileActivity) { "Calling activity must be of type FileActivity" } try { - onEditShareListener = (OnEditShareListener) context; - } catch (Exception e) { - throw new IllegalArgumentException("Calling activity must implement the interface" + e); - } - } - - @Override - public void onStart() { - super.onStart(); - searchConfig.setSearchOnlyUsers(file.isEncrypted()); - } - - @Override - public void onStop() { - super.onStop(); - searchConfig.reset(); - } - - private void resetSearchView() { - toggleSearchViewEnable(binding.searchView, true); - binding.searchView.setInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); - binding.searchView.setQueryHint(null); - binding.searchView.setQuery("", false); - binding.pickContactEmailBtn.setVisibility(View.VISIBLE); - } - - private void setupView() { - resetSearchView(); - setShareWithYou(); - - OCFile parentFile = fileDataStorageManager.getFileById(file.getParentId()); - - FileDetailSharingFragmentHelper.setupSearchView( - (SearchManager) fileActivity.getSystemService(Context.SEARCH_SERVICE), - binding.searchView, - fileActivity.getComponentName()); - viewThemeUtils.material.themeSearchCardView(binding.searchCardWrapper); - viewThemeUtils.files.themeContentSearchView(binding.searchView); - viewThemeUtils.platform.colorImageView(binding.searchViewIcon, ColorRole.ON_SURFACE_VARIANT); - viewThemeUtils.platform.colorImageView(binding.pickContactEmailBtn, ColorRole.ON_SURFACE_VARIANT); - - viewThemeUtils.material.colorMaterialButtonPrimaryOutlined(binding.sendCopyBtn); - - viewThemeUtils.material.colorMaterialButtonPrimaryBorderless(binding.sharesListInternalShowAll); - viewThemeUtils.material.colorMaterialTextButton(binding.sharesListInternalShowAll); - binding.sharesListInternalShowAll.setOnClickListener(view -> { - internalShareeListAdapter.toggleShowAll(); - int textRes = internalShareeListAdapter.isShowAll() ? R.string.show_less : R.string.show_all; - binding.sharesListInternalShowAll.setText(textRes); - }); - - viewThemeUtils.material.colorMaterialButtonPrimaryOutlined(binding.createLink); - - viewThemeUtils.material.colorMaterialButtonPrimaryBorderless(binding.sharesListExternalShowAll); - viewThemeUtils.material.colorMaterialTextButton(binding.sharesListExternalShowAll); - binding.sharesListExternalShowAll.setOnClickListener(view -> { - externalShareeListAdapter.toggleShowAll(); - int textRes = externalShareeListAdapter.isShowAll() ? R.string.show_less : R.string.show_all; - binding.sharesListExternalShowAll.setText(textRes); - }); - - if (file.canReshare() && !FileDetailSharingFragmentHelper.isPublicShareDisabled(capabilities)) { - if (file.isEncrypted() || (parentFile != null && parentFile.isEncrypted())) { - binding.internalShareHeadline.setText(getResources().getString(R.string.internal_share_headline_end_to_end_encrypted)); - binding.internalShareDescription.setVisibility(View.VISIBLE); - binding.externalSharesHeadline.setText(getResources().getString(R.string.create_end_to_end_encrypted_share_title)); - - fetchE2EECounter(() -> { - if (binding == null) { - return; - } - - if (file.getE2eCounter() == -1) { - // V1 cannot share - binding.searchContainer.setVisibility(View.GONE); - binding.createLink.setVisibility(View.GONE); - } else { - binding.createLink.setText(R.string.add_new_secure_file_drop); - binding.searchView.setQueryHint(getResources().getString(R.string.secure_share_search)); - - if (file.isSharedViaLink()) { - binding.searchView.setQueryHint(getResources().getString(R.string.share_not_allowed_when_file_drop)); - binding.searchView.setInputType(InputType.TYPE_NULL); - toggleSearchViewEnable(binding.searchView, false); - } - } - }); + onEditShareListener = context as OnEditShareListener + } catch (e: Exception) { + throw IllegalArgumentException("Calling activity must implement the interface$e") + } + } + + override fun onStart() { + super.onStart() + searchConfig.searchOnlyUsers = (file?.isEncrypted == true) + } + + override fun onStop() { + super.onStop() + searchConfig.reset() + } + + private fun resetSearchView() { + binding?.run { + toggleSearchViewEnable(searchView, true) + searchView.inputType = InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS + searchView.setQueryHint(null) + searchView.setQuery("", false) + pickContactEmailBtn.setVisibility(View.VISIBLE) + } + } + + private fun setupView() { + resetSearchView() + setShareWithYou() + + binding?.run { + val parentFile = file?.parentId?.let { fileDataStorageManager?.getFileById(it) } + + FileDetailSharingFragmentHelper.setupSearchView( + fileActivity?.getSystemService(Context.SEARCH_SERVICE) as SearchManager?, + searchView, + fileActivity?.componentName + ) + + viewThemeUtils.material.themeSearchCardView(searchCardWrapper) + viewThemeUtils.files.themeContentSearchView(searchView) + viewThemeUtils.platform.colorImageView(searchViewIcon, ColorRole.ON_SURFACE_VARIANT) + viewThemeUtils.platform.colorImageView(pickContactEmailBtn, ColorRole.ON_SURFACE_VARIANT) + + viewThemeUtils.material.colorMaterialButtonPrimaryOutlined(sendCopyBtn) + + viewThemeUtils.material.colorMaterialButtonPrimaryBorderless(sharesListInternalShowAll) + viewThemeUtils.material.colorMaterialTextButton(sharesListInternalShowAll) + sharesListInternalShowAll.setOnClickListener { + internalShareeListAdapter?.toggleShowAll() + val textRes = if (internalShareeListAdapter?.isShowAll == true) R.string.show_less else R.string.show_all + sharesListInternalShowAll.setText(textRes) + } + + viewThemeUtils.material.colorMaterialButtonPrimaryOutlined(createLink) + + viewThemeUtils.material.colorMaterialButtonPrimaryBorderless(sharesListExternalShowAll) + sharesListExternalShowAll.let { viewThemeUtils.material.colorMaterialTextButton(it) } + sharesListExternalShowAll.setOnClickListener { + externalShareeListAdapter!!.toggleShowAll() + val textRes = if (externalShareeListAdapter?.isShowAll == true) R.string.show_less else R.string.show_all + sharesListExternalShowAll.setText(textRes) + } + + if (file?.canReshare() == true && !FileDetailSharingFragmentHelper.isPublicShareDisabled(capabilities)) { + if (file?.isEncrypted == true || (parentFile != null && parentFile.isEncrypted)) { + internalShareHeadline.text = resources.getString(R.string.internal_share_headline_end_to_end_encrypted) + internalShareDescription.visibility = View.VISIBLE + externalSharesHeadline.text = resources.getString(R.string.create_end_to_end_encrypted_share_title) + + fetchE2EECounter { + if (binding == null) { + return@fetchE2EECounter + } + if (file?.e2eCounter == -1L) { + // V1 cannot share + searchContainer.visibility = View.GONE + createLink.visibility = View.GONE + } else { + createLink.setText(R.string.add_new_secure_file_drop) + searchView.setQueryHint(resources.getString(R.string.secure_share_search)) + + if (file?.isSharedViaLink == true) { + searchView.setQueryHint(resources.getString(R.string.share_not_allowed_when_file_drop)) + searchView.inputType = InputType.TYPE_NULL + toggleSearchViewEnable(searchView, false) + } + } + } + } else { + createLink.setText(R.string.create_link) + searchView.setQueryHint(getResources().getString(R.string.share_search_internal)) + } + + createLink.setOnClickListener(View.OnClickListener { v: View? -> createPublicShareLink() }) } else { - binding.createLink.setText(R.string.create_link); - binding.searchView.setQueryHint(getResources().getString(R.string.share_search_internal)); + searchView.setQueryHint(getResources().getString(R.string.resharing_is_not_allowed)) + createLink.visibility = View.GONE + externalSharesHeadline.visibility = View.GONE + searchView.inputType = InputType.TYPE_NULL + pickContactEmailBtn.setVisibility(View.GONE) + toggleSearchViewEnable(searchView, false) + createLink.setOnClickListener(null) } - binding.createLink.setOnClickListener(v -> createPublicShareLink()); - - } else { - binding.searchView.setQueryHint(getResources().getString(R.string.resharing_is_not_allowed)); - binding.createLink.setVisibility(View.GONE); - binding.externalSharesHeadline.setVisibility(View.GONE); - binding.searchView.setInputType(InputType.TYPE_NULL); - binding.pickContactEmailBtn.setVisibility(View.GONE); - toggleSearchViewEnable(binding.searchView, false); - binding.createLink.setOnClickListener(null); + checkShareViaUser() + + if (file?.isFolder == true) { + sendCopyBtn.visibility = View.GONE + } + sendCopyBtn.setOnClickListener { + startActivity( + Intent.createChooser( + IntentUtil.createSendIntent(requireContext(), file!!), + requireContext().getString(R.string.activity_chooser_send_file_title) + ) + ) + } } - checkShareViaUser(); - if (file.isFolder()) { - binding.sendCopyBtn.setVisibility(View.GONE); - } - binding.sendCopyBtn.setOnClickListener(v -> - startActivity(Intent.createChooser(IntentUtil.createSendIntent(requireContext(), file), - requireContext().getString(R.string.activity_chooser_send_file_title))) - ); } - private void fetchE2EECounter(Runnable onComplete) { - final Context context = requireContext(); + private fun fetchE2EECounter(onComplete: Runnable?) { + val context = requireContext() - new Thread(() -> { + Thread { try { - OwnCloudClient client = clientFactory.create(user); - Object metadata = RefreshFolderOperation.getDecryptedFolderMetadata(true, file, client, user, context); - if (metadata instanceof DecryptedFolderMetadataFile decryptedMetadata) { - file.setE2eCounter(decryptedMetadata.getMetadata().getCounter()); - fileDataStorageManager.saveFile(file); + val client = clientFactory.create(user) + val metadata = RefreshFolderOperation.getDecryptedFolderMetadata(true, file, client, user, context) + if (metadata is DecryptedFolderMetadataFile) { + file?.setE2eCounter(metadata.metadata.counter) + fileDataStorageManager?.saveFile(file) } - } catch (Exception e) { - Log_OC.e(TAG, "Error refreshing E2E counter: " + e.getMessage()); + } catch (e: Exception) { + Log_OC.e(TAG, "Error refreshing E2E counter: " + e.message) } - - final var activity = getActivity(); - if (activity != null) { - activity.runOnUiThread(onComplete); - } - }).start(); + val activity = getActivity() + activity?.runOnUiThread(onComplete) + }.start() } - private void checkShareViaUser() { - if (!MDMConfig.INSTANCE.shareViaUser(requireContext())) { - binding.searchContainer.setVisibility(View.GONE); + private fun checkShareViaUser() { + if (!shareViaUser(requireContext())) { + binding?.searchContainer?.visibility = View.GONE } } - private void toggleSearchViewEnable(View view, boolean enable) { - view.setEnabled(enable); - if (view instanceof ViewGroup viewGroup) { - for (int i = 0; i < viewGroup.getChildCount(); i++) { - toggleSearchViewEnable(viewGroup.getChildAt(i), enable); + private fun toggleSearchViewEnable(view: View, enable: Boolean) { + view.setEnabled(enable) + if (view is ViewGroup) { + for (i in 0.. + DisplayUtils.setAvatar( + it, + userId, + this@FileDetailSharingFragment, + resources.getDimension( + R.dimen.file_list_item_avatar_icon_radius + ), + resources, + sharedWithYouAvatar, + context + ) + } + } + sharedWithYouAvatar.setVisibility(View.VISIBLE) + + val note = file?.getNote() + + if (!TextUtils.isEmpty(note)) { + sharedWithYouNote.text = file?.getNote() + sharedWithYouNoteContainer.visibility = View.VISIBLE + } else { + sharedWithYouNoteContainer.visibility = View.GONE + } } } } - @Override - public void copyInternalLink() { - OwnCloudAccount account = accountManager.getCurrentOwnCloudAccount(); + override fun copyInternalLink() { + val account = accountManager.getCurrentOwnCloudAccount() if (account == null) { - DisplayUtils.showSnackMessage(this, R.string.could_not_retrieve_url); - return; + DisplayUtils.showSnackMessage(this, R.string.could_not_retrieve_url) + return } - FileDisplayActivity.showShareLinkDialog(fileActivity, file, createInternalLink(account, file)); + file?.let { FileActivity.showShareLinkDialog(fileActivity, file, createInternalLink(account, it)) } } - private String createInternalLink(OwnCloudAccount account, OCFile file) { - return account.getBaseUri() + "/index.php/f/" + file.getLocalId(); + private fun createInternalLink(account: OwnCloudAccount, file: OCFile): String { + return account.baseUri.toString() + "/index.php/f/" + file.localId } - @Override - public void createPublicShareLink() { - if (capabilities != null && (capabilities.getFilesSharingPublicPasswordEnforced().isTrue() || - capabilities.getFilesSharingPublicAskForOptionalPassword().isTrue())) { + override fun createPublicShareLink() { + if (capabilities != null && (capabilities?.filesSharingPublicPasswordEnforced?.isTrue == true || + capabilities?.filesSharingPublicAskForOptionalPassword?.isTrue == true) + ) { // password enforced by server, request to the user before trying to create - requestPasswordForShareViaLink(true, - capabilities.getFilesSharingPublicAskForOptionalPassword().isTrue()); - + requestPasswordForShareViaLink( + true, + capabilities!!.filesSharingPublicAskForOptionalPassword.isTrue + ) } else { // create without password if not enforced by server or we don't know if enforced; - fileOperationsHelper.shareFileViaPublicShare(file, null); + fileOperationsHelper?.shareFileViaPublicShare(file, null) } } - @Override - public void createSecureFileDrop() { - fileOperationsHelper.shareFolderViaSecureFileDrop(file); + override fun createSecureFileDrop() { + fileOperationsHelper?.shareFolderViaSecureFileDrop(file!!) } - private void showSendLinkTo(OCShare publicShare) { - if (file.isSharedViaLink()) { - if (TextUtils.isEmpty(publicShare.getShareLink())) { - fileOperationsHelper.getFileWithLink(file, viewThemeUtils); + private fun showSendLinkTo(publicShare: OCShare) { + if (file?.isSharedViaLink == true) { + if (TextUtils.isEmpty(publicShare.shareLink)) { + fileOperationsHelper?.getFileWithLink(file!!, viewThemeUtils) } else { - FileDisplayActivity.showShareLinkDialog(fileActivity, file, publicShare.getShareLink()); + FileActivity.showShareLinkDialog(fileActivity, file, publicShare.shareLink) } } } - public void copyLink(OCShare share) { - if (file.isSharedViaLink()) { - if (TextUtils.isEmpty(share.getShareLink())) { - fileOperationsHelper.getFileWithLink(file, viewThemeUtils); + override fun copyLink(share: OCShare) { + if (file?.isSharedViaLink == true) { + if (TextUtils.isEmpty(share.shareLink)) { + fileOperationsHelper?.getFileWithLink(file!!, viewThemeUtils) } else { - ClipboardUtil.copyToClipboard(requireActivity(), share.getShareLink()); + copyToClipboard(requireActivity(), share.shareLink) } } } - /** - * show share action bottom sheet - * - * @param share - */ - @Override @VisibleForTesting - public void showSharingMenuActionSheet(OCShare share) { - if (fileActivity != null && !fileActivity.isFinishing()) { - new FileDetailSharingMenuBottomSheetDialog(fileActivity, this, share, viewThemeUtils, file.isEncrypted()).show(); - } - } - - /** - * show quick sharing permission dialog - * - * @param share - */ - @Override - public void showPermissionsDialog(OCShare share) { - new QuickSharingPermissionsBottomSheetDialog(fileActivity, this, share, viewThemeUtils, file.isEncrypted()).show(); - } - - /** - * Updates the UI after the result of an update operation on the edited {@link OCFile}. - * - * @param result {@link RemoteOperationResult} of an update on the edited {@link OCFile} sharing information. - * @param file the edited {@link OCFile} - * @see #onUpdateShareInformation(RemoteOperationResult) - */ - public void onUpdateShareInformation(RemoteOperationResult result, OCFile file) { - this.file = file; - - onUpdateShareInformation(result); - } - - /** - * Updates the UI after the result of an update operation on the edited {@link OCFile}. Keeps the current {@link - * OCFile held by this fragment}. - * - * @param result {@link RemoteOperationResult} of an update on the edited {@link OCFile} sharing information. - * @see #onUpdateShareInformation(RemoteOperationResult, OCFile) - */ - public void onUpdateShareInformation(RemoteOperationResult result) { + override fun showSharingMenuActionSheet(share: OCShare?) { + if (fileActivity != null && fileActivity?.isFinishing == false) { + FileDetailSharingMenuBottomSheetDialog( + fileActivity, + this, + share, + viewThemeUtils, + file?.isEncrypted == true + ).show() + } + } + + override fun showPermissionsDialog(share: OCShare?) { + QuickSharingPermissionsBottomSheetDialog(fileActivity, this, share, viewThemeUtils, file?.isEncrypted == true).show() + } + + fun onUpdateShareInformation(result: RemoteOperationResult<*>, file: OCFile?) { + this.file = file + + onUpdateShareInformation(result) + } + + fun onUpdateShareInformation(result: RemoteOperationResult<*>) { if (binding == null) { - return; + return } - if (result.isSuccess()) { - refreshUiFromDB(); + if (result.isSuccess) { + refreshUiFromDB() } else { - setupView(); + setupView() } } - /** - * Get {@link OCShare} instance from DB and updates the UI. - */ - private void refreshUiFromDB() { - refreshSharesFromDB(); - // Updates UI with new state - setupView(); + private fun refreshUiFromDB() { + refreshSharesFromDB() + setupView() } - private void unShareWith(OCShare share) { - fileOperationsHelper.unShareShare(file, share.getId()); + private fun unShareWith(share: OCShare) { + fileOperationsHelper?.unShareShare(file, share.id) } - /** - * Starts a dialog that requests a password to the user to protect a share link. - * - * @param createShare When 'true', the request for password will be followed by the creation of a new public - * link; when 'false', a public share is assumed to exist, and the password is bound to it. - * @param askForPassword if true, password is optional - */ - public void requestPasswordForShareViaLink(boolean createShare, boolean askForPassword) { - SharePasswordDialogFragment dialog = SharePasswordDialogFragment.newInstance(file, - createShare, - askForPassword); - dialog.show(getChildFragmentManager(), SharePasswordDialogFragment.PASSWORD_FRAGMENT); + fun requestPasswordForShareViaLink(createShare: Boolean, askForPassword: Boolean) { + val dialog = newInstance( + file, + createShare, + askForPassword + ) + dialog.show(getChildFragmentManager(), SharePasswordDialogFragment.PASSWORD_FRAGMENT) } - @Override - public void requestPasswordForShare(OCShare share, boolean askForPassword) { - SharePasswordDialogFragment dialog = SharePasswordDialogFragment.newInstance(share, askForPassword); - dialog.show(getChildFragmentManager(), SharePasswordDialogFragment.PASSWORD_FRAGMENT); + override fun requestPasswordForShare(share: OCShare?, askForPassword: Boolean) { + val dialog = newInstance(share, askForPassword) + dialog.show(getChildFragmentManager(), SharePasswordDialogFragment.PASSWORD_FRAGMENT) } - @Override - public void showProfileBottomSheet(User user, String shareWith) { - if (user.getServer().getVersion().isNewerOrEqual(NextcloudVersion.nextcloud_23)) { - new RetrieveHoverCardAsyncTask(user, - shareWith, - fileActivity, - clientFactory, - viewThemeUtils).execute(); + override fun showProfileBottomSheet(user: User, shareWith: String?) { + if (user.server.version.isNewerOrEqual(NextcloudVersion.nextcloud_23)) { + RetrieveHoverCardAsyncTask( + user, + shareWith, + fileActivity, + clientFactory, + viewThemeUtils + ).execute() } } - public void refreshCapabilitiesFromDB() { - capabilities = fileDataStorageManager.getCapability(user.getAccountName()); + fun refreshCapabilitiesFromDB() { + capabilities = fileDataStorageManager?.getCapability(user?.accountName) } - /** - * Get public link from the DB to fill in the "Share link" section in the UI. Takes into account server capabilities - * before reading database. - */ @SuppressFBWarnings("PSC") - public void refreshSharesFromDB() { + fun refreshSharesFromDB() { if (binding == null) { - return; + return } - OCFile newFile = fileDataStorageManager.getFileById(file.getFileId()); + val newFile = file?.fileId?.let { fileDataStorageManager?.getFileById(it) } if (newFile != null) { - file = newFile; + file = newFile } if (internalShareeListAdapter == null) { - DisplayUtils.showSnackMessage(this, R.string.could_not_retrieve_shares); - return; + DisplayUtils.showSnackMessage(this, R.string.could_not_retrieve_shares) + return } - internalShareeListAdapter.removeAll(); + internalShareeListAdapter!!.removeAll() // to show share with users/groups info - List shares = fileDataStorageManager.getSharesWithForAFile(file.getRemotePath(), - user.getAccountName()); - - List internalShares = new ArrayList<>(); - List externalShares = new ArrayList<>(); - - for (OCShare share : shares) { - if (share.getShareType() != null) { - switch (share.getShareType()) { - case PUBLIC_LINK: - case FEDERATED_GROUP: - case FEDERATED: - case EMAIL: - externalShares.add(share); - break; - - default: - internalShares.add(share); - break; + val shares = fileDataStorageManager?.getSharesWithForAFile( + file?.remotePath, + user?.accountName + ) ?: listOf() + + val internalShares = ArrayList() + val externalShares = ArrayList() + + for (share in shares) { + if (share.shareType != null) { + when (share.shareType) { + ShareType.PUBLIC_LINK, ShareType.FEDERATED_GROUP, ShareType.FEDERATED, ShareType.EMAIL -> externalShares.add( + share + ) + + else -> internalShares.add(share) } } } - - internalShareeListAdapter.addShares(internalShares); - ViewExtensionsKt.setVisibleIf(binding.sharesListInternalShowAll, internalShareeListAdapter.shares.size() > 3); - addExternalAndPublicShares(externalShares); - ViewExtensionsKt.setVisibleIf(binding.sharesListExternalShowAll, externalShareeListAdapter.shares.size() > 3); + internalShareeListAdapter?.addShares(internalShares) + internalShareeListAdapter?.shares?.size?.let { binding?.sharesListInternalShowAll?.setVisibleIf(it > 3) } + + addExternalAndPublicShares(externalShares) + externalShareeListAdapter?.shares?.size?.let { binding?.sharesListExternalShowAll?.setVisibleIf(it > 3) } } - private void addExternalAndPublicShares(List externalShares) { - final var publicShares = fileDataStorageManager.getSharesByPathAndType(file.getRemotePath(), ShareType.PUBLIC_LINK, ""); - externalShareeListAdapter.removeAll(); - final var shares = OCShareExtensionsKt.mergeDistinctByToken(externalShares, publicShares); - externalShareeListAdapter.addShares(shares); + private fun addExternalAndPublicShares(externalShares: MutableList) { + val publicShares = + fileDataStorageManager?.getSharesByPathAndType(file?.remotePath, ShareType.PUBLIC_LINK, "") + externalShareeListAdapter?.removeAll() + publicShares?.let { + externalShares.mergeDistinctByToken(it) + externalShareeListAdapter?.addShares(it) + } } - private void checkContactPermission() { - if (PermissionUtil.checkSelfPermission(requireActivity(), Manifest.permission.READ_CONTACTS)) { - pickContactEmail(); + private fun checkContactPermission() { + if (checkSelfPermission(requireActivity(), Manifest.permission.READ_CONTACTS)) { + pickContactEmail() } else { - requestContactPermissionLauncher.launch(Manifest.permission.READ_CONTACTS); + requestContactPermissionLauncher.launch(Manifest.permission.READ_CONTACTS) } } - private void pickContactEmail() { - Intent intent = new Intent(Intent.ACTION_PICK, ContactsContract.CommonDataKinds.Email.CONTENT_URI); + private fun pickContactEmail() { + val intent = Intent(Intent.ACTION_PICK, ContactsContract.CommonDataKinds.Email.CONTENT_URI) - if (intent.resolveActivity(requireContext().getPackageManager()) != null) { - onContactSelectionResultLauncher.launch(intent); + if (intent.resolveActivity(requireContext().packageManager) != null) { + onContactSelectionResultLauncher.launch(intent) } else { - DisplayUtils.showSnackMessage(this, R.string.file_detail_sharing_fragment_no_contact_app_message); + DisplayUtils.showSnackMessage(this, R.string.file_detail_sharing_fragment_no_contact_app_message) } } - private void handleContactResult(@NonNull Uri contactUri) { + private fun handleContactResult(contactUri: Uri) { // Define the projection to get all email addresses. - String[] projection = {ContactsContract.CommonDataKinds.Email.ADDRESS}; + val projection = arrayOf(ContactsContract.CommonDataKinds.Email.ADDRESS) - Cursor cursor = fileActivity.getContentResolver().query(contactUri, projection, null, null, null); + val cursor = fileActivity?.contentResolver?.query(contactUri, projection, null, null, null) if (cursor != null) { if (cursor.moveToFirst()) { // The contact has only one email address, use it. - int columnIndex = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Email.ADDRESS); + val columnIndex = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Email.ADDRESS) if (columnIndex != -1) { // Use the email address as needed. // email variable contains the selected contact's email address. - String email = cursor.getString(columnIndex); - binding.searchView.post(() -> { + val email = cursor.getString(columnIndex) + binding!!.searchView.post(Runnable { if (binding == null) { - return; + return@Runnable } - - binding.searchView.setQuery(email, false); - binding.searchView.requestFocus(); - }); + binding?.searchView?.setQuery(email, false) + binding?.searchView?.requestFocus() + }) } else { - DisplayUtils.showSnackMessage(this, R.string.email_pick_failed); - Log_OC.e(FileDetailSharingFragment.class.getSimpleName(), "Failed to pick email address."); + DisplayUtils.showSnackMessage(this, R.string.email_pick_failed) + Log_OC.e(FileDetailSharingFragment::class.java.getSimpleName(), "Failed to pick email address.") } } else { - DisplayUtils.showSnackMessage(this, R.string.email_pick_failed); - Log_OC.e(FileDetailSharingFragment.class.getSimpleName(), "Failed to pick email address as no Email found."); + DisplayUtils.showSnackMessage(this, R.string.email_pick_failed) + Log_OC.e( + FileDetailSharingFragment::class.java.getSimpleName(), + "Failed to pick email address as no Email found." + ) } - cursor.close(); + cursor.close() } else { - DisplayUtils.showSnackMessage(this, R.string.email_pick_failed); - Log_OC.e(FileDetailSharingFragment.class.getSimpleName(), "Failed to pick email address as Cursor is null."); + DisplayUtils.showSnackMessage(this, R.string.email_pick_failed) + Log_OC.e( + FileDetailSharingFragment::class.java.getSimpleName(), + "Failed to pick email address as Cursor is null." + ) } } - @Override - public void onSaveInstanceState(@NonNull Bundle outState) { - super.onSaveInstanceState(outState); - outState.putParcelable(ARG_FILE, file); - outState.putParcelable(ARG_USER, user); + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + outState.putParcelable(ARG_FILE, file) + outState.putParcelable(ARG_USER, user) } - @Override - public void avatarGenerated(Drawable avatarDrawable, Object callContext) { + override fun avatarGenerated(avatarDrawable: Drawable?, callContext: Any?) { if (binding == null) { - return; + return } - binding.sharedWithYouAvatar.setImageDrawable(avatarDrawable); + binding?.sharedWithYouAvatar?.setImageDrawable(avatarDrawable) } - @Override - public boolean shouldCallGeneratedCallback(String tag, Object callContext) { - return false; + override fun shouldCallGeneratedCallback(tag: String?, callContext: Any?): Boolean { + return false } - private boolean isReshareForbidden(OCShare share) { - return ShareType.FEDERATED == share.getShareType() || - capabilities != null && capabilities.getFilesSharingResharing().isFalse(); + private fun isReshareForbidden(share: OCShare): Boolean { + return ShareType.FEDERATED == share.shareType || + capabilities != null && capabilities!!.filesSharingResharing.isFalse } @VisibleForTesting - public void search(String query) { - SearchView searchView = requireView().findViewById(R.id.searchView); - searchView.setQuery(query, true); + fun search(query: String?) { + val searchView = requireView().findViewById(R.id.searchView) + searchView.setQuery(query, true) } - @Override - public void advancedPermissions(OCShare share) { - modifyExistingShare(share, FileDetailsSharingProcessFragment.SCREEN_TYPE_PERMISSION); + override fun advancedPermissions(share: OCShare) { + modifyExistingShare(share, FileDetailsSharingProcessFragment.SCREEN_TYPE_PERMISSION) } - @Override - public void sendNewEmail(OCShare share) { - modifyExistingShare(share, FileDetailsSharingProcessFragment.SCREEN_TYPE_NOTE); + override fun sendNewEmail(share: OCShare) { + modifyExistingShare(share, FileDetailsSharingProcessFragment.SCREEN_TYPE_NOTE) } - @Override - public void unShare(OCShare share) { + override fun unShare(share: OCShare) { if (binding == null) { - return; + return } - unShareWith(share); + unShareWith(share) - FileEntity entity = fileDataStorageManager.getFileEntity(file); + val entity = fileDataStorageManager!!.getFileEntity(file) - if (binding.sharesListInternal.getAdapter() instanceof ShareeListAdapter adapter) { - adapter.remove(share); + if (binding?.sharesListInternal?.adapter is ShareeListAdapter) { + val adapter = binding?.sharesListInternal?.adapter as ShareeListAdapter + adapter.remove(share) if (entity != null && adapter.isAdapterEmpty()) { - entity.setSharedWithSharee(0); - fileDataStorageManager.updateFileEntity(entity); + entity.sharedWithSharee = 0 + fileDataStorageManager?.updateFileEntity(entity) } - } else if (binding.sharesListExternal.getAdapter() instanceof ShareeListAdapter adapter) { - adapter.remove(share); + } else if (binding?.sharesListExternal?.adapter is ShareeListAdapter) { + val adapter = binding?.sharesListExternal?.adapter as ShareeListAdapter + adapter.remove(share) if (entity != null && adapter.isAdapterEmpty()) { - entity.setSharedViaLink(0); - fileDataStorageManager.updateFileEntity(entity); + entity.sharedViaLink = 0 + fileDataStorageManager?.updateFileEntity(entity) } } else { - DisplayUtils.showSnackMessage(this, R.string.failed_update_ui); + DisplayUtils.showSnackMessage(this, R.string.failed_update_ui) } } - @Override - public void sendLink(OCShare share) { - if (file.isSharedViaLink() && !TextUtils.isEmpty(share.getShareLink())) { - FileDisplayActivity.showShareLinkDialog(fileActivity, file, share.getShareLink()); + override fun sendLink(share: OCShare) { + if (file?.isSharedViaLink == true && !TextUtils.isEmpty(share.shareLink)) { + FileActivity.showShareLinkDialog(fileActivity, file, share.shareLink) } else { - showSendLinkTo(share); + showSendLinkTo(share) } } - @Override - public void addAnotherLink(OCShare share) { - createPublicShareLink(); + override fun addAnotherLink(share: OCShare?) { + createPublicShareLink() } - private void modifyExistingShare(OCShare share, int screenTypePermission) { - onEditShareListener.editExistingShare(share, screenTypePermission, !isReshareForbidden(share)); + private fun modifyExistingShare(share: OCShare, screenTypePermission: Int) { + onEditShareListener?.editExistingShare(share, screenTypePermission, !isReshareForbidden(share)) } - @Override - public void onQuickPermissionChanged(OCShare share, int permission) { - fileOperationsHelper.setPermissionsToShare(share, permission); + override fun onQuickPermissionChanged(share: OCShare, permission: Int) { + fileOperationsHelper?.setPermissionsToShare(share, permission) } - @Override - public void openShareDetailWithCustomPermissions(OCShare share) { - modifyExistingShare(share, FileDetailsSharingProcessFragment.SCREEN_TYPE_PERMISSION_WITH_CUSTOM_PERMISSION); + override fun openShareDetailWithCustomPermissions(share: OCShare) { + modifyExistingShare(share, FileDetailsSharingProcessFragment.SCREEN_TYPE_PERMISSION_WITH_CUSTOM_PERMISSION) } - //launcher for contact permission - private final ActivityResultLauncher requestContactPermissionLauncher = - registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> { - if (isGranted) { - pickContactEmail(); - } else { - DisplayUtils.showSnackMessage(this, R.string.contact_no_permission); + private val requestContactPermissionLauncher = registerForActivityResult( + ActivityResultContracts.RequestPermission() + ) { isGranted: Boolean -> + if (isGranted) { + pickContactEmail() + } else { + DisplayUtils.showSnackMessage(this, R.string.contact_no_permission) + } + } + + private val onContactSelectionResultLauncher = registerForActivityResult( + ActivityResultContracts.StartActivityForResult() + ) { result: ActivityResult -> + if (result.resultCode == Activity.RESULT_OK) { + val intent = result.data + if (intent == null) { + DisplayUtils.showSnackMessage(this, R.string.email_pick_failed) + return@registerForActivityResult + } + + val contactUri = intent.data + if (contactUri == null) { + DisplayUtils.showSnackMessage(this, R.string.email_pick_failed) + return@registerForActivityResult } - }); - - //launcher to handle contact selection - private final ActivityResultLauncher onContactSelectionResultLauncher = - registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), - result -> { - if (result.getResultCode() == Activity.RESULT_OK) { - Intent intent = result.getData(); - if (intent == null) { - DisplayUtils.showSnackMessage(this, R.string.email_pick_failed); - return; - } - - Uri contactUri = intent.getData(); - if (contactUri == null) { - DisplayUtils.showSnackMessage(this, R.string.email_pick_failed); - return; - } - - handleContactResult(contactUri); - - } - }); - - public interface OnEditShareListener { - void editExistingShare(OCShare share, int screenTypePermission, boolean isReshareShown); - - void onShareProcessClosed(); + + handleContactResult(contactUri) + } + } + + interface OnEditShareListener { + fun editExistingShare(share: OCShare?, screenTypePermission: Int, isReshareShown: Boolean) + + fun onShareProcessClosed() + } + + companion object { + private const val TAG = "FileDetailSharingFragment" + private const val ARG_FILE = "FILE" + private const val ARG_USER = "USER" + + @JvmStatic + fun newInstance(file: OCFile?, user: User?): FileDetailSharingFragment = FileDetailSharingFragment().apply { + arguments = Bundle().apply { + putParcelable(ARG_FILE, file) + putParcelable(ARG_USER, user) + } + } } } From a35f3c9ba00a2380497ecc8ad549bc4dcbfc1c33 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Sat, 23 May 2026 08:42:57 +0300 Subject: [PATCH 03/10] wip Signed-off-by: alperozturk96 --- .../ui/fragment/FileDetailSharingFragment.kt | 28 ++++++---- .../fragment/share/RemoteShareRepository.kt | 53 ++++++++----------- .../ui/fragment/share/ShareRepository.kt | 2 +- 3 files changed, 41 insertions(+), 42 deletions(-) diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.kt b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.kt index 7b7f93f17a76..4e4aca6416d8 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.kt +++ b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.kt @@ -36,6 +36,7 @@ import androidx.annotation.VisibleForTesting import androidx.appcompat.widget.SearchView import androidx.core.view.size import androidx.fragment.app.Fragment +import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager import com.nextcloud.android.common.ui.theme.utils.ColorRole import com.nextcloud.client.account.User @@ -80,6 +81,7 @@ import com.owncloud.android.utils.DisplayUtils.AvatarGenerationListener import com.owncloud.android.utils.PermissionUtil.checkSelfPermission import com.owncloud.android.utils.theme.ViewThemeUtils import edu.umd.cs.findbugs.annotations.SuppressFBWarnings +import kotlinx.coroutines.launch import javax.inject.Inject class FileDetailSharingFragment : Fragment(), ShareeListAdapterListener, AvatarGenerationListener, Injectable, @@ -197,21 +199,25 @@ class FileDetailSharingFragment : Fragment(), ShareeListAdapterListener, AvatarG val storageManager = fileDataStorageManager ?: return val remotePath = file?.remotePath ?: return - val shareRepository: ShareRepository = RemoteShareRepository(clientRepository, activity, storageManager) - shareRepository.fetchSharees(remotePath, { + val shareRepository: ShareRepository = RemoteShareRepository(clientRepository, storageManager) + lifecycleScope.launch { + val result = shareRepository.fetchSharees(remotePath) if (binding == null) { - return@fetchSharees + return@launch } - refreshCapabilitiesFromDB() - refreshSharesFromDB() - stopLoadingAnimationAndShowShareContainer() - }, { - if (binding == null) { - return@fetchSharees + + // success + if (result) { + refreshCapabilitiesFromDB() + refreshSharesFromDB() + stopLoadingAnimationAndShowShareContainer() + return@launch } + + // fail stopLoadingAnimationAndShowShareContainer() - DisplayUtils.showSnackMessage(this, R.string.error_fetching_sharees) - }) + DisplayUtils.showSnackMessage(this@FileDetailSharingFragment, R.string.error_fetching_sharees) + } } // stop loading animation diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/share/RemoteShareRepository.kt b/app/src/main/java/com/owncloud/android/ui/fragment/share/RemoteShareRepository.kt index 2a371c1bcbe7..44130012037e 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/share/RemoteShareRepository.kt +++ b/app/src/main/java/com/owncloud/android/ui/fragment/share/RemoteShareRepository.kt @@ -7,53 +7,46 @@ package com.owncloud.android.ui.fragment.share -import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.lifecycleScope import com.nextcloud.repository.ClientRepository import com.owncloud.android.datamodel.FileDataStorageManager import com.owncloud.android.lib.common.utils.Log_OC import com.owncloud.android.operations.GetSharesForFileOperation import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch import kotlinx.coroutines.withContext class RemoteShareRepository( private val clientRepository: ClientRepository, - lifecycleOwner: LifecycleOwner, private val fileDataStorageManager: FileDataStorageManager ) : ShareRepository { private val tag = "RemoteShareRepository" - private val scope = lifecycleOwner.lifecycleScope - override fun fetchSharees(remotePath: String, onCompleted: () -> Unit, onError: () -> Unit) { - scope.launch(Dispatchers.IO) { - val client = clientRepository.getOwncloudClient() ?: return@launch - val operation = - GetSharesForFileOperation( - path = remotePath, - reshares = true, - subfiles = false, - storageManager = fileDataStorageManager - ) + @Suppress("DEPRECATION") + override suspend fun fetchSharees(remotePath: String): Boolean = withContext(Dispatchers.IO) { + val client = clientRepository.getOwncloudClient() ?: return@withContext false + val operation = + GetSharesForFileOperation( + path = remotePath, + reshares = true, + subfiles = false, + storageManager = fileDataStorageManager + ) - @Suppress("DEPRECATION") - val result = operation.execute(client) + val result = operation.execute(client) - Log_OC.i(tag, "Remote path for the refresh shares: $remotePath") + Log_OC.i(tag, "Remote path for the refresh shares: $remotePath") - withContext(Dispatchers.Main) { - if (result.isSuccess) { - Log_OC.d(tag, "Successfully refreshed shares for the specified remote path.") - onCompleted() - } else { - Log_OC.w( - tag, - "Failed to refresh shares for the specified remote path. " + - "An error occurred during the operation." - ) - onError() - } + withContext(Dispatchers.Main) { + val isSuccess = result.isSuccess + if (isSuccess) { + Log_OC.d(tag, "Successfully refreshed shares for the specified remote path.") + } else { + Log_OC.w( + tag, + "Failed to refresh shares for the specified remote path. " + + "An error occurred during the operation." + ) } + return@withContext isSuccess } } } diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/share/ShareRepository.kt b/app/src/main/java/com/owncloud/android/ui/fragment/share/ShareRepository.kt index 1f34ef5f11ca..4b4036195bf4 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/share/ShareRepository.kt +++ b/app/src/main/java/com/owncloud/android/ui/fragment/share/ShareRepository.kt @@ -8,5 +8,5 @@ package com.owncloud.android.ui.fragment.share interface ShareRepository { - fun fetchSharees(remotePath: String, onCompleted: () -> Unit, onError: () -> Unit) + suspend fun fetchSharees(remotePath: String): Boolean } From eef06ff9b74bf286a49e52a7a6441f44f881b046 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Wed, 3 Jun 2026 12:15:48 +0300 Subject: [PATCH 04/10] wip Signed-off-by: alperozturk96 --- .../ui/fragment/FileDetailSharingFragment.kt | 204 +++++++++++------- 1 file changed, 122 insertions(+), 82 deletions(-) diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.kt b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.kt index 4e4aca6416d8..750a883385bf 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.kt +++ b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.kt @@ -81,7 +81,9 @@ import com.owncloud.android.utils.DisplayUtils.AvatarGenerationListener import com.owncloud.android.utils.PermissionUtil.checkSelfPermission import com.owncloud.android.utils.theme.ViewThemeUtils import edu.umd.cs.findbugs.annotations.SuppressFBWarnings +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import javax.inject.Inject class FileDetailSharingFragment : Fragment(), ShareeListAdapterListener, AvatarGenerationListener, Injectable, @@ -279,78 +281,21 @@ class FileDetailSharingFragment : Fragment(), ShareeListAdapterListener, AvatarG setShareWithYou() binding?.run { - val parentFile = file?.parentId?.let { fileDataStorageManager?.getFileById(it) } - FileDetailSharingFragmentHelper.setupSearchView( fileActivity?.getSystemService(Context.SEARCH_SERVICE) as SearchManager?, searchView, fileActivity?.componentName ) - viewThemeUtils.material.themeSearchCardView(searchCardWrapper) - viewThemeUtils.files.themeContentSearchView(searchView) - viewThemeUtils.platform.colorImageView(searchViewIcon, ColorRole.ON_SURFACE_VARIANT) - viewThemeUtils.platform.colorImageView(pickContactEmailBtn, ColorRole.ON_SURFACE_VARIANT) - - viewThemeUtils.material.colorMaterialButtonPrimaryOutlined(sendCopyBtn) - - viewThemeUtils.material.colorMaterialButtonPrimaryBorderless(sharesListInternalShowAll) - viewThemeUtils.material.colorMaterialTextButton(sharesListInternalShowAll) - sharesListInternalShowAll.setOnClickListener { - internalShareeListAdapter?.toggleShowAll() - val textRes = if (internalShareeListAdapter?.isShowAll == true) R.string.show_less else R.string.show_all - sharesListInternalShowAll.setText(textRes) - } - - viewThemeUtils.material.colorMaterialButtonPrimaryOutlined(createLink) + themeView(this) + setupShareList(this) - viewThemeUtils.material.colorMaterialButtonPrimaryBorderless(sharesListExternalShowAll) - sharesListExternalShowAll.let { viewThemeUtils.material.colorMaterialTextButton(it) } - sharesListExternalShowAll.setOnClickListener { - externalShareeListAdapter!!.toggleShowAll() - val textRes = if (externalShareeListAdapter?.isShowAll == true) R.string.show_less else R.string.show_all - sharesListExternalShowAll.setText(textRes) - } if (file?.canReshare() == true && !FileDetailSharingFragmentHelper.isPublicShareDisabled(capabilities)) { - if (file?.isEncrypted == true || (parentFile != null && parentFile.isEncrypted)) { - internalShareHeadline.text = resources.getString(R.string.internal_share_headline_end_to_end_encrypted) - internalShareDescription.visibility = View.VISIBLE - externalSharesHeadline.text = resources.getString(R.string.create_end_to_end_encrypted_share_title) - - fetchE2EECounter { - if (binding == null) { - return@fetchE2EECounter - } - if (file?.e2eCounter == -1L) { - // V1 cannot share - searchContainer.visibility = View.GONE - createLink.visibility = View.GONE - } else { - createLink.setText(R.string.add_new_secure_file_drop) - searchView.setQueryHint(resources.getString(R.string.secure_share_search)) - - if (file?.isSharedViaLink == true) { - searchView.setQueryHint(resources.getString(R.string.share_not_allowed_when_file_drop)) - searchView.inputType = InputType.TYPE_NULL - toggleSearchViewEnable(searchView, false) - } - } - } - } else { - createLink.setText(R.string.create_link) - searchView.setQueryHint(getResources().getString(R.string.share_search_internal)) - } - - createLink.setOnClickListener(View.OnClickListener { v: View? -> createPublicShareLink() }) + val parentFile = file?.parentId?.let { fileDataStorageManager?.getFileById(it) } + setupShareView(this, parentFile) } else { - searchView.setQueryHint(getResources().getString(R.string.resharing_is_not_allowed)) - createLink.visibility = View.GONE - externalSharesHeadline.visibility = View.GONE - searchView.inputType = InputType.TYPE_NULL - pickContactEmailBtn.setVisibility(View.GONE) - toggleSearchViewEnable(searchView, false) - createLink.setOnClickListener(null) + setupDisabledShareView(this) } checkShareViaUser() @@ -358,6 +303,7 @@ class FileDetailSharingFragment : Fragment(), ShareeListAdapterListener, AvatarG if (file?.isFolder == true) { sendCopyBtn.visibility = View.GONE } + sendCopyBtn.setOnClickListener { startActivity( Intent.createChooser( @@ -367,27 +313,117 @@ class FileDetailSharingFragment : Fragment(), ShareeListAdapterListener, AvatarG ) } } + } + + private fun themeView(binding: FileDetailsSharingFragmentBinding) { + binding.run { + viewThemeUtils.material.run { + themeSearchCardView(searchCardWrapper) + colorMaterialButtonPrimaryOutlined(sendCopyBtn) + colorMaterialButtonPrimaryBorderless(sharesListInternalShowAll) + colorMaterialTextButton(sharesListInternalShowAll) + colorMaterialButtonPrimaryOutlined(createLink) + colorMaterialButtonPrimaryBorderless(sharesListExternalShowAll) + } + viewThemeUtils.platform.run { + colorImageView(searchViewIcon, ColorRole.ON_SURFACE_VARIANT) + colorImageView(pickContactEmailBtn, ColorRole.ON_SURFACE_VARIANT) + } + viewThemeUtils.files.run { + themeContentSearchView(searchView) + } + } + } + + private fun setupShareList(binding: FileDetailsSharingFragmentBinding) { + binding.run { + sharesListInternalShowAll.setOnClickListener { + internalShareeListAdapter?.toggleShowAll() + val textRes = if (internalShareeListAdapter?.isShowAll == true) R.string.show_less else R.string.show_all + sharesListInternalShowAll.setText(textRes) + } + sharesListExternalShowAll.let { viewThemeUtils.material.colorMaterialTextButton(it) } + sharesListExternalShowAll.setOnClickListener { + externalShareeListAdapter?.toggleShowAll() + val textRes = if (externalShareeListAdapter?.isShowAll == true) R.string.show_less else R.string.show_all + sharesListExternalShowAll.setText(textRes) + } + } } - private fun fetchE2EECounter(onComplete: Runnable?) { - val context = requireContext() + private fun setupViewForEncryptedShare(binding: FileDetailsSharingFragmentBinding) { + binding.run { + internalShareHeadline.text = resources.getString(R.string.internal_share_headline_end_to_end_encrypted) + internalShareDescription.visibility = View.VISIBLE + externalSharesHeadline.text = resources.getString(R.string.create_end_to_end_encrypted_share_title) + + lifecycleScope.launch { + val result = fetchE2EECounter() + if (!result) { + return@launch + } - Thread { - try { - val client = clientFactory.create(user) - val metadata = RefreshFolderOperation.getDecryptedFolderMetadata(true, file, client, user, context) - if (metadata is DecryptedFolderMetadataFile) { - file?.setE2eCounter(metadata.metadata.counter) - fileDataStorageManager?.saveFile(file) + withContext(Dispatchers.Main) { + if (file?.e2eCounter == -1L) { + // V1 cannot share + searchContainer.visibility = View.GONE + createLink.visibility = View.GONE + } else { + createLink.setText(R.string.add_new_secure_file_drop) + searchView.setQueryHint(resources.getString(R.string.secure_share_search)) + + if (file?.isSharedViaLink == true) { + searchView.setQueryHint(resources.getString(R.string.share_not_allowed_when_file_drop)) + searchView.inputType = InputType.TYPE_NULL + toggleSearchViewEnable(searchView, false) + } + } } - } catch (e: Exception) { - Log_OC.e(TAG, "Error refreshing E2E counter: " + e.message) } - val activity = getActivity() - activity?.runOnUiThread(onComplete) - }.start() + } + } + + private fun setupShareView(binding: FileDetailsSharingFragmentBinding, parentFile: OCFile?) { + binding.run { + if (file?.isEncrypted == true || (parentFile != null && parentFile.isEncrypted)) { + setupViewForEncryptedShare(this) + } else { + createLink.setText(R.string.create_link) + searchView.setQueryHint(getResources().getString(R.string.share_search_internal)) + } + + createLink.setOnClickListener { createPublicShareLink() } + } + } + + private fun setupDisabledShareView(binding: FileDetailsSharingFragmentBinding) { + binding.run { + searchView.setQueryHint(getResources().getString(R.string.resharing_is_not_allowed)) + createLink.visibility = View.GONE + externalSharesHeadline.visibility = View.GONE + searchView.inputType = InputType.TYPE_NULL + pickContactEmailBtn.setVisibility(View.GONE) + toggleSearchViewEnable(searchView, false) + createLink.setOnClickListener(null) + } + } + + private suspend fun fetchE2EECounter(): Boolean = withContext(Dispatchers.IO) { + return@withContext try { + val context = requireContext() + val client = clientFactory.create(user) + val metadata = RefreshFolderOperation.getDecryptedFolderMetadata(true, file, client, user, context) + if (metadata is DecryptedFolderMetadataFile) { + file?.setE2eCounter(metadata.metadata.counter) + fileDataStorageManager?.saveFile(file) + } + true + } catch (e: Exception) { + Log_OC.e(TAG, "Error refreshing E2E counter: " + e.message) + false + } } private fun checkShareViaUser() { @@ -485,13 +521,17 @@ class FileDetailSharingFragment : Fragment(), ShareeListAdapterListener, AvatarG } override fun copyLink(share: OCShare) { - if (file?.isSharedViaLink == true) { - if (TextUtils.isEmpty(share.shareLink)) { - fileOperationsHelper?.getFileWithLink(file!!, viewThemeUtils) - } else { - copyToClipboard(requireActivity(), share.shareLink) - } + val file = file ?: return + if (!file.isSharedViaLink) { + return } + + if (TextUtils.isEmpty(share.shareLink)) { + fileOperationsHelper?.getFileWithLink(file, viewThemeUtils) + return + } + + copyToClipboard(requireActivity(), share.shareLink) } @VisibleForTesting From 1689952fa8e45235ce8907eb601de3912c898e68 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Wed, 3 Jun 2026 13:15:37 +0300 Subject: [PATCH 05/10] wip Signed-off-by: alperozturk96 --- .../ui/fragment/FileDetailSharingFragment.kt | 103 ++++++++++-------- 1 file changed, 55 insertions(+), 48 deletions(-) diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.kt b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.kt index 750a883385bf..85142f0658dd 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.kt +++ b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.kt @@ -184,7 +184,7 @@ class FileDetailSharingFragment : Fragment(), ShareeListAdapterListener, AvatarG file?.isEncrypted == true, SharesType.EXTERNAL ) - externalShareeListAdapter!!.setHasStableIds(true) + externalShareeListAdapter?.setHasStableIds(true) binding?.sharesListExternal?.setAdapter(externalShareeListAdapter) binding?.sharesListExternal?.setLayoutManager(LinearLayoutManager(requireContext())) binding?.pickContactEmailBtn?.setOnClickListener { checkContactPermission() } @@ -536,15 +536,17 @@ class FileDetailSharingFragment : Fragment(), ShareeListAdapterListener, AvatarG @VisibleForTesting override fun showSharingMenuActionSheet(share: OCShare?) { - if (fileActivity != null && fileActivity?.isFinishing == false) { - FileDetailSharingMenuBottomSheetDialog( - fileActivity, - this, - share, - viewThemeUtils, - file?.isEncrypted == true - ).show() + if (fileActivity == null || fileActivity?.isFinishing == true) { + return } + + FileDetailSharingMenuBottomSheetDialog( + fileActivity, + this, + share, + viewThemeUtils, + file?.isEncrypted == true + ).show() } override fun showPermissionsDialog(share: OCShare?) { @@ -593,15 +595,17 @@ class FileDetailSharingFragment : Fragment(), ShareeListAdapterListener, AvatarG } override fun showProfileBottomSheet(user: User, shareWith: String?) { - if (user.server.version.isNewerOrEqual(NextcloudVersion.nextcloud_23)) { - RetrieveHoverCardAsyncTask( - user, - shareWith, - fileActivity, - clientFactory, - viewThemeUtils - ).execute() + if (!user.server.version.isNewerOrEqual(NextcloudVersion.nextcloud_23)) { + return } + + RetrieveHoverCardAsyncTask( + user, + shareWith, + fileActivity, + clientFactory, + viewThemeUtils + ).execute() } fun refreshCapabilitiesFromDB() { @@ -624,7 +628,7 @@ class FileDetailSharingFragment : Fragment(), ShareeListAdapterListener, AvatarG return } - internalShareeListAdapter!!.removeAll() + internalShareeListAdapter?.removeAll() // to show share with users/groups info val shares = fileDataStorageManager?.getSharesWithForAFile( @@ -687,41 +691,44 @@ class FileDetailSharingFragment : Fragment(), ShareeListAdapterListener, AvatarG val projection = arrayOf(ContactsContract.CommonDataKinds.Email.ADDRESS) val cursor = fileActivity?.contentResolver?.query(contactUri, projection, null, null, null) - - if (cursor != null) { - if (cursor.moveToFirst()) { - // The contact has only one email address, use it. - val columnIndex = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Email.ADDRESS) - if (columnIndex != -1) { - // Use the email address as needed. - // email variable contains the selected contact's email address. - val email = cursor.getString(columnIndex) - binding!!.searchView.post(Runnable { - if (binding == null) { - return@Runnable - } - binding?.searchView?.setQuery(email, false) - binding?.searchView?.requestFocus() - }) - } else { - DisplayUtils.showSnackMessage(this, R.string.email_pick_failed) - Log_OC.e(FileDetailSharingFragment::class.java.getSimpleName(), "Failed to pick email address.") - } - } else { - DisplayUtils.showSnackMessage(this, R.string.email_pick_failed) - Log_OC.e( - FileDetailSharingFragment::class.java.getSimpleName(), - "Failed to pick email address as no Email found." - ) - } - cursor.close() - } else { + if (cursor == null) { DisplayUtils.showSnackMessage(this, R.string.email_pick_failed) Log_OC.e( FileDetailSharingFragment::class.java.getSimpleName(), "Failed to pick email address as Cursor is null." ) + return + } + + if (!cursor.moveToFirst()) { + DisplayUtils.showSnackMessage(this, R.string.email_pick_failed) + Log_OC.e( + FileDetailSharingFragment::class.java.getSimpleName(), + "Failed to pick email address as no Email found." + ) + return } + + // The contact has only one email address, use it. + val columnIndex = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Email.ADDRESS) + if (columnIndex == -1) { + DisplayUtils.showSnackMessage(this, R.string.email_pick_failed) + Log_OC.e(FileDetailSharingFragment::class.java.getSimpleName(), "Failed to pick email address.") + cursor.close() + return + } + + // Use the email address as needed. + // email variable contains the selected contact's email address. + val email = cursor.getString(columnIndex) + binding!!.searchView.post(Runnable { + if (binding == null) { + return@Runnable + } + binding?.searchView?.setQuery(email, false) + binding?.searchView?.requestFocus() + }) + cursor.close() } override fun onSaveInstanceState(outState: Bundle) { @@ -767,7 +774,7 @@ class FileDetailSharingFragment : Fragment(), ShareeListAdapterListener, AvatarG unShareWith(share) - val entity = fileDataStorageManager!!.getFileEntity(file) + val entity = fileDataStorageManager?.getFileEntity(file) if (binding?.sharesListInternal?.adapter is ShareeListAdapter) { val adapter = binding?.sharesListInternal?.adapter as ShareeListAdapter From 66dda6f58d81b9ac31294a3e3565cd9bf7856902 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 5 Jun 2026 17:08:46 +0300 Subject: [PATCH 06/10] wip Signed-off-by: alperozturk96 --- .../ui/fragment/FileDetailSharingFragment.kt | 561 +++++++++--------- 1 file changed, 285 insertions(+), 276 deletions(-) diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.kt b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.kt index 85142f0658dd..aa913642f163 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.kt +++ b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.kt @@ -72,7 +72,6 @@ import com.owncloud.android.ui.dialog.SharePasswordDialogFragment import com.owncloud.android.ui.dialog.SharePasswordDialogFragment.Companion.newInstance import com.owncloud.android.ui.fragment.QuickSharingPermissionsBottomSheetDialog.QuickPermissionSharingBottomSheetActions import com.owncloud.android.ui.fragment.share.RemoteShareRepository -import com.owncloud.android.ui.fragment.share.ShareRepository import com.owncloud.android.ui.fragment.util.FileDetailSharingFragmentHelper import com.owncloud.android.ui.helpers.FileOperationsHelper import com.owncloud.android.utils.ClipboardUtil.copyToClipboard @@ -116,83 +115,123 @@ class FileDetailSharingFragment : Fragment(), ShareeListAdapterListener, AvatarG @Inject lateinit var searchConfig: UsersAndGroupsSearchConfig + // region lifecycle methods override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - if (savedInstanceState != null) { - file = savedInstanceState.getParcelableArgument(ARG_FILE, OCFile::class.java) - user = savedInstanceState.getParcelableArgument(ARG_USER, User::class.java) - } else { - val arguments = getArguments() - - if (arguments != null) { - file = arguments.getParcelableArgument(ARG_FILE, OCFile::class.java) - user = arguments.getParcelableArgument(ARG_USER, User::class.java) - } - } + initArguments(savedInstanceState) + fileActivity = (activity as FileActivity?) requireNotNull(file) { "File may not be null" } requireNotNull(user) { "Account may not be null" } - - fileActivity = activity as FileActivity? requireNotNull(fileActivity) { "FileActivity may not be null" } } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - if (fileActivity == null) { - return - } - + fileActivity ?: return fileDataStorageManager = fileActivity?.storageManager fileOperationsHelper = fileActivity?.fileOperationsHelper - // start animation before loading process - val blinkAnimation = AnimationUtils.loadAnimation(requireContext(), R.anim.blink) - binding?.shimmerLayout?.getRoot()?.startAnimation(blinkAnimation) + startAnimation() + + val userId = getUserId() + + setupInternalShares(userId) + setupExternalShares(userId) + + binding?.pickContactEmailBtn?.setOnClickListener { checkContactPermission() } + + fetchSharees() + setupView() + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + binding = FileDetailsSharingFragmentBinding.inflate(inflater, container, false) + return binding!!.getRoot() + } + + override fun onDestroyView() { + super.onDestroyView() + binding = null + } + + override fun onAttach(context: Context) { + super.onAttach(context) + require(activity is FileActivity) { "Calling activity must be of type FileActivity" } + + try { + onEditShareListener = context as OnEditShareListener + } catch (e: Exception) { + throw IllegalArgumentException("Calling activity must implement the interface$e") + } + } + + override fun onStart() { + super.onStart() + searchConfig.searchOnlyUsers = (file?.isEncrypted == true) + } + + override fun onStop() { + super.onStop() + searchConfig.reset() + } + // endregion + // region private methods + private fun initArguments(savedInstanceState: Bundle?) { + val args = (savedInstanceState ?: arguments) ?: return + file = args.getParcelableArgument(ARG_FILE, OCFile::class.java) + user = args.getParcelableArgument(ARG_USER, User::class.java) + } + + private fun getUserId(): String { val accountManager = AccountManager.get(requireContext()) - val userId = accountManager.getUserData( + return accountManager.getUserData( user?.toPlatformAccount(), AccountUtils.Constants.KEY_USER_ID ) + } - // internal shares - internalShareeListAdapter = ShareeListAdapter( - fileActivity!!, - ArrayList(), - this, - userId, - user, - viewThemeUtils, - file?.isEncrypted == true, - SharesType.INTERNAL - ) - internalShareeListAdapter?.setHasStableIds(true) - binding?.sharesListInternal?.setAdapter(internalShareeListAdapter) - binding?.sharesListInternal?.setLayoutManager(LinearLayoutManager(requireContext())) + private fun setupInternalShares(userId: String) { + internalShareeListAdapter = createShareListAdapter(userId, SharesType.INTERNAL) + binding?.sharesListInternal?.run { + adapter = internalShareeListAdapter + layoutManager = createShareListLayoutManager() + } + } + + private fun setupExternalShares(userId: String) { + externalShareeListAdapter = createShareListAdapter(userId, SharesType.EXTERNAL) + binding?.sharesListExternal?.run { + adapter = internalShareeListAdapter + layoutManager = createShareListLayoutManager() + } + } - // external shares - externalShareeListAdapter = ShareeListAdapter( + private fun createShareListAdapter(userId: String, type: SharesType): ShareeListAdapter { + return ShareeListAdapter( fileActivity!!, ArrayList(), this, userId, user, viewThemeUtils, - file?.isEncrypted == true, - SharesType.EXTERNAL - ) - externalShareeListAdapter?.setHasStableIds(true) - binding?.sharesListExternal?.setAdapter(externalShareeListAdapter) - binding?.sharesListExternal?.setLayoutManager(LinearLayoutManager(requireContext())) - binding?.pickContactEmailBtn?.setOnClickListener { checkContactPermission() } + (file?.isEncrypted == true), + type + ).apply { + setHasStableIds(true) + } + } - // start loading process - fetchSharees() + private fun createShareListLayoutManager(): LinearLayoutManager { + return LinearLayoutManager(requireContext()) + } - setupView() + private fun startAnimation() { + val blinkAnimation = AnimationUtils.loadAnimation(requireContext(), R.anim.blink) + binding?.shimmerLayout?.getRoot()?.startAnimation(blinkAnimation) } private fun fetchSharees() { @@ -201,14 +240,13 @@ class FileDetailSharingFragment : Fragment(), ShareeListAdapterListener, AvatarG val storageManager = fileDataStorageManager ?: return val remotePath = file?.remotePath ?: return - val shareRepository: ShareRepository = RemoteShareRepository(clientRepository, storageManager) + val shareRepository = RemoteShareRepository(clientRepository, storageManager) lifecycleScope.launch { val result = shareRepository.fetchSharees(remotePath) if (binding == null) { return@launch } - // success if (result) { refreshCapabilitiesFromDB() refreshSharesFromDB() @@ -216,56 +254,21 @@ class FileDetailSharingFragment : Fragment(), ShareeListAdapterListener, AvatarG return@launch } - // fail stopLoadingAnimationAndShowShareContainer() DisplayUtils.showSnackMessage(this@FileDetailSharingFragment, R.string.error_fetching_sharees) } } - // stop loading animation private fun stopLoadingAnimationAndShowShareContainer() { - if (binding == null) { - return - } - - val shimmerLayout = binding!!.shimmerLayout.getRoot() - shimmerLayout.clearAnimation() - shimmerLayout.visibility = View.GONE - - binding?.shareContainer?.visibility = View.VISIBLE - } - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - binding = FileDetailsSharingFragmentBinding.inflate(inflater, container, false) - return binding!!.getRoot() - } - - override fun onDestroyView() { - super.onDestroyView() - binding = null - } - - override fun onAttach(context: Context) { - super.onAttach(context) - require(activity is FileActivity) { "Calling activity must be of type FileActivity" } - - try { - onEditShareListener = context as OnEditShareListener - } catch (e: Exception) { - throw IllegalArgumentException("Calling activity must implement the interface$e") + binding?.run { + shimmerLayout.root.run { + clearAnimation() + visibility = View.GONE + } + shareContainer.visibility = View.VISIBLE } } - override fun onStart() { - super.onStart() - searchConfig.searchOnlyUsers = (file?.isEncrypted == true) - } - - override fun onStop() { - super.onStop() - searchConfig.reset() - } - private fun resetSearchView() { binding?.run { toggleSearchViewEnable(searchView, true) @@ -476,40 +479,10 @@ class FileDetailSharingFragment : Fragment(), ShareeListAdapterListener, AvatarG } } - override fun copyInternalLink() { - val account = accountManager.getCurrentOwnCloudAccount() - - if (account == null) { - DisplayUtils.showSnackMessage(this, R.string.could_not_retrieve_url) - return - } - - file?.let { FileActivity.showShareLinkDialog(fileActivity, file, createInternalLink(account, it)) } - } - private fun createInternalLink(account: OwnCloudAccount, file: OCFile): String { return account.baseUri.toString() + "/index.php/f/" + file.localId } - override fun createPublicShareLink() { - if (capabilities != null && (capabilities?.filesSharingPublicPasswordEnforced?.isTrue == true || - capabilities?.filesSharingPublicAskForOptionalPassword?.isTrue == true) - ) { - // password enforced by server, request to the user before trying to create - requestPasswordForShareViaLink( - true, - capabilities!!.filesSharingPublicAskForOptionalPassword.isTrue - ) - } else { - // create without password if not enforced by server or we don't know if enforced; - fileOperationsHelper?.shareFileViaPublicShare(file, null) - } - } - - override fun createSecureFileDrop() { - fileOperationsHelper?.shareFolderViaSecureFileDrop(file!!) - } - private fun showSendLinkTo(publicShare: OCShare) { if (file?.isSharedViaLink == true) { if (TextUtils.isEmpty(publicShare.shareLink)) { @@ -520,57 +493,6 @@ class FileDetailSharingFragment : Fragment(), ShareeListAdapterListener, AvatarG } } - override fun copyLink(share: OCShare) { - val file = file ?: return - if (!file.isSharedViaLink) { - return - } - - if (TextUtils.isEmpty(share.shareLink)) { - fileOperationsHelper?.getFileWithLink(file, viewThemeUtils) - return - } - - copyToClipboard(requireActivity(), share.shareLink) - } - - @VisibleForTesting - override fun showSharingMenuActionSheet(share: OCShare?) { - if (fileActivity == null || fileActivity?.isFinishing == true) { - return - } - - FileDetailSharingMenuBottomSheetDialog( - fileActivity, - this, - share, - viewThemeUtils, - file?.isEncrypted == true - ).show() - } - - override fun showPermissionsDialog(share: OCShare?) { - QuickSharingPermissionsBottomSheetDialog(fileActivity, this, share, viewThemeUtils, file?.isEncrypted == true).show() - } - - fun onUpdateShareInformation(result: RemoteOperationResult<*>, file: OCFile?) { - this.file = file - - onUpdateShareInformation(result) - } - - fun onUpdateShareInformation(result: RemoteOperationResult<*>) { - if (binding == null) { - return - } - - if (result.isSuccess) { - refreshUiFromDB() - } else { - setupView() - } - } - private fun refreshUiFromDB() { refreshSharesFromDB() setupView() @@ -580,84 +502,6 @@ class FileDetailSharingFragment : Fragment(), ShareeListAdapterListener, AvatarG fileOperationsHelper?.unShareShare(file, share.id) } - fun requestPasswordForShareViaLink(createShare: Boolean, askForPassword: Boolean) { - val dialog = newInstance( - file, - createShare, - askForPassword - ) - dialog.show(getChildFragmentManager(), SharePasswordDialogFragment.PASSWORD_FRAGMENT) - } - - override fun requestPasswordForShare(share: OCShare?, askForPassword: Boolean) { - val dialog = newInstance(share, askForPassword) - dialog.show(getChildFragmentManager(), SharePasswordDialogFragment.PASSWORD_FRAGMENT) - } - - override fun showProfileBottomSheet(user: User, shareWith: String?) { - if (!user.server.version.isNewerOrEqual(NextcloudVersion.nextcloud_23)) { - return - } - - RetrieveHoverCardAsyncTask( - user, - shareWith, - fileActivity, - clientFactory, - viewThemeUtils - ).execute() - } - - fun refreshCapabilitiesFromDB() { - capabilities = fileDataStorageManager?.getCapability(user?.accountName) - } - - @SuppressFBWarnings("PSC") - fun refreshSharesFromDB() { - if (binding == null) { - return - } - - val newFile = file?.fileId?.let { fileDataStorageManager?.getFileById(it) } - if (newFile != null) { - file = newFile - } - - if (internalShareeListAdapter == null) { - DisplayUtils.showSnackMessage(this, R.string.could_not_retrieve_shares) - return - } - - internalShareeListAdapter?.removeAll() - - // to show share with users/groups info - val shares = fileDataStorageManager?.getSharesWithForAFile( - file?.remotePath, - user?.accountName - ) ?: listOf() - - val internalShares = ArrayList() - val externalShares = ArrayList() - - for (share in shares) { - if (share.shareType != null) { - when (share.shareType) { - ShareType.PUBLIC_LINK, ShareType.FEDERATED_GROUP, ShareType.FEDERATED, ShareType.EMAIL -> externalShares.add( - share - ) - - else -> internalShares.add(share) - } - } - } - - internalShareeListAdapter?.addShares(internalShares) - internalShareeListAdapter?.shares?.size?.let { binding?.sharesListInternalShowAll?.setVisibleIf(it > 3) } - - addExternalAndPublicShares(externalShares) - externalShareeListAdapter?.shares?.size?.let { binding?.sharesListExternalShowAll?.setVisibleIf(it > 3) } - } - private fun addExternalAndPublicShares(externalShares: MutableList) { val publicShares = fileDataStorageManager?.getSharesByPathAndType(file?.remotePath, ShareType.PUBLIC_LINK, "") @@ -731,32 +575,23 @@ class FileDetailSharingFragment : Fragment(), ShareeListAdapterListener, AvatarG cursor.close() } - override fun onSaveInstanceState(outState: Bundle) { - super.onSaveInstanceState(outState) - outState.putParcelable(ARG_FILE, file) - outState.putParcelable(ARG_USER, user) - } - - override fun avatarGenerated(avatarDrawable: Drawable?, callContext: Any?) { - if (binding == null) { - return - } - binding?.sharedWithYouAvatar?.setImageDrawable(avatarDrawable) + private fun isReshareForbidden(share: OCShare): Boolean { + return ShareType.FEDERATED == share.shareType || + capabilities != null && capabilities!!.filesSharingResharing.isFalse } - override fun shouldCallGeneratedCallback(tag: String?, callContext: Any?): Boolean { - return false + private fun modifyExistingShare(share: OCShare, screenTypePermission: Int) { + onEditShareListener?.editExistingShare(share, screenTypePermission, !isReshareForbidden(share)) } + // endregion - private fun isReshareForbidden(share: OCShare): Boolean { - return ShareType.FEDERATED == share.shareType || - capabilities != null && capabilities!!.filesSharingResharing.isFalse + // region overridden methods + override fun onQuickPermissionChanged(share: OCShare, permission: Int) { + fileOperationsHelper?.setPermissionsToShare(share, permission) } - @VisibleForTesting - fun search(query: String?) { - val searchView = requireView().findViewById(R.id.searchView) - searchView.setQuery(query, true) + override fun openShareDetailWithCustomPermissions(share: OCShare) { + modifyExistingShare(share, FileDetailsSharingProcessFragment.SCREEN_TYPE_PERMISSION_WITH_CUSTOM_PERMISSION) } override fun advancedPermissions(share: OCShare) { @@ -807,18 +642,191 @@ class FileDetailSharingFragment : Fragment(), ShareeListAdapterListener, AvatarG createPublicShareLink() } - private fun modifyExistingShare(share: OCShare, screenTypePermission: Int) { - onEditShareListener?.editExistingShare(share, screenTypePermission, !isReshareForbidden(share)) + override fun copyInternalLink() { + val account = accountManager.getCurrentOwnCloudAccount() + + if (account == null) { + DisplayUtils.showSnackMessage(this, R.string.could_not_retrieve_url) + return + } + + file?.let { FileActivity.showShareLinkDialog(fileActivity, file, createInternalLink(account, it)) } } - override fun onQuickPermissionChanged(share: OCShare, permission: Int) { - fileOperationsHelper?.setPermissionsToShare(share, permission) + override fun createPublicShareLink() { + if (capabilities != null && (capabilities?.filesSharingPublicPasswordEnforced?.isTrue == true || + capabilities?.filesSharingPublicAskForOptionalPassword?.isTrue == true) + ) { + // password enforced by server, request to the user before trying to create + requestPasswordForShareViaLink( + true, + capabilities!!.filesSharingPublicAskForOptionalPassword.isTrue + ) + } else { + // create without password if not enforced by server or we don't know if enforced; + fileOperationsHelper?.shareFileViaPublicShare(file, null) + } } - override fun openShareDetailWithCustomPermissions(share: OCShare) { - modifyExistingShare(share, FileDetailsSharingProcessFragment.SCREEN_TYPE_PERMISSION_WITH_CUSTOM_PERMISSION) + override fun createSecureFileDrop() { + fileOperationsHelper?.shareFolderViaSecureFileDrop(file!!) + } + + override fun copyLink(share: OCShare) { + val file = file ?: return + if (!file.isSharedViaLink) { + return + } + + if (TextUtils.isEmpty(share.shareLink)) { + fileOperationsHelper?.getFileWithLink(file, viewThemeUtils) + return + } + + copyToClipboard(requireActivity(), share.shareLink) + } + + @VisibleForTesting + override fun showSharingMenuActionSheet(share: OCShare?) { + if (fileActivity == null || fileActivity?.isFinishing == true) { + return + } + + FileDetailSharingMenuBottomSheetDialog( + fileActivity, + this, + share, + viewThemeUtils, + file?.isEncrypted == true + ).show() + } + + override fun showPermissionsDialog(share: OCShare?) { + QuickSharingPermissionsBottomSheetDialog(fileActivity, this, share, viewThemeUtils, file?.isEncrypted == true).show() + } + override fun requestPasswordForShare(share: OCShare?, askForPassword: Boolean) { + val dialog = newInstance(share, askForPassword) + dialog.show(getChildFragmentManager(), SharePasswordDialogFragment.PASSWORD_FRAGMENT) + } + + override fun showProfileBottomSheet(user: User, shareWith: String?) { + if (!user.server.version.isNewerOrEqual(NextcloudVersion.nextcloud_23)) { + return + } + + RetrieveHoverCardAsyncTask( + user, + shareWith, + fileActivity, + clientFactory, + viewThemeUtils + ).execute() + } + + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + outState.putParcelable(ARG_FILE, file) + outState.putParcelable(ARG_USER, user) + } + + override fun avatarGenerated(avatarDrawable: Drawable?, callContext: Any?) { + if (binding == null) { + return + } + binding?.sharedWithYouAvatar?.setImageDrawable(avatarDrawable) + } + + override fun shouldCallGeneratedCallback(tag: String?, callContext: Any?): Boolean { + return false + } + // endregion + + // region public methods + @VisibleForTesting + fun search(query: String?) { + val searchView = requireView().findViewById(R.id.searchView) + searchView.setQuery(query, true) + } + + fun onUpdateShareInformation(result: RemoteOperationResult<*>, file: OCFile?) { + this.file = file + + onUpdateShareInformation(result) + } + + fun onUpdateShareInformation(result: RemoteOperationResult<*>) { + if (binding == null) { + return + } + + if (result.isSuccess) { + refreshUiFromDB() + } else { + setupView() + } + } + + fun requestPasswordForShareViaLink(createShare: Boolean, askForPassword: Boolean) { + val dialog = newInstance( + file, + createShare, + askForPassword + ) + dialog.show(getChildFragmentManager(), SharePasswordDialogFragment.PASSWORD_FRAGMENT) + } + + fun refreshCapabilitiesFromDB() { + capabilities = fileDataStorageManager?.getCapability(user?.accountName) + } + + @SuppressFBWarnings("PSC") + fun refreshSharesFromDB() { + if (binding == null) { + return + } + + val newFile = file?.fileId?.let { fileDataStorageManager?.getFileById(it) } + if (newFile != null) { + file = newFile + } + + if (internalShareeListAdapter == null) { + DisplayUtils.showSnackMessage(this, R.string.could_not_retrieve_shares) + return + } + + internalShareeListAdapter?.removeAll() + + // to show share with users/groups info + val shares = fileDataStorageManager?.getSharesWithForAFile( + file?.remotePath, + user?.accountName + ) ?: listOf() + + val internalShares = ArrayList() + val externalShares = ArrayList() + + for (share in shares) { + if (share.shareType != null) { + when (share.shareType) { + ShareType.PUBLIC_LINK, ShareType.FEDERATED_GROUP, ShareType.FEDERATED, ShareType.EMAIL -> externalShares.add( + share + ) + + else -> internalShares.add(share) + } + } + } + + internalShareeListAdapter?.addShares(internalShares) + internalShareeListAdapter?.shares?.size?.let { binding?.sharesListInternalShowAll?.setVisibleIf(it > 3) } + + addExternalAndPublicShares(externalShares) + externalShareeListAdapter?.shares?.size?.let { binding?.sharesListExternalShowAll?.setVisibleIf(it > 3) } } + // endregion + // region private values private val requestContactPermissionLauncher = registerForActivityResult( ActivityResultContracts.RequestPermission() ) { isGranted: Boolean -> @@ -848,6 +856,7 @@ class FileDetailSharingFragment : Fragment(), ShareeListAdapterListener, AvatarG handleContactResult(contactUri) } } + // endregion interface OnEditShareListener { fun editExistingShare(share: OCShare?, screenTypePermission: Int, isReshareShown: Boolean) From e234391606cf1bef5f52d50935a942f8b4ec0c2d Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 5 Jun 2026 17:52:52 +0300 Subject: [PATCH 07/10] wip Signed-off-by: alperozturk96 --- .../android/ui/adapter/ShareeListAdapter.kt | 2 + .../ui/fragment/FileDetailSharingFragment.kt | 179 ++++++++++-------- 2 files changed, 104 insertions(+), 77 deletions(-) diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/ShareeListAdapter.kt b/app/src/main/java/com/owncloud/android/ui/adapter/ShareeListAdapter.kt index ef449afabfac..2921f83148da 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/ShareeListAdapter.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/ShareeListAdapter.kt @@ -186,4 +186,6 @@ class ShareeListAdapter( shares.add(OCShare().apply { shareType = ShareType.INTERNAL }) } } + + fun getExpandOrCollapseActionTextId(): Int = (if (isShowAll) R.string.show_less else R.string.show_all) } diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.kt b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.kt index aa913642f163..8d77b14a7c55 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.kt +++ b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.kt @@ -30,6 +30,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.view.animation.AnimationUtils +import android.widget.ImageView import androidx.activity.result.ActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.annotation.VisibleForTesting @@ -38,6 +39,7 @@ import androidx.core.view.size import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager +import com.google.android.material.button.MaterialButton import com.nextcloud.android.common.ui.theme.utils.ColorRole import com.nextcloud.client.account.User import com.nextcloud.client.account.UserAccountManager @@ -293,7 +295,6 @@ class FileDetailSharingFragment : Fragment(), ShareeListAdapterListener, AvatarG themeView(this) setupShareList(this) - if (file?.canReshare() == true && !FileDetailSharingFragmentHelper.isPublicShareDisabled(capabilities)) { val parentFile = file?.parentId?.let { fileDataStorageManager?.getFileById(it) } setupShareView(this, parentFile) @@ -343,19 +344,23 @@ class FileDetailSharingFragment : Fragment(), ShareeListAdapterListener, AvatarG private fun setupShareList(binding: FileDetailsSharingFragmentBinding) { binding.run { sharesListInternalShowAll.setOnClickListener { - internalShareeListAdapter?.toggleShowAll() - val textRes = if (internalShareeListAdapter?.isShowAll == true) R.string.show_less else R.string.show_all - sharesListInternalShowAll.setText(textRes) + expandOrCollapseAdapter(internalShareeListAdapter, sharesListInternalShowAll) } - sharesListExternalShowAll.let { viewThemeUtils.material.colorMaterialTextButton(it) } + sharesListExternalShowAll.setOnClickListener { - externalShareeListAdapter?.toggleShowAll() - val textRes = if (externalShareeListAdapter?.isShowAll == true) R.string.show_less else R.string.show_all - sharesListExternalShowAll.setText(textRes) + expandOrCollapseAdapter(externalShareeListAdapter, sharesListExternalShowAll) } + viewThemeUtils.material.colorMaterialTextButton(sharesListExternalShowAll) } } + private fun expandOrCollapseAdapter(adapter: ShareeListAdapter?, button: MaterialButton) { + adapter ?: return + adapter.toggleShowAll() + val actionTextId = adapter.getExpandOrCollapseActionTextId() + button.setText(actionTextId) + } + private fun setupViewForEncryptedShare(binding: FileDetailsSharingFragmentBinding) { binding.run { internalShareHeadline.text = resources.getString(R.string.internal_share_headline_end_to_end_encrypted) @@ -370,24 +375,32 @@ class FileDetailSharingFragment : Fragment(), ShareeListAdapterListener, AvatarG withContext(Dispatchers.Main) { if (file?.e2eCounter == -1L) { - // V1 cannot share - searchContainer.visibility = View.GONE - createLink.visibility = View.GONE - } else { - createLink.setText(R.string.add_new_secure_file_drop) - searchView.setQueryHint(resources.getString(R.string.secure_share_search)) - - if (file?.isSharedViaLink == true) { - searchView.setQueryHint(resources.getString(R.string.share_not_allowed_when_file_drop)) - searchView.inputType = InputType.TYPE_NULL - toggleSearchViewEnable(searchView, false) - } + disableE2EEShareForV1(binding) + return@withContext + } + + createLink.setText(R.string.add_new_secure_file_drop) + searchView.setQueryHint(resources.getString(R.string.secure_share_search)) + + if (file?.isSharedViaLink == true) { + setupSearchViewForSharedLink(searchView) } } } } } + private fun disableE2EEShareForV1(binding: FileDetailsSharingFragmentBinding) { + binding.searchContainer.visibility = View.GONE + binding.createLink.visibility = View.GONE + } + + private fun setupSearchViewForSharedLink(searchView: SearchView) { + searchView.setQueryHint(resources.getString(R.string.share_not_allowed_when_file_drop)) + searchView.inputType = InputType.TYPE_NULL + toggleSearchViewEnable(searchView, false) + } + private fun setupShareView(binding: FileDetailsSharingFragmentBinding, parentFile: OCFile?) { binding.run { if (file?.isEncrypted == true || (parentFile != null && parentFile.isEncrypted)) { @@ -430,9 +443,11 @@ class FileDetailSharingFragment : Fragment(), ShareeListAdapterListener, AvatarG } private fun checkShareViaUser() { - if (!shareViaUser(requireContext())) { - binding?.searchContainer?.visibility = View.GONE + if (shareViaUser(requireContext())) { + return } + + binding?.searchContainer?.visibility = View.GONE } private fun toggleSearchViewEnable(view: View, enable: Boolean) { @@ -448,35 +463,44 @@ class FileDetailSharingFragment : Fragment(), ShareeListAdapterListener, AvatarG binding?.run { if (accountManager.userOwnsFile(file, user)) { sharedWithYouContainer.visibility = View.GONE - } else { - sharedWithYouUsername.text = String.format(getString(R.string.shared_with_you_by), file?.ownerDisplayName) - user?.let { - file?.ownerId?.let { userId -> - DisplayUtils.setAvatar( - it, - userId, - this@FileDetailSharingFragment, - resources.getDimension( - R.dimen.file_list_item_avatar_icon_radius - ), - resources, - sharedWithYouAvatar, - context - ) - } - } - sharedWithYouAvatar.setVisibility(View.VISIBLE) + return + } + + sharedWithYouUsername.text = String.format(getString(R.string.shared_with_you_by), file?.ownerDisplayName) + setupUserAvatar(sharedWithYouAvatar) + setupNote(this) + } + } - val note = file?.getNote() + private fun setupUserAvatar(sharedWithYouAvatar: ImageView) { + val user = user ?: return + val userId = file?.ownerId ?: return - if (!TextUtils.isEmpty(note)) { - sharedWithYouNote.text = file?.getNote() - sharedWithYouNoteContainer.visibility = View.VISIBLE - } else { - sharedWithYouNoteContainer.visibility = View.GONE - } - } + DisplayUtils.setAvatar( + user, + userId, + this@FileDetailSharingFragment, + resources.getDimension( + R.dimen.file_list_item_avatar_icon_radius + ), + resources, + sharedWithYouAvatar, + context + ) + + sharedWithYouAvatar.setVisibility(View.VISIBLE) + } + + private fun setupNote(binding: FileDetailsSharingFragmentBinding) { + val note = file?.getNote() + + if (!note.isNullOrEmpty()) { + binding.sharedWithYouNote.text = file?.getNote() + binding.sharedWithYouNoteContainer.visibility = View.VISIBLE + return } + + binding.sharedWithYouNoteContainer.visibility = View.GONE } private fun createInternalLink(account: OwnCloudAccount, file: OCFile): String { @@ -513,11 +537,13 @@ class FileDetailSharingFragment : Fragment(), ShareeListAdapterListener, AvatarG } private fun checkContactPermission() { - if (checkSelfPermission(requireActivity(), Manifest.permission.READ_CONTACTS)) { + val canReadContacts = (checkSelfPermission(requireActivity(), Manifest.permission.READ_CONTACTS)) + if (canReadContacts) { pickContactEmail() - } else { - requestContactPermissionLauncher.launch(Manifest.permission.READ_CONTACTS) + return } + + requestContactPermissionLauncher.launch(Manifest.permission.READ_CONTACTS) } private fun pickContactEmail() { @@ -525,9 +551,10 @@ class FileDetailSharingFragment : Fragment(), ShareeListAdapterListener, AvatarG if (intent.resolveActivity(requireContext().packageManager) != null) { onContactSelectionResultLauncher.launch(intent) - } else { - DisplayUtils.showSnackMessage(this, R.string.file_detail_sharing_fragment_no_contact_app_message) + return } + + DisplayUtils.showSnackMessage(this, R.string.file_detail_sharing_fragment_no_contact_app_message) } private fun handleContactResult(contactUri: Uri) { @@ -538,7 +565,7 @@ class FileDetailSharingFragment : Fragment(), ShareeListAdapterListener, AvatarG if (cursor == null) { DisplayUtils.showSnackMessage(this, R.string.email_pick_failed) Log_OC.e( - FileDetailSharingFragment::class.java.getSimpleName(), + TAG, "Failed to pick email address as Cursor is null." ) return @@ -547,7 +574,7 @@ class FileDetailSharingFragment : Fragment(), ShareeListAdapterListener, AvatarG if (!cursor.moveToFirst()) { DisplayUtils.showSnackMessage(this, R.string.email_pick_failed) Log_OC.e( - FileDetailSharingFragment::class.java.getSimpleName(), + TAG, "Failed to pick email address as no Email found." ) return @@ -557,7 +584,7 @@ class FileDetailSharingFragment : Fragment(), ShareeListAdapterListener, AvatarG val columnIndex = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Email.ADDRESS) if (columnIndex == -1) { DisplayUtils.showSnackMessage(this, R.string.email_pick_failed) - Log_OC.e(FileDetailSharingFragment::class.java.getSimpleName(), "Failed to pick email address.") + Log_OC.e(TAG, "Failed to pick email address.") cursor.close() return } @@ -565,7 +592,7 @@ class FileDetailSharingFragment : Fragment(), ShareeListAdapterListener, AvatarG // Use the email address as needed. // email variable contains the selected contact's email address. val email = cursor.getString(columnIndex) - binding!!.searchView.post(Runnable { + binding?.searchView?.post(Runnable { if (binding == null) { return@Runnable } @@ -577,7 +604,7 @@ class FileDetailSharingFragment : Fragment(), ShareeListAdapterListener, AvatarG private fun isReshareForbidden(share: OCShare): Boolean { return ShareType.FEDERATED == share.shareType || - capabilities != null && capabilities!!.filesSharingResharing.isFalse + capabilities != null && (capabilities?.filesSharingResharing?.isFalse == true) } private fun modifyExistingShare(share: OCShare, screenTypePermission: Int) { @@ -631,7 +658,7 @@ class FileDetailSharingFragment : Fragment(), ShareeListAdapterListener, AvatarG } override fun sendLink(share: OCShare) { - if (file?.isSharedViaLink == true && !TextUtils.isEmpty(share.shareLink)) { + if (file?.isSharedViaLink == true && !share.shareLink.isNullOrEmpty()) { FileActivity.showShareLinkDialog(fileActivity, file, share.shareLink) } else { showSendLinkTo(share) @@ -653,19 +680,21 @@ class FileDetailSharingFragment : Fragment(), ShareeListAdapterListener, AvatarG file?.let { FileActivity.showShareLinkDialog(fileActivity, file, createInternalLink(account, it)) } } + private fun OCCapability?.isPasswordEnforced(): Boolean { + return this?.filesSharingPublicPasswordEnforced?.isTrue == true && filesSharingPublicAskForOptionalPassword.isTrue + } + override fun createPublicShareLink() { - if (capabilities != null && (capabilities?.filesSharingPublicPasswordEnforced?.isTrue == true || - capabilities?.filesSharingPublicAskForOptionalPassword?.isTrue == true) - ) { - // password enforced by server, request to the user before trying to create + if (capabilities?.isPasswordEnforced() == true) { requestPasswordForShareViaLink( true, - capabilities!!.filesSharingPublicAskForOptionalPassword.isTrue + (capabilities?.filesSharingPublicAskForOptionalPassword?.isTrue == true) ) - } else { - // create without password if not enforced by server or we don't know if enforced; - fileOperationsHelper?.shareFileViaPublicShare(file, null) + return } + + // create without password + fileOperationsHelper?.shareFileViaPublicShare(file, null) } override fun createSecureFileDrop() { @@ -678,7 +707,7 @@ class FileDetailSharingFragment : Fragment(), ShareeListAdapterListener, AvatarG return } - if (TextUtils.isEmpty(share.shareLink)) { + if (share.shareLink.isNullOrEmpty()) { fileOperationsHelper?.getFileWithLink(file, viewThemeUtils) return } @@ -725,14 +754,13 @@ class FileDetailSharingFragment : Fragment(), ShareeListAdapterListener, AvatarG override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) - outState.putParcelable(ARG_FILE, file) - outState.putParcelable(ARG_USER, user) + outState.run { + putParcelable(ARG_FILE, file) + putParcelable(ARG_USER, user) + } } override fun avatarGenerated(avatarDrawable: Drawable?, callContext: Any?) { - if (binding == null) { - return - } binding?.sharedWithYouAvatar?.setImageDrawable(avatarDrawable) } @@ -742,15 +770,12 @@ class FileDetailSharingFragment : Fragment(), ShareeListAdapterListener, AvatarG // endregion // region public methods - @VisibleForTesting fun search(query: String?) { - val searchView = requireView().findViewById(R.id.searchView) - searchView.setQuery(query, true) + binding?.searchView?.setQuery(query, true) } fun onUpdateShareInformation(result: RemoteOperationResult<*>, file: OCFile?) { this.file = file - onUpdateShareInformation(result) } From 5819505204a4f0529e325e908779e3b99c3b5b66 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 5 Jun 2026 18:27:16 +0300 Subject: [PATCH 08/10] wip Signed-off-by: alperozturk96 --- .../ui/fragment/FileDetailSharingFragment.kt | 169 +++++++++--------- 1 file changed, 88 insertions(+), 81 deletions(-) diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.kt b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.kt index 8d77b14a7c55..ca9f041dcbba 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.kt +++ b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.kt @@ -25,7 +25,6 @@ import android.net.Uri import android.os.Bundle import android.provider.ContactsContract import android.text.InputType -import android.text.TextUtils import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -81,14 +80,19 @@ import com.owncloud.android.utils.DisplayUtils import com.owncloud.android.utils.DisplayUtils.AvatarGenerationListener import com.owncloud.android.utils.PermissionUtil.checkSelfPermission import com.owncloud.android.utils.theme.ViewThemeUtils -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import javax.inject.Inject -class FileDetailSharingFragment : Fragment(), ShareeListAdapterListener, AvatarGenerationListener, Injectable, - FileDetailsSharingMenuBottomSheetActions, QuickPermissionSharingBottomSheetActions { +@Suppress("TooManyFunctions", "LargeClass", "TooGenericExceptionCaught", "ReturnCount") +class FileDetailSharingFragment : + Fragment(), + ShareeListAdapterListener, + AvatarGenerationListener, + Injectable, + FileDetailsSharingMenuBottomSheetActions, + QuickPermissionSharingBottomSheetActions { private var file: OCFile? = null private var user: User? = null private var capabilities: OCCapability? = null @@ -212,24 +216,20 @@ class FileDetailSharingFragment : Fragment(), ShareeListAdapterListener, AvatarG } } - private fun createShareListAdapter(userId: String, type: SharesType): ShareeListAdapter { - return ShareeListAdapter( - fileActivity!!, - ArrayList(), - this, - userId, - user, - viewThemeUtils, - (file?.isEncrypted == true), - type - ).apply { - setHasStableIds(true) - } + private fun createShareListAdapter(userId: String, type: SharesType): ShareeListAdapter = ShareeListAdapter( + fileActivity!!, + ArrayList(), + this, + userId, + user, + viewThemeUtils, + (file?.isEncrypted == true), + type + ).apply { + setHasStableIds(true) } - private fun createShareListLayoutManager(): LinearLayoutManager { - return LinearLayoutManager(requireContext()) - } + private fun createShareListLayoutManager(): LinearLayoutManager = LinearLayoutManager(requireContext()) private fun startAnimation() { val blinkAnimation = AnimationUtils.loadAnimation(requireContext(), R.anim.blink) @@ -244,7 +244,7 @@ class FileDetailSharingFragment : Fragment(), ShareeListAdapterListener, AvatarG val shareRepository = RemoteShareRepository(clientRepository, storageManager) lifecycleScope.launch { - val result = shareRepository.fetchSharees(remotePath) + val result = shareRepository.fetchSharees(remotePath) if (binding == null) { return@launch } @@ -503,18 +503,22 @@ class FileDetailSharingFragment : Fragment(), ShareeListAdapterListener, AvatarG binding.sharedWithYouNoteContainer.visibility = View.GONE } - private fun createInternalLink(account: OwnCloudAccount, file: OCFile): String { - return account.baseUri.toString() + "/index.php/f/" + file.localId - } + private fun createInternalLink(account: OwnCloudAccount, file: OCFile): String = + account.baseUri.toString() + "/index.php/f/" + file.localId private fun showSendLinkTo(publicShare: OCShare) { - if (file?.isSharedViaLink == true) { - if (TextUtils.isEmpty(publicShare.shareLink)) { - fileOperationsHelper?.getFileWithLink(file!!, viewThemeUtils) - } else { - FileActivity.showShareLinkDialog(fileActivity, file, publicShare.shareLink) - } + val file = file ?: return + + if (!file.isSharedViaLink) { + return + } + + if (publicShare.shareLink.isNullOrEmpty()) { + fileOperationsHelper?.getFileWithLink(file, viewThemeUtils) + return } + + FileActivity.showShareLinkDialog(fileActivity, file, publicShare.shareLink) } private fun refreshUiFromDB() { @@ -526,7 +530,7 @@ class FileDetailSharingFragment : Fragment(), ShareeListAdapterListener, AvatarG fileOperationsHelper?.unShareShare(file, share.id) } - private fun addExternalAndPublicShares(externalShares: MutableList) { + private fun addExternalAndPublicShares(externalShares: List) { val publicShares = fileDataStorageManager?.getSharesByPathAndType(file?.remotePath, ShareType.PUBLIC_LINK, "") externalShareeListAdapter?.removeAll() @@ -592,24 +596,45 @@ class FileDetailSharingFragment : Fragment(), ShareeListAdapterListener, AvatarG // Use the email address as needed. // email variable contains the selected contact's email address. val email = cursor.getString(columnIndex) - binding?.searchView?.post(Runnable { - if (binding == null) { - return@Runnable + binding?.searchView?.post( + Runnable { + if (binding == null) { + return@Runnable + } + binding?.searchView?.setQuery(email, false) + binding?.searchView?.requestFocus() } - binding?.searchView?.setQuery(email, false) - binding?.searchView?.requestFocus() - }) + ) cursor.close() } - private fun isReshareForbidden(share: OCShare): Boolean { - return ShareType.FEDERATED == share.shareType || - capabilities != null && (capabilities?.filesSharingResharing?.isFalse == true) - } + private fun isReshareForbidden(share: OCShare): Boolean = ( + ShareType.FEDERATED == share.shareType || + capabilities?.filesSharingResharing?.isFalse == true + ) private fun modifyExistingShare(share: OCShare, screenTypePermission: Int) { onEditShareListener?.editExistingShare(share, screenTypePermission, !isReshareForbidden(share)) } + + private val externalShareTypes = setOf( + ShareType.PUBLIC_LINK, + ShareType.FEDERATED_GROUP, + ShareType.FEDERATED, + ShareType.EMAIL + ) + + private suspend fun loadAndPartitionShares(): Pair, List> = withContext(Dispatchers.IO) { + val shares = fileDataStorageManager + ?.getSharesWithForAFile(file?.remotePath, user?.accountName) + ?: emptyList() + + val (external, internal) = shares + .filter { it.shareType != null } + .partition { it.shareType in externalShareTypes } + + return@withContext internal to external + } // endregion // region overridden methods @@ -680,9 +705,9 @@ class FileDetailSharingFragment : Fragment(), ShareeListAdapterListener, AvatarG file?.let { FileActivity.showShareLinkDialog(fileActivity, file, createInternalLink(account, it)) } } - private fun OCCapability?.isPasswordEnforced(): Boolean { - return this?.filesSharingPublicPasswordEnforced?.isTrue == true && filesSharingPublicAskForOptionalPassword.isTrue - } + private fun OCCapability?.isPasswordEnforced(): Boolean = + this?.filesSharingPublicPasswordEnforced?.isTrue == true && + filesSharingPublicAskForOptionalPassword.isTrue override fun createPublicShareLink() { if (capabilities?.isPasswordEnforced() == true) { @@ -731,7 +756,13 @@ class FileDetailSharingFragment : Fragment(), ShareeListAdapterListener, AvatarG } override fun showPermissionsDialog(share: OCShare?) { - QuickSharingPermissionsBottomSheetDialog(fileActivity, this, share, viewThemeUtils, file?.isEncrypted == true).show() + QuickSharingPermissionsBottomSheetDialog( + fileActivity, + this, + share, + viewThemeUtils, + file?.isEncrypted == true + ).show() } override fun requestPasswordForShare(share: OCShare?, askForPassword: Boolean) { val dialog = newInstance(share, askForPassword) @@ -764,9 +795,7 @@ class FileDetailSharingFragment : Fragment(), ShareeListAdapterListener, AvatarG binding?.sharedWithYouAvatar?.setImageDrawable(avatarDrawable) } - override fun shouldCallGeneratedCallback(tag: String?, callContext: Any?): Boolean { - return false - } + override fun shouldCallGeneratedCallback(tag: String?, callContext: Any?): Boolean = false // endregion // region public methods @@ -804,50 +833,27 @@ class FileDetailSharingFragment : Fragment(), ShareeListAdapterListener, AvatarG capabilities = fileDataStorageManager?.getCapability(user?.accountName) } - @SuppressFBWarnings("PSC") fun refreshSharesFromDB() { - if (binding == null) { - return - } + val binding = binding ?: return - val newFile = file?.fileId?.let { fileDataStorageManager?.getFileById(it) } - if (newFile != null) { - file = newFile - } + file = file?.fileId?.let { fileDataStorageManager?.getFileById(it) } ?: file - if (internalShareeListAdapter == null) { + internalShareeListAdapter?.removeAll() ?: run { DisplayUtils.showSnackMessage(this, R.string.could_not_retrieve_shares) return } - internalShareeListAdapter?.removeAll() - - // to show share with users/groups info - val shares = fileDataStorageManager?.getSharesWithForAFile( - file?.remotePath, - user?.accountName - ) ?: listOf() - - val internalShares = ArrayList() - val externalShares = ArrayList() + lifecycleScope.launch { + val (internalShares, externalShares) = loadAndPartitionShares() - for (share in shares) { - if (share.shareType != null) { - when (share.shareType) { - ShareType.PUBLIC_LINK, ShareType.FEDERATED_GROUP, ShareType.FEDERATED, ShareType.EMAIL -> externalShares.add( - share - ) + withContext(Dispatchers.Main) { + internalShareeListAdapter?.addShares(internalShares) + binding.sharesListInternalShowAll.setVisibleIf(internalShares.size > MIN_SHOW_ALL_VISIBLE_ITEM_COUNT) - else -> internalShares.add(share) - } + addExternalAndPublicShares(externalShares) + binding.sharesListExternalShowAll.setVisibleIf(externalShares.size > MIN_SHOW_ALL_VISIBLE_ITEM_COUNT) } } - - internalShareeListAdapter?.addShares(internalShares) - internalShareeListAdapter?.shares?.size?.let { binding?.sharesListInternalShowAll?.setVisibleIf(it > 3) } - - addExternalAndPublicShares(externalShares) - externalShareeListAdapter?.shares?.size?.let { binding?.sharesListExternalShowAll?.setVisibleIf(it > 3) } } // endregion @@ -893,6 +899,7 @@ class FileDetailSharingFragment : Fragment(), ShareeListAdapterListener, AvatarG private const val TAG = "FileDetailSharingFragment" private const val ARG_FILE = "FILE" private const val ARG_USER = "USER" + private const val MIN_SHOW_ALL_VISIBLE_ITEM_COUNT = 3 @JvmStatic fun newInstance(file: OCFile?, user: User?): FileDetailSharingFragment = FileDetailSharingFragment().apply { From 29965c20c9e34feb7448672b4ec7071b86376188 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 5 Jun 2026 18:31:37 +0300 Subject: [PATCH 09/10] wip Signed-off-by: alperozturk96 --- .../owncloud/android/ui/fragment/FileDetailSharingFragment.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.kt b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.kt index ca9f041dcbba..8c6bad21078d 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.kt +++ b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.kt @@ -211,7 +211,7 @@ class FileDetailSharingFragment : private fun setupExternalShares(userId: String) { externalShareeListAdapter = createShareListAdapter(userId, SharesType.EXTERNAL) binding?.sharesListExternal?.run { - adapter = internalShareeListAdapter + adapter = externalShareeListAdapter layoutManager = createShareListLayoutManager() } } From 9eeec97c5867d1c580cdc26856c5066ac8349363 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 5 Jun 2026 18:37:26 +0300 Subject: [PATCH 10/10] wip Signed-off-by: alperozturk96 --- .../owncloud/android/ui/fragment/FileDetailSharingFragment.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.kt b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.kt index 8c6bad21078d..85ad35ead624 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.kt +++ b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.kt @@ -1,6 +1,7 @@ /* * Nextcloud Android client application * + * @author Alper Öztürk * @author Andy Scherzinger * @author Chris Narkiewicz * @author TSI-mc